为什么用数据库连接池?

为什么要用数据库连接池?

如果我们分析一下典型的【连接数据库】所涉及的步骤,我们将理解为什么:

  1. 使用数据库驱动程序打开与数据库的连接
  2. 打开TCP套接字以读取/写入数据
  3. 通过套接字读取/写入数据
  4. 关闭连接
  5. 关闭套接字

很明显,【连接数据库】是相当昂贵的操作,因此,应该想办法尽可能地减少、避免这种操作。

这就是数据库连接池发挥作用的地方。通过简单地实现数据库连接容器(允许我们重用大量现有连接),我们可以有效地节省执行大量昂贵【连接数据库】的成本,从而提高数据库驱动应用程序的整体性能。

↑ 译自A Simple Guide to Connection Pooling in Java ,有删改

HikariCP快速入门

HikariCP是一个轻量级的高性能JDBC连接池。GitHub链接:https://github.com/brettwooldridge/HikariCP

1、依赖

  1. HikariCP
  2. slf4j (不需要日志实现也能跑)
  3. logback-core
  4. logback-classic

1和2以及相应数据库的JDBC驱动是必要的,日志实现可以用其它方案。

2、简单的草稿程序

  1. package org.sample.dao;
  2.  
  3. import com.zaxxer.hikari.HikariConfig;
  4. import com.zaxxer.hikari.HikariDataSource;
  5. import org.sample.entity.Profile;
  6. import org.sample.exception.DaoException;
  7.  
  8. import java.sql.Connection;
  9. import java.sql.PreparedStatement;
  10. import java.sql.SQLException;
  11.  
  12. public class Test {
  13. private static HikariConfig config = new HikariConfig();
  14. private static HikariDataSource ds;
  15.  
  16. static {
  17. config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/profiles?characterEncoding=utf8");
  18. config.setUsername("root");
  19. config.setPassword("???????");
  20. config.addDataSourceProperty("cachePrepStmts", "true");
  21. config.addDataSourceProperty("prepStmtCacheSize", "250");
  22. config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
  23. ds = new HikariDataSource(config);
  24. config = new HikariConfig();
  25. }
  26.  
  27. public static Connection getConnection() throws SQLException {
  28. return ds.getConnection();
  29. }
  30.  
  31. private Test(){}
  32.  
  33. public static void main(String[] args) {
  34. Profile profile = new Profile();
  35. profile.setUsername("testname3");
  36. profile.setPassword("123");
  37. profile.setNickname("testnickname");
  38. int i = 0;
  39. try {
  40. Connection conn = Test.getConnection();
  41. String sql = "INSERT ignore INTO `profiles`.`profile` (`username`, `password`, `nickname`) " +
  42. "VALUES (?, ?, ?)"; // 添加ignore出现重复不会抛出异常而是返回0
  43. try (PreparedStatement ps = conn.prepareStatement(sql)) {
  44. ps.setString(1, profile.getUsername());
  45. ps.setString(2, profile.getPassword());
  46. ps.setString(3, profile.getNickname());
  47. i = ps.executeUpdate();
  48. }
  49. } catch (SQLException e) {
  50. throw new DaoException(e);
  51. }
  52. System.out.println(i);
  53. }
  54. }

3、设置连接池参数(只列举常用的)

一台四核的电脑基本可以全部采用默认设置?

autoCommit:控制由连接池所返回的connection默认的autoCommit状况。默认值为是true。
connectionTimeout:该参数决定无可用connection时的最长等待时间,超时将抛出SQLException。允许的最小值为250,默认值是30000(30秒)。
maximumPoolSize:该参数控制连接池所允许的最大连接数(包括在用连接和空闲连接)。基本上,此值将确定应用程序与数据库实际连接的最大数量。它的合理值最好由你的具体执行环境确定。当连接池达到最大连接数,并且没有空闲连接时,调用getConnection()将会被阻塞,最长等待时间取决于connectionTimeout。 对于这个值设定多少比较好,涉及的东西有点多,详细可参看About Pool Sizing,一般可以简单用这个公式计算:连接数 = ((核心数 * 2) + 有效磁盘数),默认值是10。
minimumIdle:控制最小的空闲连接数,当连接池内空闲的连接数少于minimumIdle,且总连接数不大于maximumPoolSize时,HikariCP会尽力补充新的连接。出于性能方面的考虑,不建议设置此值,而是让HikariCP把连接池当做固定大小的处理,minimumIdle的默认值等于maximumPoolSize。
maxLifetime:用来设置一个connection在连接池中的最大存活时间。一个使用中的connection永远不会被移除,只有在它关闭后才会被移除。用微小的负衰减来避免连接池中的connection一次性大量灭绝。我们强烈建议设置这个值,它应该比数据库所施加的时间限制短个几秒。如果设置为0,则表示connection的存活时间为无限大,当然还要受制于idleTimeout。默认值是1800000(30分钟)。(不大理解,然而mysql的时间限制不是8个小时???)
idleTimeout:控制一个connection所被允许的最大空闲时间。当空闲的连接数超过minimumIdle时,一旦某个connection的持续空闲时间超过idleTimeout,就会被移除。只有当minimumIdle小于maximumPoolSize时,这个参数才生效。默认值是600000(10分钟)。
poolName:用户定义的连接池名称,主要显示在日志记录和JMX管理控制台中,以标识连接池以及它的配置。默认值由HikariCP自动生成。

