Springboot事务

Springboot事务

https://blog.csdn.net/weixin_50626214/article/details/124947695

一、事务是为了解决数据安全问题而存在的。

最经典的例子就是银行转账问题,A账户给B账户转账100元,A账户扣除100元后由于不可抗力因素导致程序中断,B账户没有收到那100元,A账户那100元凭空消失,肯定是不行的。A扣款和B收款操作要么同时成功,要么同时失败,这个时候就需要引入事务操作。

二、事务的四个特性ACID

  • 原子性:一个事务是一个不可分割的工作单位。
  • 一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的。
  • 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

三、事务管理方式

spring支持编程式事务管理声明式事务管理两种方式。

(1)编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager

对于编程式事务管理,spring推荐使用TransactionTemplate

(2)声明式事务管理建立在AOP之上的。

其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

@Transactional注解:

它是声明式事务管理编程中使用的注解,放在接口实现类接口实现方法上,并且只对public方法才起作用。只读的接口不需要事务管理,防止影响系统性能。

@Transactional 实质是使用了 JDBC 的事务来进行事务控制的,实现原理:

事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,执行所有数据库命令。[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接 connection 逻辑上新建一个会话session;DataSource 与 TransactionManager 配置相同的数据源)

事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,然后关闭该代理 connection 对象。(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

四、事务的隔离级别(4种)

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。隔离级别越高,数据库的并发性能就越差。

第一种隔离级别:Read uncommitted(读未提交)

在该隔离级别下,所有事务都可以看到其它未提交事务的执行结果。即在该级别下,事务的修改即便没有提交,对其他事务也都是可见的可能出现脏读、不可重复读、幻读

第二种隔离级别:Read committed(读提交)

该隔离级别满足了隔离的简单定义,一个事务只能看见已经提交事务所做的改变。这是Oracle数据库默认的事务隔离级别避免了脏读可能出现不可重复读、幻读

第三种隔离级别:Repeatable read(可重复读取)

可以确保同一个事务在多次读取同样的数据时,返回同样的结果。这是MySQL数据库默认的事务隔离级别。这样避免了不可重复读和脏读但是有时可能会出现幻读

第四种隔离级别:Serializable(可序化)

它通过强制事务排序,使事务一个一个的进行,事务之间不可能再存在相互冲突,从而解决幻读问题

五、脏读、不可重复读、幻读

https://blog.csdn.net/qq_41776884/article/details/81608777

1、脏读

脏读就是指当A事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,B事务也访问这个数据,然后使用了这个数据。这时候如果事务A回滚,那么B事务读到的数据是不被承认的。

2、不可重复读

(重点在修改,体现在值不同)

指在A事务内,多次读同一数据。在A事务还没有结束时,B事务也访问该同一数据。那么,在A事务中的两次读数据之间,由于B事务的修改,那么A事务两次读到的的数据可能是不一样的。这样就发生了在A事务内两次读到的数据是不一样的。

3、幻读

(重点在增加或删除,体现在记录数不同)

是指当事务不是独立执行时发生的一种现象,例如A事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,B事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作A事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

六、事务的传播行为(7种)

https://blog.csdn.net/jy02268879/article/details/84322459

前提:事务的传播行为是针对嵌套事务而言的。

关键是要理解 嵌套 这两个字 以及它的调用方(外层方法、内层方法)

如在

(1)外层方法(有事务注解)调用内层方法(有事务注解)

(2)外层方法(无事务注解)调用内层方法(有事务注解)

1.REQUIRED

@Transactional(propagation = Propagation.REQUIRED)
spring中的默认事务传播行为就是它。如果业务方法执行时已经在一个事务中,则加入当前事务

否则重新开启一个事务。外层事务提交了,内层才会提交。内/外只要有报错,他俩会一起回滚。

只要内层方法报错抛出异常,即使外层有try-catch,该事务也会回滚。

因为内外层方法在同一个事务中,内层只要抛出了异常,这个事务就会被设置成rollback-only,即使外层try-catch内层的异常,该事务也会回滚。

2.REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)

支持事务。每次都是创建一个新事物,如果当前已经在事务中了,会挂起当前事务。内层事务结束,内层就提交了,不用等着外层一起提交。

外层报错回滚,不影响内层。内层报错回滚,外层try-catch内层的异常,外层不会回滚。

内层报错回滚,然后又会抛出异常,外层如果没有捕获处理内层抛出来的这个异常,外层还是会回滚的。

3.NESTED

