一文学会Spring 实现事务,事务的隔离级别以及事务的传播机制

目录

一.Spring (Spring Boot) 实现事务

1.通过代码的方式手动实现事务 (手动档的车)

2.通过注解的方式实现声明式事务 (自动挡的车)

二.事务的4大特性(ACID)

三.事务的隔离级别

①Mysql的事务隔离级别:

②Spring的事务隔离级别:

四.事务的传播机制

①事务传播机制的概念

②Spring 事务传播机制包含以下 7 种:

1. Propagation.REQUIRED

2. Propagation.SUPPORTS

3. Propagation.MANDATORY (mandatory:强制性)

4. Propagation.REQUIRES_NEW

5. Propagation.NOT_SUPPORTED

6. Propagation.NEVER

7. Propagation.NESTED

③以情侣关系为例理解这七种传播机制


一.Spring (Spring Boot) 实现事务

1.通过代码的方式手动实现事务 (手动档的车)

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

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 1.开启事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); //transactionStatus就是事务

        // 手动设置创建时间和修改时间的默认值
        userInfo.setCreatetime(LocalDateTime.now().toString());
        userInfo.setUpdatetime(LocalDateTime.now().toString());

        int result = userService.add(userInfo);
        System.out.println("添加: " + result);

        // 2.回滚事务
//        transactionManager.rollback(transactionStatus);

        transactionManager.commit(transactionStatus);
        return result;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public Integer add(UserInfo userInfo) {
        return userMapper.add(userInfo);
    }
}
@Mapper
public interface UserMapper {
    int add(UserInfo userInfo);
}

 UserMapper.xml中的添加操作

<mapper namespace="com.example.demo.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username,password,createtime,updatetime)
        values(#{username},#{password},#{createtime},#{updatetime})
    </insert>
</mapper>

运行前数据库中的数据:

运行程序提交事务:

 

此时的代码中是没有执行回滚事务的操作的,所以数据库当中能够看到新增的数据

如果开启回滚事务,关闭提交事务,则idea中能够看见操作的信息,但是数据库中找不到新增的数据

2.通过注解的方式实现声明式事务 (自动挡的车)

通过使用@Transactional注解来实现

@Transactional的特点:

Ⅰ.可以添加在类上或方法上

        该注解只在public修饰的方法上生效,在类上添加该注解则表明当前类的所有公共方法都会自动提交或回滚事务

Ⅱ.在方法执行前开启事务,在方法执行完(没有任何异常) 自动提交事务,但是如果方法在执行期间出现异常,那么将自动回滚事务

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

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int result = userService.add(userInfo);
        System.out.println("添加 insert: " + result);
        int num = 10 / 0; //算数异常,查看是否会回滚事务
        return result;
    }
}

 通过控制台可以看到前面的添加数据的操作执行成功了,后面的代码报异常了

数据库中的数据:

 可以发现数据库中没有新数据的添加,说明事务回滚了

将@Transactional注释掉再次运行程序,数据库就能够在发生异常的时候添加新数据了

 也可以手动进行回滚事务

 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

有一种情况程序发生异常也不会进行回滚,就是程序进行try-catch的时候

上述情况的解决方案:

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

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int result = userService.add(userInfo);
        System.out.println("添加 insert: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 1.将异常继续抛出
            throw e;
            // 2.使用代码手动回滚事务
//            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

 1.将异常抛出

 2.手动回滚事务

两者都不会在数据库中添加新数据

二.事务的4大特性(ACID)

  • 原子性(Atomicity) : 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间的某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个 事务从来没有执⾏过⼀样。
  • 一致性(Consistency) : 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。
  • 持久性(Durability) : 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
  • 隔离性(Isolation) : 数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务 并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化 (Serializable)。

三.事务的隔离级别

①Mysql的事务隔离级别:

1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,⽽未提交的数据可能会发⽣回 滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读

2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据, 因此它不会有脏读问题。但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间 的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读

3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询 的结果⼀致。但也会有新的问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败 (因为唯⼀约束的原因)。明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这就叫幻读 (Phantom Read)。

4. SERIALIZABLE:序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突,从⽽解决 了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多。

②Spring的事务隔离级别:

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

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

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

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

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

四.事务的传播机制

①事务传播机制的概念

Spring 事务传播机制定义了多个包含事务的方法,相互调用时,事务是如何在这些方法之间进行传递的。

事务传播机制时保证一个事务在多个调用方法之间的可控性(稳定性)

②Spring 事务传播机制包含以下 7 种:

1. Propagation.REQUIRED

默认的事务传播机制它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。

示例Ⅰ:方法testA和testB的事务级别为PROPAGATION_REQUIRED,在testA方法里面调用testB方法,testA方法有事务,则在执行testB方法时就加入该事务(当前存在事务,则加入这个事务),此时在执行testB方法的时候如果抛出异常了,由于testA和testB方法是处于同一个事务,所以两者都会发生回滚,数据库里面的数据仍然保持原本状态

示例Ⅱ:方法testA没有声明事务,testB声明了事务且事务级别为Propagation_REQUIRED,在testA方法里面调用testB方法,此时执行testB方法时会自动创建一个事务(如果当前没有事务,则自己创建一个事务),此时在执行testB方法的时候如果抛出异常了,testB方法里面的操作会进行回滚,而testA方法里面的操作不受影响

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

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Autowired
    private LogService logService;

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 添加用户
        int result = userService.add(userInfo);
        if(result > 0) {
            // 添加日志
            logService.add();
        }
        return result;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer add(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("用户添加: " + result);
        return result;
    }
}
@Service
public class LogService {