4、MySQL配置

参阅MySQL Configuration

  1. jdbcUrl=jdbc:mysql://127.0.0.1:3306/profiles?characterEncoding=utf8
  2. username=root
  3. password=test
  4. dataSource.cachePrepStmts=true
  5. dataSource.prepStmtCacheSize=250
  6. dataSource.prepStmtCacheSqlLimit=2048
  7. dataSource.useServerPrepStmts=true
  8. dataSource.useLocalSessionState=true
  9. dataSource.rewriteBatchedStatements=true
  10. dataSource.cacheResultSetMetadata=true
  11. dataSource.cacheServerConfiguration=true
  12. dataSource.elideSetAutoCommits=true
  13. dataSource.maintainTimeStats=false

5、修改Java连接数据库#02#中的代码

① HikariCPDataSource.java,hikari.properties如上所示。

  1. package org.sample.db;
  2.  
  3. import com.zaxxer.hikari.HikariConfig;
  4. import com.zaxxer.hikari.HikariDataSource;
  5.  
  6. import java.sql.Connection;
  7. import java.sql.SQLException;
  8.  
  9. public class HikariCPDataSource {
  10. private static final String HIKARI_PROPERTIES_FILE_PATH = "/hikari.properties";
  11. private static HikariConfig config = new HikariConfig(HIKARI_PROPERTIES_FILE_PATH);
  12. private static HikariDataSource ds = new HikariDataSource(config);
  13.  
  14. public static Connection getConnection() throws SQLException {
  15. return ds.getConnection();
  16. }
  17. }

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

② ConnectionFactory.java

  1. package org.sample.db;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.SQLException;
  5.  
  6. /**
  7. * 线程池版
  8. */
  9. public class ConnectionFactory {
  10.  
  11. private ConnectionFactory() {
  12. // Exists to defeat instantiation
  13. }
  14.  
  15. private static final ThreadLocal<Connection> LocalConnectionHolder = new ThreadLocal<>();
  16.  
  17. public static Connection getConnection() throws SQLException {
  18. Connection conn = LocalConnectionHolder.get();
  19. if (conn == null || conn.isClosed()) {
  20. conn = HikariCPDataSource.getConnection();
  21. LocalConnectionHolder.set(conn);
  22. }
  23. return conn;
  24. }
  25.  
  26. public static void removeLocalConnection() {
  27. LocalConnectionHolder.remove();
  28. }
  29. }

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

③ ConnectionProxy.java(代码分层有错误!)

  1. package org.sample.manager;
  2.  
  3. import org.sample.db.ConnectionFactory;
  4. import org.sample.exception.DaoException;
  5.  
  6. import java.sql.Connection;
  7.  
  8. /**
  9. * 对应线程池版本ConnectionFactory,方便在Service层进行事务控制
  10. */
  11. public class ConnectionProxy {
  12. public static void setAutoCommit(boolean autoCommit) {
  13. try {
  14. Connection conn = ConnectionFactory.getConnection();
  15. conn.setAutoCommit(autoCommit);
  16. } catch (Exception e) {
  17. throw new DaoException(e);
  18. }
  19. }
  20.  
  21. public static void commit() {
  22. try {
  23. Connection conn = ConnectionFactory.getConnection();
  24. conn.commit();
  25. } catch (Exception e) {
  26. throw new DaoException(e);
  27. }
  28. }
  29.  
  30. public static void rollback() {
  31. try {
  32. Connection conn = ConnectionFactory.getConnection();
  33. conn.rollback();
  34. } catch (Exception e) {
  35. throw new DaoException(e);
  36. }
  37. }
  38.  
  39. public static void close() {
  40. try {
  41. Connection conn = ConnectionFactory.getConnection();
  42. conn.close();
  43. ConnectionFactory.removeLocalConnection();
  44. } catch (Exception e) {
  45. throw new DaoException(e);
  46. }
  47. }
  48.  
  49. // TODO 设置隔离级别
  50. }

