druid数据库连接池问题:java.sql.SQLException: connection holder is null

项目中使用了alibaba/druid数据库连接池,但是发现运行着一段时间后,总会报

java.sql.SQLException: connection holder is null

查阅很多解决办法都说设置。

 <property name="removeAbandoned" value="true" />

 <property name="removeAbandonedTimeout" value="1800" />

这两个参数就可以了,但我的项目里,并不是这种超时间使用连接没有回收而导致的,所以这种解决方法并不能解决我的问题。

而且druid版本已经是1.1.2最新的版本,排除了druid非bug原因,那就是我本身项目代码问题了。

在druid官方github问题里也有很多同学的发现问题发析原因,顿时有了思路。

参考连接:  原因分析1原因分析2

两个问题都指出跨线程使用druid而引发的,温少也建议不要跨线程使用数据连接。

所以经过两天的查资料,和不断地尝试,终于找出问题所在。

首先我的项目里是用了spring-boot,连接池配置这些就不贴出来了,都大同小异。

而我的事务管理也是用的是spring的 @Transactional 注入管理。

问题在于,我的一个service方法里的一个方法里,该方法在事务提交之后,会再次触发另一个service执行代码。

@Service
@Transactional(value = "transaction", rollbackFor = Exception.class)
public class a_service {
    
    @Autowired
    private Dao dao;

    @Autowired
    private ApplicationEventPublisher publisher;
    
    /**
     *该方法执行所有代码之后,会触发publisher.publishEvent方法
     */
    public String test(){
        .....省略执行代码.....
        
        publisher.publishEvent(params);
    }


}

@Service
@Transactional(value = "transaction", rollbackFor = Exception.class)
public class b_service {

    @Autowired
    private Dao dao;
    
    /**
     * 当事务提交后, 才会真正的执行@TransactionalEventListener配置的Listener, 如果Listener抛异常, 方法返回失
     * 败, 但事务不会回滚.
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public String test(){
        dao.save(params);
    }


}

 

问题就出于 b_service执行这里,当触发b_service方法时,b_service的事务隔离级别是默认PROPAGATION_REQUIRED,即如果当前存在事务则加入该事务

PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

由于b_service方法事务监听a_service方法,a_service的连接提交事务后就回收到连接池中,b_service获取方法运行完之后归还连接,b_service获取的连接可能是a_service的准备归返的连接,所以就造成跨线程使用链接问题

解决方法就是在b_service 事务管理设置事务级别 propagation = Propagation.REQUIRES_NEW

@Service
@Transactional(value = "transaction", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public class b_service {

    @Autowired
    private Dao dao;
    
    /**
     * 当事务提交后, 才会真正的执行@TransactionalEventListener配置的Listener, 如果Listener抛异常, 方法返回失
     * 败, 但事务不会回滚.
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public String test(){
        dao.save(params);
    }


}

参考资料

https://github.com/alibaba/druid/issues/1429

https://github.com/alibaba/druid/issues/1889

http://timerbin.iteye.com/blog/2332995

https://my.oschina.net/haogrgr/blog/224010

 

ps:

@TransactionalEventListener, 可以方便在事务周期内处理一些事情, 比如事务提交后触发某一事件.

    一个场景就是, 当插入记录提交事务后, 异步发送消息到其他系统, 或本地记录日志等操作, 现在可以通过TransactionalEventListener来做了. 

更多关于TransactionalEventListener使用

上面的文字解释可能表达得不太好,也有些歧意,如果有更好见解,可以联系私信本人,谢谢

转载于:https://my.oschina.net/u/1789379/blog/1506450

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值