事务、@Transactional失效场景

[TOC]

参考链接:
MySql的四种事务隔离级别 - 超级小小黑 - 博客园
一口气说出 6种 @Transactional 注解失效场景

一、事务

事务的特性:
A(atomicity):原子性,一个事务时一个不可分割的工作单位,要么都提交,要不都不提交。
C(consistency):一致性,事务前后,数据状态一致。^ 一致性例子
I(isolation):隔离性,事务互不干扰。
D(durability):持久性,一旦提交,数据永久性更改。

编程式事务

在代码中手动提交事务、回滚等操作,代码侵入性比较强

try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}

声明式事务

基于AOP面向切面,将业务与事务处理解耦,代码侵入性很低,所以是最常用的事务管理方式。

声明式事务实现方式有两种,一种基于TX和AOP的xml配置文件方式,第二种就是基于@Transactional注解。

@Transactional
@GetMapping("/test")
public String test() {
    int insert = cityInfoDictMapper.insert(cityInfoDict);
}

二、@Transactional介绍

1. @Transactional注解作用于哪些地方。

@Transactional作用于接口方法

  • 作用于类:
    表示该类的public方法都配置相同的事务属性信息。
  • 作用于方法:
    类配置了@Transactional,该类下方法也配置了@Transactional,方法的事务会覆盖类的事务。
  • 作用于接口:
    不推荐这种方法,因为一旦标注在Interface上并且配置了Spring AOP使用CGlib动态代理,将会导致@Transactional注解会失效。
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;

    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() throws Exception {
        return "success";
    }
}

2.@Transactional注解属性

  • propagation

    propagation代表事务的传播属性,

    Propagation.REQUIRED:(默认)如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。(也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务)

    Propagation.SUPPORT:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

    Propagation.MADATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

    Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。(当类A中的a方法用默认Propagation.REQUIRED模式,类B中的b方法上采用Propagation.REQUIRES_NEW模式,然后在a方法中调用b方法操作数据库,然而a方法抛出异常,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停a方法的事务)

    Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

    Propagetion.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

    Propagetion.NESTED:和Propagetion.REQUIRED效果一样。

  • isolation

    isalation事务的隔离级别

    Isolation.DEFAULT:(默认)使用底层数据库默认的隔离级别。大多数数据库默认的事务隔离级别是Read committed,比如Sql Server,Oracle。Mysql的默认隔离级别是Repeatable read。

    Isolation.READ_UNCOMMITTED
    允许脏读,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过”排他写锁“实现。

    Isolation.READ_COMMITTED
    允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

    Isolation.REPEATABLE_READ
    禁止不可重复读取和脏读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

    Isolation.SERIALIZABLE
    它要求事务序列化执行,事务只能一个接着一个的执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

    读数据一致性 脏读 不可重复读 幻读
    未提交读 最低级别只能保证不读取物理上损坏的数据 :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
    已提交读 语句级 :x: :heavy_check_mark: :heavy_check_mark:
    可重复读 事务级 :x: :x: :heavy_check_mark:
    序列化 最高级别,事务级 :x: :x: :x:
    1. 脏读
      又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
      例如:事务T1修改了一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因Rollback了,那么事务T2读取的就是脏数据。

    2. 不可重复读
      同一个事务中,多次读出的同一数据是不一致的。
      例如:事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。

    3. 幻读
      不好表述直接上例子吧:

      在仓库管理中,管理员要给刚到的一批商品进行入库管理,当然入库之前肯定是要查一下之前有没有入库记录,确保正确性。管理员A确保库中不存在该商品之后给该商品进行入库操作,假如这时管理员B因为手快将该商品进行了入库操作。这时管理员A发现该商品已经在库中。就像刚刚发生了幻读一样,本来不存在的东西,突然之间他就有了。

      注:三种问题看似不太好理解,脏读侧重的是数据的正确性。不可重复读侧重的是对数据的修改,幻读侧重于数据的新增和删除。

  • timeout

    timeout事务的超时时间。
    默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

  • readOnly

    readOnly:指定事务是否为只读事务。
    默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置成read-only为true

  • rellbackFor

    rollbackFor用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

  • noRollbackFor

    noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

三、@Transactional失效场景

1.@Transactional应用在非public方法上

编译不会报错,但是自己要注意

2.@Transactional属性propagetion设置错误

若配置以下三种propagation,事务有可能不回滚。

Propagation.SUPPORT^ Propagation.SUPPORT

Propagation.NOT_SUPPORTED[^ Propagation.NOT_SUPPORTED]

Propagetion.NEVER[1]

3.@Transactional注解rollbackbakcFor设置错误

4.同一个类中方法调用,导致@Transactional失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务还是不起作用的。这也是经常犯错误的一个地方。

public class Test{

    @GetMapping("/A")
    public String A(){
        String result = this.B();
        return result;
    }
    @GetMapping("/B")
    @Transactional(rollbackFor=Exception.class)
    public String B(){
        String result = "1";
        result+="2";
        throw new Exception("模拟报错..");
        result+="3";
        return result;
    }
}



/*访问/A,调用的B的事务不起作用,访问/B,B的事务起作用*/

为啥会出现这种情况?
其实着还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成代理对象来管理。

5.异常被catch,没有抛出给Spring

这种情况最常见!

@Transactional
private Integer A() throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
         * A 插入字段为 2的数据
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
         * B 插入字段为 3的数据
         */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

答案:不能!

因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

spring的事务实在调用业务方法之前开始的,业务方法执行完毕之后才执行commitorrollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception并在你的业务方法中没有catch到的话,事务会回滚。

在业务方法中一般不需要catch异常,如果非要catch,一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception),否则事务失效。

6.数据库引擎不支持事务

mysql默认使用innodb引擎,所以基本不会出现这个问题,关于数据库引擎,都使用innodb即可。


  1. 以非事务的方式运行,如果当前存在事务,则抛出异常。
    [^ Propagation.NOT_SUPPORTED]: 以非事务的方式运行,如果当前存在事务,暂停当前的事务

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

推荐阅读更多精彩内容