其它地方把LocalConnectionFactory改为ConnectionFactory,LocalConnectionProxy改为ConnectionProxy就行了!后续如果要换其它连接池,只需要改变ConnectionFactory.java里的一小点代码。

6、测试

  1. package org.sample.manager;
  2.  
  3. import org.junit.Test;
  4. import org.sample.dao.ProfileDAO;
  5. import org.sample.dao.impl.ProfileDAOImpl;
  6. import org.sample.entity.Profile;
  7. import org.sample.exception.DaoException;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.Collections;
  11. import java.util.LinkedList;
  12. import java.util.List;
  13. import java.util.concurrent.CountDownLatch;
  14. import java.util.concurrent.ExecutorService;
  15. import java.util.concurrent.Executors;
  16. import java.util.concurrent.TimeUnit;
  17. import java.util.logging.Logger;
  18.  
  19. import static org.junit.Assert.assertTrue;
  20.  
  21. public class DaoTest {
  22.  
  23. private static final Logger LOGGER = Logger.getLogger(DaoTest.class.getName());
  24.  
  25. private static final String ORIGIN_STRING = "hello";
  26. private static String RandomString() {
  27. return Math.random() + ORIGIN_STRING + Math.random();
  28. }
  29. private static Profile RandomProfile() {
  30. Profile profile = new Profile(RandomString(), ORIGIN_STRING, RandomString());
  31. return profile;
  32. }
  33.  
  34. private static final ProfileDAO PROFILE_DAO = ProfileDAOImpl.INSTANCE;
  35.  
  36. private class Worker implements Runnable {
  37. private final Profile profile = RandomProfile();
  38.  
  39. @Override
  40. public void run() {
  41. LOGGER.info(Thread.currentThread().getName() + " has started his work");
  42. try {
  43. // ConnectionProxy.setAutoCommit(false);
  44. PROFILE_DAO.saveProfile(profile);
  45. // ConnectionProxy.commit();
  46. } catch (DaoException e) {
  47. e.printStackTrace();
  48. } finally {
  49. try {
  50. ConnectionProxy.close();
  51. } catch (DaoException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. LOGGER.info(Thread.currentThread().getName() + " has finished his work");
  56. }
  57. }
  58.  
  59. /**
  60. * numTasks指并发线程数。
  61. * -- 不用连接池:
  62. * numTasks<=100正常运行,完成100个任务耗时大概是550ms~600ms
  63. * numTasks>100报错“too many connections”,偶尔不报错,这是来自mysql数据库本身的限制
  64. * -- 采用连接池
  65. * numTasks>10000仍正常运行,完成10000个任务耗时大概是26s(池大小是10)
  66. */
  67. private static final int NUM_TASKS = 2000;
  68.  
  69. @Test
  70. public void test() throws Exception {
  71. List<Runnable> workers = new LinkedList<>();
  72. for(int i = 0; i != NUM_TASKS; ++i) {
  73. workers.add(new Worker());
  74. }
  75. assertConcurrent("Dao test ", workers, Integer.MAX_VALUE);
  76. }
  77.  
  78. public static void assertConcurrent(final String message, final List<? extends Runnable> runnables, final int maxTimeoutSeconds) throws InterruptedException {
  79. final int numThreads = runnables.size();
  80. final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
  81. final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
  82. try {
  83. final CountDownLatch allExecutorThreadsReady = new CountDownLatch(numThreads);
  84. final CountDownLatch afterInitBlocker = new CountDownLatch(1);
  85. final CountDownLatch allDone = new CountDownLatch(numThreads);
  86. for (final Runnable submittedTestRunnable : runnables) {
  87. threadPool.submit(new Runnable() {
  88. public void run() {
  89. allExecutorThreadsReady.countDown();
  90. try {
  91. afterInitBlocker.await();
  92. submittedTestRunnable.run();
  93. } catch (final Throwable e) {
  94. exceptions.add(e);
  95. } finally {
  96. allDone.countDown();
  97. }
  98. }
  99. });
  100. }
  101. // wait until all threads are ready
  102. assertTrue("Timeout initializing threads! Perform long lasting initializations before passing runnables to assertConcurrent", allExecutorThreadsReady.await(runnables.size() * 10, TimeUnit.MILLISECONDS));
  103. // start all test runners
  104. afterInitBlocker.countDown();
  105. assertTrue(message +" timeout! More than" + maxTimeoutSeconds + "seconds", allDone.await(maxTimeoutSeconds, TimeUnit.SECONDS));
  106. } finally {
  107. threadPool.shutdownNow();
  108. }
  109. assertTrue(message + "failed with exception(s)" + exceptions, exceptions.isEmpty());
  110. }
  111. }

本来打算调整连接池参数观察对性能影响的,结果发现即使参数不变,运行时间起伏也有点大。所以暂时先这样了。。。具体原因待探究!

JAVA连接数据库 #03# HikariCP的更多相关文章

  1. Java连接数据库 #04# Apache Commons DbUtils

    索引 通过一个简单的调用看整体结构 Examples 修改JAVA连接数据库#03#中的代码 DbUtils并非是什么ORM框架,只是对原始的JDBC进行了一些封装,以便我们少写一些重复代码.就“用” ...

  2. java连接数据库时的报错

    //java连接数据库时的报错 1 package Java数据库编程; import java.sql.DriverManager; import java.sql.SQLException; im ...

  3. Java连接数据库的辣几句话

    Java连接数据库的辣几句话 1.java连接Oracle数据库 使用以下代码三个步骤: 1.下载ojdbc.jar包并导入项目中.附下载地址:http://download.csdn.net/det ...

  4. servlet中Java连接数据库后的基本操作

    servlet中Java连接数据库后的基本操作 在eclipse中新建一个工程:login 在Server中新建一个服务器,基本的操作不用说了,在前两天的笔记中可以找到; 需要知道数据库的用户名和密码 ...

  5. Java学习03

    Java学习03 1.java面试一些问题 一.什么是变量 变量是指在程序执行期间可变的数据.类中的变量是用来表示累的属性的,在编程过程中,可以对变量的值进行修改.变量通常是可变的,即值是变化的 二. ...

  6. java连接数据库

    package com.shsxt.jdbcs; import java.sql.Connection; import java.sql.DriverManager; import java.sql. ...

  7. Java连接数据库的4中方式详解

    Java连接数据库的方式有多种:根据所需要的不同数据库驱动分,分为四种: 1:1类驱动.这就是JDBC-ODBC桥的方式. 但这种方式不适合程序的重用与维护,不推荐使用.需要数据库的ODBC驱动. 2 ...

  8. Java连接数据库 #06# SQL与代码分离(精化版本)

    索引 DAO层依赖关系草图 应用示例 接Java连接数据库#05#,对代码进行改进. DAO层依赖关系草图 应用示例(只需3步!) 1.首先定义接口类: package org.sample.shop ...

  9. Java连接数据库 #05# SQL与代码分离

    索引 读取html中的SQL语句 缺陷总结 在Java连接数据库 #04#里大概是这样放sql语句的: package org.sample.shop.db.queryrunner; import o ...

随机推荐

  1. Spark创建空的DataFrame

    前言 本文主要给出Spark创建空的DataFrame的代码示例,这里讲的空的DataFrame主要指有列名(可以自己随意指定),但是没有行的DataFrame,因为自己在开发过程中有这个需求,之前并 ...

  2. DataFrame WordCount

    测试数据: ** * 使用DataFrame实现WordCount */ object DataFrameWordCount { def main(args: Array[String]): Unit ...

  3. MySQL 基础 DDL和DML

    DDL 数据库定义语句 创建数据库 create table if exits 数据库.表名( field1 数据类型 约束类型 commit 字段注释, field2 数据类型 约束类型 commi ...

  4. 截取字符串后几位用 length

  5. 15个Node.js项目列表

    前言: Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台,是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascri ...

  6. WireShark过滤器选项

    首先说几个最常用的关键字,"eq" 和 "=="等同,可以使用 "and" 表示并且,"or"表示或者."!& ...

  7. js神秘的电报密码---哈弗曼编码

    哈夫曼编码,根据每个单词在文本中出现的次数频率为权值,频率高的权值大.然后每次取两个频率最小的生成树,最后生成一颗大树.从根节点到该单词的路径,左边为0,右边为1, function HFM(){ v ...

  8. .yml文件格式

    http://yaml.org/ YAML: YAML Ain't Markup Language What It Is: YAML is a human friendly data serializ ...

  9. ida脚本学习

    #!/usr/bin/env python #coding:utf-8 from idc import * import idaapi import idautils import os os.sys ...

  10. 漏洞复现:Struts2 S2-032 漏洞环境

    Struts2 S2-032 漏洞环境 http://vulapps.evalbug.com/s_struts2_s2-032/ POC: http://127.0.0.1/memoindex.act ...