当前位置: 代码迷 >> 综合 >> 【SpringBoot学习】07、事务管理
  详细解决方案

【SpringBoot学习】07、事务管理

热度:33   发布时间:2023-12-29 22:06:13.0

文章目录

  • 1 事务
    • 1.1 概述
    • 1.2 事务的隔离级别
      • 1.2.1 并发异常
      • 1.2.2 四个隔离级别
    • 1.3 隔离实现机制
      • 1.3.1 悲观锁
        • 共享锁(S锁)
        • 排他锁(X锁)
      • 1.3.2 乐观锁
  • 2 Spring事务管理
    • 2.1 声明式事务
      • 2.1.1 Service示例代码
      • 2.1.2 测试
    • 2.2 编程式事务

1 事务

【数据库入门】动力节点mysql入门基础03有关于事务的概述

1.1 概述

在这里插入图片描述
事务可以保证多个操作原子性,要么全成功,要么全失败。对于数据库来说事务保证批量的 DML 要么全成功,要么全失败。事务具有四个特征 ACID

  • 原子性( Atomicity)
    整个事务中的所有操作,必须作为一个单元全部完成(或全部取消)。
  • 一致性( Consistency)
    所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的一致性。
    比方说A转给B 100块,A的账户必然减少100块,B的账户必然增加100块。如果A给B转了100之后系统宕机,A减少100块,B却没有增加100块,这就是数据的不一致性。有点类似于物质守恒。
  • 隔离性(Isolation)
    一个事务不会影响其他事务的运行。
  • 持久性(Durability)
    在事务完成以后,该事务对数据库所作的更改将持久地保存在数据库之中,并不会被回滚

1.2 事务的隔离级别

1.2.1 并发异常

? 当多个客户端并发地访问同一个表时,可能出现下面的一致性问题:

  • 脏读取( Dirty Read)
    一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交,这就出现了脏读取。
    在这里插入图片描述

  • 不可重复读( Non-repeatable Read)
    在同一个事务中,同一个读操作对同一个数据的前后两次读取产生了不同的结果,这就是不可重复读。
    在这里插入图片描述

  • 幻像读( Phantom Read)
    幻像读是指在同一个事务中以前没有的行,由于其他事务的提交而出现的新行。
    在这里插入图片描述

  • 第一类丢失更新
    某一个事务的回滚,导致另外一个事务已经更新的数据丢失了
    在这里插入图片描述

  • 第二类丢失更新
    某一个事务的提交,导致另外一个事务已经更新的数据丢失了
    在这里插入图片描述

1.2.2 四个隔离级别

InnoDB 实现了四个隔离级别,用以控制事务所做的修改,并将修改通告至其它并发的事务:

  • 读未提交( READ UMCOMMITTED) 没有提交就读到了
    导致了:脏读
  • 读已提交( READ COMMITTED) 提交之后才能读到
    解决了:脏读
    导致了:不可重复读
    在事务开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次再读取的时候,读到的数据是4条,3不等于4,称为不可重复读取。
  • 可重复读( REPEATABLE READ) 提交之后也读不到,永远读取的都是刚开启事务时的数据
    该隔离级别为 InnoDB 的缺省设置。mysql中默认的事务隔离级别就是这个
    解决了:不可重复读
    导致了:幻读
    早晨9点开始开启了事务,只要事务不结束,到晚上9点,读到的数据还是那样。读到的是假象。不够绝对的真实。
  • 串行化( SERIALIZABLE) 【序列化】
    这是最高隔离级别,效率最低。解决了所有的问题。这种隔离级别表示事务排队不能并发。每一次读取到的数据都是最真实的,并且效率是最低的。
    在这里插入图片描述
    在这里插入图片描述

1.3 隔离实现机制

在这里插入图片描述

1.3.1 悲观锁

悲观锁是数据库自带的
悲观锁看待事情比较悲观,认为如果并发就一定会有问题,既然一定会有问题就要提前对数据加锁

共享锁(S锁)

事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。

加了共相锁之后只能读数据,不能改数据

排他锁(X锁)

事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。

加了排他锁既可以读又可以改

1.3.2 乐观锁

乐观锁需要自己定义
乐观锁看待事情比较乐观,认为即使并发了也不会有问题,假设它不会有问题,该读读该写写。当读取数据计算完了需要更改数据的时候,检查数据是否发生更改。如果数据发生更改,说明处理过程中有人改过数据,那就放弃这次操作;否则没人改就提交数据

怎么识别数据变没变?
一个表加上一个字段,或者是时间戳,或者是版本号
任何人更新数据之前检查时间戳或者版本号变没变,如果变了就放弃本次更新,如果没变就提交更新,修改时间戳或者版本号

2 Spring事务管理

在这里插入图片描述
官网文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#spring-data-tier
链接可能会失效,可按如下顺序找到:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Spring对任何数据库做事务管理时,API都是统一的
具体来说有两种管理事务的方法

1.声明式事务:不需要写逻辑,只需要在xml配置文件里或者通过注解在方法上做配置就可以使用

2.编程式事务:需要编程,需要用到Transaction Template这个类

两种管理事务的方式,平时优先选第一种,简单;如果要处理的业务比较复杂,只有中间一小部分的事务想管理,就用第二种

2.1 声明式事务

demo是模拟某一个业务,所以把代码写在业务层

杜撰一个需求:注册用户之后自动给用户发一个帖子:新人报到。一个业务完成了两个新增操作(新增用户和新增帖子),要保证业务的事务性

2.1.1 Service示例代码

@Service
public class DemoService {
    @Autowiredprivate UserMapper userMapper;@Autowiredprivate DiscussPostMapper discussPostMapper;//isolation = Isolation.READ_COMMITTED:隔离级别是:读已提交//propagation传播机制 a调用b//REQUIRED 支持当前事务a,如果不存在,就创建新事务b//REQUIRES_NEW 创建新事务b且暂停当前事务//NESTED 如果当前存在事务a,则嵌套在该事务a中执行(b有独立的提交和回滚);否则和REQUIRED一样@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)public Object demo(){
    //新增用户User user = new User();user.setUsername("alpha");user.setSalt(CommunityUtil.generateUUID().substring(0,5));user.setPassword(CommunityUtil.md5("123"+user.getSalt()));user.setEmail("alpha@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("hello");post.setContent("hi");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);//人为造错,看数据能不能回滚Integer.valueOf("abc");return "ok";}
}

2.1.2 测试

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class TransactionTests {
    @Autowiredprivate DemoService demoService;@Testpublic void testSave1(){
    System.out.println(demoService.save1());}
}

报错
在这里插入图片描述
没有插进去,说明数据回滚了
在这里插入图片描述

2.2 编程式事务

@Autowiredprivate TransactionTemplate transactionTemplate;public Object save2(){
    //设置隔离级别transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);//设置传播机制transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//执行SQL访问事务return transactionTemplate.execute(new TransactionCallback<Object>() {
    @Overridepublic Object doInTransaction(TransactionStatus transactionStatus) {
    //新增用户User user = new User();user.setUsername("beta");user.setSalt(CommunityUtil.generateUUID().substring(0,5));user.setPassword(CommunityUtil.md5("123"+user.getSalt()));user.setEmail("beta@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/999.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("nihao");post.setContent("11");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);Integer.valueOf("abc");return "ok";}});}
    @Testpublic void testSave2(){
    System.out.println(demoService.save2());}

在这里插入图片描述
在这里插入图片描述

总结:两种管理事务的方式,平时优先选第一种,简单;如果要处理的业务比较复杂,只有中间一小部分的事务想管理,就用第二种