spring对事物的管理相对来说比较人性化,你可以采用编程式事物管理,或者是注解式事物管理;也可以采用两者结合的形式来完成。
大多数情况下我们只需要使用注解式事物管理即可满足日常开发需要。但是对于某些特殊场景需要我们单独来管理事物,比方说客户的一次提交,会影响到多个表的操作和事件导致响应过慢。这种情况下我们想到的方案是两种:第一种是加缓存,先成功保存缓存并返回,之后异步来保存剩下的全部数据交互。第二种情况就是多线程执行,多个事物同提交或者同回滚。
对于方案一需要我们有强大的缓存系统,比较常见的是redis集群。我们今天不做讨论。
第二种方案的情况下对于事物的管理,通过在网上大肆搜索一番并没有给出一个很好的方案。所以我同时结合java的并发工具类Exchanger和spring的注解事物+spring的编程式事物来完成多个事物的同提交和同回滚。
下面开始正文:
首先当然是spring配置文件中的事物管理配置和数据源之类的配置,这些基本上都是通用的:
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/> <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/> <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/> <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/> <tx:method name="get*" read-only="true" /> <tx:method name="select*" read-only="true" /> <tx:method name="find*" read-only="true" /> <tx:method name="query*" read-only="true" /> tx:attributes> tx:advice> <aop:config proxy-target-class="true"> <aop:pointcut id="allServices" expression="execution(*.*.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="allServices"/> aop:config>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="${jdbc.initialSize}" /> <property name="minIdle" value="${jdbc.minIdle}" /> <property name="maxActive" value="${jdbc.maxActive}" /> <property name="maxWait" value="${jdbc.maxWait}" /> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${jdbc.validationQuery}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <property name="testOnBorrow" value="${jdbc.testOnBorrow}" /> <property name="testOnReturn" value="${jdbc.testOnReturn}" /> <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" /> <property name="filters" value="${jdbc.filters}" /> bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> bean>
spring基于注解的事物主要就是开启事物注解,然后配置数据源,配置事物管理器,给事物管理器配置数据源。
接下来我们看对于业务实现类中的保存多线程方法该如何结合Exchanger工具类和spring的编程式控制事物来完成多线程事物控制;
@Component public class ServiceImpl implements Service { private Logger logger = Logger.getLogger(ServiceImpl.class); @Resource private AMapper aMapper; @Autowired private DataSourceTransactionManager txManager; @Autowired private BMapper bMapper; /** * 插入一条数据 * @param record * @return */ public Mapinsert(Model record) { Map map = new HashMap ();
final ExchangerexchangerFirst = new Exchanger();//用于第一阶段信息交换 final Exchanger exchangerSecond = new Exchanger();//用于第二阶段信息交换,判断是否提交全部事物 if(record!=null) {
//需要保存数据 开启编程式事物控制。 DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus status1 = txManager.getTransaction(defaultTransactionDefinition);//获取事物状态,手动管理 Future
Map resultMap = new HashMap(); DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); defaultTransactionDefinition.setTimeout(60); defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus status2 = txManager.getTransaction(defaultTransactionDefinition);//新线程中的手动事物 ModelB b=new ModelB(); try { bMapper.insert(b); String str1=exchangerFirst.exchange(exchanger,15, TimeUnit.SECONDS); logger.info("线程交换信息1:"+str1); String str2=exchangerSecond.exchange(exchanger,15, TimeUnit.SECONDS);//设置超时,防止假死 logger.info("线程交换信息2:"+str1);//如果线程收到的信息为1表示需要提交事物
if ("1".equals(str2)) { logger.info("保存联系人线程事物提交"); txManager.commit(status2); }else { logger.info("保存联系人线程事物回滚"); txManager.rollback(status2); } } catch (Exception e) { try { String str1=exchangerFirst.exchange("0",15, TimeUnit.SECONDS); String str2=exchangerSecond.exchange("0",15, TimeUnit.SECONDS); logger.info("保存事物异常回滚");//如果异常,只做交互但是不管交互信息都回滚 txManager.rollback(status2); } catch (Exception e1) { e1.printStackTrace();
txManager.rollback(status2);//交互过程中有异常同样回滚 } } return resultMap; } }); try { aMapper.insertSelective(record); String exchange = exchangerFirst.exchange(i+"",15, TimeUnit.SECONDS); logger.info("保存字典交换信息1:"+exchange);//根据第一次交换信息来判断是否需要回滚 if("1".equals(exchange)){ String exchange1 = exchangerSecond.exchange("1",15, TimeUnit.SECONDS);//1表示需要提交 logger.info("保存交换信息2:"+exchange1); logger.info("保存事物提交"); txManager.commit(status1);//因为第一次收到的是1所以是可以正常提交事务的。 //保存数据成功 map.put("result", "success"); map.put("message", "保存成功"); map.put("did", record.getDid()); }else{ String exchange1 = exchangerSecond.exchange("0",15, TimeUnit.SECONDS);//因为拿到的是异常情况的所以需要回滚并告知对方0 logger.info("保存交换信息2:"+exchange1); logger.info("保存事物回滚"); txManager.rollback(status1); //保存数据失败 map.put("result", "error"); map.put("message", "保存失败"); } } catch (Exception e) { logger.info("保存失败"); //同样需要第二次通知所有需要交互线程,事物回滚;
类似的需要通知对方,回滚事务而不是提交事务 } } }else { //数据存在问题 map.put("result", "null"); map.put("message", "请将数据填写完整"); } return map; }
这样就可以通过二次线程交互来保证同时成功都提交,有一个失败异常,则都回滚了。
总结,多线程事务的控制难点在于线程和线程之间无法交互,新启线程的事物又不受主线程事物的控制,所以我们需要通过线程信息的交互来达到同时提交和同时回滚,类似于分布式事物的二阶段提交方法。而实现方案中考虑到超时设置交换信息的超时时间,根据实际情况来设置。从而达到更好的效果。有任何问题,可以继续讨论。