手写数据库连接池 - Kangaroo-Pool
数据库连接池
什么是数据库连接池?
- 其实是一种池化思想,为了节省资源,因为数据库连接是昂贵的资源
- 所以就把数据放在
参考Druid数据库连接池的配置名称
- 配置文件 注意 :配置文件必须这样写 和官方手册的一致 否则报错
- url=jdbc:mysql:///test
- driverClassName=com.mysql.jdbc.Driver
- username=root
- password=root
| 配置 | 缺省 | 说明 |
| —- | —- | —- |
| name | | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) |
| url | | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto |
| username | | 连接数据库的用户名 |
| password | | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter |
| driverClassName | | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) |
| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
| maxActive | 8 | 最大连接池数量 |
| maxIdle | 8 | 已经不再使用,配置了也没效果 |
| minIdle | | 最小连接池数量 |
| maxWait | | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 |
| poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
| maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
| validationQuery | | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 |
| testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
| testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
| testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
| timeBetweenEvictionRunsMillis | | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
| numTestsPerEvictionRun | | 不再使用,一个DruidDataSource只支持一个EvictionRun |
| minEvictableIdleTimeMillis | | |
| connectionInitSqls | | 物理连接初始化的时候执行的sql |
| exceptionSorter | | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 |
| filters | | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall |
| proxyFilters | | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
数据库连接池设计思路
- 使用集合或者队列实现(初步思路)
- 需要的时候取,关闭的时候将其放到池子里面即可
- 数据库连接的生产策略有多个,一个是池子创建的时候就创建连接对象
- 另一种是需要再创建
- 我这里实现的是需要再创建,但是还是有问题的,可以选择创建的时候不放在池子里面,当关闭的时候放在池子里面
项目目录结构

