目录
5000 次连接数据库问题
package Test;
import java.sql.Connection;
/**
* @Author: Gin
* @Description: 传统方式创建 5000 次数据库连接
* @Modified By: Gin
* @Date: Created in 17:02 2021/9/14
*/
public class Test15 {
public static void main(String[] args) {
// 计算 5000 次连接数据库耗时多少
long start = System.currentTimeMillis();
System.out.println("开始连接:");
for (int i = 0; i < 5000; i++){
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 做一些工作:比如得到 PreparedStatement,发送 SQL 语句等
// ......
// 关闭
JDBCUtils.close(null, null, connection);
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+ (end - start) +" ms"); // 耗时:27190 ms
}
}
- 传统的
JDBC
数据库连接使用DriverManager
来获取,每次向数据库建立连接的时候都要将Connection
加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)
。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。 - 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,
MySQL
崩溃。 - 解决传统开发中的数据库连接问题,可以采用
数据库连接池技术(connection pool)。
数据库连接池
数据库连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
- 在数据库连接池技术中,
close()
不是真的断掉连接,而是把connection 连接对象
放回连接池中。 - 画出示意图
数据库连接池种类
JDBC
的数据库连接池使用javax.sql.DataSource
来表示,DataSource
只是一个接口,该接口通常由第三方提供实现C3P0
数据库连接池,速度相对较慢,稳定性不错(hibernate、spring)DBCP
数据库连接池,速度相对C3P0
较快,但不稳定Proxool
数据库连接池,有监控连接池状态的功能,稳定性较C3P0
差一点BoneCP
数据库连接池,速度快Druid(德鲁伊)
是阿里提供的数据库连接池,集DBCP、C3PO、Proxool
优点于一身的数据库连接池【使用最多】
<!-- C3P0 依赖 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
通过 D3P0 方式获取一个数据库连接
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @Author: Gin
* @Description:
* @Modified By: Gin
* @Date: Created in 18:41 2021/9/14
*/
public class Test0 {
// 方式 1:相关参数,在程序中指定 url、user、password 等
@Test
public void testC3P0_1() throws Exception {
// 1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
// 2. 通过配置文件 mysql.properties 获取配置信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 读取相关参数
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
// 给数据源 comboPooledDataSource 设置相关参数
// 注意:连接管理是由 comboPooledDataSource 来管理的
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
// 设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
// 设置最大连接数
comboPooledDataSource.setMaxPoolSize(50);
// 获取一个连接
// 这个方法就是从 DataSource 接口实现过来的
Connection connection = comboPooledDataSource.getConnection();
System.out.println("OK~");
// 关闭连接
connection.close();
}
}
测试获取 5000 次连接的效率
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @Author: Gin
* @Description:
* @Modified By: Gin
* @Date: Created in 18:41 2021/9/14
*/
public class Test0 {
// 方式 1:相关参数,在程序中指定 url、user、password 等
@Test
public void testC3P0_1() throws Exception {
// 1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
// 2. 通过配置文件 mysql.properties 获取配置信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 读取相关参数
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
// 给数据源 comboPooledDataSource 设置相关参数
// 注意:连接管理是由 comboPooledDataSource 来管理的
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
// 设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
// 设置最大连接数
comboPooledDataSource.setMaxPoolSize(50);
// 测试耗时多少毫秒
System.out.println("开始连接:");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
// 获取一个连接
// 这个方法就是从 DataSource 接口实现过来的
Connection connection = comboPooledDataSource.getConnection();
// 关闭连接
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+ (end - start) + " ms"); // 耗时:860 ms
}
}
相比于传统方式的 27190 ms,C3P0 数据库连接池连接方式效率要高得多
C3P0 的第二种方式:基于配置文件模板来完成
c3p0-config.xml【.xml文件名必须是这样】
<c3p0-config>
<!-- 数据源名称,代表连接池 -->
<named-config>Gin</named-config>
<!--使用默认的配置读取数据库连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name="initialPoolSize">10</property>
<!--最大的连接数量-->
<property name="maxPoolSize">50</property>
<!-- 每次增长的连接数 -->
<property name="acquireIncrement">5</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 没每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
<!--超时时间-->
<property name="checkoutTimeout">3000</property>
</default-config>
</c3p0-config>
// 第二种方式:使用配置文件模板来完成
// 添加 C3P0 的 jar 包
// 将 c3p0-config.xml 放在 src 目录下即可
@Test
public void testC3P0_2() throws SQLException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("Gin");
// 测试连接 5000 次
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();
// System.out.println("连接ok");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+ (end - start) +" ms"); // 耗时:990 ms
}
德鲁伊连接池
druid 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
druid.properties 配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai\
&rewriteBatchedStatements=true
username=root
password=root
initialSize=10
minIdle=5
maxActive=50
maxWait=5000
用 Druid 实现获取 500000 次 MySQL 连接
package Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @Author: Gin
* @Description:
* @Modified By: Gin
* @Date: Created in 19:49 2021/9/14
*/
public class Test17 {
@Test
public void testDruid() throws Exception {
// 添加 druid 的 jar 包
// 在 src 目录下添加 druid.properties 文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
// 创建一个指定参数的数据库连接池,Druid 连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 测试 500000 次获取连接
System.out.println("开始连接:");
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+ (end - start) +" ms");
}
}
连接量越大,Druid 的性能体现的越明显
使用 Druid 改进 封装数据库连接 操作
package Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
/**
* @Author: Gin
* @Description:
* @Modified By: Gin
* @Date: Created in 20:20 2021/9/14
*/
public class JDBCUtilsByDruid {
private static DataSource dataSource;
// 在静态块中完成对 dataSource 的初始化
static {
Properties properties = new Properties();
try{
properties.load(new FileInputStream("src\\druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
// 获取 Connection 连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 放回连接
public static void close(ResultSet resultSet, Statement statement, Connection connection){
// 判断是否为空
try{
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
}catch (Exception e){
// 将编译异常转为运行异常
throw new RuntimeException(e);
}
}
}
在 Test18
中测试通过 Druid 封装的数据库连接操作
package Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @Author: Gin
* @Description:
* @Modified By: Gin
* @Date: Created in 20:32 2021/9/14
*/
public class Test18 {
// 测试 Druid 实现的 封装数据库操作
public static void main(String[] args) {
// 连接
Connection conn = null;
// SQL 语句
String sql = "select `name`, password from admin where `name` = ?";
PreparedStatement ps = null;
ResultSet rs = null;
try{
// conn = JDBCUtils.getConnection();
// 运行类型:class com.mysql.cj.jdbc.ConnectionImpl
// System.out.println(conn.getClass());
conn = JDBCUtilsByDruid.getConnection();
// 运行类型:class com.alibaba.druid.pool.DruidPooledConnection
System.out.println(conn.getClass());
ps = conn.prepareStatement(sql);
// 给问号赋值
ps.setString(1, "Sherry");
rs = ps.executeQuery();
while(rs.next()){
// String name = rs.getString("name");
// String password = rs.getString("password");
String name = rs.getString(1);
String password = rs.getString(2);
System.out.println(name + "\t" + password);
}
}catch(SQLException e){
e.printStackTrace();
}finally {
JDBCUtilsByDruid.close(rs, ps, conn);
// JDBCUtils.close(rs, ps, conn);
}
}
}
JDBCUtils 与 JDBCUtilsByDruid 的 close() 方法
JDBCUtils 的 conn
和 JDBCUtilsByDruid 的 conn
运行类型的不同可知:
JDBCUtils 的 conn
的 close()
方法是调用了 MySQL 底层的关闭流方法
JDBCUtilsByDruid 的 conn
的 close()
方法是调用的 Druid 底层的关闭方法
二者底层的具体实现不一样。