druid数据源检测数据库连接有效性testOnBorrow、testOnReturn、testWhileIdle属性原理分析
druid多数据源建立连接后,可以通过配置对连接的有效性进行检查,想要更好的运用好数据库连接检查配置就应该了解源码,了解控制原理。
druid多数据源检测数据库连接的有效性属性配置如下:
#mysql默认使用ping模式,可以通过设置系统属性System.getProperties().setProperty("druid.mysql.usePingMethod", "false")更改为sql模式
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。默认:SELECT 1
spring.emily.datasource.config.mysql.validation-query="SELECT 1"
#单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法,默认:-1
spring.emily.datasource.config.mysql.validation-query-timeout=-1
#申请连接时执行validationQuery检测连接是否有效,这个配置会降低性能。默认:false(如果test-on-borrow为true,那么test-while-idle无效)
spring.emily.datasource.config.mysql.test-on-borrow=false
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。默认:true
spring.emily.datasource.config.mysql.test-while-idle=true
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。默认:false
spring.emily.datasource.config.mysql.test-on-return=false
获取数据库连接DruidPooledConnection对象入口代码com.alibaba.druid.pool.DruidDataSource#getConnection()
@Overridepublic DruidPooledConnection getConnection() throws SQLException {return getConnection(maxWait);}public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {//对数据库连接池进行初始化 init();if (filters.size() > 0) {FilterChainImpl filterChain = new FilterChainImpl(this);return filterChain.dataSource_connect(this, maxWaitMillis);} else {//获取数据库连接对象return getConnectionDirect(maxWaitMillis);}}
com.alibaba.druid.pool.DruidDataSource#init初始化方法会对ValidConnectionChecker接口的实现类进行初始化
private void initValidConnectionChecker() {if (this.validConnectionChecker != null) {return;}String realDriverClassName = driver.getClass().getName();if (JdbcUtils.isMySqlDriver(realDriverClassName)) {this.validConnectionChecker = new MySqlValidConnectionChecker();} else if (realDriverClassName.equals(JdbcConstants.ORACLE_DRIVER)|| realDriverClassName.equals(JdbcConstants.ORACLE_DRIVER2)) {this.validConnectionChecker = new OracleValidConnectionChecker();} else if (realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER)|| realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER_SQLJDBC4)|| realDriverClassName.equals(JdbcConstants.SQL_SERVER_DRIVER_JTDS)) {this.validConnectionChecker = new MSSQLValidConnectionChecker();} else if (realDriverClassName.equals(JdbcConstants.POSTGRESQL_DRIVER)|| realDriverClassName.equals(JdbcConstants.ENTERPRISEDB_DRIVER)|| realDriverClassName.equals(JdbcConstants.POLARDB_DRIVER)) {this.validConnectionChecker = new PGValidConnectionChecker();}}
此方法分别对不同种类的数据库连接检测有效性的实现类进行初始化,接下来会在建立连接、归还链接过程中用到;
com.alibaba.druid.pool.DruidDataSource#getConnectionDirect方法是获取数据库连接的具体实现:
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {int notFullTimeoutRetryCnt = 0;for (;;) {// handle notFullTimeoutRetryDruidPooledConnection poolableConnection;try {//从数据库连接池中获取连接对象句柄poolableConnection = getConnectionInternal(maxWaitMillis);} catch (GetConnectionTimeoutException ex) {if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {notFullTimeoutRetryCnt++;if (LOG.isWarnEnabled()) {LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);}continue;}throw ex;}//如果开启了获取连接有效性检查,则进行有效性检查if (testOnBorrow) {//有效性检查boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);if (!validate) {if (LOG.isDebugEnabled()) {LOG.debug("skip not validate connection.");}discardConnection(poolableConnection.holder);continue;}} else {if (poolableConnection.conn.isClosed()) {discardConnection(poolableConnection.holder); // 传入null,避免重复关闭continue;}//如果testOnBorrow为false,且testWhileIdle为ture,则在判定连接在空闲时间大于timeBetweenEvictionRunsMillis(心跳检查时间)时才做判定,对性能损耗几乎为0if (testWhileIdle) {final DruidConnectionHolder holder = poolableConnection.holder;long currentTimeMillis = System.currentTimeMillis();long lastActiveTimeMillis = holder.lastActiveTimeMillis;long lastExecTimeMillis = holder.lastExecTimeMillis;long lastKeepTimeMillis = holder.lastKeepTimeMillis;if (checkExecuteTime&& lastExecTimeMillis != lastActiveTimeMillis) {lastActiveTimeMillis = lastExecTimeMillis;}if (lastKeepTimeMillis > lastActiveTimeMillis) {lastActiveTimeMillis = lastKeepTimeMillis;}long idleMillis = currentTimeMillis - lastActiveTimeMillis;long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;if (timeBetweenEvictionRunsMillis <= 0) {timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;}//连接空闲时间大于等于守护线程中心跳间隔时间时才进行有效性检查if (idleMillis >= timeBetweenEvictionRunsMillis|| idleMillis < 0 // unexcepted branch) {boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);if (!validate) {if (LOG.isDebugEnabled()) {LOG.debug("skip not validate connection.");}discardConnection(poolableConnection.holder);continue;}}}}if (removeAbandoned) {StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();poolableConnection.connectStackTrace = stackTrace;poolableConnection.setConnectedTimeNano();poolableConnection.traceEnable = true;activeConnectionLock.lock();try {activeConnections.put(poolableConnection, PRESENT);} finally {activeConnectionLock.unlock();}}if (!this.defaultAutoCommit) {poolableConnection.setAutoCommit(false);}return poolableConnection;}}
com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal(com.alibaba.druid.pool.DruidConnectionHolder, java.sql.Connection)方法对从数据库中获取到的连接有效性进行检查
protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {String sqlFile = JdbcSqlStat.getContextSqlFile();String sqlName = JdbcSqlStat.getContextSqlName();if (sqlFile != null) {JdbcSqlStat.setContextSqlFile(null);}if (sqlName != null) {JdbcSqlStat.setContextSqlName(null);}try {//validConnectionChecker属性是init方法中对各数据库驱动进行初始化的有效性检查实现类if (validConnectionChecker != null) {//对连接进行有效性检查boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);...}
对mysql数据源进行连接有效性检查实现类MySqlValidConnectionChecker源码分析
public class MySqlValidConnectionChecker extends ValidConnectionCheckerAdapter implements ValidConnectionChecker, Serializable {public static final int DEFAULT_VALIDATION_QUERY_TIMEOUT = 1;public static final String DEFAULT_VALIDATION_QUERY = "SELECT 1";private static final long serialVersionUID = 1L;private static final Log LOG = LogFactory.getLog(MySqlValidConnectionChecker.class);private Class<?> clazz;private Method ping;private boolean usePingMethod = false;public MySqlValidConnectionChecker(){try {//8.0之前的mysql驱动clazz = Utils.loadClass("com.mysql.jdbc.MySQLConnection");if (clazz == null) {//8.0之后的驱动clazz = Utils.loadClass("com.mysql.cj.jdbc.ConnectionImpl");}if (clazz != null) {//获取pingInternal方法示例ping = clazz.getMethod("pingInternal", boolean.class, int.class);}if (ping != null) {//实际是使用ping模式检测连接有效性usePingMethod = true;}} catch (Exception e) {LOG.warn("Cannot resolve com.mysql.jdbc.Connection.ping method. Will use 'SELECT 1' instead.", e);}configFromProperties(System.getProperties());}@Overridepublic void configFromProperties(Properties properties) {if (properties == null) {return;}//根据系统属性设置判定连接有效性的方法String property = properties.getProperty("druid.mysql.usePingMethod");if ("true".equals(property)) {setUsePingMethod(true);} else if ("false".equals(property)) {setUsePingMethod(false);}}public boolean isUsePingMethod() {return usePingMethod;}public void setUsePingMethod(boolean usePingMethod) {this.usePingMethod = usePingMethod;}public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {if (conn.isClosed()) {return false;}if (usePingMethod) {if (conn instanceof DruidPooledConnection) {conn = ((DruidPooledConnection) conn).getConnection();}if (conn instanceof ConnectionProxy) {conn = ((ConnectionProxy) conn).getRawObject();}if (clazz.isAssignableFrom(conn.getClass())) {if (validationQueryTimeout <= 0) {validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;}try {//使用ping的方式判定连接有效性ping.invoke(conn, true, validationQueryTimeout * 1000);} catch (InvocationTargetException e) {Throwable cause = e.getCause();if (cause instanceof SQLException) {throw (SQLException) cause;}throw e;}return true;}}String query = validateQuery;if (validateQuery == null || validateQuery.isEmpty()) {query = DEFAULT_VALIDATION_QUERY;}Statement stmt = null;ResultSet rs = null;try {//使用sql的方式校验连接的有效性stmt = conn.createStatement();if (validationQueryTimeout > 0) {stmt.setQueryTimeout(validationQueryTimeout);}rs = stmt.executeQuery(query);return true;} finally {JdbcUtils.close(rs);JdbcUtils.close(stmt);}}}
默认情况下是使用ping的方式判定连接的有效性,但是可以通过druid.mysql.usePingMethod系统属性修改为sql判定连接有效性模式。
com.alibaba.druid.pool.vendor.OracleValidConnectionChecker是oracle校验连接有效性的实现类
public class OracleValidConnectionChecker extends ValidConnectionCheckerAdapter implements ValidConnectionChecker, Serializable {private static final long serialVersionUID = -2227528634302168877L;private int timeout = 1;private String defaultValidateQuery = "SELECT 'x' FROM DUAL";public OracleValidConnectionChecker(){configFromProperties(System.getProperties());}@Overridepublic void configFromProperties(Properties properties) {if (properties == null) {return;}//可以通过系统属性方式修改超时时间String property = properties.getProperty("druid.oracle.pingTimeout");if (property != null && property.length() > 0) {int value = Integer.parseInt(property);setTimeout(value);}}public void setTimeout(int seconds) {this.timeout = seconds;}//采用sql模式校验有效性public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {if (validateQuery == null || validateQuery.isEmpty()) {validateQuery = this.defaultValidateQuery;}if (conn.isClosed()) {return false;}if (conn instanceof DruidPooledConnection) {conn = ((DruidPooledConnection) conn).getConnection();}if (conn instanceof ConnectionProxy) {conn = ((ConnectionProxy) conn).getRawObject();}if (validateQuery == null || validateQuery.isEmpty()) {return true;}int queryTimeout = validationQueryTimeout <= 0 ? timeout : validationQueryTimeout;Statement stmt = null;ResultSet rs = null;try {stmt = conn.createStatement();stmt.setQueryTimeout(queryTimeout);rs = stmt.executeQuery(validateQuery);return true;} finally {JdbcUtils.close(rs);JdbcUtils.close(stmt);}}
}
oracle采用的是sql校验连接有效性的模式,默认超时时间是1s,可以通过系统属性druid.oracle.pingTimeout修改超时时间,也可以通过validation-query-timeout属性配置修改超时时间。
另外连接池会在com.alibaba.druid.pool.DruidDataSource#recycle方法中回收连接的时候通过属性testOnReturn判定是否需要判定连接的有效性。
GitHub地址:https://github.com/mingyang66/spring-parent
druid数据源检测数据库连接有效性testOnBorrow、testOnReturn、testWhileIdle属性原理分析相关推荐
- SpringBoot(配置druid数据源、配置MyBatis、事务控制、druid 监控)
SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少 数据层操作,所有的开发都一定秉持着 MVC 设计模式的原则,MVC 里面业务层不可少 ...
- SpringBoot整合JDBC、整合Druid数据源详解教程
目录 一.整合JDBC 1. 环境准备 1. 创建数据库 2. 创建SpringBoot项目 3. IDEA连接数据库 2. 编写数据库配置信息 3. 编写测试类测试 4. CRUD操作数据库 1. ...
- springboot默认数据源如何设置连接数_spring boot基于DRUID数据源密码加密及数据源监控实现...
项目源码路径:https://github.com/Syske/learning-dome-code.git 前言 随着需求和技术的日益革新,spring boot框架是越来越流行,也越来越多地出现在 ...
- springboot 配置DRUID数据源
druid 是阿里开源的数据库连接池. 开发时整合 druid 数据源过程. 1.修改pom.xml <dependency><groupId>mysql</grou ...
- boot druid 长时间不连接 异常_Spring Boot学习:如何使用Druid数据源
Druid概述 Druid是阿里巴巴开源的一款非常优秀的数据库连接池.在Java应用程序开发中,常用的连接池还有DBCP.C3P0.Proxool等. SpringBoot2.X 版本开始默认的是Hi ...
- SpringBoot整合阿里Druid数据源及Spring-Data-Jpa
SpringBoot整合阿里Druid数据源及Spring-Data-Jpa https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=224 ...
- 阿里巴巴Druid数据源及使用
原文链接:http://www.cnblogs.com/leechenxiang/p/5694397.html 原文链接:http://blog.sina.com.cn/s/blog_6813fb24 ...
- druid 连接池的释放 配合上spring bean销毁_spring boot基于DRUID数据源密码加密及数据源监控实现...
项目源码路径:https://github.com/Syske/learning-dome-code.git 前言 随着需求和技术的日益革新,spring boot框架是越来越流行,也越来越多地出现在 ...
- Druid 数据源连接池配置
在 Spring Boot 的配置文件中对 Druid 数据源连接池进行配置 # Druid连接池的配置 spring:datasource:druid:initial-size: 5 #初始化连接大 ...
- Springboot整合Quartz集群部署以及配置Druid数据源
参考链接: https://blog.csdn.net/wangmx1993328/article/details/105441308 https://blog.csdn.net/qq_3966905 ...
最新文章
- kali python3安装scapy库_Kali-Python scapy模块-扫描
- (44)MessageBoxA 监视器(过写拷贝,不使用 shellcode 注入)
- JZOJ 5234. 【NOIP2017模拟8.7A组】外星人的路径
- 微信小程序——真机调试方法(vConsole)
- PIL 学习笔记(1)
- 【数据结构】图的遍历(BFS和DFS)
- 更改 pandas dataframe 中两列的位置
- python 在线字典_python3
- dede 验证码不显示 vdimgck.php,织梦(dedecms)后台登录验证码不显示或不正常的解决方法...
- 数据库MySQL/mariadb知识点——操作篇(4)数据操作语句
- 基于SSM的废品商城
- JAVA 实现《2048游戏》游戏
- 手把手教你Charles抓包工具使用
- CSS命名及书写规范
- python爬虫-字体反爬全流程(woft文件-转换字体-字体图片-图片识别全流程)
- centos有道linux安装,centos7安装有道词典(不能发音和取词)
- 交换机和路由器的区别计算机网络,交换机和路由器的区别,教您交换机和路由器的区别...
- js 迅雷 批量下载
- django笔记10 cookie整理
- html怎么打出一个圆点,如何打出两个字中间的圆点
热门文章
- 创宇猎幽斩获“2022年网络安全优秀创新成果大赛”三等奖!
- php查询qq等级,php仿QQ等级太阳显示函数_php
- IL汇编语言介绍(译)
- 使用Mono.Cecil对MSIL进行注入
- php面试兴趣爱好,简历中的兴趣爱好该怎么写?
- r语言nonzerocoef函数_文献汇报||Lasso方法在肿瘤基因位点筛选中的应用
- 关于流浪狗社会现状的调查报告
- Android Go项目 来电铃声与UI不同步问题
- c语言数组文曲星猜数游戏编程,第7章 数组-8数组的其他应用——文曲星猜数游戏.pdf...
- Android实现网页动态背景“五彩蛛网”