代码实现
package cn.icanci.pool;import cn.icanci.pool.exceptions.ResourceNotFoundException;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.Properties;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.TimeUnit;/** * @Author: icanci * @ProjectName: kangaroo-orm * @PackageName: cn.icanci.pool * @Date: Created in 2020/9/1 16:15 * @ClassAction: 袋鼠数据库连接池 * - * - KangarooDatabaseConnectionPool */public class KangarooPool { /*---------------------------- 单例创建对象 ---------------------------- */ /** * 因为数据库连接池池是重量级对象 * 饿汉式单例模式模式 */ private static final KangarooPool INSTANCE = new KangarooPool(); private KangarooPool() { } /** * 返回一个连接池对象 * * @return 返回KangarooPool对象 */ public static KangarooPool getInstance() { return INSTANCE; } /*---------------------------- 方法参数 ----------------------------*/ /** * 执行懒加载数据库连接地址 */ private static String url = null; /** * 执行懒加载驱动名称 */ private static String driverClassName = null; /** * 执行懒加载用户名 */ private static String username = null; /** * 执行懒加载用户密码 */ private static String password = null; /** * 默认初始化连接池大小 */ private static Integer initialSize = 4; /** * 默认最大连接池大小为8 */ private static Integer maxActive = initialSize << 1; /** * 连接最大等待时间 单位:毫秒 */ private static Long maxWait = 10000L; /** * 当前拿到了数据库连接池的哪个值 */ private static Integer poolSize = 0; /** * 用来保存数据库连接 */ private static ConcurrentLinkedQueue<Connection> poolQueue = null; /** * 读取配置文件的对象 */ private static final Properties props = new Properties(); /** * 文件路径地址 */ private static String userPropertiesPath = null; /*---------------------------- 静态代码块初始化 ---------------------------- */ static { try { String resourcePath = getResourcePath(); InputStream in = new FileInputStream(resourcePath); props.load(in); url = props.getProperty("url"); driverClassName = props.getProperty("driverClassName"); username = props.getProperty("username"); password = props.getProperty("password"); initialSize = props.getProperty("initialSize") == null ? initialSize : Integer.parseInt(props.getProperty("initialSize")); maxActive = props.getProperty("maxActive") == null ? maxActive : Integer.parseInt(props.getProperty("maxActive")); maxWait = props.getProperty("maxWait") == null ? maxWait : Long.parseLong(props.getProperty("maxWait")); poolQueue = new ConcurrentLinkedQueue<>(); Class.forName(driverClassName); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } /*---------------------------- 方法 ---------------------------- */ /** * 获取配置文件路径 * * @return 返回配置文件路径 */ private static String getResourcePath() { // 获取路径class编译路径 // /E:/IdeaHome/SourceCode/kangaroo-orm/out/test/jdbc-template/ // 此时应该删除前面的路径 / String pathDir = KangarooPool.class.getResource("/").getPath(); String reallyDir = new StringBuffer(pathDir).deleteCharAt(0).toString(); File targetPath = new File(reallyDir); getKangarooPoolPropertiesPath(targetPath); if (null != userPropertiesPath) { return userPropertiesPath; } throw new ResourceNotFoundException("无法找到或者加载 kangaroo-pool.properties 文件"); } /** * 获取配置文件路径 * * @param file 对象 */ private static void getKangarooPoolPropertiesPath(File file) { assert file != null; File[] files = file.listFiles(); assert files != null; for (File f : files) { assert f != null; if (f.isDirectory()) { getKangarooPoolPropertiesPath(f.getAbsoluteFile()); } else { if ("kangaroo-pool.properties".equalsIgnoreCase(f.getName())) { userPropertiesPath = f.getAbsolutePath(); return; } } } } /** * 获取连接信息 * * @return 返回数据库连接对象 */ public synchronized Connection getConnection() throws Exception { if (poolSize >= maxActive) { this.wait(); // throw new NoConnectionException("连接池暂时没有可用的连接"); } else { createConnection(); } Connection remove = poolQueue.remove(); if (poolSize > 1) { poolSize--; } if (null == remove) { createConnection(); remove = poolQueue.remove(); } return remove; } /** * 创建连接 * * @return 返回数据库连接对象 * @throws Exception */ public synchronized void createConnection() throws Exception { // 每次调用,只要小于最大值,即创建对象 if (poolSize <= maxActive) { Connection conn = DriverManager.getConnection(url, username, password); poolQueue.add(conn); poolSize++; System.out.println("创建了:" + conn); System.out.println("此时连接池大小:" + getPoolSize()); } else { close(); } } /** * 销毁连接池 */ public synchronized static void distory() throws SQLException { if (poolSize == 0) { return; } while (!poolQueue.isEmpty()) { poolQueue.remove().close(); } poolSize = 0; System.out.println("连接池已经清空"); } /** * 关闭数据库连接 * * @param conn 需要关闭的连接 */ public synchronized void close(Connection conn) throws Exception { // 如果数据库连接为 null 直接结束方法 if (null == conn) { return; } poolSize++; poolQueue.add(conn); close(); } /** * 获取数据库连接池的连接数量 * * @return 返回数据库连接池的连接数量 */ public Integer getPoolSize() { return poolSize; } /** * 关闭连接 * * @throws Exception */ private synchronized void close() throws Exception { // 数据连接大于 初始值,小于最大值 是可以销毁的连接 // 等待 maxWait 的时间 TimeUnit.MILLISECONDS.sleep(maxWait); if (poolSize >= initialSize) { poolSize--; } this.notifyAll(); }}
url=jdbc:mysql://localhost:3306/kangaroo-orm?useSSL=false&serverTimezone=UTCdriverClassName=com.mysql.cj.jdbc.Driverusername=rootpassword=rootinitialSize=5maxActive=10maxWait=3000
package cn.icanci.pool.exceptions;/** * @Author: icanci * @ProjectName: kangaroo-orm * @PackageName: cn.icanci.pool.exceptions * @Date: Created in 2020/9/1 18:35 * @ClassAction: 暂时无可用连接异常 */public class NoConnectionException extends RuntimeException { private static final long serialVersionUID = 516271018338902872L; public NoConnectionException() { super(); } /** * @param s the detail message. */ public NoConnectionException(String s) { super(s); }}