    @Transactional(propagation = Propagation.REQUIRED)
    public int add() {
        int num = 10 / 0;// 算术异常
        return 1;
    }
}

 运行结果:

 

在添加用户完成后进行添加日志,在添加日志里面抛出一个算术异常,令程序回滚,验证添加日志的这个方法已经加入了当前事务中,会整体发生回滚,前面的添加用户操作不生效 

2. Propagation.SUPPORTS

如果当前存在事务,则加⼊该事务(与Propagation_REQUIRED相同);如果当前没有事务,则以⾮事务的⽅式继续运⾏。

示例Ⅰ:方法testA声明了事务,方法testB的事务级别为Propagation_SUPPORTS,在testA方法里面调用testB方法,则在执行testB方法时就加入到testA的事务

示例Ⅱ:方法testA没有声明事务,方法testB的事务级别为Propagation_SUPPORTS,在testA方法里面调用testB方法,则在执行testB方法时就以非事务的方式运行。此时如果testB抛出异常了,也不会发生回滚,所以抛出异常前面的操作能够执行成功

3. Propagation.MANDATORY (mandatory:强制性)

如果当前存在事务,则加⼊该事务;如果当 前没有事务,则抛出异常。

示例:方法testA没有声明事务,方法testB的事务级别为Propagation_MANDATORY,testA方法里面调用testB方法,在执行testB方法的时候就会直接抛出事务要求的异常,testB方法里面的操作就没有被执行。

如果testA方法有声明事务且事务级别为Propagation_REQUIRED,则testB方法就会使用testA方法开启的事务,如果在执行testB方法的时候遇见了异常就能够正常的回滚了

4. Propagation.REQUIRES_NEW

表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂 起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开 启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_REQUIRES_NEW,testA方法中调用testB方法,在执行testB方法的时候会创建一个新的事务,如果testA方法在调用testB方法之后发生了异常,因为两者不是在同一个事务下,所以不会影响到testB方法中的操作,testB方法中刚刚执行的操作不会进行回滚

5. Propagation.NOT_SUPPORTED

以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NOT_SUPPORTED,testA方法中调用testB方法,因为testB方法是以非事务方式运行的,如果在执行testB方法的时候抛出了异常,testB抛异常前的操作不会回滚,抛异常后的操作不会执行,而在testA方法中执行testB前的操作会由于程序发生了异常而回滚

6. Propagation.NEVER

以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NEVER,testA方法中调用testB方法,因为testB方法的事务级别为Propagation_NEVER,所以已进入testB方法就会抛出事务异常,testA方法检测到了异常就会进行回滚

7. Propagation.NESTED

如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如 果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NESTED,testA方法中调用testB方法

Ⅰ.如果执行完testB方法之后,在testA方法中抛出了异常,则testA方法和testB方法中的操作都会进行回滚

Ⅱ.如果异常发生在testB方法里面,因为testA方法里面捕获了testB的异常,所以只有testB方法中的操作会进行回滚,而testA方法中的操作不会受到影响,如果将testB的传播类型改为Propagation_REQUIRED的话,那就testA方法和testB方法都会发生回滚

此处的testA方法就像是老板,testB方法就像是员工,老板公司倒闭了,员工自然就没工作了。而员工犯错了,老板做出应对措施,把员工给开除掉,此级别的事务差不多就是这个意思

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

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Autowired
    private LogService logService;

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 添加用户
        int result = userService.add(userInfo);
        if(result > 0) {
            // 日志
            try {
                logService.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
@Service
public class LogService {
    @Transactional(propagation = Propagation.NESTED)
    public int add() {
        int num = 10 / 0;
        return 1;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public Integer add(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("用户添加: " + result);
        return result;
    }
}

运行结果:用户添加成功

③以情侣关系为例理解这七种传播机制

相关推荐

  1. 事务隔离级别

    2024-06-10 00:04:02       31 阅读
  2. 事务隔离级别

    2024-06-10 00:04:02       10 阅读
  3. spring 事务隔离级别

    2024-06-10 00:04:02       12 阅读
  4. MySQL事务隔离级别

    2024-06-10 00:04:02       38 阅读
  5. MySQL 事务隔离级别

    2024-06-10 00:04:02       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-10 00:04:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-10 00:04:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-10 00:04:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-10 00:04:02       20 阅读

热门阅读

  1. C语言每日一题——分数加减(以最简形式输出)

    2024-06-10 00:04:02       10 阅读
  2. 面试 Redis 八股文十问十答第五期

    2024-06-10 00:04:02       7 阅读
  3. ZeroMq传输视频的几种方案

    2024-06-10 00:04:02       9 阅读
  4. 【面试宝藏】MySQL 面试题解析

    2024-06-10 00:04:02       10 阅读
  5. css文本超长溢出显示省略号 ...

    2024-06-10 00:04:02       9 阅读
  6. 贪心算法05(leetcode435,763,56)

    2024-06-10 00:04:02       8 阅读
  7. 前端学习笔记

    2024-06-10 00:04:02       11 阅读
  8. Web前端的工作内容:深度解析与探索

    2024-06-10 00:04:02       11 阅读
  9. ubuntu, esp-idf, arduino

    2024-06-10 00:04:02       10 阅读
  10. Golang time CST以及UTC介绍

    2024-06-10 00:04:02       8 阅读
  11. Go基础、面试、底层

    2024-06-10 00:04:02       9 阅读
  12. Ant Design Vue 动态表头并填充数据

    2024-06-10 00:04:02       9 阅读