数据库连接池技术
1. 数据库连接池概述
1.1 什么是数据库连接池?
-
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
-
数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
-
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
-
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
- 工作原理
1.2 为什么要使用数据库连接池?
-
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
-
这种模式开发,存在的问题:
-
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
-
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
-
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
-
1.3 数据库连接池技术有哪些优点?
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源。
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
2. 常见的数据库连接池(DBCP、C3P0、Druid)
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
- D a t a S o u r c e 通常被称为数据源 \textcolor{red}{DataSource 通常被称为数据源} DataSource通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
- DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
- 特别注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接, 但 并没有关闭数据库的物理连接,它仅仅把数据库连接释放 \textcolor{red}{并没有关闭数据库的物理连接,它仅仅把数据库连接释放} 并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
3. Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
3.1 src下的配置文件:【druid.properties】
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false
username=root
password=abc123
//初始化连接数
initialSize=10
//最大连接数
maxActive=20
//最大等待时间(超时时间)
maxWait=1000
3.2 通过druid获取数据库连接
package com.jdbc.druid;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
/**
*数据库连接池演示
*@author 18305
*
*/
public class DruidTest {
public static void main(String[] args) throws Exception {
//1.导入jar包和源码 注意版本
//2.定义配置文件(druid.properties)
//3.加载配置文件
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
//4.获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
//5.获取对象的数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
-
详细配置参数
配置 缺省 说明 name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto username 连接数据库的用户名 password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 maxActive 8 最大连接池数量 maxIdle 8 已经不再使用,配置了也没效果 minIdle 最小连接池数量 maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun minEvictableIdleTimeMillis connectionInitSqls 物理连接初始化的时候执行的sql exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
4. 进阶实战
4.1 建立数据表(tb_brand)并插入数据
CREATE TABLE tb_brand(
`id` int NOT NULL AUTO_INCREMENT,
`brand_name` varchar(20) NULL,
`company_name` varchar(20) NULL,
`ordered` int NULL,
`description` varchar(100) NULL,
`status` int NULL,
PRIMARY KEY (`id`)
)
INSERT INTO tb_brand(`brand_name`, `company_name`, `ordered`, `description`, `status`)
VALUES ('OPPOReno8', 'OPPO', 5, '照亮你的美!', 0)
INSERT INTO tb_brand(`brand_name`, `company_name`, `ordered`, `description`, `status`)
VALUES ('IQOO10', 'VIVO', 100, '充电五分钟,游戏两小时', 1)
INSERT INTO tb_brand(`brand_name`, `company_name`, `ordered`, `description`, `status`)
VALUES ('xiaomi11', '小米科技有限公司', 50, 'Are you ok!', 1)
4.2 创建实体类Brand
package com.jdbc.druid;
public class Brand {
private Integer id;
private String brandName;
private String companyName;
private Integer ordered;
private String description;
private Integer status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer ststus) {
this.status = ststus;
}
@Override
public String toString() {
return "Brand [id=" + id + ", brandName=" + brandName + ", companyName=" + companyName + ", ordered=" + ordered
+ ", description=" + description + ", status=" + status + "]";
}
}
4.3 建立测试类 进行查询操作(返回一条、多条记录)
package com.jdbc.druid;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Properties;
import javax.sql.DataSource;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class BrandTest {
/**
* 1.查询一条
* 参数不需要
* 结果保存到Brand
* @throws Exception
*/
@Test
public void testSelectOne() throws Exception {
//1.获取数据库连接
//1.1加载配置文件
Properties pros = new Properties();
pros.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
//1.2获取连接池的对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
//1.3获取对象的数据库连接
Connection connection = dataSource.getConnection();
//2.定义sql语句
String sql = "select * from tb_brand where id = 2";
// 3.获取PreparedStatement对象
PreparedStatement ps = connection.prepareStatement(sql);
// 4.执行sql
ResultSet resultSet = ps.executeQuery();
// 5.处理数据
Brand brand = new Brand();
while (resultSet.next()) {
// 5.1获取数据
int id = resultSet.getInt("id");
String brand_name = resultSet.getString("brand_name");
String company_name = resultSet.getString("company_name");
int ordered = resultSet.getInt("ordered");
String description = resultSet.getString("description");
int status = resultSet.getInt("status");
// 5.2封装Brand对象
brand.setId(id);
brand.setBrandName(brand_name);
brand.setCompanyName(company_name);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
}
System.out.println(brand);
//6.释放资源
connection.close();
ps.close();
resultSet.close();
}
/**
* 2.查询所有
* 参数不需要
* 结果保存到List<Brand>
* @throws Exception
*/
@Test
public void TestSelectAll() throws Exception {
// 1获取数据库连接
// 1.1加载配置文件
Properties pros = new Properties();
pros.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
// 1.2获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
// 1.3获取对象的数据库连接
Connection connection = dataSource.getConnection();
// 2.定义sql语句
String sql = "Select * from tb_brand";
// 3.获取PreparedStatement对象
PreparedStatement ps = connection.prepareStatement(sql);
// 4.执行sql
ResultSet resultSet = ps.executeQuery();
// 5.处理结果到list
ArrayList<Brand> brandList = new ArrayList<Brand>();
Brand brand = new Brand();
while (resultSet.next()) {
// 5.1获取数据
int id = resultSet.getInt("id");
String brand_name = resultSet.getString("brand_name");
String company_name = resultSet.getString("company_name");
int ordered = resultSet.getInt("ordered");
String description = resultSet.getString("description");
int status = resultSet.getInt("status");
// 5.2封装Brand对象
brand.setId(id);
brand.setBrandName(brand_name);
brand.setCompanyName(company_name);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
// 5.3装载集合
brandList.add(brand);
}
System.out.println(brandList);
//6.释放资源
connection.close();
ps.close();
resultSet.close();
}
}
testSelectOne()单元测试方法返回:
八月 20, 2022 10:19:52 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
Brand [id=2, brandName=IQOO10, companyName=VIVO, ordered=100, description=充电五分钟,游戏两小时, status=1]
testSelectAll()单元测试方法返回:
八月 20, 2022 10:39:21 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
[Brand [id=1, brandName=OPPOReno8, companyName=OPPO, ordered=5, description=照亮你的美!, status=0],
Brand [id=2, brandName=IQOO10, companyName=VIVO, ordered=100, description=充电五分钟,游戏两小时, status=1],
Brand [id=3, brandName=xiaomi11, companyName=小米科技有限公司, ordered=50, description=Are you ok!, status=1]]
4.4 建立测试类,执行数据增删改操作
package com.jdbc.druid;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;
import javax.sql.DataSource;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class TestUpdate {
/**
* -->添加
* 1.需要参数 2.结果是布尔类型
*/
@Test
public void Add() throws Exception {
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1;
String description = "绕地球一圈";
int status = 1;
// 1.获取数据库连接connection
// 1.1加载配置文件
Properties pros = new Properties();
pros.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
// 1.2获取数据库连接池的对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
// 1.3获取对象的数据库连接
Connection connection = dataSource.getConnection();
// 2.定义sql语句
String sql = "insert into tb_brand(brand_name,company_name,ordered,description,status)values(?,?,?,?,?)";
// 3.获取PreparedStatement对象
PreparedStatement ps = connection.prepareStatement(sql);
// 4.设置参数
ps.setString(1, brandName);
ps.setString(2, companyName);
ps.setInt(3, ordered);
ps.setString(4, description);
ps.setInt(5, status);
// 5.执行sql
int i = ps.executeUpdate(); // 影响行数
// 6.处理结果
if (i > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加时便");
}
// 7.释放资源
connection.close();
ps.close();
}
/**
* -->修改
* 1.需要参数 2.结果是布尔类型
* @throws Exception
*/
@Test
public void update() throws Exception {
String brandName = "OPPOReno4";
String companyName = "OPPO";
int ordered = 20;
String description = "照亮你的美!";
int status = 6;
// 1.获取数据库的连接
// 1.1加载配置文件
Properties pros = new Properties();
pros.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
// 1.2获取数据库连接池的对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(pros);
// 1.3获取对象的数据库连接
Connection connection = dataSource.getConnection();
//System.out.println(connection); 测试数据库已连接
// 2.定义sql语句
String sql = "update tb_brand set brand_name=?,company_name=?,ordered=?,description=?,status=? where id=?;";
// 3.获取PreparedStatement对象
PreparedStatement ps = connection.prepareStatement(sql);
// 4.设置参数
ps.setString(1, brandName);
ps.setString(2, companyName);
ps.setInt(3, ordered);
ps.setString(4, description);
ps.setInt(5, status);
ps.setInt(6, 1);
// 5.执行sql
int i = ps.executeUpdate(); // 影响行数
// 6.处理结果
if (i > 0) {
System.out.println("修改成功");
} else {
System.out.println("修改失败");
}
// 7.关闭资源
connection.close();
ps.close();
}
}
Add()单元测试方法返回:
八月 20, 2022 10:11:07 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
添加成功
update()单元测试方法返回:
八月 20, 2022 10:11:55 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
修改成功