Spring中的事务与事务传播机制

事务

在学习MySQL时我们学习过事务,而到了Spring的学习时,同样会学习到事务,两个东西的事务的定义都是一样的:将一组操作封装成一个执行单元,要么全部成功,要么全部失败

在Spring中使用事务有两种方式

一种是使用编程式事务

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    //编程式事务
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器 -> 所有的事务都是通过这个事务管理器来进行管理的

    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public int del(Integer id){

        if(id == null || id < 0){
            return  -1;
        }

        //开启事务
        TransactionStatus transactionStatus =
                dataSourceTransactionManager.getTransaction(transactionDefinition);

        int result = userService.del(id);

        System.out.println("删除: " + result);

//            dataSourceTransactionManager.commit(transactionStatus);//

        dataSourceTransactionManager.rollback(transactionStatus);//为了不污染数据库,所以使用rollback就可以回滚事务

        return result;

    }

}

一种是使用声明式事务

@RestController
@RequestMapping("/user3")
public class UserControllerTransaction {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/add")
    public int add(String username,String password){
        if (null == username || null == password || username.equals(" ") || password.equals(" ")){
            return 0;
        }
        User user = new User();
        user.setUsername("wangwu");
        user.setPassword("123456");
        int result = userService.add(user);

        return result;
    }
}

两者的区别在于一种是使用编写大量代码的方式启动事务,另一种是使用注解的方式启动事务

很明显使用注解的方式启动事务的声明式事务更加简单好用,但是使用声明式事务也需要一定的注意:声明式事务只能作用于public修饰的方法上,如果作用在public修饰的类上那么该类的所有public方法都会被添加事务


我们使用声明式事务时,如果方法内出现了异常但是异常被捕获了,那么此时我们的事务不会进行回滚操作

    @RequestMapping("/del")
    @Transactional
    public int del(Integer id){
        if (id == null || id <= 0){
            return 0;
        }
        try{
            int num = 10 / 0;
        }catch (Exception e){
            System.out.println(e);
        }
        return userService.del(id);
    }

Spring中的事务与事务传播机制_第1张图片

可以看到,当我们的代码内部出现了异常但是事务并没有出现回滚操作

原因在于在代码里面自己把异常处理了,所以代码里面不会抛出异常

当Transactional没有检测到异常的出现的时候那么就会认为代码式正常执行的

所以事务不会进行回滚操作

解决方案:1.不要添加try catch或者抛出异常让框架Transactional来感知异常,自动回滚

        try{
            int num = 10 / 0;
        }catch (Exception e){
            throw e;
        }

2.自己手动添加回滚操作

        try{
            int num = 10 / 0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

事务的四大特性:原子性,隔离性,持久性,一致性,其中Spring事务的隔离性与学习MySQL时遇到的隔离性几乎是完全一样的:

 Spring 中事务隔离级别包含以下 5 种:

1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主

2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读

 3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重 复读

4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级 别)

5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题但是性能太低

可以看到在Spring中只有Isolation.DEFAULT这个隔离级别是特有的,其余隔离级别与MySQL相同


事务传播机制

事务传播机制级别分为七个

1.Propagation.REQUIRED:Spring默认的事务传播级别,如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就创建一个事务

2.Propagation.SUPPORTS:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就以非事务的方式运行

3.Propagation.MANDATORY:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就抛出异常

4.Propagation.REQUIRES_NEW:如果当前存在事务那么就挂起当前事务,重新开启自己的事务,也就是说无论当前是否存在事务,都会新开一个事务,开启的事务相互独立互不干扰

5.Propagation.NOT_SUPPORTED:如果当前存在事务那么就挂起当前事务,以非事务的方式进行运行

6.Propagation.NEVER:以非事务的方式进行运行,如果存在事务那么就抛出异常

7.Propagation.NESTED:如果当前存在事务,那么就创建一个事务作为当前事务的嵌套事务进行运行,如果当前没有事务就等价于Propagation.REQUIRED

这里重点说一下Propagation.NESTED,什么叫做嵌套事务呢?说白了就是记录一个保存点,当事务回滚的时候回滚到保存点的位置,使用Propagation.NESTED可以实现部分事务回滚的效果,因此之前的事务是不受影响的

下面使用代码的方式演示部分事务传播机制级别

Propagation.REQUIRED

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插⼊⽤户操作
    userService.add(user);
    // 插⼊⽇志
    logService.add("⽤户插⼊:" + user.getUsername());
    return true;
}

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES)
    public int add(String content){

        int i = 10 / 0;
        Log log  = new Log();
        log.setId(1);
        log.setMessage(content);
        return logMapper.add(log);
    }
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES)
    public int add(User user){
        int result = userMapper.add(user);
        return result;
    }
}

当我们的代码里面有错误时,整个事务都会被回滚 

1.UserService程序执行完成

 2. LogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚

 3. 数据库中没有插⼊任何数据,也就是步骤 1 中的⽤户插⼊⽅法也回滚了


  REQUIRES_NEW

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插⼊⽤户操作
    userService.add(user);
    // 插⼊⽇志
    logService.add("⽤户插⼊:" + user.getUsername());
    return true;
}

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(String content){

        int i = 10 / 0;
        Log log  = new Log();
        log.setId(1);
        log.setMessage(content);
        return logMapper.add(log);
    }
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(User user){
        int result = userMapper.add(user);
        return result;
    }
}

执行结果:UserSevice顺利插入数据,LogService插入数据失败,但两者都没有影响UserController的执行


 Propagation.NEVER

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插⼊⽤户操作
    userService.add(user);
    return true;
}


@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NEVER)
    public int add(User user){
        int result = userMapper.add(user);
        return result;
    }
}

 程序直接报错,没有插入任何数据


  Propagation.NESTED

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插⼊⽤户操作
    userService.add(user);
    return true;
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public int add(User user){
        int result = userMapper.add(user);
        // 插⼊⽇志
        logService.add("⽤户插⼊:" + user.getUsername());
        return result;
    }
}

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.NESTED)
    public int add(String content){

        int i = 10 / 0;
        Log log  = new Log();
        log.setId(1);
        log.setMessage(content);
        return logMapper.add(log);
    }
}

执行结果:User和Log都没有插入数据

原因在于:UserController调用了UserService,UserService执行完毕插入数据调用LogService,LogService出现异常,由于是嵌套事务,执行回滚操作,回滚到调用该方法的位置,回滚到UserService,由于代码中出现异常,所以UserService也需要进行回滚到调用该方法的位置,所以两个数据并没有插入

但是如果说LogService出现异常的时候处理了异常,那么此时只有LogService会出现回滚而UserService可以不用回滚

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.NESTED)
    public int add(String content){
        try{
            int num = 10 / 0;
        }catch (Exception e){
           TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        Log log  = new Log();
        log.setId(1);
        log.setMessage(content);
        return logMapper.add(log);
    }
}

从实现了部分回滚的操作


Require和Nested两种事务传播机制级别的区别

Require是将当前事务加入到整个事务当中,假如出现了回滚那么整个事务都需要进行回滚

Nested是将当前事务嵌套到整个事务当中,假如出现了回滚那么可以实现部分事务回滚的操作,部分事务回滚到保存点(也就是调用该方法的代码处),不会影响上一个方法的执行结果

你可能感兴趣的:(spring,java,后端)