一.业务场景
在调用其他服务接口进行数据同步时,同步数据成功则入库,同步数据失败时,则记录失败信息到日志表中,并回滚同步操作。
/**
*
* @ClassName: ZncjSyncInfoService
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
*/
@Service
@Transactional
public class ZncjSyncInfoService {
/**
* 同步数据
*/
public void syncDetail(ZncjSyncParam zncjSyncParam){
ZncjSyncInfo syncEntity=new ZncjSyncInfo();
try {
BeanUtils.copyProperties(zncjSyncParam,syncEntity);
syncEntity.setSyncTime(DateUtil.now());
//持久化到库
if(this.saveOrUpdate(syncEntity)==0)
this.throwRuntimeEx("智能排序同步数据入库失败");
} catch (Exception e) {
// TODO: handle exception
//同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
this.handlerFailLog(syncEntity, e.getMessage());
this.throwRuntimeEx(e);
}
}
/**
* 同步失败时,保存记录失败日志
*/
public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
BeanUtils.copyProperties(syncEntity, syncFailEntity);
syncFailEntity.setFailContent(failMessage);
syncFailEntity.setCreateTime(syncEntity.getSyncTime());
int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
if(result==0)
this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
return result;
}
}
上面代码没能达到预期的结果
(同步数据入库失败时,回滚数据,并且日志信息入库成功)
失败原因有如下:
-
同一个业务类调用业务方法时不会发生事务,即
handlerFailLog()
方法没有产生事务,spring事务默认的Aop模式使得spring事务拦截器只对外部方法调用有效。注意,日志表之所以会回滚,是因为syncDetail()
存在事务,内部方法调用时,handlerFailLog()
自动加入到事务中,并不是handlerFailLog()
自身的事务导致回滚的。 -
spring事务的传播行为,默认值为
Propagation.REQUIRED
,即如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。因此即使将handlerFailLog()
方法移动到外部类中进行调用,满足产生事务的条件,handlerFailLog()方法产生的事务也会自动加入syncDetail()
方法的事务中,导致日志数据回滚。
二.解决方案
主要的解决途径是将handlerFailLog()
方法的事务传播级别设置为Propagation.REQUIRES_NEW
(重新创建一个新的事务,如果当前存在事务,暂停当前的事务),让handlerFailLog()
方法产生的事务不加入到当前事务中,在独立的事务中运行。另外,解决方法内部调用不产生事务的方式有两种:
- 第一种,将
handlerFailLog()
方法移动到外部方法中,然后再进行调用,代码实现如下。
/**
*
* @ClassName: ZncjSyncInfoService
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
*/
@Service
@Transactional
public class ZncjSyncInfoService {
@Resource
private ZncjSyncFailLogService zncjSyncFailLogService;
/**
* 同步数据
*/
public void syncDetail(ZncjSyncParam zncjSyncParam){
ZncjSyncInfo syncEntity=new ZncjSyncInfo();
try {
BeanUtils.copyProperties(zncjSyncParam,syncEntity);
syncEntity.setSyncTime(DateUtil.now());
//持久化到库
if(this.saveOrUpdate(syncEntity)==0)
this.throwRuntimeEx("智能排序同步数据入库失败");
} catch (Exception e) {
// TODO: handle exception
//同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
this.zncjSyncFailLogService.handlerFailLog(syncEntity, e.getMessage());
this.throwRuntimeEx(e);
}
}
}
/**
*
* @ClassName: ZncjSyncFailLogService
* @Description: 同步失败日志业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
*/
@Service
public class ZncjSyncFailLogService{
/**
* 同步失败时,保存记录失败日志
*/
@Transactional(propagation=Propagation.REQUIRES_NEW)
public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
BeanUtils.copyProperties(syncEntity, syncFailEntity);
syncFailEntity.setFailContent(failMessage);
syncFailEntity.setCreateTime(syncEntity.getSyncTime());
int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
if(result==0)
this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
return result;
}
}
- 第二种,将业务类
ZncjSyncInfoService
通过spring的注解注入到自身中,实现方式相对于第一种比较简单,代码实现如下。
/**
*
* @ClassName: ZncjSyncInfoService
* @Description: 同步业务类
* @author allen
* @date 2018年6月8日 下午3:20:45
*
*/
@Service
@Transactional
public class ZncjSyncInfoService {
@Resource
private ZncjSyncInfoService zncjSyncInfoService ;
/**
* 同步数据
*/
public void syncDetail(ZncjSyncParam zncjSyncParam){
ZncjSyncInfo syncEntity=new ZncjSyncInfo();
try {
BeanUtils.copyProperties(zncjSyncParam,syncEntity);
syncEntity.setSyncTime(DateUtil.now());
//持久化到库
if(this.saveOrUpdate(syncEntity)==0)
this.throwRuntimeEx("智能排序同步数据入库失败");
} catch (Exception e) {
// TODO: handle exception
//同步异常时处理 ,并继续抛出异常,保存当前事务能够正常回滚
this.zncjSyncInfoService.handlerFailLog(syncEntity, e.getMessage());
this.throwRuntimeEx(e);
}
}
/**
* 同步失败时,保存记录失败日志
*/
@Transactional(propagation=Propagation.REQUIRES_NEW)
public int handlerFailLog(ZncjSyncInfo syncEntity,String failMessage){
ZncjSyncFailLog syncFailEntity=new ZncjSyncFailLog();
BeanUtils.copyProperties(syncEntity, syncFailEntity);
syncFailEntity.setFailContent(failMessage);
syncFailEntity.setCreateTime(syncEntity.getSyncTime());
int result=this.daoUtil.zncjSyncFailLogMapper.insertSelective(syncFailEntity);
if(result==0)
this.throwRuntimeEx("添加同步失败日志信息的操作出错啦!");
return result;
}
}
三.知识点梳理
- 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
-
@Transactional
注解只能应用到 public 方法才有效。 - 内部方法调用不能被spring事务拦截器拦截到,不能产生事务。
-
@Transactional
注解的属性介绍:
value 和 transactionManager 属性
它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
propagation 属性
事务的传播行为,默认值为 Propagation.REQUIRED。
可选的值有:
-
Propagation.REQUIRED
如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
-
Propagation.SUPPORTS
如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
-
Propagation.MANDATORY
如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
-
Propagation.REQUIRES_NEW
重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
-
Propagation.NOT_SUPPORTED
以非事务的方式运行,如果当前存在事务,暂停当前的事务。
-
Propagation.NEVER
以非事务的方式运行,如果当前存在事务,则抛出异常。
-
Propagation.NESTED
和 Propagation.REQUIRED 效果一样。
isolation 属性
事务的隔离级别,默认值为 Isolation.DEFAULT。
可选的值有:
- Isolation.DEFAULT(使用底层数据库默认的隔离级别)
- Isolation.READ_UNCOMMITTED
- Isolation.READ_COMMITTED
- Isolation.REPEATABLE_READ
- Isolation.SERIALIZABLE
timeout 属性
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 属性
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor 属性
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor 属性
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
网友评论