前言
要手写实现一个数据库连接池首先肯定要理解其作用和工作原理
数据库连接池的作用包括:
1 节省资源:数据库连接池会对连接进行复用,避免了每次连接时都需要建立新的连接和释放连接的开销,从而节省了资源。
2 提高性能:数据库连接池可以预先初始化一定数量的连接,当请求到来时,可以直接从连接池中取出连接,避免了连接初始化和释放的时间,从而提高了系统的响应速度,更有效的应对大量请求和并发。
3 避免连接泄露:连接池会调控连接的使用情况,防止因为应用程序没有正确释放连接而造成连接泄露的问题,并定期清理连接。
4 方便管理:连接池提供了一系列的管理工具,如连接池大小的动态调整、连接的最大使用时间的限制等,方便对连接池进行管理和维护。(动态扩缩没写)
数据库连接池的大致工作原理:
当应用程序需要连接数据库时,它会先向连接池请求连接,连接池会检查连接是否可用,如果有可用的连接,则返回给应用程序使用;如果没有可用连接,则连接池会创建新的连接,直到连接池中连接数量达到上限。当应用程序不再需要连接时,它会将连接返回给连接池,连接池将连接标记为可用状态,以便下一次请求时可以直接使用。同时,连接池还会对连接进行管理,包括连接的创建、销毁、监控和维护等,以保证连接池的可靠性和稳定性。
接着是手写连接池代码的解析:
首先编写配置类 不做解释
public class DbConfig {
private static String driver; //连接驱动
private static String url; //连接地址
private static String username; //连接名
private static String password; //连接密码
private static Integer minFreeConnections=2; //空闲连接池,最小连接数,默认为2
private static Integer maxFreeConnections=8; //空闲连接池,最大连接数,默认为8
private static Integer maxActiveConnection=8; //活跃连接池,最大连接数,默认为8
private static Integer initConnections=2; //初始化连接数,默认为2个
private static Long connectionTimeOut=1000*60*20L; //连接超时时间,默认为20分钟
private static Long recheckTime=1000*60L; //自检循环时间,默认为60秒
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("D:\\javaProject\\TVWebNew2\\src\\main\\resources\\jdbc.properties"));
url = properties.getProperty("jdbc.url");
driver = properties.getProperty("jdbc.driver");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
minFreeConnections = Integer.parseInt(properties.getProperty("jdbc.minFreeConnections"));
maxFreeConnections = Integer.parseInt(properties.getProperty("jdbc.maxFreeConnections"));
maxActiveConnection = Integer.parseInt(properties.getProperty("jdbc.maxActiveConnection"));
initConnections = Integer.parseInt(properties.getProperty("jdbc.initConnections"));
} catch (IOException e) {
e.printStackTrace();
}
}
public DbConfig() {
}
// 省略.......get/set方法.......
接着创建ConntectionBean类用于维护其相应的连接和该连接开始创建使用的时间,用于后面判断是否超时
public class ConnectionBean {
private Connection conn;
private Long startTime;
public ConnectionBean(Connection conn, Long startTime) {
this.conn = conn;
this.startTime = startTime;
}
public Connection getConn() {
return conn;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) {
this.startTime = startTime;
}
}
最后开始编写核心Pool
连接池的核心就是两个集合,分别维护空闲连接和活跃连接
创建成员
//空闲连接池 使用线程安全的集合
private CopyOnWriteArrayList<Connection> freePool = new CopyOnWriteArrayList<Connection>();
//活跃连接池 使用线程安全的集合
private CopyOnWriteArrayList<ConnectionBean> activePool = new CopyOnWriteArrayList<ConnectionBean>();
//连接池配置
private DbConfig dbConfig;
//记录已创建的连接数
private AtomicInteger countConnection = new AtomicInteger(0);
构造器 用于初始化配置文件 和创建连接 并开启自检线程
public ConnectionPool(DbConfig dbConfig) {
this.dbConfig = dbConfig;
try {
Class.forName(dbConfig.getDriver());
//初始化连接
for (int i = 0; i < dbConfig.getInitConnections(); i++) {
//创建连接
Connection connection = newConnection();
if (connection != null) {
freePool.add(connection);
}
}
//开启一个固定时间间隔启动方法的线程
TimeCheck timeCheck = new TimeCheck();
Timer timer = new Timer();
timer.schedule(timeCheck,0,dbConfig.getRecheckTime());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
创建新连接并放入空闲连接池 不解释
//创建新连接
private synchronized Connection newConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
//连接数加1
countConnection.incrementAndGet();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
公共方法获取连接
代码逻辑在注释中 不难理解
这里阻塞线程时使用的是固定时间的等待,这样会导致有可能一直等待直到超时
但我尚未找到较好的方法优化
//从线程池中获取连接
public synchronized Connection getConnection() {
Connection connection = null;
try {
// 如果当前线程池中还有可用连接,直接从连接池中获取
if (activePool.size() < dbConfig.getMaxActiveConnection()) {
//如果空闲连接池里存在连接,拿出连接
if (freePool.size() > 0) {
connection = freePool.remove(0);
} else {
//不够就创建连接
connection = newConnection();
}
//对连接进行校验
if (isAlive(connection)) {
//通过就放入活跃连接池
activePool.add(new ConnectionBean(connection, System.currentTimeMillis()));
}
}else {
//如果没有就等待
wait(dbConfig.getRecheckTime());
}
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
释放连接
public synchronized boolean releaseConnection(Connection connection) {
//判断是否可用
if (isAlive(connection)) {
//3.从活跃池中移除连接
for (int i = 0; i < activePool.size(); i++) {
if (activePool.get(i).getConn() == connection) {
activePool.remove(i);//可能出现下标问题
}
}
//2.判断空闲池是否已满
if (freePool.size() < dbConfig.getMaxFreeConnections()) {
//未满,回收连接
freePool.add(connection);
} else {
//已满
try {
countConnection.decrementAndGet();
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//4.唤醒所有被阻塞的线程
notifyAll();
return true;
}
return false;
}
接下来就是重要的自检 包括:
1. 检查所有空闲连接是否可用,不可用的直接关闭连接
2. 检查当前连接数是否满足最低空闲连接数,若低于最小空闲数,新增连接放入空闲池 维护空闲连接池
3. 检查活跃连接池中的连接是否超时 实现连接的最大使用时间和连接的定期清理
class TimeCheck extends TimerTask{
@Override
public void run() {
// 1. 检查所有空闲连接是否可用,不可用的直接关闭连接
Iterator<Connection> iterator = freePool.iterator();
while (iterator.hasNext()) {
Connection connection = iterator.next();
if (!isAlive(connection)) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//不可用直接关闭移除,连接数减1
iterator.remove();
countConnection.decrementAndGet();
}
}
// 2. 检查当前连接数是否满足最低空闲连接数,若低于最小空闲数,新增连接放入空闲池 维护空闲连接池
int count = countConnection.get();
if (count < dbConfig.getMinFreeConnections()) {
//循环两者之差,也就是所需要新建的连接
for (int i = count; i < dbConfig.getMinFreeConnections(); i++) {
Connection connection = newConnection();
//非空就放入空闲连接池
if (connection != null) {
freePool.add(connection);
}
}
}
// System.out.println("连接检查...");
// 3 检查活跃连接池中的连接是否超时 实现连接的最大使用时间和连接的定期清理
for (int i = 0; i < activePool.size(); i++) {
ConnectionBean connectionBean = activePool.get(i);
//获取该连接开始使用连接
long startTime = connectionBean.getStartTime();
//获取系统当前时间
long currentTimeMillis = System.currentTimeMillis();
try {
//如果超时
if ((currentTimeMillis - startTime) > dbConfig.getConnectionTimeOut()) {
Connection conn = connectionBean.getConn();
if (isAlive(conn)) {
//关闭
conn.close();
//移除活跃连接池
freePool.remove(i);
countConnection.decrementAndGet();
System.out.println("关闭超时连接...");
}
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
这里使用到了成员内部类,因为自检方法需要定时开启线程,一个类继承TimerTask就可以调用run方法,并设置调用时间。相关的代码逻辑都详细写在代码块中,不做过多解释