在一个事务中使用多线程操作数据库时,若同时存在对数据库的读写操作,可能出现数据读取的不准确,因为多线程将不会共享同一个事务(也就是说子线程和主线程的事务不一样),为了解决这个问题,可以使用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(); } }