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

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.jar
b.在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_UNCOMMITTED

c.创建一个连接的工具类
        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包,其他的工程就可以使用了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值