web 学习笔记16-JDBC连接池 扩展已知类

来源:互联网 发布:域名注册购买 编辑:程序博客网 时间:2024/06/05 08:19

1、连接池:

为什么要用连接池,如果你的服务器servlet有个添加数据的方法,里面需要获取一个数据库连接,如果有100000个人同时访问那么你就需要创建10000个连接,而且连接是个很耗时的操作,也会浪费内存,导致服务器内存溢出。模拟下连接池,就类似一个池子,提前放好一些连接,需要的时候直接拿来用。例如:mysql的jar包需要先导入。a.一个数据库的工具类
        public class JdbcUtils {            private static String driverClass = "" ;            private static String url = "" ;            private static String user = "" ;            private static String password  = "";            static{                ResourceBundle rb = ResourceBundle.getBundle("dbcfg") ;                driverClass = rb.getString("driverClass") ;                url = rb.getString("url") ;                user = rb.getString("user") ;                password = rb.getString("password") ;                try {                    Class.forName(driverClass) ;                } catch (ClassNotFoundException e) {                    e.printStackTrace();                }            }            public static Connection getConnection(){                try {                    return DriverManager.getConnection(url, user, password) ;                } catch (SQLException e) {                    e.printStackTrace();                }                return null ;            }            public static void release(ResultSet rs ,Statement stmt,Connection conn){                if(rs != null){                    try {                        rs.close() ;                    } catch (SQLException e) {                        e.printStackTrace();                    }                }                if(stmt != null){                    try {                        stmt.close() ;                    } catch (SQLException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }                if(conn != null){                    try {                        conn.close() ;                    } catch (SQLException e) {                        e.printStackTrace();                    }                }            }        }
b.模拟创建一个连接池
        public class MyConnectionPool1 {            //增删方便            private static LinkedList<Connection> pool = new LinkedList<Connection>() ;            static{                for (int i = 0; i < 10; i++) {                    Connection conn = JdbcUtils.getConnection() ;                    pool.add(conn) ;                }            }            public synchronized static Connection getConnection(){                if(pool.size() > 0)                    return pool.removeFirst() ;                else                    throw new RuntimeException("对不起,服务器忙") ;            }            public static void close(Connection conn){                if(conn != null)                    pool.addLast(conn) ;   //放回池中               }        }
c.模拟连接池的原理(dao层的实现类)
        public class DaoImpl {            @Test            public void add(){                //拿到连接                Connection conn = null ;                PreparedStatement pstmt = null ;                try {                    conn = MyConnectionPool1.getConnection() ;   // 左边; 抽象层   右边: 真实的对象 com.mysql.jdbc.Connecotion                    pstmt = conn.prepareStatement("") ;                    //.......                } catch (Exception e) {                    e.printStackTrace() ;                }finally{                    JdbcUtils.release(null, pstmt, null) ;                    MyConnectionPool1.close(conn) ;                }            }        }

2、我们实际开发中一般使用数据源 DataSource:

底层也是连接池,我们创建一个类,实现DataSource接口例如
        public class MyDataDource2 implements DataSource{            private static LinkedList<Connection> pool = new LinkedList<Connection>() ;            static{//写一个静态块,默认放10个连接                for (int i = 0; i < 10; i++) {                    Connection conn = JdbcUtils.getConnection() ;                    pool.add(conn) ;                }            }            @Override            public Connection getConnection() throws SQLException {                if(pool.size() > 0 ){                     return pool.removeFirst() ;  //右边: com.mysql.jdbc.Connection                }else                    throw new RuntimeException("对不起,服务器忙") ;            }            //未实现的一些方法        }
    我们的dao实现类:
        public class DaoImpl {            private  DataSource ds ;            public DaoImpl(DataSource ds) {//我们将数据源通过构造函数传进来                this.ds = ds ;            }            @Test            public void add(){                DataSource ds = new MyDataDource2()  ; ;                //拿到连接                Connection conn = null ;                PreparedStatement pstmt = null ;                try {                    conn = ds.getConnection() ;   //通过数据源去拿取连接                    pstmt = conn.prepareStatement("") ;                    //.......                } catch (Exception e) {                    e.printStackTrace() ;                }finally{                    if(pstmt != null){                        try {                            pstmt.close() ;                        } catch (SQLException e) {                            e.printStackTrace();                        }                    }                    if(conn != null){                        try {                            conn.close() ;    //不能关,这个是调用真实mysql连接对象的close方法                                               //一定要还回池中,此时需要改写close方法.                        } catch (SQLException e) {                            e.printStackTrace();                        }                    }                }            }        }
我们现在遇到的一个问题就是:我们拿到了conn,但是不能关(关了连接池的意义就没了),又不能放回池中。    我的人想在MyDataDource2类中添加一个close方法,然后调用,实际是不行的(conn是一个接口,怎么可能调用它实现类的方法呢)

3、扩展已知类功能-包装类:

通常3种方法:    子类    包装类(装饰模式、适配器模式)    动态代理上面有个不能调用close方法关闭连接,有放回不到池中,我们就想办法扩展mysql的Connect类的功能然close方法不是关闭连接,而是将连接放回池中。如何扩展已知类的功能(不能修改源码)a.子类    继承父类    不好,不用,与具体的类相关b.包装类(装饰模式)    步骤    1. 写一个类,实现和被包装类相同的接口 (使他们具有相同的行为)    2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关)    3. 编写一个构造函数,传入被包装类对象 (注入: DI)    4. 对于需要改写的方法,写自己的代码    5. 对于不需要改写的方法,引用被包装类的对象的对应方法即可    例如:
        创建个包装类        public class MyConnection3 implements Connection{//步骤1            private Connection conn ;//步骤2,这个是java.sql接口,mysql里面的Connect也实现了这个接口            private LinkedList<Connection> pool ;            //注意,这个形参的conn就是你传进来的mysql的真实对象。如果你传的oracle的conn,就包装oracle            public MyConnection3(Connection conn,LinkedList<Connection> pool){//步骤3                this.conn = conn ;                this.pool = pool ;            }            @Override            public PreparedStatement prepareStatement(String sql) throws SQLException {                return conn.prepareStatement(sql);//步骤5,默认使用mysql的prepareStatement方法            }            @Override            public void close() throws SQLException {//步骤4                //需要将连接还回池中                pool.addLast(conn) ;            }            //------------下面是默认实现的接口,省略,        }
    改写下数据源类
        public class MyDataDource2 implements DataSource{            private static LinkedList<Connection> pool = new LinkedList<Connection>() ;            static{//写一个静态块,默认放10个连接                for (int i = 0; i < 10; i++) {                    Connection conn = JdbcUtils.getConnection() ;                    pool.add(conn) ;                }            }            @Override            public Connection getConnection() throws SQLException {                if(pool.size() > 0 ){                     Connection conn =  pool.removeFirst() ;  //右边: com.mysql.jdbc.Connection                    MyConnection3 mconn = new MyConnection3(conn,pool);//这里我们使用了自己改写的包装类,可以调用自己的close                    return mconn;                }else                    throw new RuntimeException("对不起,服务器忙") ;            }            //未实现的一些方法        }
    我们的dao实现类不需要改,直接可以调用close方法了,就把conn放回到池子中了。    注意:关键地方就是我们创建了一个自己的类MyConnection3,替代了mysql里面的Connection类的功能。        我们定义的类可以自定义,功能也可以更多。c.适配器模式:    和包装类似,只不过这个将适配器类单独写出来,我们再写一个类继承适配器类。    创建一个适配器类:    步骤:        1. 写一个类,实现和被包装类相同的接口 (使他们具有相同的行为)        2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关)        3. 编写一个构造函数,传入被包装类对象 (注入: DI)        4. 对所有的方法方法,引用被包装类的对象的对应方法即可
            public class MyConenctionAdapter implements Connection {                private Connection conn ;                public MyConenctionAdapter(Connection conn) {                    this.conn = conn ;                }                @Override                public PreparedStatement prepareStatement(String sql) throws SQLException {                    return conn.prepareStatement(sql);                }                //----以下所有的方法都调用mysql connect类里面的方法            }
    创建一个我们自己的类,继承这个适配器类,然后重写下close方法:    步骤:        1. 写一个类,继承适配器类 (使他们具有相同的行为)        2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关)        3. 编写一个构造函数,传入被包装类对象 (注入: DI)        4. 对需要改写的方法重写代码即可
            public class MyConnection extends MyConenctionAdapter {                private Connection conn ;                private LinkedList<Connection> pool ;                public MyConnection(Connection conn,LinkedList<Connection> pool) {                    super(conn) ;                    this.pool = pool ;                    this.conn = conn;                }                @Override                public void close() throws SQLException {                    pool.add(conn) ;//重写close方法,将连接放回池中                }            }
上面2种方式类似,都是通过实现了你要扩展类的相同接口。    包装类是直接实现接口的时候就把close方法重写了    适配器模式就是你继承的时候再重写close方法。

4、扩展已知类功能-静态代理

实际中不用简单的代码演示下:a.有个Person类:
        public class Person {            public void eat() {                System.out.println("吃饭");            }            public void sleep(){                System.out.println("睡觉");            }        }
b.创建一个代理类:
        //我们代理类里面定义和Person类里面一样的方法        public class ProxyPerson {            private Person p ;            public ProxyPerson(Person p) {//将person对象传进来                this.p = p ;            }            public void eat() {                p.eat() ;            }            public void sleep(){                p.sleep() ;                System.out.println("睡5分钟");            }        }
c.使用:
        public class Test {            public static void main(String[] args) {                //没有使用代理                Person p = new  Person();                p.eat() ;                p.sleep() ;                //使用代理,                ProxyPerson pp = new ProxyPerson(p) ;                pp.eat() ;                pp.sleep() ;//除了打印“睡觉” 还会打印“睡5分钟”            }        }
注意:和包装类相似,只不过我们写了和被包装类一样的方法,而不是实现接口。

5、扩展已知类功能-动态代理

简单代码演示下动态代理的原理    a.有个通用的接口
            public interface Human {                public void eat() ;                public void sing(float money) ;                public void dance(float money) ;            }
    b.一个实现类(可以理解 歌手)
            public class SpringBrother implements Human {                @Override                public void eat() {                    System.out.println("吃饭");                }                @Override                public void sing(float money) {                    System.out.println("拿到" + money);                }                @Override                public void dance(float money) {                    System.out.println("拿到" + money);                }            }
    c.代理类(可理解为经纪人)
            //模拟动态代理的原理            public class ProxyHuman implements Human {                private Human man ;                public ProxyHuman(Human man) {                    this.man = man ;                }                @Override                public void eat() {                    man.eat() ;                }                @Override                public void sing(float money) {                    if(money > 1000)                        man.sing(money/2) ;                }                @Override                public void dance(float money) {                    if(money > 2000)                        man.sing(money/2) ;                }            }
    d.使用代理类
            public class Test {                public static void main(String[] args) {                    Human man = new ProxyHuman(new SpringBrother()) ;                    man.eat() ;                    man.sing(2000) ;                    man.dance(4000) ;                }            }
实际的应用中是没有ProxyHuman这个代理类的,java虚拟机帮我们做了,我们需要使用Proxy类代码演示:基于接口的动态代理
        public class Test {            public static void main(String[] args) {                //定义为final是为了下面匿名内部类可以调用                final SpringBrother sb = new SpringBrother() ;                //可以查看帮助文档,看看参数                Human man = (Human)Proxy.newProxyInstance(sb.getClass().getClassLoader(),                        sb.getClass().getInterfaces(),                         new InvocationHandler() {                        /**                         * invoke(Object proxy, Method method, Object[] args)                         * proxy: 代理人                         * method: 代理的方法                         * args: 方法的参数                         */                            @Override                            public Object invoke(Object proxy, Method method, Object[] args)                                    throws Throwable {                                if(method.getName().equals("sing")){//唱歌方法                                    float money = (Float)args[0] ;                                    if(money > 1000){                                        Object retVal = method.invoke(sb, money/2) ;                                        return retVal;                                    }else                                        return null ;                                }                                if(method.getName().equals("dance")){//跳舞方法                                    float money = (Float)args[0] ;                                    if(money > 2000){                                        Object retVal = method.invoke(sb, money/2) ;                                        return retVal ;                                    }else                                        return null ;                                }                                Object ret =method.invoke(sb, args) ;                                return ret ;                            }                        }) ;                man.eat() ;                man.sing(1500) ;                man.dance(2500) ;            }        }
其实也很简单,我们是用了java提供了类来实现了动态代理的功能基于子类的动态代理可以使用第三方包net.sf.cglib.proxy.Enhancer来实现,就不演示了。注意:我们动态代理就是给某个特定的方法设个条件是否执行,或者在调用这个close方法的时候不执行,来执行我们自己的代码。    就相当于包装类里面,我们重新实现了接口里面的close方法。

6、使用动态代理改造我们的 MyDataDource2 类:

例如:
        public class MyDataDource2 implements DataSource{            private static LinkedList<Connection> pool = new LinkedList<Connection>() ;            static{//写一个静态块,默认放10个连接                for (int i = 0; i < 10; i++) {                    Connection conn = JdbcUtils.getConnection() ;                    pool.add(conn) ;                }            }            @Override            public Connection getConnection() throws SQLException {                if(pool.size() > 0 ){                    //定义为final,便于匿名内部类使用                    final Connection conn = pool.removeFirst() ;  //右边: com.mysql.jdbc.Connection                    //采用动态代理                    Connection ProxyConn =  (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(),                             conn.getClass().getInterfaces(),                              new InvocationHandler() {                                @Override                                public Object invoke(Object arg0, Method method, Object[] args)                                        throws Throwable {                                    if(method.getName().equals("close")){                                        //不要关闭,而是放回池中                                        pool.add(conn) ;                                        return null ;                                    }                                    Object retVal = method.invoke(conn, args) ;                                    return retVal ;                                }                            }) ;                    return ProxyConn;                }else                    throw new RuntimeException("对不起,服务器忙") ;            }            //未实现的一些方法        }

7、DBCP数据源:

第三方提供的数据源使用步骤:a.将jar包拷贝到当前工程    commons-dbcp-1.4.jar    commons-pool-1.5.6.jarb.在src目录下创建一个配置文件 dfcpconfig.properties    #连接设置    driverClassName=com.mysql.jdbc.Driver    url=jdbc:mysql://localhost:3306/test    username=root    password=root    #<!-- 初始化连接 -->    initialSize=10    #最大连接数量    maxActive=50    #<!-- 最大空闲连接 -->    maxIdle=20    #<!-- 最小空闲连接 -->    minIdle=5    #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->    maxWait=60000    #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]     #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。    connectionProperties=useUnicode=true;characterEncoding=gbk    #指定由连接池所创建的连接的自动提交(auto-commit)状态。    defaultAutoCommit=true    #driver default 指定由连接池所创建的连接的只读(read-only)状态。    #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)    defaultReadOnly=false    #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。    #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE    defaultTransactionIsolation=READ_UNCOMMITTEDc.创建一个连接的工具类
        public class DBCPUtils {            private static DataSource ds ;            static {                //将配置文件加载进来                InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties") ;                Properties props = new Properties() ;                try {                    props.load(in) ;                    ds = BasicDataSourceFactory.createDataSource(props) ;                } catch (Exception e) {                    throw new RuntimeException("服务器忙") ;                }            }            //提供获取练级的方法            public static Connection getConnection(){                try {                    return ds.getConnection() ;                } catch (SQLException e) {                    throw new RuntimeException("服务器忙") ;                }            }        }
d.使用
        public class TestDBCPUtils {            public static void main(String[] args) {                Connection conn = DBCPUtils.getConnection() ;                System.out.println(conn.getClass().getName());            }        }

8、C3P0数据源:

第三方数据源和上面的方法类似a.拷贝jar包b.在src目录下创建配置文件 c3p0-config.xml
        <?xml version="1.0" encoding="UTF-8"?>        <c3p0-config>            <default-config>                <property name="driverClass">com.mysql.jdbc.Driver</property>                <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>                <property name="user">root</property>                <property name="password">root</property>                <property name="acquireIncrement">5</property>                <property name="initialPoolSize">10</property>                <property name="minPoolSize">5</property>                <property name="maxPoolSize">20</property>            </default-config>            <named-config name="mysql">                <property name="driverClass">com.mysql.jdbc.Driver</property>                <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>                <property name="user">root</property>                <property name="password">root</property>                <property name="acquireIncrement">5</property>                <property name="initialPoolSize">10</property>                <property name="minPoolSize">5</property>                <property name="maxPoolSize">20</property>            </named-config>            <named-config name="oracle">                <property name="driverClass">com.mysql.jdbc.Driver</property>                <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>                <property name="user">root</property>                <property name="password">root</property>                <property name="acquireIncrement">5</property>                <property name="initialPoolSize">10</property>                <property name="minPoolSize">5</property>                <property name="maxPoolSize">20</property>            </named-config>        </c3p0-config>  
c.创建工具类
        public class C3p0Utils {            private static DataSource ds ;            static{                ds = new ComboPooledDataSource() ;            }            //提供获取连接的方法            public static Connection getConnection(){                try {                    return ds.getConnection() ;                } catch (SQLException e) {                    throw new RuntimeException("服务器忙") ;                }            }        }
d.使用:
        public class TestC3p0Utils {            public static void main(String[] args) {                Connection conn = C3p0Utils.getConnection() ;                System.out.println(conn.getClass().getName());            }        }
上面两种情况是在java工程里面使用第三方的数据源,我们web工程里面可以使用tomcat自带的数据源

9、tomcat数据源的配置

tomcat里面已经有了dbcp的jar包了D:\tomcat\apache-tomcat-7.0.77\lib\tomcat-dbcp.jar步骤:    a、拷贝数据库驱动jar包到Tomcat\lib目录下    b、在应用的META-INF目录下建立一个名称为context.xml的配置文件。
            可以查看帮助文档            <?xml version = "1.0"?>            <Context>              <Resource name="jdbc/day16" auth="Container" type="javax.sql.DataSource"                           maxActive="100" maxIdle="30" maxWait="10000"                           username="root" password="root" driverClassName="com.mysql.jdbc.Driver"                           url="jdbc:mysql://localhost:3306/test"/>            </Context>
    c、启动Tomcat,数据源就给你建好了    d、在应用中如何获取数据源,jsp页面中使用
            <%                Context initContext = new InitialContext();                Context envContext = (Context) initContext.lookup("java:/comp/env");                DataSource ds = (DataSource) envContext.lookup("jdbc/test");                Connection conn = ds.getConnection();                out.write(conn.toString() + "jjjjj") ;            %>
    注意:不要在main方法中获取数据源,获取不到。因为main调用重新开了个虚拟机。

10、数据库元数据的获取:

MetaData元数据:数据库、表、列等定义的信息例如:数据库的元信息:DatabaseMetaData
        Connection conn = DBCPUtils.getConnection();// 获取连接对象        DatabaseMetaData dbmd = conn.getMetaData();// 获取DataBaseMetaData对象        String name = dbmd.getDatabaseProductName();// 获取数据库产品的名称
获取PreparedStatement占位符的元信息
        Connection conn = DBCPUtils.getConnection();// 获取连接对象        PreparedStatement pstmt = conn.prepareStatement("??????");// 创建预处理对象        ParameterMetaData pmd = pstmt.getParameterMetaData();// 获取ParameterMetaData对象        int n = pmd.getParameterCount();// 获取参数的个数
等等,我们其实都可以通过方法的名称就知道。

11、自定义JDBC的框架

之前我们DaoImpl里面的数据库操作很麻烦,而且很多重复,我们可以封装下。这个地方我们就用到元数据了,可以动态的获取参数个数,返回查询结果的字段名称等。我们新建一个DBAsssist类,里面把一些连接,执行sql语句的逻辑放进来
        //自定义框架        public class DBAsssist {            // 执行添改删语句,定义可变参数好处就是,有参数就传,没有就不传            public boolean update(String sql, Object... params) {                Connection conn = DBCPUtils.getConnection();// 拿到连接对象                int t = 0;                try {                    // 创建预处理命令对象                    PreparedStatement pstmt = conn.prepareStatement(sql);                    // 对?进行赋值                    // 获取ParameterMetaData对象                    ParameterMetaData pmd = pstmt.getParameterMetaData();                    int n = pmd.getParameterCount();// 拿到?的个数                    if (n > 0) {                        if (params == null || params.length != n) {// sql语句里有?号                            throw new RuntimeException("参数的个数不匹配");                        }                        for (int i = 0; i < n; i++) {// 依次给每个?赋值                            pstmt.setObject(i + 1, params[i]);                        }                    }                    t = pstmt.executeUpdate();//执行sql语句                } catch (SQLException e) {                    e.printStackTrace();                } finally {                    try {                        conn.close(); // 还回池中了                    } catch (SQLException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }                return t > 0 ? true : false;            }            //查询的就不演示了        }
使用:
        @Test        public void test() {            DBAsssist db = new DBAsssist();            db.update("insert into account(id,name,money) values(?,?,?)", 1, "张三",2000);        }
后面我们可以把我们写好的类打包成jar包,其他的工程就可以使用了。
原创粉丝点击