7.java程序员必知必会类库之数据库连接池

前言

在java中,“池”化的设计思想随处可见,池化的最终目的是为了对象复用,降低系统创建、销毁对象的成本,提升资源的可管理性
尤其是一些大对象,创建销毁比较消耗资源的对象,池化可以极大提高效率,减少系统响应时间,提高系统并发度
常见的有线程池,实例池(spring容器),连接池等。本节我们介绍连接池里面的数据库连接池。

1. 有无连接池管理图示

1.1 没有连接池管理的时候

没有数据库连接池管理的话,每次外部请求过来,请求到我们web层,dao层为每个请求都会创建连接,执行脚本,释放连接,其中dao层和数据库建立连接,底层都是TCP请求,每次都要三次握手,四次挥手。如下如所示:
在这里插入图片描述

1.2 交给连接池管理

在交给连接池管理连接后,dao层和数据库交互的时候,直接从连接池获取连接。不再自己手动创建销毁连接,如下图所示:
在这里插入图片描述

1.3 手动创建连接代码demo

1.3.1 pom坐标引入

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
    <scope>runtime</scope>
</dependency>

1.3.2 测试demo

@Test
public void test() throws Exception{
    StopWatch stopWatch=new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 10000; i++) {
        //步骤
        //1  获取数据库连接的URL mysql8 必须要给一个时区  serverTimezone=UTC
        String url="jdbc:mysql://xx.xx.xx.xx:3306/openplatform?serverTimezone=UTC";
        //2 获取数据库连接的用户名和密码
        String username="xxxlatformopr";
        String password="xxx";
        //3 加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //4 获取数据库连接对象  DriverManager依据数据库的不同,管理JDBC驱动
        Connection connection= DriverManager.getConnection(url,username,password);
        //5 获取操作数据库的Statement对象
        Statement statement=connection.createStatement();
        //6向数据库发送sql
        String sql="select * from t_cust_info limit 1";
        //7 通过statement对象 发送查询请求 拿回结果集
        ResultSet resultSet = statement.executeQuery(sql);
        //8遍历结果集 做一系列操作
        while (resultSet.next()){
            Object id = resultSet.getObject("cust_id");
            //System.out.println(id);
            Object uname = resultSet.getObject("cust_name");
            //System.out.println(uname);
            Object idNo = resultSet.getObject("id_no");
            //System.out.println(idNo);
           // System.out.println("==============");
        }
        //关闭资源
        resultSet.close();
        statement.close();
        connection.close();
   }
    stopWatch.stop();
    double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
    System.out.println(totalTimeSeconds);
    //5.644
}

1.4 性能分析

在上面的代码中,循环遍历了10000次,每次都会重新创建连接,查询,销毁链接。本地测试100次,平均每次耗时44.09秒中间甚至偶发的报错,tcp连接超过限制

2. C3p0

2.1 介绍

C3PO是一个开源的JDBC连接池,它能够自动维护和回收数据库连接使用C3PO,可以设置最大连接数、最小连接数、最大空闲时间等参数从而对连接进行有效控制。同时,C3PO还提供了丰富的监控功能,可以帮助我们追踪和排除数据库连接问题

2.2 使用

2.2.1 pom坐标引入

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
    <scope>runtime</scope>
</dependency>

2.2.2 代码demo

2.2.2.1 连接工具类
public class C3p0Util {

    private static DataSource dataSource = null;

    static{
    	//与配置文件的配置名字 named-config需要保持一致
        dataSource = new ComboPooledDataSource("mysqlapp");
    }

    //从连接池中获取连接
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();        }
        return null;
    }

    //释放连接回连接池
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}
2.2.2.2 c3p0-config.xml模板

放到项目resource下面


<c3p0-config>
    <named-config name="mysqlapp">
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://x.x.x.x:3306/openplatform</property>
        <property name="user">formopr</property>
        <property name="password">xxxxx</property>

        <!-- 进行数据库连接池管理的基本信息 -->
        <!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- c3p0数据库连接池中初始化时的连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- c3p0数据库连接池维护的最少连接数 -->
        <property name="minPoolSize">10</property>
        <!-- c3p0数据库连接池维护的最多的连接数 -->
        <property name="maxPoolSize">200</property>
        <!-- c3p0数据库连接池最多维护的Statement的个数 -->
        <property name="maxStatements">50</property>
        <!-- 每个连接中可以最多使用的Statement的个数 -->
        <property name="maxStatementsPerConnection">2</property>

    </named-config>
