一、背景介绍
在之前的文章中,我们介绍了 ORM 框架相关的使用方式,这些框架其实都有一个显著的特点,那就是会经常跟数据库打交道。
熟悉 JDBC 的同学可能知道,用 Java 来操作数据库,通常需要先创建一个数据库连接,然后通过这个连接来执行相关的 SQL 语句,当执行完毕之后需要再次手动释放连接。每当来一个涉及数据库的业务操作时,都需要经历同样的操作步骤,示例如下:
// 1.加载数据库驱动包
Class.forName(DRIVER_CLASS);
// 2.创建一个数据库连接实例
Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
// 3.执行SQL语句
Statement statement = conn.createStatement();
statement.executeUpdate("insert into tb_user(id, name) values(1, 'tom') ");
....
// 4.关闭连接
statement.close();
conn.close();
当请求量不高的时候,这种模式不会存在很大的问题,可以正常提供服务,但是当请求量超过 1000 的时候,可能数据库连接数就不够用了,请求创建数据库连接的客户端会直接报错。
为了解决数据库连接数不够用的问题,于是诞生了数据库连接池。其核心思想是:用户需要访问数据库时,并非创建一个新的连接,而是从连接池中取出一个已建立的空闲连接对象,使用它来访问和操作数据库;当用户访问数据库完毕之后,也并非将连接关闭掉,而是将连接对象还回到连接池中,以便下一个请求访问使用,实现连接复用的效果。
事实上,也确实如此,通过连接池来管理数据库的连接,可以有效的提高数据库访问效率,降低连接异常,提升系统响应速度等。
目前,市面上开源的数据库连接池框架非常的多,下面,我们列举几个比较知名的 JDBC 开源连接池组件,简要的了解一下它们的发展历史。
- C3P0:一款很古老的 JDBC 连接池,因作为 Hibernate 框架内置的数据库连接池而被开发者所熟知,但是由于性能较差,且代码复杂度很高,官方已经放弃维护
- DBCP:由 Apache 开发的一个 Java 数据库连接池项目,Tomcat 默认使用的连接池组件,采用单线程来操作连接,性能不好,能支持的并发量低,逐渐被淘汰
- Tomcat Jdbc Pool:这个数据库连接池可以看作是 DBCP 的升级版,它支持异步方式获取连接,在高并发应用环境下依然保存较好的效果,Tomcat 7及以后默认的连接池组件
- BoneCP:一款高效、免费的 JDBC 连接池,BoneCP 号称是最快的连接池框架,不过从 2013 年后不再更新,稳定性不佳
- Druid:阿里出品的一个数据库连接池,功能比较全面,有着高可用且扩展较好的特点,同时还自带监控服务,国内流行度非常高
- HikariCP:数据库连接池的一个后起之秀,在 BoneCP 基础上开发的一个高性能的 JDBC 连接池,号称性能最好,目前已作为 SpringBoot2 默认的数据库连接池组件
从实际的性能测试来看,排名如下:HikariCP > Druid > tomcat-jdbc > dbcp > c3p0。其中HikariCP的性能最好,这主要得益于它采用最大限度的避免锁竞争的处理思路,进一步加快了连接池的处理效率。
其次,Druid 功能最为全面,比如支持 SQL 拦截、慢 SQL 监控等,同时具有良好的扩展性,性能也不错。
总的来看,如果追求高性能,可以选择 HikariCP 连接池;如果看中更多的功能支持,可以选择 Druid。
下面我们一起来看看这两款连接池的具体应用方式。
二、HicariCP
在此,我们介绍两种方式来完成 HicariCP 连接池的配置初始化,以便于对它的使用有更清晰的理解。
- 第一种:通过自定义配置文件加载 HicariCP
- 第二种:SpringBoot 整合 HicariCP
2.1、自定义配置文件加载 HicariCP
2.1.1、添加 HicariCP 依赖库
首先在pom.xml文件中,添加 HicariCP 依赖库,内容如下:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
2.1.2、编写 HicariCP 相关的配置属性
然后在application.properties文件中,自定义 HicariCP 相关的配置属性,内容如下:
# 自定义 hikari 数据源配置属性
hikari.driver-class-name=com.mysql.jdbc.Driver
hikari.url=jdbc:mysql://localhost:3306/test
hikari.username=root
hikari.password=root
hikari.pool-name=HikariCP
hikari.minimum-idle=5
hikari.maximum-pool-size=20
hikari.idle-timeout=600000
hikari.auto-commit=true
hikari.max-lifetime=1800000
hikari.connection-timeout=30000
hikari.connection-test-query=SELECT 1
2.1.3、编写 HikariDataSource 初始化方法
接着创建一个HikariDataSourceConfig,用于初始化HikariDataSource类并将其注入到 Bean 工厂中,内容如下:
@Configuration
publicclass HikariDataSourceConfig {
@Value("${hikari.driver-class-name}")
private String driverClassName;
@Value("${hikari.url}")
private String url;
@Value("${hikari.username}")
private String userName;
@Value("${hikari.password}")
private String password;
@Value("${hikari.pool-name}")
private String poolName;
@Value("${hikari.minimum-idle}")
private Integer minimumIdle;
@Value("${hikari.maximum-pool-size}")
private Integer maximumPoolSize;
@Value("${hikari.idle-timeout}")
private Integer idleTimeout;
@Value("${hikari.auto-commit}")
private Boolean autoCommit;
@Value("${hikari.max-lifetime}")
private Integer maxLifetime;
@Value("${hikari.connection-timeout}")
private Integer connectionTimeout;
@Value("${hikari.connection-test-query}")
private String connectionTestQuery;
@Bean
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
// 数据源的驱动类型
config.setDriverClassName(driverClassName);
// 数据源的连接地址
config.setJdbcUrl(url);
// 数据源的用户名
config.setUsername(userName);
// 数据源的密码
config.setPassword(password);
// 连接池名字
config.setPoolName(poolName);
// 最小连接数
config.setMinimumIdle(minimumIdle);
// 最大连接数
config.setMaximumPoolSize(maximumPoolSize);
// 空闲连接存活最大时间,默认10分钟
config.setIdleTimeout(idleTimeout);
// 此属性控制从池中获取的连接的默认自动提交行为,默认值:true
config.setAutoCommit(autoCommit);
// 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
config.setMaxLifetime(maxLifetime);
// 数据库连接超时时间,默认30秒
config.setConnectionTimeout(connectionTimeout);
// 连接测试query
config.setConnectionTestQuery(connectionTestQuery);
// 初始化 Hikari 连接池
HikariDataSource ds = new HikariDataSource(config);
return ds;
}
}
最后启动服务,即可实现数据源的加载。此方案采用的是通过自定义配置文件完成连接池的手动初始化管理。
2.2、SpringBoot 整合 HicariCP(推荐)
在上文中,我们介绍了通过自定义配置文件来实现HicariCP的加载。其实也可以在 SpringBoot 的自动装配下完成HicariCP的加载。
2.2.1、添加 jdbc 依赖库
如果当前版本是Spring Boot 2.0及以上的版本,HicariCP会作为默认的数据库连接池组件。
当添加spring-boot-starter-jdbc依赖包的时候,会自动添加HicariCP相关的依赖包,无需再次重复添加。
<!-- 添加 jdbc 支持(默认含 HicariCP 依赖包) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2.2.2、添加 HicariCP 相关的配置属性
与上文不同,本次我们需要采用 Spring Boot 能识别的属性配置,以便帮助自动完成HicariCP数据源的初始化。
# 添加hikari数据源配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
最后启动服务,即可实现数据源的加载。这种实现方式与上文介绍的方式效果一样,并且配置更加简单。
三、Druid
Druid 作为一个开源数据库连接池组件,因其强大的监控功能,在国内应用也非常广泛。
在此,我们也介绍两种方式来完成 Druid 连接池的配置初始化,以便于对它的使用有更清晰的理解。
- 第一种:通过自定义配置文件加载 Druid
- 第二种:SpringBoot 整合 Druid
3.1、自定义配置文件加载 Druid
3.1.1、添加 Druid 依赖库
首先在pom.xml文件中,添加 Druid 依赖库,内容如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
3.1.2、编写 Druid 相关的配置属性
然后在application.properties文件中,自定义 Druid 相关的配置属性,内容如下:
# 添加druid数据源配置
druid.driver-class-name=com.mysql.jdbc.Driver
druid.url=jdbc:mysql://localhost:3306/test
druid.username=root
druid.password=root
druid.initialSize=5
druid.minIdle=5
druid.maxActive=20
druid.maxWait=60000
druid.minEvictableIdleTimeMillis=300000
druid.timeBetweenEvictionRunsMillis=60000
druid.validationQuery=SELECT 1 FROM DUAL
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=false
druid.filters=stat,wall
druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
druid.useGlobalDataSourceStat=true
3.1.3、编写 DruidDataSource 初始化方法
接着创建一个DruidConfig,用于初始化DruidDataSource类并将其注入到 Bean 工厂中,内容如下:
@Configuration
publicclass DruidConfig {
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(DruidConfig.class);
@Value("${druid.driver-class-name}")
private String driverClassName;
@Value("${druid.url}")
private String url;
@Value("${druid.username}")
private String username;
@Value("${druid.password}")
private String password;
@Value("${druid.initialSize}")
private Integer initialSize;
@Value("${druid.minIdle}")
private Integer minIdle;
@Value("${druid.maxActive}")
private Integer maxActive;
@Value("${druid.maxWait}")
private Integer maxWait;
@Value("${druid.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${druid.timeBetweenEvictionRunsMillis}")
private Integer timeBetweenEvictionRunsMillis;
@Value("${druid.validationQuery}")
private String validationQuery;
@Value("${druid.testWhileIdle}")
privateboolean testWhileIdle;
@Value("${druid.testOnBorrow}")
privateboolean testOnBorrow;
@Value("${druid.testOnReturn}")
privateboolean testOnReturn;
@Value("${druid.poolPreparedStatements}")
privateboolean poolPreparedStatements;
@Value("${druid.filters}")
private String filters;
@Value("${druid.connectionProperties}")
private String connectionProperties;
@Value("${druid.useGlobalDataSourceStat}")
privateboolean useGlobalDataSourceStat;
@Bean
public DruidDataSource dataSourceDefault(){
DruidDataSource datasource = new DruidDataSource();
// 数据源的驱动类型
datasource.setDriverClassName(driverClassName);
// 数据源的连接地址
datasource.setUrl(url);
// 数据源的用户名
datasource.setUsername(username);
// 数据源的密码
datasource.setPassword(password);
// 初始化连接池大小
datasource.setInitialSize(initialSize);
// 设置最小连接数
datasource.setMinIdle(minIdle);
// 设置最大连接数
datasource.setMaxActive(maxActive);
// 设置获取连接时的最大等待时间
datasource.setMaxWait(maxWait);
// 一个连接在池中最小生存的时间,单位是毫秒
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 检测连接是否有效的 SQL语句
datasource.setValidationQuery(validationQuery);
// 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
datasource.setTestWhileIdle(testWhileIdle);
// 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
datasource.setTestOnBorrow(testOnBorrow);
// 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
datasource.setTestOnReturn(testOnReturn);
// 是否打开PSCache,oracle支持,Mysql不支持
datasource.setPoolPreparedStatements(poolPreparedStatements);
// druid监控配置信息
try {
// 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
datasource.setFilters(filters);
} catch (SQLException e) {
LOGGER.error("druid configuration initialization filter", e);
}
// 通过connectProperties属性来打开mergeSql功能;慢SQL记录
datasource.setConnectionProperties(connectionProperties);
// 合并多个DruidDataSource的监控数据
datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
return datasource;
}
}
3.1.4、编写监控服务初始化方法
在上文我们有说到,Druid 自带强大的监控服务,通过相关配置类即可将其开启,内容如下:
@Configuration
publicclass DruidMonitorConfig {
/**
* 这里相当于servlet的web.xml
* @return
*/
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet());
// 添加映射
bean.addUrlMappings("/druid/*");
//设置一些初始化参数
Map<String, String> initParas = new HashMap<>();
initParas.put("loginUsername", "admin");
initParas.put("loginPassword", "123456");
//允许谁能防伪
initParas.put("allow", "");//这个值为空或没有就允许所有人访问,ip白名单
//initParas.put("allow","localhost");//只允许本机访问,多个ip用逗号,隔开
//initParas.put("deny","");//ip黑名单,拒绝谁访问 deny和allow同时存在优先deny
initParas.put("resetEnable", "false");//禁用HTML页面的Reset按钮
bean.setInitParameters(initParas);
return bean;
}
/**
* 配置一个过滤器,Servlet按上面的方式注册Filter也可以这样
* @return
*/
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
//可以设置也可以获取,设置一个阿里巴巴的过滤器
bean.setFilter(new WebStatFilter());
bean.addUrlPatterns("/*");
//可以过滤和排除哪些东西
Map<String, String> initParams = new HashMap<>();
//把不需要监控的过滤掉,这些不进行统计
initParams.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
return bean;
}
}
最后启动服务,即可实现数据源的加载。
同时,在浏览器访问http://127.0.0.1:8080/druid/页面,输入在DruidMonitorConfig配置类中的账号、密码,即可登陆监控服务,查询相关 SQL 监控看板,部分界面如下:
图片
3.2、SpringBoot 整合 Druid(推荐)
如果觉得以上配置很麻烦,也可以通过 SpringBoot 的自动装配下完成Druid的加载。
3.2.1、添加 Druid-starter 依赖库
首先添加druid-spring-boot-starter依赖包,通过它来完成配置参数自动装配,内容如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
3.2.2、添加 Druid 相关的配置属性
本次我们需要采用 Spring Boot 能识别的属性配置,以便帮助自动完成Druid数据源的初始化。
# 添加druid数据源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=20
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=false
# 以下是配置监控信息(可选)
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.merge-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=5000
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=12345678
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusinotallow=*.js,*.css,/druid/*
最后启动服务,即可实现数据源的加载。效果等于通过自定义配置文件实现手动加载的结果。
四、小结
本文主要围绕 Spring Boot 整合数据库连接池组件,实现系统连接数的可控管理目标进行一次知识内容的整理和总结!
五、参考
1.https://zhuanlan.zhihu.com/p/460846041
2.https://developer.aliyun.com/article/1000769