文章目录
22【数据库连接池】
1.1 连接池概述
我们使用JDBC操作数据库时,必须首先先建立一个Connection网络连接,使用完毕之后再将这个连接关闭释放对应的资源,连接对象需要不停的创建,不停的关闭。而创建/关闭一个网络连接是非常消耗资源的一个操作,我们希望创建出来的连接使用完毕后不关闭,而是交给某个容器进行管理,等到下次使用的时候再从容器中拿出来;这个容器就是我们所说的连接池!
Tips:连接池的思想和我们之前学习过的线程池的概念是一模一样的,只不过线程池里面存储的都是线程,而连接池里面存储的都是Connection连接;
- 没有使用连接池的示例图:
-
连接对象的使用问题:
- 1)数据库创建连接通常需要消耗相对较多的资源,创建时间也较长,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,这样数据库连接对象的使用率低。
- 2)假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出
-
连接池需要解决两个问题
- 1)提高创建连接对象的速度
- 2)提高连接对象的使用率,每个连接对象应该重复使用
使用连接池的情况:
1.2 连接池的使用
1.2.1 DataSource接口
在JDK中提供有一个连接池的顶层接口,但JDK本身并没有具体实现,由第三方厂商来实现;这里也是采用面向对象来进行解耦操作;
- 数据源接口中的方法:
DataSource接口中的方法 | 描述 |
---|---|
Connection getConnection() | 从连接池中得到一个连接对象 |
- 常用连接池参数
常用参数 | 描述 |
---|---|
初始连接数 | 连接池创建的时候,一开始在连接池中有多少个连接对象 |
最大连接数 | 连接池中最多可以创建多少个连接对象 |
最长等待时间 | 如果连接池中所有的连接对象都在使用,新来的用户等待多久以后抛出异常。单位毫秒 |
最长空闲回收时间 | 如果某个连接对象长时间没有用户使用,多久以后进行回收。不是所有的连接池都支持。 |
需要注意的是,这些参数并不是JDK中规定的,而是我们认为一个连接池中应该要具备的一些公共参数,而且JDK中并没有对连接池进行实现,连接池具体会有那些参数可以设置这个JDK也并没有进行规范,而是交给其他的第三方厂商来自行决定,我们要根据使用的连接池厂商不同来具体的设置连接池参数;
1.2.2 自定义连接池
1)创建数据源MyDataSource类:
- 接口:DataSource,包含getConnection()方法,抛出SQLException
- 类:MyDataSourceDataSource接口
- 成员对象:private static LinkedList pool
用于存放连接对象,相当于连接池。 - 成员变量:initPoolSize初始连接数5,currSize当前连接数0,maxPoolSize最大连接数10
- 私有成员方法: Connection
createConnection(),返回一个连接对象,每创建一个currSize加1. - 构造方法:在构造方法中调用方法创建5个连接对象放在集合中,添加到集合末尾。
- 公有方法:Connection getConnection()方法,三种情况选其中一种。
- 如果当前连接池中有连接对象(即size大于0),则直接从集合中removeFirst()得到一个连接
- 如果当前连接池没有连接对象(即size小于0),并且连接数小于最大连接数,调用createConnection()创建连接返回。
- 如果当前连接数已经等于最大连接数,则抛出SQLException异常。
- 公有方法:releaseConnection(Connection conn),把连接对象addLast()放回池中。
2)代码实现
package com.dfbz.pool;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
/\*\*
\* @author lscl
\* @version 1.0
\* @intro: 这是第三方厂商实现连接池的类
\*/
public class MyDataSource implements DataSource {
//创建连接池的几个属性
private int initPoolSize = 5; //初始连接数
private int maxPoolSize = 10; //最大连接数
private int currSize = 0; //当前已经创建了多少个连接对象,计数的作用
//创建一个集合,模拟连接池
private LinkedList<Connection> pool = new LinkedList<>();
//在构造方法中创建好初始的连接数
public MyDataSource() {
for (int i = 0; i < initPoolSize; i++) {
//创建一个连接对象,并且加到池中最后一个
pool.addLast(createConnection());
}
}
//写一个方法创建连接对象
private Connection createConnection() {
try {
//连接数加1
currSize++;
return DriverManager.getConnection("jdbc:mysql:///test?useSSL=true","root","admin");
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/\*\*
\* 得到一个连接对象
\* @return
\* @throws SQLException
\*/
@Override
public Connection getConnection() throws SQLException {
//如果连接池中有连接对象,直接返回即可
if (pool.size() > 0) {
//从连接池中取一个元素,返回
return pool.removeFirst();
}
//如果连接池中没有对象了,但当前连接数小于最大连接数的
else if (pool.size() == 0 && currSize < maxPoolSize) {
return createConnection(); //再创建一个新的连接对象
}
//如果已经到达最大连接数
else {
throw new SQLException("已经到达最大连接数:" + maxPoolSize);
}
}
/\*\*
\* 关闭连接的方法
\*/
public void releaseConnection(Connection conn) {
//将连接对象放回到连接池中
pool.addLast(conn);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
4)测试类使用数据源类:
- 创建MyDataSource对象
- 在类中测试看能否得到1连接对象。
- 先从连接池中拿10个输出,再拿11个输出,等待2秒出现异常。
- 当i==5时,释放其中一个对象,则可以得到11个连接对象,其中有一个连接是重用的。
- 代码:
package com.dfbz.demo;
import com.dfbz.pool.MyDataSource;
import java.sql.Connection;
/\*\*
\* @author lscl
\* @version 1.0
\* @intro:
\*/
public class Demo01\_MyDataSource {
public static void main(String[] args) throws Exception {
//创建数据源(默认初始化10个连接)
MyDataSource ds = new MyDataSource();
//从数据源中得到10个连接对象
for (int i = 1; i <= 10; i++) {
Connection conn = ds.getConnection();
System.out.println("得到第" + i + "个连接" + conn);
if (i == 5) {
// 放到连接池中
ds.releaseConnection(conn);
}
}
}
}
1.2 Druid连接池
1.2.1 Druid简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过多年生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid
1.2.2 Druid常用的配置参数
参数 | 说明 |
---|---|
url | 连接字符串 |
username | 用户名 |
password | 密码 |
driverClassName | 驱动库com.mysql.jdbc.Driver |
initialSize | 初始连接数 |
maxActive | 最大连接数 |
maxWait | 最长等待时间 |
1.2.3 DRUID连接池基本使用
- API介绍
DruidDataSourceFactory的方法 | 方法 |
---|---|
public static DataSource createDataSource(Properties properties) | 通过属性对象得到数据源对象 |
在src目录下新建一个Druid配置文件,命名为:druid.properties(文件名不强制)
- 参数:初始连接数5个,最大连接池10,最长等待时间2秒
# 连接字符串
url=jdbc:mysql://localhost:3306/test?useSSL=true
# 用户名
username=root
password=admin
driverClassName=com.mysql.jdbc.Driver
#初始连接数
initialSize=5
#最大连接数
maxActive=10
#最长等待时间
maxWait=2000
- 测试代码:
package com.dfbz.demo;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
/\*\*
\* @author lscl
\* @version 1.0
\* @intro:
\*/
public class Demo02\_DruidDataSource {
public static void main(String[] args) throws Exception {
//创建属性对象
Properties properties = new Properties();
//src就是类路径所在的目录,从类路径下加载属性文件,转成字节输入流
InputStream in
= Demo02\_DruidDataSource.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(in);
// 根据加载到的配置来创建一个数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 获取11个连接对象
for (int i = 1; i <= 11; i++) {
Connection conn = dataSource.getConnection();
System.out.println("第" + i + "个连接对象:" + conn);
// 释放一个
if (i == 5) {
conn.close();
}
}
}
}
- 执行效果:
1.3 连接池工具类
1.3.1 创建工具类
- 示例代码:
package com.dfbz.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/\*\*
\* @author lscl
\* @version 1.0
\* @intro: 数据源的工具类
\*/
public class DataSourceUtils {
private static DataSource ds;
/\*\*
\* 在静态代码块中创建数据源对象
\*/
static {
// 创建属性对象
Properties info = new Properties();
try (
// 得到输入流
InputStream in = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");) {
// 加载到属性对象中
info.load(in);
ds = DruidDataSourceFactory.createDataSource(info);
} catch (Exception e) {
e.printStackTrace();
}
}
/\*\*
\* 得到数据源
\*/
public static DataSource getDataSource() {
return ds;
}
/\*\*
\* 从连接池中得到连接对象
\*/
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/\*\*
\* 释放资源
\*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
//关闭结果集
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(Connection conn, Statement stmt) {
close(conn, stmt, null);
}