</c3p0-config>
2.2.2.3 测试demo
@Test
public void testC3p0() throws Exception{
    StopWatch stopWatch=new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 10000; i++) {
        //步骤
        //4 获取数据库连接对象  DriverManager依据数据库的不同,管理JDBC驱动
        Connection connection = C3p0Util.getConnection();
        //5 获取操作数据库的Statement对象
        Statement statement=connection.createStatement();
        //6向数据库发送sql
        String sql="select * from t_cust_info limit 1";
        //7 通过statement对象 发送查询请求 拿回结果集
        ResultSet resultSet = statement.executeQuery(sql);
        //8遍历结果集 做一系列操作
        while (resultSet.next()){
            Object id = resultSet.getObject("cust_id");
            //System.out.println(id);
            Object uname = resultSet.getObject("cust_name");
            //System.out.println(uname);
            Object idNo = resultSet.getObject("id_no");
            //System.out.println(idNo);
            // System.out.println("==============");
        }
        C3p0Util.release(connection,statement,resultSet);
    }
    stopWatch.stop();
    double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
    System.out.println(totalTimeSeconds);
}

2.3 性能测试

上面的代码,本地测试100次,平均每次运行耗时:5.671s,可以看到相对自己手动创建链接,时间大幅减少

2.4 常用参数配置

<c3p0-config>   
    <default-config>   
    <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->   
    <property name="acquireIncrement">3</property>   
 
    <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->   
    <property name="acquireRetryAttempts">30</property>   
       
    <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->   
    <property name="acquireRetryDelay">1000</property>   
       
    <!--连接关闭时默认将所有未提交的操作回滚。Default: false -->   
    <property name="autoCommitOnClose">false</property>   
       
    <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么   
    属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试   
    使用。Default: null-->   
    <property name="automaticTestTable">Test</property>   
       
    <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效   
    保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试   
    获取连接失败后该数据源将申明已断开并永久关闭。Default: false-->   
    <property name="breakAfterAcquireFailure">false</property>   
       
    <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出   
    SQLException,如设为0则无限期等待。单位毫秒。Default: 0 -->   
    <property name="checkoutTimeout">100</property>   
       
    <!--通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。   
    Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->   
    <property name="connectionTesterClassName"></property>   
       
    <!--指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可   
    Default: null-->   
    <property name="factoryClassLocation">null</property>   
       
    <!--强烈不建议使用该方法,将这个设置为true可能会导致一些微妙而奇怪的bug-->   
    <property name="forceIgnoreUnresolvedTransactions">false</property>   
       
    <!--每60秒检查所有连接池中的空闲连接。Default: 0 -->   
    <property name="idleConnectionTestPeriod">60</property>   
       
    <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->   
    <property name="initialPoolSize">3</property>   
       
    <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->   
    <property name="maxIdleTime">60</property>   
       
    <!--连接池中保留的最大连接数。Default: 15 -->   
    <property name="maxPoolSize">15</property>   
       
    <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements   
    属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。   
    如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0-->   
    <property name="maxStatements">100</property>   
       
    <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->   
    <property name="maxStatementsPerConnection"></property>   
       
    <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能   
    通过多线程实现多个操作同时被执行。Default: 3-->   
    <property name="numHelperThreads">3</property>   
       
    <!--当用户调用getConnection()时使root用户成为去获取连接的用户。主要用于连接池连接非c3p0   
    的数据源时。Default: null-->   
    <property name="overrideDefaultUser">root</property>   
       
    <!--与overrideDefaultUser参数对应使用的一个参数。Default: null-->   
    <property name="overrideDefaultPassword">password</property>   
       
    <!--密码。Default: null-->   
    <property name="password"></property>   
       
    <!--定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意:   
    测试的表必须在初始数据源的时候就存在。Default: null-->   
    <property name="preferredTestQuery">select id from test where id=1</property>   
       
    <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->   
    <property name="propertyCycle">300</property>   
       
    <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的   
    时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable   
    等方法来提升连接测试的性能。Default: false -->   
    <property name="testConnectionOnCheckout">false</property>   
       
    <!--如果设为true那么在取得连接的同时将校验连接的有效性。Default: false -->   
    <property name="testConnectionOnCheckin">true</property>   
       
    <!--用户名。Default: null-->   
    <property name="user">root</property>   
       
    <!--早期的c3p0版本对JDBC接口采用动态反射代理。在早期版本用途广泛的情况下这个参数   
    允许用户恢复到动态反射代理以解决不稳定的故障。最新的非反射代理更快并且已经开始   
    广泛的被使用,所以这个参数未必有用。现在原先的动态反射与新的非反射代理同时受到   
    支持,但今后可能的版本可能不支持动态反射代理。Default: false-->   
    <property name="usesTraditionalReflectiveProxies">false</property> 
    </default-config>      
