Spring中的事宜传播的几种方式
Spring中的事务是怎么传播的?
Spring 框架中对TransactionDefinition 接口中的定义 如下:
1.PROPAGATION_REQUIRED
支持当前的事务,如果当前没有事务,就新建一个
2.PROPAGATION_SUPPORTS
支持当前事务,如果不存在事务执行非事务方式。对于事务同步的事务管理器来说
Propagation_Supports 跟一点事务没有的方式是有轻微的区别的。当定义一个事务
范围时,将会申请同步机制,因此,相同的数据源将共享这个事务范围(取决于事务
管理器的实际同步配置)
3.PROPAGATION_MANDATORY
支持当前的事务,如果不存在事务,则抛出异常
4.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,挂起当前的事务
新创建的事务方法区域外面所有事务管理器的事务都将被挂起,等待当前方法事务的结束。
这一点特别应用于JtaTransactionManager。
5.PROPAGATION_NOT_SUPPORTED
执行非事务方式,如果当前存在事务,挂起。
6.PROPAGATION_NEVER
执行非事务方式,如果当前事务存在,抛出一个异常
7.PROPAGATION_NESTED
如果当前事务存在,在一个嵌套的事务中执行,如果当前没有事务,行为方式跟PROPAGATION_REQUIRED相似
实际上嵌套事务的创建仅特定事务管理器支持这种方式。(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
几点区别:PROPAGATION_REQUIRES_NEW 与 PROPAGATION_NESTED
PROPAGATION_REQUIRES_NEW:在给定的区域中创建一个新的、独立的内部事务。这个事务有他自己的隔离区域和一系列的锁,
他的事务提交或者是回滚将完全独立于外面的事务。在内部事务开始运行之前,外部事务是会被挂起的,直到内部事务执行结束
,外部事务才会恢复。
例如:这种内部独立的事务机制会应用于通过sequences产生记录的ID,这个时候访问sequence表应当发生在一个独立的事务中,
为了使这个Sequence加锁产生序列的时间尽可能的短;这里应当避免将产生序列的方法跟其他逻辑过程放在一个事务中,这样会
导致Sequence表被锁的时间更长,在一个并发量较高的应用中,如果同一时刻在其他的方法中也需要产生序列,那就要等待,刚才
那个事务的释放,所以这个产生序列的方法应该在一个独立的事务中;
PROPAGATION_NESTED:嵌套事务是当前事务一个真实的子事务。当嵌套事务开始时,保存点(savepoint)将形成。如果嵌套事务失败,
将回滚到嵌套事务开始的保存点。嵌套事务又是外面事务的一部分,所以嵌套事务仅当外事务结束时候才会被提交。
这两个事务最大的区别在于PROPAGATION_REQUIRES_NEW是一个全新的事务,完全独立;而PROPAGATION_NESTED是外部事务的子事务
外部事务commit,子事务也会被commit;如果外部事务rollback,子事务也会被rollback;
举个spring事务使用的例子:
由于记账扣款比较频繁,考虑将生成的记账分录批量进行扣款处理;每笔交易有两笔分录(CR与DR);事务配置:PROPAGATION_REQUIRES
扣款的方法如下:
@Transactional
public void updateBalanceByCollection(MemberAccount memberAccount,List<AccountingEntry> entryList) {//同一个账户下面的所有未记账成功的分录
BigDecimal balance = memberAccount.getBalance();
for (AccountingEntry accountingEntry : entryList) {
AccountingDeal accountingDeal = accountingDealService.findById(accountingEntry.getDealId());
if(accountingDeal.getStatus().intValue()==0){
switch (accountingEntry.getChangeType().intValue()) {
case 1:
balance = balance.add(accountingEntry.getChangeAmount());
break;
case 2:
balance = balance.subtract(accountingEntry.getChangeAmount());
break;
}
accountingEntry.setStatus(1);//标记为变更成功
accountingEntryService.update(accountingEntry);
//由于事务此处不是立即保存到数据库的,需等到方法执行完毕
if(accountingEntryService.isUnSuccess(accountingEntry.getDealId(),accountingEntry.getId())){
//当另外一条记账分录也扣款成功时,将此笔交易置为成功
//deal对应的分录是都已经成功过账则更新deal状态
accountingDealService.updateSuccess(accountingEntry.getDealId());
}
}
}
memberAccountService.updateMemberBalance(balance, memberAccount.getMerchantId(), memberAccount.getAccountCode());//批量更新会员账户余额
}
事务在spring中配置了PROPAGATION_REQUIRES类型 如果在此方法中调用的类中没有事务,沿用当前的事务,如accountingDealService.updateSuccess,accountingEntryService.update均沿用了
当前方法的事务
问题:此方法如果出现异常,将会回滚掉for循环中所有的记录
解决方法:可以将数据库更新的部分放在单独的一个方法中,此方法是用PROPAGATION_NESTED 如果当前分录保存时发生异常,只回滚一条数据,不至于全部回滚