Spring事务及事务传播机制

一.事务回顾

1.什么是事务

事务定义 :将一组操作封装成⼀个执行单元(封装到一起),要么全部成功,要么全部失败。

2.为什么要使用事务

还是用一个老生常谈的例子来说

张三给李四转账  : 

张三账户-100;

李四账户+100; 

如果在张三账户-100的时候发生异常,此时李四账户+100的操作还没有完成,张三账户的钱不就平白无故的消失了,而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

3.MySQL 中的事务使用 

--开启事务
start transaction;
-- 业务执行
-- 提交事务
commit;
-- 回滚事务
rollback;

二.Spring中的事务

0.准备工作

1.先来创建数据库

        -- 创建数据库
        drop database if exists mycnblog;
        create database mycnblog default character set utf8mb4;
        -- 使⽤数据数据
        use mycnblog;
        -- 创建表[⽤户表]
        drop table if exists userinfo;
        create table userinfo(
        id int primary key auto_increment,
        username varchar(100) not null,
        password varchar(32) not null,
        photo varchar(500) default '',
        createtime datetime default now(),
        updatetime datetime default now(),
        `state` int default 1
        ) default charset 'utf8mb4';

        -- 添加⼀个⽤户信息
        insert into `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
        `createtime`, `updatetime`, `state`) values
        (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
        ;
        -- 添加⼀个用户日志表
        DROP TABLE IF EXISTS userlog;
        CREATE TABLE userlog(
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(100) NOT NULL,
        createtime DATETIME DEFAULT NOW( ),
        updatetime DATETIME DEFAULT NOW()
        ) DEFAULT CHARSET 'utf8mb4';

2.实体类

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
}
@Data
public class UserLog {
    private Integer id;
    private String username;
    private Date cratetime;
    private Date updatetime;

    public UserLog() {
    }

    public UserLog(String username) {
        this.username = username;
    }
}

3.Mapper

@Mapper
public interface UserMapper {
    @Select("select * from userinfo")
    List<User> selectAll();
     @Insert("insert into userinfo(username,password) values (#{username},#{password}))")
    Integer insert(User user);
}
@Mapper
public interface UserLogMapper {
    @Insert("insert into userlog(username) values (#{username})")
    Integer insert(UserLog userLog);
}

4.Service

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public List<User> selectAll() {
        return userMapper.selectAll();
    }

    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
}
@Service
public class UserLogService {
    @Autowired
    UserLogMapper userLogMapper;

    public Integer addUser(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
}
Spring 中的事务操作分为两类:
1. 编程式事务(手动写代码操作事务).
2. 声明式事务(利用注解自动开启和提交事务).
普通的添加操作
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    UserService userService;

    @RequestMapping("/add")
    public Integer addUser() {
        User user = new User();
        user.setName("zxn123");
        user.setPwd("123456");
        return userService.addUser(user);

    }
}

 可以看到是添加成功的

1.手动

 加入事务之后进行rollback

@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;
    //数据库事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        dataSourceTransactionManager.rollback(transaction);
        return insert;

    }
}

仍然执行上面的操作.数据库没有发生改变,说明数据进行了回滚

 加入事务之后进行rollback日志

  加入事务之后进行commit日志

 2.注解

加入@Transactional注解

@RestController
@Slf4j
@RequestMapping("/trans2")
public class TransactionalController2 {
    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        return insert;

    }
}

1.没有异常,自动提交

2.有异常,自动rollback

 可以看到没有提交操作.

3.@Transactional 作用范围

@Transactional 可以用来修饰方法或类:
修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法。
修饰类时:表明该注解对该类中所有的 public 方法都生效。

4.@Transactional 参数说明

参数 作用
value 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为,默认值Propagation.REQUIRED
isolation 事务的隔离级别,默认值为lsolation.DEFAULT
timeout 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务.
readOnly
 
指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true.
rollbackFor 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
noRollbackFor 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.
noRollbackForClassName
 
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.

演示noRollbackFor

    @Transactional(noRollbackFor = ArithmeticException.class)
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        int a = 10 / 0;
        return insert;

    }

此时可以看到是有ArithmeticException错误的,我们添加数据

 可以看到是添加成功了的 

@Transactional默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚
即Exception的子类中,除了RuntimeException及其子类

 因此我们可以自行设置,即可对所有的异常进行回滚

 @Transactional(rollbackFor = Exception.class)

5.注解的注意事项

@Transactional 在异常被捕获的情况下,不会进行事务自动回滚

    @RequestMapping("/add2")
    @Transactional
    public Integer add2(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        // 插⼊数据库
        int result = userService.addUser(user);
        try {
            // 执⾏了异常代码(0不能做除数)
            int i = 10 / 0;
        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return result;
    }

 此时可以看到仍然是添加成功的了

三.事务的隔离级别

1.事务的特性

  1. 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态

  3. 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(具体见事务的隔离级别)

  4. 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

2.事务的隔离级别

  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。

  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

事务隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED) x
可重复读(REPEATABLE READ) x x
串行化(SERIALIZABLE) x x x

3.Spring事务的隔离级别

而 Spring 中事务隔离级别包含以下 5 种:
  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2.  Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3.  Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。
Spring 中事务隔离级别只需要设置 @Transactional 里的 isolation 属性即可,具体实现代码如下:

 @Transactional(isolation = Isolation.READ_COMMITTED)

四.Spring 事务传播机制

1.事务传播机制是什么

Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。
事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性的(稳定性的)。
事务传播机制解决的是一个事务在多个节点(方法)中传递的问题

2.事务的传播机制

Spring 事务传播机制包含以下 7 种:
  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的 方式继续运行。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当 前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED

以上事务传播机制,可分为三类

 

3.Spring 事务传播机制使用和各种场景演示 

1.支持当前事务(REQUIRED)

没有错误的模拟

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
@RestController
@Slf4j
@RequestMapping("/trans3")
public class TransactionalController3 {
    @Autowired
    private UserService userService;
    @Autowired
    private UserLogService userLogService;
    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        //插入用户表
        userService.addUser(user);
        UserLog userLog = new UserLog(username);
        //插入日志表
        userLogService.addUserLog(userLog);
        return true;

    }
}

  

 错误发生时的模拟

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 

 可以看到数据并没有发生改变

2.不支持当前事务(REQUIRES_NEW) 

修改隔离级别

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 可以看到userinfo表示添加成功的了

3.不支持当前事务,NEVER 抛异常

    @Transactional(propagation = Propagation.NEVER)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NEVER)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

执行,会出现以下报错日志 

4.NESTED 嵌套事务 

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 捕获异常

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        userLogMapper.insert(userLog);
        try {
            int i = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return 1;
    }

 可以看到userinfo表示添加成功,而userlog表示添加失败的了.

这个只是回滚了部分事务,如果是required的话,会回滚所有的事务. 

嵌套事务(NESTED)和加入事务(REQUIRED )的区别:

  • 整个事务如果全部执行成功,二者的结果是一样的。
  • 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果。
     

猜你喜欢

转载自blog.csdn.net/qq_64580912/article/details/131996805