Java池化思想之数据库连接池

      参考哔哩哔哩教学视频:楠哥教你学Java  (这个视频真的讲的巨好,强推!!)
  【【性能优化】Java池化思想之数据库连接池】 https://www.bilibili.com/video/BV1Gr4y1c7jd/?share_source=copy_web&vd_source=ddb053e0f18a3b69213ce4c206e54471

         池化思想一般指的是对”对象“的池化。对象池化是程序开发中非常常用的一种优化的技术。常见的比如说:字符串常量池、数据库连接池以及线程池,这三个都是池化思想的一种具体的体现。它的核心思想呢是如果一个类被频繁的请求使用,那么我们就没有必要每次都创建一个实例化对象,我们可以将这个类的一些实例化对象直接保存到对象池当中,当我们需要使用的时候,直接从对象池中去获取对象,使用完毕后就归还给对象池,这样我们就可以实现代码的复用,避免每次都重复的去创建对象,这样的话我们就可以极大的提升程序的效率。

        同时这个对象池它的具体实现不做特殊的要求,它可以是一个数组,也可以是一个集合,总之它是一个容器,这个容器只要能装载这些对象都是可以的。

        接下来我们以数据库连接池为例,给大家演示池化思想的实现。

        如果说我们没有数据库连接池的话,那我们每一次访问数据库的话,都需要去建立一个数据库连接对象,数据库访问完毕后,又要去销毁这个数据库连接对象 ,当我下一次访问的时候,我又需要去创建一个新的连接,用完之后又销毁,由于数据库连接和释放的都是重量级的操作,在没有数据库连接池的情况下,我们这样反反复复创建连接对象、销毁连接对象是比较浪费资源,大大的降低系统的效率。所以我们就用到了池化思想,引入了数据库连接池的概念。

        数据库连接池是用来维护数据库连接的这么一个集合,当我们需要访问数据库的时候,我们就不需要每次都去建立数据库连接对象,我们直接从数据库连接池中获取一个连接对象使用即可,然后,当我们的数据库访问操作完成之后,我们也不需要去释放这个连接,我们只需要直接将数据库连接对象返回到数据库连接池中,供下一次的访问数据库时去使用,就是资源反复利用的这么一个思想,这样的话我们就可以极大的提升程序的效率。

        我们目前主流的数据库连接池,用的是C3P0组件,所以接下来我们就使用C3P0来创建数据库连接池:

        数据库连接时创建起来非常的简单,我们首先去声明一个数据源,这个数据源我们用的是ComboPooledDataSource,这个类是C3P0提供的这么一个类:

        ComboPooledDataSource这个类是什么意思?我们知道java默认提供的数据源是datasource. datasource是javax.sql包中的一个接口,任何的第三方组件或者说框架,你要去使用java去构建数据源的话,都必须去实现datasource接口,它的实现类才是数据源,这个就相当于是一个规范,你必须遵守这个规范:

        显然ComboPooledDataSource肯定就是datasource的一个实现类,否则的话你无法去完成这个数据源的创建,源码如下:

        所以ComboPooledDataSource就是datasource的一个实现类。

        接下来我们就需要去设置数据库连接的各种参数/数据库连接池的基本配置:

        这个数据库的连接池的基本配置呢,已经给他写好了。 数据源有了,有了之后,我们怎么去获取这个数据库连接对象呢?直接通过data source get connection,这个方法就可以从数据库连接池当中去获取的连接对象:连接池里边就预先创建了很多个连接对象放到这儿,然后你程序要用的话呢,你只需要去调用getConnection()方法就获取,然后给你程序返回。

public class C3pOTest {
    public static void main(String[] args) throws Exception{
        ComboPooLedDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        dataSource.setUser("root") ;
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc :mysqL://locaLhost:3306/ test");
        Connection connection = dataSource.getConnection();
        System.out.println( connection.getClass().getName());//我们现在打印,看看这个连接对象:
    }
}
 

        我们现在打印,看看这个连接对象:现在的连接对象并不是我们mysql默认的连接对象:

        现在的连接对象并不是我们mysql默认的连接对象,而是NewProxyConnection,NewProxyConnection其实是C3P0封装的一个类,它底层的实现了connection接口:

        Connection接口同样的是JDK提供的数据库连接对象的一个接口,它跟DataSource数据源一样的都是由JDK提供的,所以这个C3P0这个组件里边是分别对这个数据源和连接对象都做了一个实现.

        通过NewProxyConnection这个名字就可以看出来它其实是一个代理的类,Proxy代理,也就是说C3P0数据库连接时返回给我们的其实是一个代理,在代理类当中,它会保存了(封装)真正的mysql数据库连接对象,成员变量叫inner它的类型是connection类型:

        我们现在如何拿到真正的数据库连接对象呢?无法使用connection.inner,因为inner属性是被protected修饰的,无法直接访问,可以通过反射机制拿到的,这是反射的一个应用。

pubLic static object getInner(0bject connection){ 
        Object result = null;
        Field field = null;
        try {
            field = connection.getClass().getDeclaredField("inner");
            result = field.get(connection);
        } catch (Exception e) {
            e.printStackTrace() ;
        }
        return result;
}

