如何自己手写一套数据库连接池?

文章目录

  • 1、术语介绍
  • 2、实现原理
  • 3、代码实现
    • 3.1连接池配置文件
    • 3.2连接池管理
    • 3.3连接池接口
    • 3.4连接池实现
    • 3.5演示代码
    • 3.6演示效果说明

关于数据库连接池原理,什么杂七杂八的,本文不再重复啰嗦。有什么不理解的参考如下文章。
阅读本文手写一套数据库连接池,您可能需要了解如下几个知识点:
1、 数据库连接池的原理及作用
2、 并发队列介绍及使用
3、 配置文件properties信息映射到Java对象
在我们配置连接池的时候,会配置一些数据,比如最小空闲连接数,最大空闲连接数等等,本文中,您需要理解如下几个概念。

1、术语介绍

空闲连接池:用来存放已经被创建,但是未被使用的连接的容器。
活动连接池:用来存放已经被创建,并且被使用的连接的容器。
**最大空闲数:**空闲连接池中,最多存在的空闲连接数量。
初始化连接数:第一次加载的时候,需要创建的连接数量,一般大于最小空闲数,小于最大空闲数。
最大连接数:空闲连接和活动连接之和。

2、实现原理

1、初始化:第一次加载的时候,根据配置的初始化连接数,创建连接,将创建的连接放入到空闲连接池;
2、获取连接:优先从空闲池获取,如果空闲池没有,就创建一个新的连接
如何自己手写一套数据库连接池?_第1张图片
3、释放连接:将需要释放的连接从活动连接池中移除,如果空闲连接池没有满,放将移除的连接放入到空闲连接池,如果空闲连接池已经满了,则关闭此链接。

3、代码实现

代码结构如下
如何自己手写一套数据库连接池?_第2张图片

3.1连接池配置文件

##驱动名称:不推荐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:数据库连接池属性信息
 * @author hutao
 * @mail [email protected]
 * @date 2020年07月08日
 */
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 + "]";
	}
}

3.2连接池管理

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:数据库连接池管理
 * @author hutao
 * @mail [email protected]
 * @date 2020年07月08日
 */
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对象
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	private static void properites2Object() throws NoSuchFieldException, IllegalAccessException {
		properties = new DbProperties();
		ResourceBundle resourceBundle = ResourceBundle.getBundle(sourcePath);
		//获取资源文件中所有的key
		Enumeration<String> 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:获取连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月09日
	 */
	public static Connection getConnection() {
		return connectionPool.getConnection();
	}

	/**
	 * @Description:释放连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月09日
	 */
	public static void releaseConnection(Connection connection) {
		connectionPool.releaseConnection(connection);
	}

3.3连接池接口

import java.sql.Connection;

/**
 * @Description:数据库连接池
 * @author hutao
 * @mail [email protected]
 * @date 2020年07月08日
 */
public interface DbPoolService {
	
	/**
	 * @Description:判断连接是否可用,可用返回true
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	public boolean isAvailable(Connection connection);

	/**
	 * @Description:使用重复利用机制获取连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	public Connection getConnection();

	/**
	 * @Description:使用可回收机制释放连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月09日
	 */
	public void releaseConnection(Connection connection);
}

3.4连接池实现

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:数据库连接池实现
 * @author hutao
 * @mail [email protected]
 * @date 2020年07月09日
 */
public class DbPoolServiceImpl implements DbPoolService {
	
	/**
	 * 存放空闲连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
	 */
	private BlockingQueue<Connection> freeConnection = null;
	/**
	 * 存放活动连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
	 */
	private BlockingQueue<Connection> 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:初始化空闲线程池
	 * @author hutao
	 * @throws Exception 
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	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());
	}

	/**
	 * @Description:
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	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
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	@Override
	public boolean isAvailable(Connection connection) {
		try {
			if (connection == null || connection.isClosed()) {
				return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
		
	}

	/**
	 * @Description:使用重复利用机制获取连接:如果总连接未超过最大连接,则从空闲连接池获取连接或者创建一个新的连接,如果超过最大连接,则等待一段时间以后,继续获取连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月08日
	 */
	@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:使用可回收机制释放连接:如果连接可用,并且空闲连接池没有满,则把连接归还到空闲连接池,否则关闭连接
	 * @author hutao
	 * @mail [email protected]
	 * @date 2020年07月09日
	 */
	@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();
		}
	}
}

3.5演示代码

创建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);
		}
	}
}

3.6演示效果说明

如果每个连接都能正常工作,总共20个并发线程,每个线程执行20次数据库查询,插入操作,执行完毕后,数据库应该有400条数据
如何自己手写一套数据库连接池?_第3张图片
如何自己手写一套数据库连接池?_第4张图片
初始化阶段
在这里插入图片描述
达到最大连接数,进入重试阶段
如何自己手写一套数据库连接池?_第5张图片
连接的获取和归还到空闲池
如何自己手写一套数据库连接池?_第6张图片
空闲连接池已满,关闭连接
如何自己手写一套数据库连接池?_第7张图片
至此,完毕!

你可能感兴趣的:(程序员,笔记)