手写数据库连接池 - 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,是组合关系,并非替换关系 |

数据库连接池设计思路

  • 使用集合或者队列实现(初步思路)
  • 需要的时候取,关闭的时候将其放到池子里面即可
  • 数据库连接的生产策略有多个,一个是池子创建的时候就创建连接对象
  • 另一种是需要再创建
  • 我这里实现的是需要再创建,但是还是有问题的,可以选择创建的时候不放在池子里面,当关闭的时候放在池子里面

项目目录结构

1598970611575.png

代码实现

  • KangarooPool
  1. package cn.icanci.pool;
  2. import cn.icanci.pool.exceptions.ResourceNotFoundException;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.sql.Connection;
  8. import java.sql.DriverManager;
  9. import java.sql.SQLException;
  10. import java.util.Properties;
  11. import java.util.concurrent.ConcurrentLinkedQueue;
  12. import java.util.concurrent.TimeUnit;
  13. /**
  14. * @Author: icanci
  15. * @ProjectName: kangaroo-orm
  16. * @PackageName: cn.icanci.pool
  17. * @Date: Created in 2020/9/1 16:15
  18. * @ClassAction: 袋鼠数据库连接池
  19. * -
  20. * - KangarooDatabaseConnectionPool
  21. */
  22. public class KangarooPool {
  23. /*---------------------------- 单例创建对象 ---------------------------- */
  24. /**
  25. * 因为数据库连接池池是重量级对象
  26. * 饿汉式单例模式模式
  27. */
  28. private static final KangarooPool INSTANCE = new KangarooPool();
  29. private KangarooPool() {
  30. }
  31. /**
  32. * 返回一个连接池对象
  33. *
  34. * @return 返回KangarooPool对象
  35. */
  36. public static KangarooPool getInstance() {
  37. return INSTANCE;
  38. }
  39. /*---------------------------- 方法参数 ----------------------------*/
  40. /**
  41. * 执行懒加载数据库连接地址
  42. */
  43. private static String url = null;
  44. /**
  45. * 执行懒加载驱动名称
  46. */
  47. private static String driverClassName = null;
  48. /**
  49. * 执行懒加载用户名
  50. */
  51. private static String username = null;
  52. /**
  53. * 执行懒加载用户密码
  54. */
  55. private static String password = null;
  56. /**
  57. * 默认初始化连接池大小
  58. */
  59. private static Integer initialSize = 4;
  60. /**
  61. * 默认最大连接池大小为8
  62. */
  63. private static Integer maxActive = initialSize << 1;
  64. /**
  65. * 连接最大等待时间 单位:毫秒
  66. */
  67. private static Long maxWait = 10000L;
  68. /**
  69. * 当前拿到了数据库连接池的哪个值
  70. */
  71. private static Integer poolSize = 0;
  72. /**
  73. * 用来保存数据库连接
  74. */
  75. private static ConcurrentLinkedQueue<Connection> poolQueue = null;
  76. /**
  77. * 读取配置文件的对象
  78. */
  79. private static final Properties props = new Properties();
  80. /**
  81. * 文件路径地址
  82. */
  83. private static String userPropertiesPath = null;
  84. /*---------------------------- 静态代码块初始化 ---------------------------- */
  85. static {
  86. try {
  87. String resourcePath = getResourcePath();
  88. InputStream in = new FileInputStream(resourcePath);
  89. props.load(in);
  90. url = props.getProperty("url");
  91. driverClassName = props.getProperty("driverClassName");
  92. username = props.getProperty("username");
  93. password = props.getProperty("password");
  94. initialSize = props.getProperty("initialSize") == null ?
  95. initialSize :
  96. Integer.parseInt(props.getProperty("initialSize"));
  97. maxActive = props.getProperty("maxActive") == null ?
  98. maxActive :
  99. Integer.parseInt(props.getProperty("maxActive"));
  100. maxWait = props.getProperty("maxWait") == null ?
  101. maxWait :
  102. Long.parseLong(props.getProperty("maxWait"));
  103. poolQueue = new ConcurrentLinkedQueue<>();
  104. Class.forName(driverClassName);
  105. } catch (IOException | ClassNotFoundException e) {
  106. e.printStackTrace();
  107. }
  108. }
  109. /*---------------------------- 方法 ---------------------------- */
  110. /**
  111. * 获取配置文件路径
  112. *
  113. * @return 返回配置文件路径
  114. */
  115. private static String getResourcePath() {
  116. // 获取路径class编译路径
  117. // /E:/IdeaHome/SourceCode/kangaroo-orm/out/test/jdbc-template/
  118. // 此时应该删除前面的路径 /
  119. String pathDir = KangarooPool.class.getResource("/").getPath();
  120. String reallyDir = new StringBuffer(pathDir).deleteCharAt(0).toString();
  121. File targetPath = new File(reallyDir);
  122. getKangarooPoolPropertiesPath(targetPath);
  123. if (null != userPropertiesPath) {
  124. return userPropertiesPath;
  125. }
  126. throw new ResourceNotFoundException("无法找到或者加载 kangaroo-pool.properties 文件");
  127. }
  128. /**
  129. * 获取配置文件路径
  130. *
  131. * @param file 对象
  132. */
  133. private static void getKangarooPoolPropertiesPath(File file) {
  134. assert file != null;
  135. File[] files = file.listFiles();
  136. assert files != null;
  137. for (File f : files) {
  138. assert f != null;
  139. if (f.isDirectory()) {
  140. getKangarooPoolPropertiesPath(f.getAbsoluteFile());
  141. } else {
  142. if ("kangaroo-pool.properties".equalsIgnoreCase(f.getName())) {
  143. userPropertiesPath = f.getAbsolutePath();
  144. return;
  145. }
  146. }
  147. }
  148. }
  149. /**
  150. * 获取连接信息
  151. *
  152. * @return 返回数据库连接对象
  153. */
  154. public synchronized Connection getConnection() throws Exception {
  155. if (poolSize >= maxActive) {
  156. this.wait();
  157. // throw new NoConnectionException("连接池暂时没有可用的连接");
  158. } else {
  159. createConnection();
  160. }
  161. Connection remove = poolQueue.remove();
  162. if (poolSize > 1) {
  163. poolSize--;
  164. }
  165. if (null == remove) {
  166. createConnection();
  167. remove = poolQueue.remove();
  168. }
  169. return remove;
  170. }
  171. /**
  172. * 创建连接
  173. *
  174. * @return 返回数据库连接对象
  175. * @throws Exception
  176. */
  177. public synchronized void createConnection() throws Exception {
  178. // 每次调用,只要小于最大值,即创建对象
  179. if (poolSize <= maxActive) {
  180. Connection conn = DriverManager.getConnection(url, username, password);
  181. poolQueue.add(conn);
  182. poolSize++;
  183. System.out.println("创建了:" + conn);
  184. System.out.println("此时连接池大小:" + getPoolSize());
  185. } else {
  186. close();
  187. }
  188. }
  189. /**
  190. * 销毁连接池
  191. */
  192. public synchronized static void distory() throws SQLException {
  193. if (poolSize == 0) {
  194. return;
  195. }
  196. while (!poolQueue.isEmpty()) {
  197. poolQueue.remove().close();
  198. }
  199. poolSize = 0;
  200. System.out.println("连接池已经清空");
  201. }
  202. /**
  203. * 关闭数据库连接
  204. *
  205. * @param conn 需要关闭的连接
  206. */
  207. public synchronized void close(Connection conn) throws Exception {
  208. // 如果数据库连接为 null 直接结束方法
  209. if (null == conn) {
  210. return;
  211. }
  212. poolSize++;
  213. poolQueue.add(conn);
  214. close();
  215. }
  216. /**
  217. * 获取数据库连接池的连接数量
  218. *
  219. * @return 返回数据库连接池的连接数量
  220. */
  221. public Integer getPoolSize() {
  222. return poolSize;
  223. }
  224. /**
  225. * 关闭连接
  226. *
  227. * @throws Exception
  228. */
  229. private synchronized void close() throws Exception {
  230. // 数据连接大于 初始值,小于最大值 是可以销毁的连接
  231. // 等待 maxWait 的时间
  232. TimeUnit.MILLISECONDS.sleep(maxWait);
  233. if (poolSize >= initialSize) {
  234. poolSize--;
  235. }
  236. this.notifyAll();
  237. }
  238. }
  • kangaroo-pool.properties
  1. url=jdbc:mysql://localhost:3306/kangaroo-orm?useSSL=false&serverTimezone=UTC
  2. driverClassName=com.mysql.cj.jdbc.Driver
  3. username=root
  4. password=root
  5. initialSize=5
  6. maxActive=10
  7. maxWait=3000
  • NoConnectionException
  1. package cn.icanci.pool.exceptions;
  2. /**
  3. * @Author: icanci
  4. * @ProjectName: kangaroo-orm
  5. * @PackageName: cn.icanci.pool.exceptions
  6. * @Date: Created in 2020/9/1 18:35
  7. * @ClassAction: 暂时无可用连接异常
  8. */
  9. public class NoConnectionException extends RuntimeException {
  10. private static final long serialVersionUID = 516271018338902872L;
  11. public NoConnectionException() {
  12. super();
  13. }
  14. /**
  15. * @param s the detail message.
  16. */
  17. public NoConnectionException(String s) {
  18. super(s);
  19. }
  20. }