</c3p0-config>

3 DBCP

3.1 介绍

DBCP是Apache软件基金会下的一个开源项目,也是一个JDBC连接池。与 C3PO类似,DBCP 也能够自动维护和回收数据库连接。同时,DBCP还支持连接池配置文件的读取,这样就可以通过修改配置文件来改变连接池的参数

3.2 使用

3.2.1 pom坐标引入

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>commons-pool</groupId>
    <artifactId>commons-pool</artifactId>
    <version>1.6</version>
</dependency>

3.2.2 代码demo

3.2.2.1 连接工具类
public class JDbcConfigReadUtil {
    private static DataSource dataSource = null;
    static {
        Properties props = new Properties();
        try {
            File file = new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\2023-03\\test\\src\\main\\resources\\dbcp\\jdbc.properties");//获取文件
            InputStream inputStream = new FileInputStream(file);//获取输入流
            props.load(inputStream);//props需要通过输入流读取一个.Properties文件
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //从连接池中获取连接
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //释放连接回连接池
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}
3.2.2.2 配置properties文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://x.x.x.x:3306/openplatform
username=xx
password=xxx
3.2.2.3 测试demo
@Test
public void testDbcp() throws Exception{

    StopWatch stopWatch=new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 10000; i++) {
        //4 获取数据库连接对象  DriverManager依据数据库的不同,管理JDBC驱动
        Connection connection = JDbcConfigReadUtil.getConnection();
        //5 获取操作数据库的Statement对象
        Statement statement=connection.createStatement();
        //6向数据库发送sql
        String sql="select * from t_cust_info limit 1";
        //7 通过statement对象 发送查询请求 拿回结果集
        ResultSet resultSet = statement.executeQuery(sql);
        //8遍历结果集 做一系列操作
        while (resultSet.next()){
            Object id = resultSet.getObject("cust_id");
            //System.out.println(id);
            Object uname = resultSet.getObject("cust_name");
            //System.out.println(uname);
            Object idNo = resultSet.getObject("id_no");
            //System.out.println(idNo);
            // System.out.println("==============");
        }
        C3p0Util.release(connection,statement,resultSet);
    }
    stopWatch.stop();
    double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
    System.out.println(totalTimeSeconds);

}

3.3 性能测试

上面的代码,本地测试100次,平均每次运行耗时:5.191,可以看到相对自己手动创建链接,时间大幅减少

3.4 常用参数配置

<!--初始化连接:连接池启动时创建的初始化连接数量-->
initialSize=5
<!--maxActive: 最大连接数量-->    
maxActive=30
<!-- 连接在池中保持空闲而不被空闲连接回收器线程-->  
minEvictableIdleTimeMillis=1800000
<!--maxIdle: 最大空闲连接-->
maxIdle=5
<!--minIdle: 最小空闲连接--> 
minIdle=2
<!--maxWait: 超时等待时间以毫秒为单位 1000等于60-->
maxWait=1000
<!--对于事务是否 autoCommit-->
defaultAutoCommit=true
<!-- 在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. --> 
timeBetweenEvictionRunsMillis=600000
 <!--  在每次空闲连接回收器线程(如果有)运行时检查的连接数量 -->
numTestsPerEvictionRun=3
<!--是否自动回收超时连接-->  
removeAbandoned=true
<!--超时时间(以秒数为单位)--> 
removeAbandonedTimeout=180
<!-- 连接被泄露时是否打印 -->
logAbandoned=true
<!--连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.设置为true后如果要生效,validationQuery参数必须设置为非空字符串-->
testWhileIdle=true
validationQuery=select 1
<!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.-->
testOnBorrow=true
<!--是否在归还到池中前进行检验-->
testOnReturn=false

4 Druid

4.1 介绍

Druid 是阿里巴巴开源的一个数据库连接池。Druid 具有数据源监控SQL监控、容器集成支持、数据源防火墙等特性,可以帮助我们更好地管理数据库连接。同时,Druid 还提供了很多性能优化功能,如预编译语句缓存、分布式ID生成器等

4.2 使用

4.2.1 pom坐标引入

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>

4.2.2 代码demo

4.2.2.1 连接工具类
public class DruidUtil {
    private static DataSource dataSource = null;
    static {
        Properties props = new Properties();
        try {
            File file = new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\2023-03\\test\\src\\main\\resources\\druid\\druid.properties");//获取文件
            InputStream inputStream = new FileInputStream(file);//获取输入流
            props.load(inputStream);//props需要通过输入流读取一个.Properties文件
            dataSource=DruidDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //从连接池中获取连接
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //释放连接回连接池
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}
4.2.2.2 配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://x.x.x.x:3306/openplatform
username=xx
password=xxx
4.2.2.3 测试demo
@Test
public void testDruid() throws Exception{
    StopWatch stopWatch=new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 10000; i++) {
        //4 获取数据库连接对象  DriverManager依据数据库的不同,管理JDBC驱动
        Connection connection = DruidUtil.getConnection();
        //5 获取操作数据库的Statement对象
        Statement statement=connection.createStatement();
        //6向数据库发送sql
        String sql="select * from t_cust_info limit 1";
        //7 通过statement对象 发送查询请求 拿回结果集
        ResultSet resultSet = statement.executeQuery(sql);
        //8遍历结果集 做一系列操作
        while (resultSet.next()){
            Object id = resultSet.getObject("cust_id");
            //System.out.println(id);
            Object uname = resultSet.getObject("cust_name");
            //System.out.println(uname);
            Object idNo = resultSet.getObject("id_no");
            //System.out.println(idNo);
            // System.out.println("==============");
        }
        C3p0Util.release(connection,statement,resultSet);
    }
    stopWatch.stop();
    double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
    System.out.println(totalTimeSeconds);

}

4.3 性能测试

上面的代码,本地测试100次,平均每次运行耗时:5.58s,可以看到相对自己手动创建链接,时间大幅减少

4.4 常用参数配置

druid github 属性介绍

5. 注意事项:

