解决spring多线程不共享事务的问题

时间:2020-12-08 21:22:56

在一个事务中使用多线程操作数据库时,若同时存在对数据库的读写操作,可能出现数据读取的不准确,因为多线程将不会共享同一个事务(也就是说子线程和主线程的事务不一样),为了解决这个问题,可以使用spring的分布式事务jta,并重写JtaTransactionManager的doCommit和doRollback方法。

1、引入maven依赖

<dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>3.9.3</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency> 

2、配置xml文件

<!-- atomikos事务管理器 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
        init-method="init" destroy-method="close">
        <description>UserTransactionManager</description>
        <property name="forceShutdown">
            <value>true</value>
        </property>
    </bean>

    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="3000" />
    </bean>

    <!-- spring 事务管理器,必须使用二次开发的类,控制solr的回滚 -->
    <bean id="springTransactionManager"
        class="com.yzh.core.inner.impl.SepJtaTransactionManager">
        <property name="transactionManager">
            <ref bean="atomikosTransactionManager" />
        </property>
        <property name="userTransaction">
            <ref bean="atomikosUserTransaction" />
        </property>
        <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default -->
        <property name="allowCustomIsolationLevels" value="true"/>
    </bean>

     <!-- 开启注解事务定义,由Spring扫描注解定义的事务 -->
    <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />

3、将一些查询数据库的操作放到容器里面,在事务提交的时候执行

public class ContextKeeper {
private static final ThreadLocal<Collection<Runnable>> keepAfterSubmit = new ThreadLocal<>();

  //将需要执行的多线程放到容器中
    public static void put(Collection<Runnable> tasks) {
        keepAfterSubmit.set(tasks);
    }    //获取并移除容器中的任务
    public static Collection<Runnable> getAndRemoveSubmitTask() {
        synchronized (keepAfterSubmit) {
            Collection<Runnable> tasks = keepAfterSubmit.get();
            keepAfterSubmit.remove();
            return tasks;
        }
    }
  //移除容器中的任务
    public static void removeSubmitTask() {
        synchronized (keepAfterSubmit) {
            keepAfterSubmit.remove();
        }
    }

4、重写JtaTransactionManager的doCommit和doRollback方法。

public class SepJtaTransactionManager extends JtaTransactionManager {

    /**
     *
     */
    private static final long serialVersionUID = -1468472287996669189L;
    private static final Logger LOGGER = LoggerDeputyUtil.getSelfClassLogger();

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        super.doCommit(status);// 执行后续任务
        Collection<Runnable> tasks = ContextKeeper.getAndRemoveSubmitTask();
        if (!JudgeUtil.isEmpty(tasks)) {
            LOGGER.info("执行后续任务");
            try {
                ExecutorService pool = CommonHelper.pool();
                for (Runnable task : tasks) {
                    pool.submit(task);
                }
            } catch (Exception e) {
                ErrorLevel.LOG_HANDLERHIS_FAIL.doLog("执行后续任务失败", e);
            }
        }
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        super.doRollback(status);// 清空任务
        ContextKeeper.removeSubmitTask();
    }
}