@Transactional(propagation = Propagation.NESTED)

支持事务。如果当前事务存在,那么在嵌套的事务中执行,内层事务结束,要等着外层一起提交。如果当前事务不存在,则表现跟REQUIRED一样。

这个直接说,如果外层报错回滚,内层也会跟着回滚。

如果只是内层回滚,不影响外层。这个内层回滚不影响外层的特性是有前提的,否则内外都回滚。

内层是NESTED模式下,外层要try-catch内层的异常,外层才不会回滚。而内层是REQUIRED模式的话,即使外层try-catch内层异常,外层同样会回滚的。

4.SUPPORTS

@Transactional(propagation = Propagation.SUPPORTS)

支持事务。当前有事务就支持使用当前事务,若当前不存在事务,以非事务的方式执行。内层事务结束,要等着外层一起提交。

5.MANDATORY

@Transactional(propagation = Propagation.MANDATORY)

支持事务,如果业务方法执行时已经在一个事务中,则加入当前事务。否则抛出异常。内层事务结束,要等着外层一起提交。

6.NOT_SUPPORTED

@Transactional(propagation = Propagation.NOT_SUPPORTED)

不支持事务,以非事务的方式执行,若当前存在事务,则把当前事务挂起,等方法执行完毕后,事务恢复进行。

若A是事务执行,B(NOT_SUPPORTED非事务执行)B在A尚未提交前再操作同一条记录,会产生死锁,A、B不可操作同一条记录。

7.NEVER

@Transactional(propagation = Propagation.NEVER)

不支持事务。如果当前已经在一个事务中了,抛出异常。

@Transactional使用

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

隔离级别 Isolation

package org.springframework.transaction.annotation;

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

传播行为 Propagation

package org.springframework.transaction.annotation;

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

https://www.cnblogs.com/zhangliang88/p/13859522.html

一般情况下,@Transactional要放在service层,并且只需要放到最外层的方法上就可以了。

controller层使用@Transactional注解是无效的。但是可以在controller层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常

@RequestMapping(value = "/delrecord", method = {RequestMethod.GET})
@Transactional(rollbackFor = Exception.class) // 无效
public String delRecord(HttpServletRequest request) {
    try {
        //省略业务代码……
    } catch (Exception e) {
        log.error("操作异常",e);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动在contoller中增加事务
        return failure("操作失败!");
    }
}

特别注意:如下代码发现事务不回滚(在controller使用事务),即 this.repository.delete(id); 成功把数据删除了。

@GetMapping("delete")
@ResponseBody
@Transactional    
public void delete(@RequestParam("id") int id){       
    try {            //delete country
        this.repository.delete(id);         
        if(id == 1){              
            throw Exception("测试事务");
        }           
        //delete city
        this.repository.deleteByCountryId(id);
    }catch (Exception e){
        logger.error("delete false:" + e.getMessage());        
        return new MessageBean(101,"delete false");
    }
}

原因:默认spring事务只在发生未被捕获的 RuntimeException 时才回滚

解决方案:
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理

方案2:在controller层方法的catch语句中增加:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常

Spring事务的嵌套的详细理解,以及事务失效的场景解惑

参考资料

  1. Spring Boot 事务详解
  2. 数据库事务隔离级别(脏读、幻读、不可重复读)
  3. Spring Boot之事务(事务传播机制、嵌套事务、事务隔离机制详解)
  4. controller层使用@Transactional事务注解
  5. Spring事务的嵌套的详细理解,以及事务失效的场景解惑
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • 转自【一起来学SpringBoot(八)事务的控制 - fulinlin的博客 - CSDN博客】https://...
    VivianMQ阅读 6,273评论 0 1
  • 了解事务之前得了解几个概念: 1.脏读: 脏读简单来说就是提取未提交的数据 A事务读取B事务尚未提交的数据,此时如...
    要进大厂阅读 1,267评论 0 1
  • 关系型数据库多用到事务,在传统项目中使用xml配置,配置虽然也还好,但是看着很不美观,在使用SpringBoot框...
    Chinesszz阅读 14,795评论 2 12
  • 一、隔离级别 1. 并发产生的问题 脏读:一个事务读到了另一个未提交事务修改过的数据 幻读:一个事务先根据某些条件...
    DrunkB阅读 606评论 0 0
  • 前言 今天是平安夜,先祝大家平安夜快乐。 我们之前的数十篇文章分析了 Spring 和 Mybatis 的原理,基...
    莫那一鲁道阅读 14,023评论 3 31