添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
逆袭的可乐  ·  MYSQL 查询 ...·  1 年前    · 
知识渊博的热带鱼  ·  js: ...·  1 年前    · 
憨厚的黄豆  ·  python - ...·  1 年前    · 

由于本人对spring没有深入研究,这篇文章只是针对遇到的问题以及其产生原因进行简单记录。

在使用spring-boot-2.4.2开发的项目中,有一个controller,需要调用RPC服务,而调用RPC所需要的数据都存放在DB中。

RPC调用,在非法输入的情况下,会阻塞整个controller reqeust,之后系统就会频繁出现 can not aquire connection from the db pool 的问题,从而带崩整个web服务。

项目中使用spring-data-jpa和spring jdbcTemplate进行数据库操作。

问题排查步骤

构造测试用例

首先需要了解,Datasource的connection是什么时候被占用,又是在什么时候释放的?

  • 增加一个bean,打印connection pool的使用情况
@Component
public class TestComponent {
    @Autowired
    private HikariDataSource dataSource;
    @PostConstruct
    public void init() {
        Thread thread = new Thread(()->{
            while(true) {
                System.out.println(dataSource.getHikariPoolMXBean().getTotalConnections()
                        +":::"+dataSource.getHikariPoolMXBean().getActiveConnections());
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
        thread.setDaemon(true);
        thread.start();
  • 创建测试用的TestController、TestService
@RestController
@RequestMapping("/test/")
public class TestController {
    @Autowired
    private TestService testService;
    @GetMapping("test")
    public String test() {
        this.testService.testWithoutTx();
//        this.testService.testWithTx();
        return "{\"a\":1}";
@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语句上):

  1. 在testWithoutTx的sleep过程中(after query与end之间的输出),connection并没有被释放.
  2. 完成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一样的问题(称为问题可能不合适,只是对于我的使用场景来说不符合逻辑)。

  1. Jpa环境下,建议禁用open-in-view。(当然,将connection绑定到controller也有其优势,不能强制约束)。
  2. @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 自己管理连接,这个问题才得以解决。 在此,做一记录,日后解决了此问题再更新。 若有人解决了这个问题,望不吝赐教。