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

前言

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

1. 有无连接池管理图示

1.1 没有连接池管理的时候

没有数据库连接池管理的话,每次外部请求过来,请求到我们web层,dao层为每个请求都会创建连接,执行脚本,释放连接,其中dao层和数据库建立连接,底层都是TCP请求,每次都要三次握手,四次挥手。如下如所示:
7.java程序员必知必会类库之数据库连接池_第1张图片

1.2 交给连接池管理

在交给连接池管理连接后,dao层和数据库交互的时候,直接从连接池获取连接。不再自己手动创建销毁连接,如下图所示:
7.java程序员必知必会类库之数据库连接池_第2张图片

1.3 手动创建连接代码demo

1.3.1 pom坐标引入

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.15version>
    <scope>runtimescope>
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.mchangegroupId>
    <artifactId>c3p0artifactId>
    <version>0.9.5.2version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.15version>
    <scope>runtimescope>
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.Driverproperty>
        <property name="jdbcUrl">jdbc:mysql://x.x.x.x:3306/openplatformproperty>
        <property name="user">formoprproperty>
        <property name="password">xxxxxproperty>

        
        
        <property name="acquireIncrement">5property>
        
        <property name="initialPoolSize">10property>
        
        <property name="minPoolSize">10property>
        
        <property name="maxPoolSize">200property>
        
        <property name="maxStatements">50property>
        
        <property name="maxStatementsPerConnection">2property>

    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>   
       
    <property name="acquireIncrement">3property>   
 
       
    <property name="acquireRetryAttempts">30property>   
       
       
    <property name="acquireRetryDelay">1000property>   
       
       
    <property name="autoCommitOnClose">falseproperty>   
       
       
    <property name="automaticTestTable">Testproperty>   
       
       
    <property name="breakAfterAcquireFailure">falseproperty>   
       
       
    <property name="checkoutTimeout">100property>   
       
       
    <property name="connectionTesterClassName">property>   
       
       
    <property name="factoryClassLocation">nullproperty>   
       
       
    <property name="forceIgnoreUnresolvedTransactions">falseproperty>   
       
       
    <property name="idleConnectionTestPeriod">60property>   
       
       
    <property name="initialPoolSize">3property>   
       
       
    <property name="maxIdleTime">60property>   
       
       
    <property name="maxPoolSize">15property>   
       
       
    <property name="maxStatements">100property>   
       
       
    <property name="maxStatementsPerConnection">property>   
       
       
    <property name="numHelperThreads">3property>   
       
       
    <property name="overrideDefaultUser">rootproperty>   
       
       
    <property name="overrideDefaultPassword">passwordproperty>   
       
       
    <property name="password">property>   
       
       
    <property name="preferredTestQuery">select id from test where id=1property>   
       
       
    <property name="propertyCycle">300property>   
       
       
    <property name="testConnectionOnCheckout">falseproperty>   
       
       
    <property name="testConnectionOnCheckin">trueproperty>   
       
       
    <property name="user">rootproperty>   
       
       
    <property name="usesTraditionalReflectiveProxies">falseproperty> 
    default-config>      
c3p0-config>

3 DBCP

3.1 介绍

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

3.2 使用

3.2.1 pom坐标引入

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.15version>
    <scope>runtimescope>
dependency>
<dependency>
    <groupId>commons-dbcpgroupId>
    <artifactId>commons-dbcpartifactId>
    <version>1.4version>
dependency>
<dependency>
    <groupId>commons-poolgroupId>
    <artifactId>commons-poolartifactId>
    <version>1.6version>
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.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.16version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.15version>
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使用详解

你可能感兴趣的:(java程序员必知必会类库,druid,java,数据库连接池,druid,c3p0,dbcp)