手把手教你写一个数据库连接池!
共 9016字,需浏览 19分钟
·
2022-03-05 21:05
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
juejin.cn/post/6951343274946723870
推荐:https://www.xttblog.com/?p=5319
文章目录
术语介绍 实现原理 代码实现 连接池配置文件 连接池管理 连接池接口 连接池实现 演示代码 演示效果说明
关于数据库连接池原理,什么杂七杂八的,本文不再重复啰嗦。有什么不理解的参考如下文章。
阅读本文手写一套数据库连接池,您可能需要了解如下几个知识点:
数据库连接池的原理及作用 并发队列介绍及使用 配置文件properties信息映射到Java对象
在我们配置连接池的时候,会配置一些数据,比如最小空闲连接数,最大空闲连接数等等,本文中,您需要理解如下几个概念。
术语介绍
「空闲连接池」:用来存放已经被创建,但是未被使用的连接的容器。
「活动连接池」:用来存放已经被创建,并且被使用的连接的容器。
「最大空闲数」:空闲连接池中,最多存在的空闲连接数量。
「初始化连接数」:第一次加载的时候,需要创建的连接数量,一般大于最小空闲数,小于最大空闲数。
「最大连接数」:空闲连接和活动连接之和。
实现原理
「1、初始化」:第一次加载的时候,根据配置的初始化连接数,创建连接,将创建的连接放入到空闲连接池;
「2、获取连接」:优先从空闲池获取,如果空闲池没有,就创建一个新的连接
3、「释放连接」:将需要释放的连接从活动连接池中移除,如果空闲连接池没有满,放将移除的连接放入到空闲连接池,如果空闲连接池已经满了,则关闭此链接。
代码实现
代码结构如下
连接池配置文件
##驱动名称:不推荐com.mysql.jdbc.Driver,而是推荐使用com.mysql.cj.jdbc.Driver
driverName = com.mysql.cj.jdbc.Driver
##数据库连接地址
url = jdbc:mysql://127.0.0.1:3306/test
userName = root
passWord = root
##连接池名字
poolName = Hutao Connection Pool
##空闲池,最小连接数
minFreeConnections = 1
##初始化空闲池连接数
initFreeConnections = 3
##空闲池,最大连接数
maxFreeConnections = 5
##最大允许的连接数,一般小于数据库总连接数
maxConnections = 15
##重试获得连接的频率 一秒
retryConnectionTimeOut = 1000
##连接超时时间,默认20分钟 1000 * 60 * 20
connectionTimeOut = 1200000
/**
* @Description:数据库连接池属性信息
*/
public class DbProperties {
/* 链接属性 */
private String driverName;
private String url;
private String userName;
private String passWord;
private String poolName;
/**
* 空闲池,最小连接数
*/
private int minFreeConnections;
/**
* 空闲池,最大连接数
*/
private int maxFreeConnections;
/**
* 初始连接数
*/
private int initFreeConnections;
/**
* 重试获得连接的频率 毫秒
*/
private long retryConnectionTimeOut;
/**
* 最大允许的连接数
*/
private int maxConnections;
/**
* 连接超时时间
*/
private long connectionTimeOut;
public String getDriverName() {
return driverName;
}
public void setDriverName(String driverName) {
this.driverName = driverName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getPoolName() {
return poolName;
}
public void setPoolName(String poolName) {
this.poolName = poolName;
}
public int getMinFreeConnections() {
return minFreeConnections;
}
public void setMinFreeConnections(int minFreeConnections) {
this.minFreeConnections = minFreeConnections;
}
public int getMaxFreeConnections() {
return maxFreeConnections;
}
public void setMaxFreeConnections(int maxFreeConnections) {
this.maxFreeConnections = maxFreeConnections;
}
public int getInitFreeConnections() {
return initFreeConnections;
}
public void setInitFreeConnections(int initFreeConnections) {
this.initFreeConnections = initFreeConnections;
}
public long getRetryConnectionTimeOut() {
return retryConnectionTimeOut;
}
public void setRetryConnectionTimeOut(long retryConnectionTimeOut) {
this.retryConnectionTimeOut = retryConnectionTimeOut;
}
public int getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public long getConnectionTimeOut() {
return connectionTimeOut;
}
public void setConnectionTimeOut(long connectionTimeOut) {
this.connectionTimeOut = connectionTimeOut;
}
@Override
public String toString() {
return "DbProperties [driverName=" + driverName + ", url=" + url + ", userName=" + userName + ", passWord="
+ passWord + ", poolName=" + poolName + ", minFreeConnections=" + minFreeConnections
+ ", maxFreeConnections=" + maxFreeConnections + ", initFreeConnections=" + initFreeConnections
+ ", retryConnectionTimeOut=" + retryConnectionTimeOut + ", maxConnections=" + maxConnections
+ ", connectionTimeOut=" + connectionTimeOut + "]";
}
}
连接池管理
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.Enumeration;
import java.util.ResourceBundle;
import com.hutao.pool.database.pojo.DbProperties;
import com.hutao.pool.database.service.DbPoolService;
import com.hutao.pool.database.service.impl.DbPoolServiceImpl;
/**
* @Description:数据库连接池管理
*/
public class DbPoolManager {
private static String sourcePath = "com/hutao/resources/database";
/**
* 数据库连接池配置属性
*/
private static DbProperties properties = null;
/**
* 数据库连接池接口
*/
private static DbPoolService connectionPool = null;
/**
* 双重检查机制静态加载连接池
*/
static {
try {
if(properties == null) {
synchronized(DbPoolManager.class) {
if(properties == null) {
properites2Object();
connectionPool = new DbPoolServiceImpl(properties);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description:数据库连接池database配置文件映射到java对象
*/
private static void properites2Object() throws NoSuchFieldException, IllegalAccessException {
properties = new DbProperties();
ResourceBundle resourceBundle = ResourceBundle.getBundle(sourcePath);
//获取资源文件中所有的key
Enumeration keys = resourceBundle.getKeys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
//反射获取类中的属性字段
Field field= DbProperties.class.getDeclaredField(key);
//属性字段的类型
Type genericType = field.getGenericType();
//属性设置可访问
field.setAccessible(true);
//根据key读取对应的value值
String value = resourceBundle.getString(key);
if("int".equals(genericType.getTypeName())) {
//反射给属性赋值
field.set(properties, Integer.parseInt(value));
}else if("long".equals(genericType.getTypeName())) {
field.set(properties, Long.parseLong(value));
}else if("java.lang.String".equals(genericType.getTypeName())) {
field.set(properties,value);
}
}
}
/**
* @Description:获取连接
*/
public static Connection getConnection() {
return connectionPool.getConnection();
}
/**
* @Description:释放连接
*/
public static void releaseConnection(Connection connection) {
connectionPool.releaseConnection(connection);
}
连接池接口
import java.sql.Connection;
/**
* @Description:数据库连接池
*/
public interface DbPoolService {
/**
* @Description:判断连接是否可用,可用返回true
*/
public boolean isAvailable(Connection connection);
/**
* @Description:使用重复利用机制获取连接
*/
public Connection getConnection();
/**
* @Description:使用可回收机制释放连接
*/
public void releaseConnection(Connection connection);
}
连接池实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import com.hutao.pool.database.pojo.DbProperties;
import com.hutao.pool.database.service.DbPoolService;
/**
* @Description:数据库连接池实现
*/
public class DbPoolServiceImpl implements DbPoolService {
/**
* 存放空闲连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
*/
private BlockingQueue freeConnection = null;
/**
* 存放活动连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
*/
private BlockingQueue activeConnection = null;
/**
* 存放映射的属性配置文件
*/
private DbProperties dDbProperties;
public DbPoolServiceImpl(DbProperties dDbProperties) throws Exception {
// 获取配置文件信息
this.dDbProperties = dDbProperties;
freeConnection = new LinkedBlockingQueue<>(dDbProperties.getMaxFreeConnections());
activeConnection = new LinkedBlockingQueue<>(dDbProperties.getMaxConnections());
init();
}
/**
* @Description:初始化空闲线程池
*/
private void init() throws Exception {
System.out.println("初始化线程池开始,线程池配置属性:"+dDbProperties);
if (dDbProperties == null) {
throw new Exception("连接池配置属性对象不能为空");
}
//获取连接池配置文件中初始化连接数
for (int i = 0; i < dDbProperties.getInitFreeConnections(); i++) {
//创建Connection连接
Connection newConnection = newConnection();
if (newConnection != null) {
//将创建的新连接放入到空闲池中
freeConnection.add(newConnection);
}
}
System.out.println("初始化线程池结束,初始化线程数:"+dDbProperties.getInitFreeConnections());
}
private synchronized Connection newConnection() {
try {
Class.forName(dDbProperties.getDriverName());
return DriverManager.getConnection(dDbProperties.getUrl(), dDbProperties.getUserName(),dDbProperties.getPassWord());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* @Description:判断连接是否可用,可用返回true
*/
@Override
public boolean isAvailable(Connection connection) {
try {
if (connection == null || connection.isClosed()) {
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* @Description:使用重复利用机制获取连接:如果总连接未超过最大连接,则从空闲连接池获取连接或者创建一个新的连接,如果超过最大连接,则等待一段时间以后,继续获取连接
*/
@Override
public synchronized Connection getConnection() {
Connection connection = null;
//空闲连接和活动连接的总数加起来 小于 最大配置连接
System.out.println("当前空闲连接总数:"+freeConnection.size()+" 当前活动连接总数"+activeConnection.size()+", 配置最大连接数:"+ dDbProperties.getMaxConnections());
if (freeConnection.size()+activeConnection.size() < dDbProperties.getMaxConnections()) {
//空闲连接池,是否还有还有连接,有就取出来,没有就创建一个新的。
if (freeConnection.size() > 0) {
connection = freeConnection.poll();
System.out.println("从空闲线程池取出线程:"+connection+"当前空闲线程总数:"+freeConnection.size());
} else {
connection = newConnection();
System.out.println("空闲连接池没有连接,创建连接"+connection);
}
//拿到的连接可用,就添加活动连接池,否则就递归继续找下一个
boolean available = isAvailable(connection);
if (available) {
activeConnection.add(connection);
} else {
connection = getConnection();
}
} else {
System.out.println("当前连接数已达到最大连接数,等待"+dDbProperties.getRetryConnectionTimeOut()+"ms以后再试");
try {
wait(dDbProperties.getRetryConnectionTimeOut());
} catch (InterruptedException e) {
e.printStackTrace();
}
connection = getConnection();
}
return connection;
}
/**
* @Description:使用可回收机制释放连接:如果连接可用,并且空闲连接池没有满,则把连接归还到空闲连接池,否则关闭连接
*/
@Override
public synchronized void releaseConnection(Connection connection) {
try {
if (isAvailable(connection) && freeConnection.size() < dDbProperties.getMaxFreeConnections()) {
freeConnection.add(connection);
System.out.println("空闲线程池未满,归还连接"+connection);
} else {
connection.close();
System.out.println("空闲线程池已满,关闭连接"+connection);
}
activeConnection.remove(connection);
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
演示代码
创建20个线程,每个线程操作数据库20次,每次操作往数据库写入一条数据。
创建表结构。
CREATE TABLE `test` (
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
public class Test001 {
public static void main(String[] args) {
ThreadConnection threadConnection = new ThreadConnection();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(threadConnection, "线程:" + i);
thread.start();
}
}
}
class ThreadConnection implements Runnable {
public void run() {
for (int i = 0; i < 20; i++) {
Connection connection = DbPoolManager.getConnection();
System.out.println(Thread.currentThread().getName() + ",connection:" + connection);
Statement statement;
try {
statement = connection.createStatement();
String selectsql = "select * from test";
statement.execute(selectsql);
String insertsql = "insert into test(name) VALUES('"+Thread.currentThread().getName()+connection+"')";
statement.execute(insertsql);
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
DbPoolManager.releaseConnection(connection);
}
}
}
演示效果说明
如果每个连接都能正常工作,总共20个并发线程,每个线程执行20次数据库查询,插入操作,执行完毕后,数据库应该有400条数据。
「初始化阶段」
「达到最大连接数,进入重试阶段」
「连接的获取和归还到空闲池」
「空闲连接池已满,关闭连接」
至此,完毕!