SpringBoot显式事务

参考:https://www.jianshu.com/p/f5fc14bde8a0java

后续测试代码的完整项目:https://files.cnblogs.com/files/hellohello/demo2.rarspring

后续说的事务注解都是指 import javax.transaction.Transactional;事务注解若是修饰在类上,则等价与做用在这个类的全部方法上,若是仅修饰在函数上,则仅仅做用在这个函数上,对其余函数没有效果。数据库

  1. 只要加了事务注解,不论是加到bean上,仍是加到bean中的函数上,spring才会生成一个代理对象与对应的bean,共两个对象。
  2. 注入到容器中的是代理对象,而不是被代理的bean对象。
  3. 代理对象其实是被代理对象的子类,经过CGLib动态生成的。
  4. 其余bean中注入的虽然是代理对象,可是对于开发人员来讲,就跟直接调用实际的bean对象同样,是透明的,由于代理对象内部会调用被代理对象的对应函数。
  5. 代理对象调用被代理对象函数时,只有抛出了uncheck exception(RuntimeException或Error)时【或配置其余异常】,而且抛出异常的函数处于事务注解的做用范围内时,事务才会回滚。

测试一、二、3点:网络

服务类代码,成员函数上存在事务注解,因此会生成代理对象函数

@Service
public class ConfSystemService {
    // 用于记录被代理对象的引用
    public static ConfSystemService ins ;

    public ConfSystemService(){
        ins = this;
    }
    
    @Transactional
    public void saveWithOk(int id){
        // ...
    }
}

 

 

测试类中进行测试单元测试

@Autowired
ConfSystemService confSystemService;

@Test
public void test3(){
    Assert.isTrue(!confSystemService.equals(ConfSystemService.ins),"判断是否属于同一个对象");
    Assert.isTrue(confSystemService.getClass().getSuperclass().equals(ConfSystemService.class),"判断是不是父类子类关系");
    Assert.isTrue(!ConfSystemService.ins.getClass().getSuperclass().equals(ConfSystemService.class),"判断是不是父类子类关系");
}

 

若是把服务类中的事务注解去掉,则不会生成代理对象,那上面例子中的静态属性保存的对象,跟其余地方注册的服务对象,就是同一个对象了。测试

测试四、5点:this

在服务类中添加以下代码:spa

/**
 * dao.
 */
@Autowired
ConfSystemRepository confSystemRepository;

/**
 * 使用dao保存数据后,报错.
 * @param id .
 */
@Transactional
public void saveWithErr(int id){
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new RuntimeException("模拟错误");
}

/**
 * 正常保存数据.
 * @param id .
 */
@Transactional
public void saveWithOk(int id){
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
}

/**
 * 判断数据是否成功插入到了数据库中.
 * @param id .
 * @return .
 */
public boolean checkExist(int id){
    return confSystemRepository.findById(id).isPresent();
}

 

执行单元测试代理

@Test
public void test1() {
    try{
        confSystemService.saveWithErr(888);
    }catch (Exception e){
        e.printStackTrace();
    }
    Assert.isTrue(!confSystemService.checkExist(888));
}

@Test
public void test2() {
    confSystemService.saveWithOk(999);
    Assert.isTrue(confSystemService.checkExist(999));
}

 

第一个单元测试中,模拟了一个报错,事务就回滚了,因此检查出数据不存在。其中实际调用了代理类的saveWithErr方法,而这个方法中调用了被代理对象的saveWithErr方法,而这个方法中抛出了一个RuntimeException,这个异常被代理类的saveWithErr方法检测到,并且代理类发现当前方法处于事务注解的做用下,因此代理类就会将事务回滚,最后再将这个RuntimeException抛出去,让调用者知道这个方法报错了。

事务没有回滚

以上提到了回滚的两个必要条件:

  1. 当前方法处于事务注解的做用范围内
  2. 方法得抛出RuntimeException或Error的子异常,或配置的指定异常

测试第1条,场景:服务类中未出于事务注解做用下的方法调用了,处于事务注解做用下的方法。服务类中添加以下代码:

public void funcWithNoTrans(int id){
    saveWithErr(id);
}

 

测试类中添加以下测试用例(和以前的test1很相似,至不要调用的服务方法不同):

@Test
public void test5() {
    try{
        confSystemService.funcWithNoTrans(888);
    }catch (Exception e){
        e.printStackTrace();
    }
    Assert.isTrue(!confSystemService.checkExist(888));
}

 

 这个测试用例没法经过。是由于代理类执行当前方法时,虽然执行的被代理对象的方法中抛出了异常,可是代理类发现当前方法(funcWithNoTrans)并非处于事务注解做用下,因此事务没有回滚。

 测试第2条,场景:没有抛出uncheck exception,而是抛出了自定义的异常,同时没有作对应配置,在服务类中添加代码:

// 自定义异常
public static class MyException extends Exception {
    // ...
}

// 没有配置
@Transactional
public void saveWithCustomException(int id) throws MyException {
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new MyException();
}

// 有配置
@Transactional(rollbackOn = MyException.class)
public void saveWithCustomCfgException(int id) throws MyException {
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new MyException();
}

 

测试代码:

    @Test
    public void test6() {
        try{
            confSystemService.saveWithCustomCfgException(111);
        }catch (Exception e){
            e.printStackTrace();
        }
        Assert.isTrue(!confSystemService.checkExist(111),"抛出已配置的自定义异常");
    }

    @Test
    public void test7() {
        try{
            confSystemService.saveWithCustomException(222);
        }catch (Exception e){
            e.printStackTrace();
        }
        Assert.isTrue(confSystemService.checkExist(222),"抛出未配置的自定义异常");
    }

 

两个测试都能经过。

还有另一个场景:服务类内部出现异常了,可是内部try...catch处理掉了,致使代理类检测不出来被代理对象内部其实出现了异常,最终事务也没有回滚。

ps:测试发现,换用另外一个spring包下的Transactional注解,具备以上相同的效果,只不过配置注解那里,得改为 rollbackFor。这两个注解功能上其实没啥区别,只是配置的属性名有点差别而已。

 

其余

  被注解修饰的函数是运行在一个事务内,因此要保证这个函数运行的时间要尽量短(如不要穿插网络请求,实在不行的话,则将网络请求剥离到这个事务方法以外,这样就不影响这个方法的执行时间了)。并且在这个函数内,该抛的运行时异常要抛出来,而不要trycatch住,不然致使事务没法回滚

  事务注解,加到service方法上,service方法内调用不一样的dao来操做数据。一个sevice方法内有两个数据库修改操做以上,才须要在这个service方法上加事务注解

相关文章
相关标签/搜索