这就是反射的一个机制,反射机制其实就是把我们正常的开发流程给它反过来

接下来我们拿Connection对象,但是这样会报错 :


   

     它是被保护起来的,现在就非得要在外部去访问,那就实行这个暴力反射,我们来设置它的权限,等于负的话,表示我们可以在外部强行去访问这个属性:

pubLic static object getInner(0bject connection){ 
        Object result = null;
        Field field = null;
        try {
            field = connection.getClass().getDeclaredField("inner");
            field.setAccessible(true);
            result = field.get(connection);
        } catch (Exception e) {
            e.printStackTrace() ;
        }
        return result;
}

        第二个才是真正的连接对象,所以我们说proxy它只是一个代理,它里边包含了我们真正的连接对象,所以呢我们数据库连接池它就是这样一种机制,它是会把真正的连接对象的用一个代理去进行包装,然后把这个代理对象返回,让外部去使用,同时的话,当我们关闭连接对象的时候,也不是真正的去释放了它,而是把它还回到对象池当中,供下一次去使用。


        现在假设这个数据库连接池当中,只有一个对象,如果说这边有两次请求,那么这两次请求拿到的对象肯定是同一个,接下来的话,我们来通过代码来演示这个推论:

        首先我们是需要保证这个数据库连接池里边只有一个对象,所以我们要去设置它的参数,把它的初始化的连接池的大小设置为1,同时的话还需要去设置它的最大连接数,也设置为1,这两个一定要都去设置,只设置第一个的话呢,你不能确保它里边只有一个,因为这个是初始化,它有一个,但是如果说请求很多的话,连接池它会自动的去创建新的连接对象存进去,所以我们必须给它设置一个最大上限也只有一个,这样的话当多个请求想要获取数据库连接时,连接池就无法自动去创建对象,去补充这个连接对象了:

        dataSource.setInitialPoolSize(1);
        dataSource.setMaxPoolSize(1);

        判断是否是同一个连接对象,我们可以用双等号判断,来看结果,如果说是同一个就是true,如果说不是同一个,就是false:  

public static void main(String[] args) throws Exception{
    ComboPooledDataSource dataSource = new ComboPolledDataSource();
    dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    dataSource.setUser("root");
    dataSource.setPassword(123456);
    dataSource.JdbcUrl("jdbc:mysql://localhost:3306/test");
    dataSource.setInitialPoolSize(1);
    dataSource.setMaxPoolSize(1);
    Connection connection1 = dataSource.getConnection();
    connection.close();
    Connection connection2 = dataSource.getConnection();
    System.out.println(connection1.getClass.getName());
    System.out.println(connection2.getClass.getName());
    System.out.println(connection1 == connection2)
}

        NewProxyConnection是C3P0提供的代理对象,它只是一个代理,所以两次请求他就会返回两个不同的代理,这个代理肯定是两个不同的。但是最核心的是什么?就是这两个代理中包含真正的mysql连接对象,它一定是同一个。

        接下来再去验证,我们怎么样呢?我把它的inner属性给它取出来,然后我们来判断它的inner是不是同一个就ok了:

public static void main(String[] args) throws Exception{
    ComboPooledDataSource dataSource = new ComboPolledDataSource();
    dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    dataSource.setUser("root");
    dataSource.setPassword(123456);
    dataSource.JdbcUrl("jdbc:mysql://localhost:3306/test");
    dataSource.setInitialPoolSize(1);
    dataSource.setMaxPoolSize(1);
    Connection connection1 = dataSource.getConnection();
    Object inner1 = getInner(connection1);
    connection.close();
    Connection connection2 = dataSource.getConnection();
    Object inner2 = getInner(connection2);
    System.out.println(connection1.getClass.getName());
    System.out.println(connection2.getClass.getName());
    System.out.println(connection1 == connection2);
    System.out.println(inner1.getClass().getName());
    System.out.println(inner2.getClass().getName());
    System.out.println(inner1 == inner2);
}

  结果就是true,这个就是我们数据库连接池的一个代理模式。这里大家需要注意的是什么呢?如果说我们不关闭第一次的连接,把这个代码注释掉,那么我们现在程序会呈现一个什么样的状态呢?我们来运行看结果:

          我们可以看到程序现在处于一个阻塞状态,他一直停到这儿,它也不往下进行了,程序也不关闭,它就处于一个阻塞状态了,为什么呢?因为我们现在连接对象只有一个,现在第一次获取数据库连接的请求把这个连接对象拿走了,拿走了之后呢,你没有执行close,没有执行close的意思,就是没有把它归还过来,就一直在你这儿放着的。然后这个时候第二个数据库连接请求再获取连接的时候,因为现在已经没有连接对象了,所以说他就一直在等待获取连接对象,你这边不放,就一直等待,这样的话导致我们程序处于一个阻塞状态。 所以说大家在使用的时候,一定要去注意,用完之后一定要给它释放掉,不然的话你就会发现你的程序突然莫名其妙的就卡到那儿,就是因为我们的资源已经不够用了,这个被其他线程拿走了,这个资源呢没有还回来,新的线程无法获取到资源,所以他就一直处在一个等待状态。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值