目录
一、前言
二、原理
三、连接池种类
四、c3p0数据库连接池
1. 连接步骤
2. 连接方式一(不推荐)
3. 连接方式二(推荐)
五、德鲁伊数据库连接池
1. 连接步骤
2. 连接方式
六、Apche_DbUtils工具类
1. 实现思想
2. 底层模拟实现
3. Apche_DbUtils的使用
3.1 基本介绍
3.2 使用示例
七、总结
如果没有
JDBC
或者SQL
基础的小伙伴,可以查看我下边的有关文章:👇👇👇
JDBC(超详细) | JDBC(超详细) |
Mysql | Mysql |
一、前言
在开始本节之前,我们讨论一个问题:为什么需要数据库连接池
❓
🔰传统
JDBC
连接数据库的弊端
:
- 传统的JDBC数据库连接使用
DriverManager
来获取,每次向数据库建立连接的时候都要将Connection
加载到内存中,再验证IP地址
,用户名
和密码
(0.05s ~1s时间)。每次需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源
,容易造成服务器崩溃。- 每一次数据库连接,使用完后都得断开,如果程序出现异常而
未能关闭
,将导致数据库内存泄漏
,最终将导致重启数据库
。- 传统获取连接的方式,不能控制创建的
连接数量
,如连接过多,也可能导致内存泄漏
,MySQL崩溃
。
而解决传统开发
中的数据库连接问题,可以采用数据库连接池
(connection pool)。
二、原理
📌数据库连接池基本介绍
- 预先在
缓冲池
中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去
。 数据库连接池
负责分配
、管理
和释放数据库连接
,它允许应用程序重复使用
一个现有的数据库连接,而不是重新建立
一个。- 当应用程序向连接池请求的连接数
超过最大连接数量
时,这些请求将被加入到等待队列
中。
📌原理图
三、连接池的种类
JDBC的数据库连接池使用javax.sq1.DataSource
来表示,DataSource
只是一个接口
,该接口通常由第三方
提供实现。
💡最常使用的数据库连接池有:
c3p0
数据库连接池:速度相对较慢
,稳定性不错
。DBCP
数据库连接池:速度相对c3p0较快,但不稳定。Proxool
数据库连接池:有监控连接池状态
的功能,稳定性较C3P0差一点BoneCP
数据库连接池:速度快。Druid(德鲁伊)
是阿里提供的数据库连接池,集DBCP、c3p0、Proxool优点于一身的数据库连接池。
我们下边讲解Druid(德鲁伊)
和c3p0
这两种数据库连接池。🔻
四、c3p0 数据库连接池
1. 连接步骤
2. 连接方式一(不推荐)
我们方式一
读取相关的属性值和JDBC(超详细)这篇文章中的方式一样:都是用properties
配置文件。
📌properties配置文件
driverClassNam=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/goodsSystem?serverTimezone=UTC
username=用户名
password=密码
📌示例代码
// 方式一
@Test
public void c3p0_01() throws Exception{
// 1.创建数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
// 2.通过配置文件获取相关连接的信息
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
// 3.读取相关的属性值
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
// 4.给数据源设置相关的属性
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxPoolSize(50);
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("用时" + (end - start));
}
👆这里还测试了获取5000次连接所需要的时间。
3. 连接方式二(推荐)
方式二
我们使用XML
文件来配置相关信息。
📌XML配置文件
<c3p0-config>
<!--数据源名称代表连接池-->
<named-config name="jl">
<!--驱动类-->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!--url-->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC</property>
<!--用户名-->
<property name="user">用户名</property>
<!--密码-->
<property name="password">密码</property>
<!--每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!--初始的连接数-->
<property name="initialPoolSize">10</property>
<!--最小的连接数-->
<property name="minPoolSize">5</property>
<!--最大连接数-->
<property name="maxPoolSize">50</property>
<!--可连接的最多的命令对象数-->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
⭕这里将XML配置文件
放到src
根目录下。
📌示例代码
@Test
public void c3p0_02() throws SQLException {
// 此参数和XML配置文件中的<named-config name="jl">对应。
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("jl");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start));
}
👆这里也测试了获取5000次连接所需要的时间。
五、德鲁伊数据库连接池
1. 连接步骤
2. 连接方式
📌properties配置文件
#key=value
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/goodsSystem?serverTimezone=UTC
username=用户名
password=密码
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait time (5000 mil seconds)
maxWait=5000
📌示例代码
@Test
public void druid_01() throws Exception {
Properties properties = new Properties(); //创建Properties对象
properties.load(new FileReader("src\\druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); //创建数据库连接池
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Connection connection = dataSource.getConnection(); // 获得连接池内的连接
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start));
}
👆这里测试了获取10000000次连接所需要的时间。
💡说明:
- 使用
德鲁伊连接池
,关闭的时候使用的close()
是Druid
的方法,是将当前连接的引用断开,而不是真正的断开连接
。德鲁伊
得到的connection
运行类型是:class com.alibaba.druid.pool.DruidPooledConnection
。传统方式
得到的connection
运行类型是:class com.mysql.cj.jdbc.ConnectionImpl
。c3p0
得到的connection
运行类型是:class com.mchange.v2.c3p0.impl.NewProxyConnection
。
六、Apche_DbUtils工具类
1. 实现思想
在Java中
万事万物皆对象
,所以每条数据就是一个对象
,而数组
就是和该对象对应类型
的数组。
使用此种方法是因为:
结果集是和connection
关联的,如果关闭连接就不能使用结果集
。- 结果集不利于数据的管理。
2. 底层模拟实现
这里只是演示一下它这个工具类的
基本原理
,并不是真正官方的实现 ‼👇
@Test
public void apche_01() throws Exception{
Properties properties = new Properties();
properties.load(new FileReader("src\\druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
String sql = "select * from `actor`";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
ArrayList<Actor> list = new ArrayList<>(); // Actor类是sql查询的每条数据对应的类
while(resultSet.next()){
String name = resultSet.getString(1);
Integer salary = resultSet.getInt(2);
list.add(new Actor(name,salary));
}
connection.close();
resultSet.close();
preparedStatement.close();
for (Actor l:list) {
System.out.println(l);
}
}
3. Apche_DbUtils的使用
3.1 基本介绍
commons-dbutils
是 Apache组织提供的一个开源JDBC工具类库
,它是对JDBC的封装
,使用dbutils能极大简化
JDBC编码的工作量。
在工具类
中有一个类和一个接口比较重要:
QueryRunner类
:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理使用QueryRunner类实现查询。ResultSetHandler接口
:该接口用于处理java.sql.ResultSet
,将数据按要求转换为另一种形式。
📑ResultSetHandler的实现子类
实现子类 | 作用 |
---|---|
ArrayHandler | 把结果集中的第一行数据转成对象数组 |
BeanListHandler | 把结果集中的每一行数据都转成一个数组,再存放到List中 |
BeanHandler | 将结果集中的第一行数据封装到一个对应的JavaBean实例中。 |
ColumnListHandler | 将结果集中某一列的数据存放到List中。 |
KeyedHandler(name) | 将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key. |
MapListHandler | 将结果集中的每一行数据都封装到一个Map里,然后再存放到List |
MapHandler | 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。 |
3.2 使用示例
📌查询演示
@Test
public void apche_02(){
Connection connection = null;
List<Actor> list = null;
try {
connection = JdbcUtilByDruid.getConnection();
String sql = "select * from actor where name = ?";
QueryRunner queryRunner = new QueryRunner();
list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), "小王"); //query方法就是执行sql语句,得到resultset将其封装到ArrayList集合中
// new BeanListHandler<>(Actor.class):整个参数是将resultset转换为Actor对象,最后将其封装到ArrayList中(其中使用到了反射机制)
// 最后的“小王”,是给sql语句中的?赋值。
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtilByDruid.close(null,null,connection);
}
for (Actor actor: list) {
System.out.println(actor);
}
}
📌DML演示
@Test
public void dml() throws Exception{
Connection connection = JdbcUtilByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
// String sql = "insert into actor values(?,?)"; // 增
// String sql = "delete from actor where salary = ?"; // 删
String sql = "update actor set name = ? where name = ?"; // 改
int update = queryRunner.update(connection,sql,"小赵","小王");
System.out.println(update>0 ? "成功" : "失败");
JdbcUtilByDruid.close(null,null,connection);
}
}
💡关于上述代码的几点说明:
JdbcUtilByDruid.getConnection();
是通过我们自己封装的一个工具类来获取的。- 上边只演示了查询数据是
单个对象
的。其余种类的转换,读者可以自行验证。- 为什么没有手动关闭
ResultSet
和Statement
。这是因为:在调用query()
的时候它自己关闭了Resultset
和Preparedstatement
。
🔻query()底层源码
private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
if (conn == null) {
throw new SQLException("Null connection");
} else if (sql == null) {
if (closeConn) {
this.close(conn);
}
throw new SQLException("Null SQL statement");
} else if (rsh == null) {
if (closeConn) {
this.close(conn);
}
throw new SQLException("Null ResultSetHandler");
} else {
PreparedStatement stmt = null;
ResultSet rs = null;
Object result = null;
// 底层分别创建了PreparedStatement。ResultSet(结果集),result(结果数字)
try {
stmt = this.prepareStatement(conn, sql);
this.fillStatement(stmt, params); //给sql语句设置参数(给?,赋值)
rs = this.wrap(stmt.executeQuery()); // 执行sql语句
result = rsh.handle(rs); // 对结果集进行处理
} catch (SQLException var33) {
this.rethrow(var33, sql, params);
} finally {
try {
this.close(rs); // 关闭结果集
} finally {
this.close(stmt); // 关闭PreparedStatement
if (closeConn) {
this.close(conn);
}
}
}
return result; //最后返回结果
}
}
七、总结
以上就是这节的全部内容。主要讲了
德鲁伊
和c3p0
这两种数据库连接池。其余的数据库连接池以后用到,也会总结进来。😜
最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