  1. 上面演示代码连接池都是项目启动的时候,自己在工具类维护的,真实生产环境中,一般都是交给spring管理的
  2. 上面几个测试案例的时间响应,测试场景不完善,只是能直观上比对出来,使用连接池会比手动连接效率高很多,但是几个连接池的响应时间比较,不具有参考意义
  3. 关于数据库连接池配置参数多少合理,这是老生常谈的问题,与具体环境使用相关性很大,客户的并发量等都强相关,没有一套参数放到四海皆准的道理
  4. 如果点到具体的配置类源码里,可以看到其实jar包做的都是从配置文件读固定属性,来组装一个连接池配置类,这个连接池配置类有很多默认属性。
  5. 所以如果哪个属性失效了,需要第一时间看是不是配置的参数没有生效。举一反三,也可以想到,连接池的配置方式可以有xml格式,propertis格式等各种自定义格式,只要保证配置文件正常读取,读取的参数值正常封装就行
  6. 如果和springboot结合,可以看看有没有对应的自动装配类,将连接池的参数配置交给springboot自己装配
  7. 关于几个连接池,公司应该选择哪一个连接池?主要要看具体的业务场景和性能要求。如果需要丰富的管理和监控功能,可以选择 Druid 这样的连接池。如果需要简单易用,可以选择C3PO或DBCP 这样的常规连接池。笔者公司选择的是Druid连接池。

参考文献:
C3p0使用详解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值