本地事务

本地事务

什么是本地事务,现在有一个单体应用,就连接了一个数据库,也没有多个数据库,也没有多个项目,也不牵扯到远程调用,现在有一个这样的业务流程,下订单,减库存,减积分,这三个多数据库的操作要么同时成功要么同时失败,这就是本地事务!

事务的基本性质

原子性(Atomicity)、一致性( Consistency) 、隔离性或独立性(Isolation)和持久性(Durabilliy,简称就是ACID;
**原子性:**一系列的操作整体不可拆分,要么同时成功,要么同时失败
**一致性:**数据在事务的前后,业务整体一致。转账。A:1000; B:1000;转200 事务成功; A: 800 B: 1200
**隔离性:**事务之间互相隔离。
**持久性:**一旦事务成功,数据一定会罗盘到数据库

在上面的下订单,减库存,减积分这三个对数据库的操作是同一个事务,要么同时成功或者同时失败,本地事务指的这三个操作用的是同一个数据库,并且建立的数据库连接也是同一个,
在这里插入图片描述
一个事务开始,代表一下的所有操作都在同一个数据库连接里面,本地事务特别简单Spring提供非常便捷的 @Transactional
在使用事务的时候要注意事务的隔离级别

事务的隔离级别

(读未提交)READ UNCOMMITTED: 该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为脏读。
(读提交)READ COMMITTED : 一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读问题, Oracle和SQL Server的默认隔离级别。
(可重复读)REPEATABLE READ: 该隔离级别是MySQL默认的隔离级别,在同一个事务里, select的结果是事务开始时时间点的状态,因此,同样的select操作读到的结果会是一致的,但是,会有幻读现象。MysaL的InnoDB引擎可以通过next-key locks机制(参考下文"行锁的算法"一节)来避免幻读。
(序列化)SERIALIZABLE: 在该隔离级别下事务都是串行顺序执行的, MysQL,数据库的InnoDB引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。
隔离级别越高,数据库并发读写的能力越弱!
强—>弱
序列化>可重复读>读提交>读未提交

	//注解可以直接调整当前事务的隔离级别
 	@Transactional(isolation = Isolation.READ_COMMITTED)
    @Transactional(isolation = Isolation.DEFAULT)
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    @Transactional(isolation = Isolation.SERIALIZABLE)

事务的传播行为

PROPAGATION-REQUIRED: 如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
PROPAGATION-SUPPORTS: 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_MANDATORY: 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION-REQUIRES_NEW: 创建新事务,无论当前存不存在事务,都创建新事务。
PROPAGATION-NOT-SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION-NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION-NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,执行与PROPAGATION REQUIRED类似的操

  	@Transactional(timeout = 30)//a事务30秒超时回滚
    public void a(){
        b();//和a公用同一个事务和a同时成功同时失败
        c();//不和a公用同一个事务和a没有半毛钱关系

        int i=10/0;//这里手动异常,那么a必然回滚,b和a在同一个事务里面,那么也会回滚,但是c没有和a在同一个事务里面,所以无法回滚
    }
    @Transactional(propagation = Propagation.REQUIRED,timeout = 2)
    //如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
    //这里也设置了超时时间,但是由于和a公用同一个事务,所以设置无法生效
    public void b(){

    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)//创建新事务,无论当前存不存在事务,都创建新事务。
    public void c(){

    }

上面这是伪代码,这个伪代码是为了方便解释事务的传播行为的,但是实际上这些b、c的事务是无法生效的,这也是SpringBoot事务的一个坑
解释这个坑: 原因就是b、c事务是在同一个service里面的事务,而且a调用额是同一个service里面的b、c方法,b、c方法设置任何的事务都是无效的,都是和a公用同一个事务的,事务最大的特点就是使用代理对象来控制的,如果b、c事务方法是其他的service的话就可以,入bService. b(); cService.c();

**注意:**这里前往不要自己的service注入使用 @Autowired将自己的service注入进来,否则会循环应用

本地事务失效解决方案:
1.你也可以直接将这个b方法和c方法写到其他的service中
2.使用aop实现动态代理

1.导入AOP依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
主要是使用aspectj动态代理,这个的动态代理更强大,不是用aspectj动态代理的话默认使用jdk自带的

2.主启动类开启aspectj动态代理
@EnableAspectJAutoProxy
开启后,以后所有的动态代理都是由aspectj来实现代理的,aspectj创建的动态代理的好处就是即使没有接口也能实现动态代理,使用jdk的动态代理必须要有接口

3.暴露动态代理对象
@EnableAspectJAutoProxy(exposeProxy = true)

4.本类互相调用对象
	@Transactional(timeout = 30)//a事务30秒超时回滚
    public void a(){
        YouService youService =(YouService )AopContext.currentProxy();
        youService .b();//和a公用同一个事务和a同时成功同时失败
        youService .c();//不和a公用同一个事务和a没有半毛钱关系

        int i=10/0;//这里手动异常,那么a必然回滚,b和a在同一个事务里面,那么也会回滚,但是c没有和a在同一个事务里面,所以无法回滚
    }

演示代码

controller

@Autowired
    private UserService userService;


    @PostMapping("/save")
    public String save(){
        userService.testT();
        return "ok";
    }

    @PostMapping("/readUncommitted")//读未提交
    public String readUncommitted(){
        userService.readUncommitted();
        return "ok";
    }

    @PostMapping("/readCommitted")//读提交
    public String readCommitted(){
        userService.readCommitted();
        return "ok";
    }

    @PostMapping("/repeatableRead")//可重复读
    public String repeatableRead(){
        userService.repeatableRead();
        return "ok";
    }

    @PostMapping("/serializable")//序列化
    public String serializable(){
        userService.serializable();
        return "ok";
    }

    @PostMapping("/add")//测试事务开启后添加数据
    public String add(){
        userService.add();
        return "ok";
    }

service

@Transactional
    @Override
    public void add() {
        UserEntity userEntity=new UserEntity();
        String uuid= String.valueOf(UUID.randomUUID());
        userEntity.setUsername(uuid);
        boolean res=this.save(userEntity);
    }

    @Transactional
    @Override
    public boolean testT() {//模拟报错事务回滚
        UserEntity userEntity=new UserEntity();
        String uuid= String.valueOf(UUID.randomUUID());
        userEntity.setUsername(uuid);
        boolean res=this.save(userEntity);
        log.info(String.valueOf(res));
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int i=10/0;
        return false;
    }


    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    @Override
    public void readUncommitted() {
        log.info("读未提交");
        //在testT方法执行完save后,线程随眠后,方法还没执行完,事务也没提交
        //这里的事务隔离级别是读未提交,所以这里是能直接读取到休眠是未提交的数据的
        getAll();
    }


    @Transactional(isolation = Isolation.READ_COMMITTED)
    @Override
    public void readCommitted () {
        log.info("读提交");
        //在testT方法执行完save后,线程随眠后,方法还没执行完,事务也没提交
        //这里的事务隔离级别是读提交,所以这里是无法读取到休眠是未提交的数据的
        getAll();
    }

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    @Override
    public void repeatableRead() {
        for (int i=0;i<20;i++){
            try {
                log.info("可重复读");
                //在testT方法执行完save后,线程随眠后,方法还没执行完,事务也没提交
                //并且在repeatableRead方法进入后循环后,每隔一秒开始读取数据
                //这里的事务隔离级别是可重复读,读取到的数据是不会改变的
                //哪怕一直频繁调用add添加数据的方法,添加数据成功,并且Mysql客户端也能看到数据
                //但是这里可重复读,只能感知到事务开启时数据库中的数据情况,
                //进入事务后的数据便更了是无法感知的
                Thread.sleep(1000);
                getAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Transactional(isolation = Isolation.SERIALIZABLE)
    @Override
    public void serializable() {
        log.info("序列化");
        getAll();
    }

    public void getAll(){
        List<UserEntity> userList=this.baseMapper.selectList(null);
        log.info("获取到的数据"+userList.size());
    }

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Seata是一种分布式事务解决方案,它支持全局事务和本地事务。本地事务是Seata中的一种事务模式,可以用于在单个数据库中进行原子性操作。 Seata本地事务的实现基于数据库的本地事务机制。当应用程序需要执行一系列数据库操作时,可以将它们作为一个本地事务进行处理,以确保所有操作的一致性。在Seata中,本地事务是由应用程序主动发起并管理的,Seata则负责协调和管理分布式事务。 Seata本地事务的使用可以通过以下步骤来完成: 1. 开启本地事务:应用程序先向Seata发起开启本地事务的请求,Seata在数据库中创建一个新的本地事务,并返回一个相应的事务ID。 2. 执行数据库操作:应用程序可以执行一系列数据库操作,如插入、更新或删除数据等。这些操作都将在同一个本地事务中执行,确保操作的一致性。 3. 提交或回滚事务:在所有操作执行完毕后,应用程序可以根据执行结果选择提交或回滚本地事务。如果所有操作都成功,应用程序可以调用提交事务的接口,Seata将协调数据库进行数据的提交。如果有任何操作失败或出现异常,应用程序可以调用回滚事务的接口,Seata将协调数据库进行数据的回滚。 通过Seata的本地事务功能,应用程序可以在单个数据库中实现分布式事务的一致性。Seata通过底层的协调机制,保证了多个操作之间的原子性和一致性,提供了更可靠和高效的事务管理方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员劝退师-TAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值