@Transactional
是一种基于注解管理事务的方式,spring通过动态代理的方式为目标方法实现事务管理的增强。
@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制
@Transactional 实现原理:
1) 事务开始时,通过AOP机制,生成一个代理connection对象,
并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。 在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,执行所有数据库命令。
[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
(物理连接 connection 逻辑上新建一个会话session;
DataSource 与 TransactionManager 配置相同的数据源)
2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,然后关闭该代理 connection 对象。(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)
1)接口实现类或接口实现方法上,而不是接口类中。
2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:
3)@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。
业务:创建法律模板,添加关联法律模板关系
情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
this.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
// 插入关联关系
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);
}
单元测试:
@Autowired
private LawTemplateService lawTemplateService;
@Test
public void createRelLaw() {
LawTemplate law = new LawTemplate();
law.setLawName("成年人");
law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");
law.setLawCase("成年人行为xxx");
law.setCreateStaffId(1L);
law.setCreateTime(new Date());
List lawIds = new ArrayList<>();
lawIds.add(1L);
law.setRelLawIds(lawIds);
lawTemplateService.create(law);
}
执行报错,两个数据的操作都会回滚
情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
this.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);
}
结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
this.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);
}
结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
try {
this.insertLawInnerMappingBatch(lawTemplate);
}catch (Exception e){
e.printStackTrace();
}
}
return lawTemplate.getLawId();
}
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);
}
结果:不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。
情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Resource
private ReplyTemplateService replyTemplateService;
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
}
@Slf4j
@Service
public class ReplyTemplateService {
// 插入关联关系
public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {
throw new RuntimeException("模拟函数执行有异常!");
}
}
单元测试:
@Autowired
private LawTemplateService lawTemplateService;
@Test
public void createRelLaw() {
LawTemplate law = new LawTemplate();
law.setLawName("成年人");
law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");
law.setLawCase("成年人行为xxx");
law.setCreateStaffId(1L);
law.setCreateTime(new Date());
List lawIds = new ArrayList<>();
lawIds.add(1L);
law.setRelLawIds(lawIds);
lawTemplateService.create(law);
}
执行报错,两个数据的操作都会回滚
情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Resource
private ReplyTemplateService replyTemplateService;
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
}
@Slf4j
@Service
public class ReplyTemplateService {
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {
throw new RuntimeException("模拟函数执行有异常!");
}
}
结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。
情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Resource
private ReplyTemplateService replyTemplateService;
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
}
@Slf4j
@Service
public class ReplyTemplateService {
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {
throw new RuntimeException("模拟函数执行有异常!");
}
}
结果:create里面insertSelective正常不报错,会插入。不管 LawTemplateService 里面 insertLawInnerMappingBatch 是否报错。因为两个是不同事物的
情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Resource
private ReplyTemplateService replyTemplateService;
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
try {
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}catch (Exception e){
e.printStackTrace();
}
}
return lawTemplate.getLawId();
}
}
@Slf4j
@Service
public class ReplyTemplateService {
// 插入关联关系
@Transactional
public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {
throw new RuntimeException("模拟函数执行有异常!");
}
}
部分异常:
Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
结果:create操作失败,而且还添加了抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。两个函数用的是同一个事务。insertLawInnerMappingBatch函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,create函数里面把异常给抓出来了,这个时候create函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让继续调用了。
有@Transactional的函数里面调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,如果还是沿用之前的事务。如果报错就会抛UnexpectedRollbackException异常。
情形5: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常,添加 Propagation.REQUIRES_NEW
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Resource
private ReplyTemplateService replyTemplateService;
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
try {
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}catch (Exception e){
e.printStackTrace();
}
}
return lawTemplate.getLawId();
}
}
@Slf4j
@Service
public class ReplyTemplateService {
// 插入关联关系
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {
throw new RuntimeException("模拟函数执行有异常!");
}
}
结果: 不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。因为两个函数不是同一个事务了。
总结以上需要注意@Transactional失效的场景:
@Transactional
public Long create(LawTemplate lawTemplate) {
try {
lawTemplateMapper.insertSelective(lawTemplate);
return lawTemplate.getLawId();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
这个很好理解,事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。
异步虽然抛出了,但是抛出的是非RuntimeException
类型的异常(抛出的异常要继承RuntimeException才有效),依旧不会生效。
@Transactional
public Long create(LawTemplate lawTemplate) throws MyException {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
try {
replyTemplateService.insertLawInnerMappingBatch(lawTemplate);
}catch (Exception e){
throw new MyException ();
}
}
return lawTemplate.getLawId();
}
如果指定了回滚异常类型为Exception,那么就可以回滚非RuntimeException
类型异常了。
@Transactional(rollbackFor = Exception.class)
如果先调用createOne(),那么create()执行成功是不会回滚的,其原因就是@Transactional根本没生成代理,如果直接调用create() ,如果报错会回滚。
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
@Autowired
private LawTemplateService lawTemplateService;
public void createOne(LawTemplate lawTemplate) {
this.create(lawTemplate);
}
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
this.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
// 插入关联关系
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");
}
}
insertSelective 插入成功, insertLawInnerMappingBatch报错,并没有回滚。为啥呢? 因为createOne里面调用create没有生成代理。
修改方式,把当前类自己注入一下调用
@Slf4j
@Service
public class LawTemplateService {
@Resource
private LawTemplateMapper lawTemplateMapper;
public void createOne(LawTemplate lawTemplate) {
SpringUtils.getBean(LawTemplateService.class).create(lawTemplate);
}
@Transactional
public Long create(LawTemplate lawTemplate) {
lawTemplateMapper.insertSelective(lawTemplate);
List relLawIds = lawTemplate.getRelLawIds();
if(CollectionUtils.isNotEmpty(relLawIds)){
this.insertLawInnerMappingBatch(lawTemplate);
}
return lawTemplate.getLawId();
}
// 插入关联关系
public void insertLawInnerMappingBatch(LawTemplate lawTemplate){
throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");
}
}
insertSelective 插入成功, insertLawInnerMappingBatch报错,会回滚。
idea直接会给出提示Methods annotated with ‘@Transactional’ must be overridable
,原理很简单,private修饰的方式,spring无法生成动态代理。直接报错了
@Transactional 使用:1, 要注意抛出异常是否是运行时异常;2,注解在外部调用的函数上才有效果;3,内部调用的,要注意是否生成代理。