数据库连接池
1. 问题
在以往简单的数据库连接和释放资源过程中,每一次获取连接都是向操作系统底层申请连接资源。在申请资源的过程中需要大量的时间,还有可能因为冲突导致失败。不停的申请资源和释放资源,消耗了大量的时间和系统资源,导致程序运行速度变得缓慢。为了解决时间浪费和系统资源浪费,诞生了数据库连接池技术。
2. 连接池概述
- 传统获取连接方式
要完成一个业务,就需要获取一次数据库连接然后释放一次数据库资源。一整个复杂的业务完成,就需要完成TCP的连接、数据库的连接、SQL语句执行和返回结果、数据库关闭连接、TCP关闭连接多个步骤。
如果只是简单的本地任务和并发量不高的数据库RCUD,更容易实现的传统获取连接方式甚至可能会更好一些。
但是如果遇到复杂的网络业务和高并发,传统获取连接的方式带来的巨大的I/O消耗,显著造成数据库负载较高,因为频繁的申请资源和释放资源导致系统需要将性能浪费在处理申请请求和垃圾回收上,繁冗的步骤导致需要快速响应的业务反应较慢。 - 连接池获取连接方式
为了解决频繁的申请资源和释放资源的问题,在程序初始化的时候,通过配置预先向系统申请了一定数量的数据库连接。当一个业务要执行的时候,从预先申请的连接池中拿取一个连接使用。当业务完成后,归还从连接池借用的连接就可以了。实现了可以复用的理念,增加连接的利用率。初始化时一次申请,等到程序结束时,一次性归系统还所有申请的资源。
完美的解决了高并发时对系统资源的压力(尤其是处理器性能和内存空间),降低了数据库的负载,显著提高业务响应,同时也减少了网络的开销。只要关注执行SQL和返回数据就可以了,但是缺点就是实现起来比较麻烦。
3. 实现连接池
- 接口
Java在javax.sql包下提供了一个DataSource的接口。接口提供了一个getConnection()方法从连接池获取连接;提供了close()方法将连接归还从连接池中获取的连接(不是从连接池获取的连接则会归还系统资源)。 - 实现
就如JDBC实现一样,连接池的驱动也由各数据库厂商提供。一般数据库厂商会使用开源的连接池来实现。
4. 创建连接池
市面上主流的开源连接池有:C3P0、Druid、Apache Commons DBCP、Proxool
一般我选择的就是Druid,不仅因为这是阿里推出的开源连接池,更主要的是提供了丰富的功能,支持所有JDBC兼容的数据库,较为完整的中文文档对国内开发者友好。
- 下载驱动包
- 如果是创建的Maven项目,则直接去Maven中央仓库找就可以了:https://mvnrepository.com/artifact/com.alibaba/druid
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency>
- 如果需要手动加载jar包,也去Maven的仓库选一个版本下载:http://repo1.maven.org/maven2/com/alibaba/druid/
- 不要忘记加载数据库驱动jar包。
- 如果是创建的Maven项目,则直接去Maven中央仓库找就可以了:https://mvnrepository.com/artifact/com.alibaba/druid
- 配置配置文件
Druid使用的是properties键值对形式的配置文件,可以任意命名,需要放置到Resource资源路径下,否则无法读取到配置文件。
注意:properties文件中不可使用空格和引号/双引号# 注册驱动 driver=com.mysql.jdbc.Driver # 数据库连接 url=jdbc:mysql://IP地址:端口/数据库 # 用户名 username=用户名 # 密码 password=密码 # 连接池初始申请连接数 initialSize=5 # 连接池最大申请连接数 maxActive=10 # 获取连接超时时间 maxWait=3000
- 获取连接
public static void main(String[] args) throws Exception { // 加载配置文件 Properties ppts = new Properties(); // 获取配置文件字节输入流 InputStream is = druid.class.getClassLoader().getResourceAsStream("druid.properties"); // 将字节流的键值对装载到Properties ppts.load(is); // 获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(ppts); // 获取连接 Connection conn = ds.getConnection(); }
5. 放到工具类中的连接池
因为连接池只需要创建一次就可以,而且因为Druid的加载配置文件和获取连接步骤较为繁琐。所以可以将其写入到工具类中的静态代码中,就可以在程序初始化时自动获取连接创建连接池。当然也可以同时配置多个数据源,只要创建多个DataSource和静态代码块加载配置和初始化连接池即可,获取的时候根据重载的带参getConnection方法即可获取对应的连接,释放时则无需过多的关注区别。但是一定要记得使用完毕以后释放掉连接,否则连接池中的连接会快速消耗殆尽,接着就是获取新连接报错!
public class JDBCUtils {
private static DataSource ds;
static{
try {
// 加载配置文件
Properties ppts = new Properties();
// 获取配置文件字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
// 将字节流的键值对装载到Properties
ppts.load(is);
// 获取连接池对象
ds = DruidDataSourceFactory.createDataSource(ppts);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 释放资源
public static void close(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) {
e.printStackTrace();
}
}
if(conn != null){
try {
// 归还连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 释放资源
public static void close(Statement stmt, Connection conn) {
// 调用三个参数的释放资源,简化代码
close(null, stmt, conn);
}
// 获取连接池
public static DataSource getDataSource() {
return ds;
}
}
6. 从连接池获取连接并进行使用
private static void qryNameById() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
connection = JDBCUtil.getConnection();
String sql = "SELECT <column> FROM <table> WHERE <column> <operator> ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "xxx");
rs = pstmt.executeQuery();
if (rs.next()) {
// TODO to do something
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtil.close(rs, pstmt, conn);
}
}