@Service
public class TestService {
//jpa repository
@Autowired
private ClusterBasicRepository repository;
* 无事务增强
public void testWithoutTx() {
System.out.println("before query");
repository.findAll();
System.out.println("after query");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
System.out.println("end");
* 事务增强
@Transactional
public void testWithTx() {
((TestService)AopContext.currentProxy()).testWithoutTx();
(插一句:网上存在一个说法,spring的动态代理,如果target实现了接口,使用JDK动态代理,如果target没有实现接口,使用CGLIB;但是我在debug的过程中,就算TestService实现了接口,依然使用CGLIB,与之前的理论相悖,目前还没找出原因,当然,也不是这篇文章的重点。)
分析前,先来说一下spring的事务增强原理:利用ThreadLocal,将Connection与Thread进行绑定,也就是说,有事务的情况下,至少在事务增强的方法中(超出事务增强依然有可能不释放connection,这个也是本次问题的一个原因),connection是不会返还给connection pool的。
首先测试testWithoutTx()方法,输出如下图(breakpoint设置在controller的return语句上):
- 在testWithoutTx的sleep过程中(after query与end之间的输出),connection并没有被释放.
- 完成TestService.testWithoutTx(),进入Controller后(end之后的输出),connection依然没有被释放。
还有一个有趣的现象,如下图:
表明:整个service确实没有事务,否则在service method进入之前,就被spring的事务增强处理了。
query语句的连接为什么没有释放?
上面两张图,让我陷入了一脸懵逼的状态,这和理论知识对不上啊,那么,来看一下一张源码截图:
没错,这个对象就是驱动我们定义的JpaRepository的执行类,它居然自己加了@Transactional。
但是,理论上事务只是增强了JPA的方法,在repository.findAll()执行结束后,事务增强就结束了,connection应该被归还了才对呀!
这个问题的排查需要通过debug一遍一遍看源码,我就不罗嗦了,直接上结论:
就是这个属性引起的,默认值是true。
在启用open-in-view时,spring在整个controller的调用链路中,只要遇到一次Transactional增强逻辑,就会将connection绑定到ThreadLocal中,并在controller结束时才会释放,其实现原理时利用了springMVC的interceptor。
下面,我把open-in-view禁用,就会发现下面的现象了:
此时,尽管findAll()方法依然被事务增强,但connection能够及时释放了。
在testWithTx方法中
到目前为止,我们可以保证testWithoutTx可以及时释放connection了,但如果我们希望在testWithTx方法中,调用testWithoutTx,此时又会是什么情况呢?
一个更神奇的现象出现了,我甚至都没有执行JPA的findAll(),connection就被占用了,而第二图,在end之后connection就释放了,说明前面设置的open-in-view还是起作用的。
那么,是什么原因导致我没有任何DB操作,connection依然被占用了呢?
无DB操作情况下Connection被占用
又是一遍debug读源码,导致这个问题的原因居然是TransactionMananger,下面是源码:
也就是说,JpaTransactionMananger在启动事务的时候,会从datasource中占用connection,那么下面的情况就可能出现了:
这里,我把testWithTx()修改了一下,不去触发任何DB操作,但依然会占用connection资源,符合上面的分析结果。
Spring-JdbcTemplate是否存在上面的问题?
上面小节中,我们了解了JpaTransactionMananger会导致经过事务增强的方法,只要运行就会占用一个connection,那么非Jpa环境下的TransactionMananger,是不是还会有问题呢?
我在去掉Jpa,只使用JdbcTemplate的情况下,使用的TransactionMananger是
来看一下它的doBegin()方法:
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//!!!!!!
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
//!!!!!!
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
con.setAutoCommit(false);
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
上面我用//!!!!标记了几句话,其他的也不用看了,还是和JpaTransactionManager一个逻辑,通过验证,也确实存在和JpaTransactionMananger一样的问题(称为问题可能不合适,只是对于我的使用场景来说不符合逻辑)。
- Jpa环境下,建议禁用open-in-view。(当然,将connection绑定到controller也有其优势,不能强制约束)。
- @Transactional注解慎重添加,没有数据库操作的尽量不要加。
目前为止,还没有试过编程式事务情况下,是否存在上述问题。
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool exhausted
at org.springframework.jdbc.dataso
#回收被遗弃的(一般是忘了释放的)数据库连接到连接池中。 dataBase.removeAbandoned =false
# 数据库连接过多长时间不用将被视为被遗弃而收回连接池中。 dataBase.removeAbandonedTimeout = 30
# 将被遗弃的数据库连接的回收记入日志。 dataBase.logAbandoned = false
#连接池的最大数据...
Spring在第三方依赖包中包含了两个数据源的实现类包。其一是Apache的DBCP.其二是C3P0.可以在Spring的配置文件中利用两者中任一配置数据源。
DBCP数据源:
DBCP是一个依赖 Jakarta commons-pool对象池机制的数据库连接池,所以在类路径下还必须包括<spring_home></spring_home>/l...
开发web项目,我们肯定会和数据库打交道,因此就会涉及到数据库链接的问题。在以前我们开发传统的SSM结构的项目时进行数据库链接都是通过JDBC进行数据链接,我们每和数据库打一次交道都需要先获取一次链接,操作完后再关闭链接,这样子效率很低,因此就出现了连接池,用于高效创建并合理分配数据库链接,数据库连接池跟线程池其实也一样的道理。说到连接池就不得不说到持久层的框架。......
1、使用org.springframework.jdbc.datasource.DriverManagerDataSource 说明:需要jar包:spring-jdbc.jar。DriverManagerDataSource只是新建连接,根本没有连接池的作用,不推荐使用2、使用org.apache.commons.dbcp.BasicDataSource 说明:这是一种推荐说明的数据源配置方式,...
文章目录hikari-cp1.引入依赖2. spring配置3. springboot配置druid1.引入maven依赖2.spring配置dbcp21.引入依赖2.spring配置spring配置
hikari-cp
1.引入依赖
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
spring.datasource.test-while-idle=true
#当从连接池借用连接时,是否测试该连接
spring.datasource.test-on-borrow=false
#指定空闲连接检查、废弃连接清理、空闲连接池大小调整之间的操作时间间隔
spring.datasou
如题,本人在使用 Spring JPA 时,遇到了 "Too Many Connections" 的问题。
本人试用了许多种方法都无法解决,最后迫于无奈把整个数据层换成 Hibernate 自己管理连接,这个问题才得以解决。
在此,做一记录,日后解决了此问题再更新。
若有人解决了这个问题,望不吝赐教。