【SpringBoot学习】07、事务管理

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 {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private 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 {
    @Autowired
    private DemoService demoService;

    @Test
    public void testSave1(){
        System.out.println(demoService.save1());
    }
}

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

2.2 编程式事务

@Autowired
    private TransactionTemplate transactionTemplate;
    public Object save2(){
        //设置隔离级别
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        //设置传播机制
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        //执行SQL访问事务
        return transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public 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";
            }
        });

    }
    @Test
    public void testSave2(){
        System.out.println(demoService.save2());
    }

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

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值