@Transactional 学习和使用

知识点:

@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异常。

不同类之间函数调用:引用添加 Propagation.REQUIRES_NEW

   情形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失效的场景:

1、异常被捕获后没有抛出

    @Transactional
    public Long create(LawTemplate lawTemplate) {
        try {
            lawTemplateMapper.insertSelective(lawTemplate);
            return lawTemplate.getLawId();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
这个很好理解,事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。

2、抛出非运行时异常 得是RuntimeException 或者指定异常

异步虽然抛出了,但是抛出的是非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)

3、方法内部直接调用 -- 这个容易忽略,需要去注意!

如果先调用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报错,会回滚。

4、注解到private方法上

idea直接会给出提示Methods annotated with ‘@Transactional’ must be overridable ,原理很简单,private修饰的方式,spring无法生成动态代理。直接报错了

总结:

      @Transactional 使用:1, 要注意抛出异常是否是运行时异常;2,注解在外部调用的函数上才有效果;3,内部调用的,要注意是否生成代理。

你可能感兴趣的:(java,Transactional,不生效)