Quartz强制继承当前线程事务

先简述一下问题及原因:系统采用了动态数据源并引入了两个数据源,QuartzConfig配置了两个调度器分别对应了两个数据源。事务管理器是单个,并且根据上下文切换要处理的数据源。QuartzConfig中两个调度器,数据源分别配置两个单一数据源,如果配置事务管理器,那么事务管理器中的dataSource与调度器是不一致的(前者为动态数据源类型,后者为单数据源DruidDataSource),所以导致了Quartz在进行操作时(删除Trigger、JobDetail等)无法继承并使用当前线程已经开启的事务。
通过修改调度器的数据源配置可以解决此问题,详情见这篇文章有详细的分析过程和解决办法:动态数据源/多数据源Quartz事务问题踩坑
修改调度器的数据源配置可以解决Quartz事务问题,但因为调度器数据源改成了动态,并且操作的数据源是由上下文决定而不是调度器,因此在调度器执行过程中,若数据源发生切换,可能会引发其他问题,所以本文意在探究如何在不更改Quartz配置(让调度器仍然绑定固定的数据源)情况下,解决此问题。

从数据库连接入手

首先查看Quartz配置文件中有一个配置Quartz具体持久化操作时用的类:org.springframework.scheduling.quartz.LocalDataSourceJobStore,这是Quartz提供的,进入此类可以看到获取连接的方式是使用Spring提供的工具类,经过实测可以发现:若调度器数据源与事务管理器一致时,这样获取是能直接取到当前事务的连接,否则就会取一个新的连接。
Quartz持久化类
所以想要修改这个问题,就需要复写这个类,并且修改此处

解决步骤

  1. 这里首先要创建一个ConnectionManager,设置一个ThreadLocal用来存储已经开启了事务的连接,供Quartz操作前获取
public class ConnectionManager {
    private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

    public static Connection getConnection() {
        return connectionHolder.get();
    }

    public static void setConnection(Connection connection) {
        connectionHolder.set(connection);
    }

    public static void removeConnection() {
        connectionHolder.remove();
    }
}
  1. 还需要一个获取当前事务使用的数据库连接的方法,可以放在一个Service中,具体实现如下(系统情况不同可能获取方式不完全一致)
public static Connection getCurrentDynamicRoutingDataSourceConnection(DataSource currentDynamicRoutingDataSource) throws SQLException {
        if (DynamicDataSourceContextHolder.getCurrentTransactionDataSourceCode() != null
                && !DynamicDataSourceContextHolder.getCurrentTransactionDataSourceCode().equals(DynamicDataSourceContextHolder.getDataSourceCode())) {
            return currentDynamicRoutingDataSource.getConnection();
        }
       ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(currentDynamicRoutingDataSource);
        if (conHolder != null && conHolder.getConnectionHandle() != null) {
            DruidPooledConnection connection = (DruidPooledConnection) conHolder.getConnection();
            ConnectionProxyImpl connectionProxyImpl = (ConnectionProxyImpl) connection.getConnection();
            Assert.notNull(connectionProxyImpl, "connectionProxyImpl is null!");
            DataSourceProxy dataSourceProxy = connectionProxyImpl.getDirectDataSource();
            Assert.notNull(dataSourceProxy, "dataSourceProxy is null!");
            String dataSourceName = dataSourceProxy.getName();
            if (!dataSourceName.equals(DynamicDataSourceContextHolder.getDataSourceCode())) {
                return currentDynamicRoutingDataSource.getConnection();
            }
        }
        return DataSourceUtils.getConnection(currentDynamicRoutingDataSource);
     }
  1. 创建后,我们在具体业务函数内(已经开启事务的函数,例如使用@Transactional或手动开启),通过上述方法获取连接并将其存入ThreadLocal中,并在finally中释放
	    @Autowired
    DataSource dataSource;

    public Object test() {
        Connection connection = getCurrentDynamicRoutingDataSourceConnection(dataSource);
        ConnectionManager.setConnection(connection);
        try {
            // 业务代码
            // ...
        } finally {
            ConnectionManager.removeConnection();
        }
    }
  1. 需要新创建一个类以供Quartz持久化时调用,并且修改获取、关闭连接处,整体可以copyorg.springframework.scheduling.quartz.LocalDataSourceJobStore,修改处如下
    修改获取连接处
    如果采用此种方式进行事务,那么关闭连接处应当不执行任何操作(如果这里关闭连接,后续Quartz操作将无法使用从ThreadLocal中获取的连接,并且会抛出异常),这里为了适配不采用事务的情况(ThreadLocal中不存在conn时正常关闭连接)
    修改关闭连接处
    最后别忘了将新建的Quartz持久化类配置到Quartz的配置文件中
org.quartz.jobStore.class=com.xxx.xxxx.CustomJobStoreCMT

至此,就可以让Quartz操作正常参与到事务中了。业务类前后置操作看起来比较冗余,可以考虑新增自定义注解(以@Transactional为元注解),配合切面对有Quartz操作的函数进行统一处理,这里不展开说写法了。

总结

通过重写Quartz持久化类,并修改其获取连接方式,可以达到让Quartz操作继承已经开启的事务并正常提交、回滚。

  • 26
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值