连接池介绍
现实中"池"的概念
施工配套资源比如建筑材料,施工工具,会是提前进行工程预算采购好放到库房,需要的时候就地取,如果某些材料不够了提早发现提前采购进行补充,这是比较高效的做法。
在计算机中也是一样的,把"池"改叫仓库就好理解了,连接池也叫连接仓库,就是对应用程序数据库的连接进行存储的地方。原理和盖楼是一样的,数据库连接是我们应用程序和数据库服务器之间通讯的物理的网络通信桥梁,建立数据库连接是相对比较耗费资源也耗费时间。如果应用程序每次执行SQL之前都需要创建连接这样效率就会下降。换一个思路,在应用程序启动的时候就预先创建好连接,当应用程序需要数据库连接的时候不是直接创建,而是从连接池中提取,连接池就看作是现实中的物资仓库,一个个应用程序的连接就相当于仓库中的物资,有谁需要用就直接从仓库中提取,用完了再还到仓库中,还可能再分配给其他人使用
哪个应用程序需要使用数据库连接不是自己创建,而是连接池给分配和回收。
通过连接池可以对我们程序创建连接的总量进行控制,可以很大程度上避免由于连接过渡浪费导致程序崩溃的情况。
阿里巴巴Druid连接池
- Druid是阿里巴巴开源连接池组件,是目前世界最好的连接池之一
- Druid对数据库连接进行有效管理与重用,最大化程序程度执行效率
- 连接池负责创建管理连接,程序只负责取用与归还
Druid连接池的配置与使用
使用Druid连接池开发的步骤
- 在当前工程引入jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
- 在src目录下创建druid-config.properties文件,用来保存jdbc执行时必要的信息
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/zl_cms?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=root
initialSize=10
maxActive=20
- 编码
//1. 加载属性文件
//2. 获取DataSource数据源对象
//3. 创建数据库连接
package com.zl.jdbc.sample;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.zl.jdbc.common.DbUtils;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Properties;
/**
* Druid连接池配置与使用
*/
public class DruidSample {
public static void main(String[] args) {
//1. 加载属性文件
Properties properties = new Properties();
String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
try {
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
properties.load(new FileInputStream(propertyFile));
} catch (Exception e) {
e.printStackTrace();
}
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
//2. 获取DataSource数据源对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//3. 创建数据库连接
pstmt = conn.prepareStatement("select * from employee_info limit 0,10");
rs = pstmt.executeQuery();
while (rs.next()) {
Integer id = rs.getInt(1);
String full_name = rs.getString("full_name");
BigDecimal daily_wage = rs.getBigDecimal("daily_wage");
Date entry_date = rs.getDate("entry_date");
Timestamp create_time = rs.getTimestamp("create_time");
System.out.println("full_name:"+full_name+";daily_wage:"+daily_wage+
";entry_date:"+entry_date+";create_time:"+create_time);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
/**
* 不使用连接池:conn.close()关闭连接
* 使用连接池:conn.close()将连接回收至连接池
*/
DbUtils.closeConnection(rs,pstmt,conn);
}
}
}
initialSize=10 //初始化时创建连接数
maxActive=20 //创建连接最大数
启动项目运行到断点处观察数据库连接数量
测试最大连接数量
配置文件中maxActive=20,
- 在程序中创建20个连接
这个效果验证了连接池初始的连接数量不够了连接池会自动为我们扩展新的连接直到允许的上限
- 创建21个连接时
此时程序卡死不动了,回到监控界面,数据库已经创建了20个数据库连接,第21个因为获取不到有效的连接,就一直处于等待的状态。由于一直没有其他的连接被连接池回收所以一直拿不到新的连接。
这也是我们在配置数据库连接池中非常重要的点。设置连接池最大连接数时要测算在程序最忙碌的时候会同时有多少数据库连接,最大的连接数量一定要超过这个数,这样才能保证我们的应用程序不会因为这个问题造成阻塞。
小技巧
很多高级工程师和架构师喜欢将初始的数量和最大的数量保持一致。因为在日常工作时,应用程序最好在一开始就把所有的连接和资源都分配好,用户进来之后直接获取现成的资源,避免重新创建资源的情况。这样对于我们整体的程序管理和性能都是有帮助的。
C3P0连接池
国内一般2014年之前历史项目使用C3P0比较多,随着技术的发展阿里巴巴的开源产品不断成熟,2014年以后尤其这几年,阿里的Druid连接池用的更多。
C3P0官网
C3P0使用
- 项目引入jar包
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.19</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
- 在src目录下添加配置文件c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/zl_cms?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池初始连接数量 -->
<property name="initialPoolSize">10</property>
<!--最大连接数量-->
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
- 编码
package com.zl.jdbc.sample;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.zl.jdbc.common.DbUtils;
import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.*;
import java.util.Date;
public class C3P0Sample {
public static void main(String[] args) {
//1. 加载配置文件
//2. 创建DataSource
//创建DataSource时会默认的找到src目录下的c3p0-config.xml这个配置文件
DataSource dataSource = new ComboPooledDataSource();
//3. 得到数据库连接
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement("select * from employee_info limit 0,10");
rs = pstmt.executeQuery();
while (rs.next()) {
Integer id = rs.getInt(1);
String full_name = rs.getString("full_name");
BigDecimal daily_wage = rs.getBigDecimal("daily_wage");
Date entry_date = rs.getDate("entry_date");
Timestamp create_time = rs.getTimestamp("create_time");
System.out.println("full_name:" + full_name + ";daily_wage:" + daily_wage +
";entry_date:" + entry_date + ";create_time:" + create_time);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
/**
* 不使用连接池:conn.close()关闭连接
* 使用连接池:conn.close()将连接回收至连接池
*/
DbUtils.closeConnection(rs,pstmt,conn);
}
}
}
Commons DBUtils
Apache Commons DBUtils是Apache提供的开源JDBC工具类库,是对JDBC的简单封装,学习成本极低。使用commons-dbutils可以极大简化JDBC编码工作量。
- 引入依赖
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
代码
package com.zl.jdbc.sample;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.zl.jdbc.common.DbUtils;
import com.zl.jdbc.employee.entity.EmployeeInfoDO;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
public class DBUtilsSample {
public static void main(String[] args) {
query();
update();
}
private static void query(){
Properties properties = new Properties();
String propertyFile = DBUtilsSample.class.getResource("/druid-config.properties").getPath();
try{
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
properties.load(new FileInputStream(propertyFile));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//利用Apache DbUtils大幅简化了数据的提取过程
QueryRunner qr = new QueryRunner(dataSource);
List<EmployeeInfoDO> list = qr.query("select * from employee_info limit ?,10",
new BeanListHandler<>(EmployeeInfoDO.class), //自动将结果转换为实体类
new Object[]{10});
for (EmployeeInfoDO emp : list) {
System.out.println(emp.getFull_name());
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void update(){
Properties properties = new Properties();
String propertyFile = DBUtilsSample.class.getResource("/druid-config.properties").getPath();
Connection conn = null;
try {
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
properties.load(new FileInputStream(propertyFile));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
conn = dataSource.getConnection();
conn.setAutoCommit(false);
String sql1 = "update employee_info set daily_wage=daily_wage+100 where id=?";
String sql2 = "update employee_info set daily_wage=daily_wage+50 where id=?";
QueryRunner qr = new QueryRunner();
qr.update(conn,sql1,new Object[]{11});
qr.update(conn,sql2,new Object[]{12});
conn.commit();
}catch (Exception e){
e.printStackTrace();
try {
if(conn != null && !conn.isClosed()) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
if(conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用了commons-dbutils在执行qr.query之前会自动创建连接关闭连接,不需要我们手动开启和关闭。大幅简化了我们对数据的提取过程。