如何正确地获取一个有效的数据库连接

这几天在研究各个数据库连接池,比如ali druid, dbcp2 以及最近很火的连接池 HikariCP, 除了常规的池化连接对象管理外。关键区别就是如何创建连接,防止连接泄漏,如何获取连接这些细节的区分点.在hikariCP的wiki中,提到一篇文章 https://github.com/brettwooldridge/HikariCP/wiki/Bad-Behavior:-Handling-Database-Down, 这里面提到由于网络的问题导致获取连接会比预期的时间长的这一问题。这也是我们为什么在选择一些组件和框架时,均会优先使用偏新的版本的原因。一些老的,旧的连接池,因为api的限制,对一些极端情况的处理并不能如意。比如在测试中的c3p0,dbcp这些常规连接池,由于历史原因,在处理一些新的需求时都不能满足需求。

我们来看对于一个标准的数据库连接,我们会有以下的要求:

  1. 在规定的时间内拿到数据库连接
  2. 使用一个alive的connection进行数据访问

整个要求其实就是判断connection存活,处理超时的问题。在常规的数据访问中,因为不同sql查询的原因,我们不能够期望数据库查询在一个较小的时间内就一定能返回(比如5秒)。但是在程序中,当获取一个连接时,又期望在较小的时间内返回给应用,以方便应用能够快速响应业务。如果5s之内(或者更小)不能拿到连接,能认为当前业务失败,避免业务长时间不能响应。甚至避免整个系统所有线程均blocking在获取数据库连接这一步,而导致系统不可用.

那么整个问题变为了以下3步:

  1. 业务在较小的时间内拿到连接,如果超时则立刻返回
  2. 连接池在较小的时间内创建数据库连接,如果超时则立即返回,进行下一步尝试
  3. 连接池在较小的时间内验证已经创建好的连接当前可用,如果超时则立即返回,并标记当前连接不可用,选择其它的连接

继续阅读“如何正确地获取一个有效的数据库连接”

使用BasicDataSource引发的数据库连接中断的问题和解决方法

最近碰到一个问题,应用程序每天的第一次进行系统访问时,会报一个奇怪的错误,最后经过仔细的跟踪,错误信息找到了,如下所示:

 The last packet successfully received from the server was 60,428,178 milliseconds ago.  The last packet sent successfully to the server was 60,428,180 milliseconds ago

很明显,这个错误信息表示数据库连接实际上已经断开了,因为在深夜没有客户来访问系统。但是程序中并没有检测到这个信息,仍然使用过期的数据库连接,那肯定会报错。而在之后,datasource检测到这个问题,则尝试重新进行连接,这样接下来的链接就正常了。
在项目中使用的数据库连接池是dbcp,即apache-dbcp的BasicDataSource。最初的配置为:

<bean id="dataSourceLog" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="" />
		<property name="defaultAutoCommit" value="" />
		<property name="url" value="" />
		<property name="username" value="" />
		<property name="password" value="" />
		<property name="maxActive" value=""/>
</bean>

经仔细分析,这样的配置不会对连接的连接超时以及有效性进行检测,因此会发生以上的问题。要解决这个问题,就需要额外的检测手段和处理方法,还好,dbcp本身提供了这些配置,只不过当前系统中并没有这样的配置。

在处理这个问题中,需要处理好以下这些问题

  • 连接多少时间的数据库连接被认为是无效的
  • 如何检测一个连接是有效的
  • 如何检测那些执行SQL时间长的连接
  • 周期性连接检测需要多长时间执行一次

上面的这些问题,dbcp都提供了有效的配置方法,只需要详细的配置即可。如详细的配置如下:

//是否要进行检测
		<property name="testWhileIdle" value="true"/>
//进行检测一个连接是有效的SQL语句,比如oracle是select 1 from dual 而 mysql是 select 1
		<property name="validationQuery" value=""/>
//是否在数据库连接请求量大的时候,如总数50,当前已请求了49个,所剩不多了,检测那些执行时间久的连接,将其从池中移除掉(移除之后怎么作,由实现决定,如直接断开,或者任其继续执行等)
		<property name="removeAbandoned" value="true"/>
//一次数据库操作执行时间超过多少秒的连接被认为是需要移除的
		<property name="removeAbandonedTimeout" value=""/>
//每隔多少时间检测一次,比如每半小时检测一次,总不能总是检测,这会对性能产生影响
		<property name="timeBetweenEvictionRunsMillis" value=""/>
//每次检测时,需要检测多少个数据连接,一般设置为与最大连接数一样,这样就可以检测完所有的连接
		<property name="numTestsPerEvictionRun" value=""/>
//一个数据库连接连接多少时间之外,我们认为其应该不再适用了(可能下一次就会失效了),应该移除并重新建立连接了
		<property name="minEvictableIdleTimeMillis" value=""/>

经过这样配置之后,之前的问题就不再出现了。引起这个问题的原因是多方面的,但对于问题主因,仍是对dbcp未完全了解,总是按默认配置进行开发。在某些场景下就会突发一些想不到的问题。

在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决

在使用Mybatis中,通常使用接口来表示一个Sql Mapper的接口以及相对应的xml实现,而在spring的配置文件中,通常会使用MapperScannerConfigurer来达到批量扫描以及简化spring bean接口配置的目的,以直接让mybatis的各个接口直接成为spring的bean组件。那么,一个通常的spring配置文件如下所示:

	<bean id="datasource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"/>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="datasource"/>
	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory"/>
	</bean>
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		  p:dataSource-ref="datasource"/>
	<bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer"
		  p:basePackage="mapper"/>

上面的配置分别对应于一个基本的mybatis最需要的信息。值得需要注意的是,在配置MapperScannerConfigurer时,这里并没有指定sqlSessionFactoryName以及sqlTemplateName。在没有指定的情况下,spring就会指定默认的查找规则进行查询,如分别查找到默认的sqlSessionFactory实现和sqlSessionTemplate实现,并注入到MapperFactoryBean中。

这种配置方式,在一个数据源时,没有问题,但是在如果存在多个数据源时,上面的配置就存在问题了。在多个数据源时,如果配置不正确,或者配置的步骤不正确,将直接产生莫名奇妙的问题。而这个问题的产生,不在于开发人员,即不在于程序员本身,而在于spring,或来自于mybatis-spring,在其内部画蛇添足的注解,将导致整个多数据源配置完全不能工作。

继续阅读“在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决”

tomcat如何解析resource数据源

我们都知道在tomcat中,可以通过在context.xml中配置resource中用于配置tomcat数据源,如下所示即是一个配置例子。

<Context>
	<Resource name="jdbc/xx" auth="Container" type="javax.sql.DataSource" password="mymysql"
			  driverClassName="com.mysql.jdbc.Driver"
			  username="root" url="jdbc:mysql://127.0.0.1/xx"
			  />
</Context>

配置了如上的数据源之后,在java代码中,就可以以如下代码进行访问:

         InitialContext initialContext = new InitialContext();
	DataSource dataSource = (DataSource) initialContext.lookup("java:comp/env/jdbc/xx");

那么,tomcat是如何将resource中的信息解析成上下文中,并可以通过jndi的方式进行访问呢。这就得从contextResource对象的创建说起。

在NamingContextListener中,namingContext被创建,同时相应的comp上下文和evn上下文被创建起来。然后通过解析context.xml,将最终的jdbc/xx节点绑定在相应的上下文中,并通过解析Resource节点,最终确定数据源对象的创建。

继续阅读“tomcat如何解析resource数据源”