Spring事务详解

事务接口定义

在Spring中,事务是通过TransactionDefinition接口定义的。其中定义了访问事务属性的相关方法。核心代码如下:

public interface TransactionDefinition {
    int getPropagationBehavior();   // 事务传播行为
    int getIsolationLevel();        // 事务隔离级别
    int getTimeout();               // 事务超时
    boolean isReadOnly();           // 事务的只读属性
    String getName();               // 事务名称,供Web容器显示用,可为空
}

事务传播行为

事务传播行为是指,在开始当前事务之前,针对当前事务上下文环境,即当前是否存在事务,的执行行为。在TransactionDefinition接口定义中包括了如下几个表示传播行为的常量:

  1. PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  3. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  4. PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  5. PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  6. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED

其中前面的六种事务传播行为是Spring从EJB中引入的,他们具有相同的概念。而PROPAGATION_NESTED是Spring所特有的。以PROPAGATION_NESTED启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。另外,外部事务的回滚也会导致嵌套子事务的回滚。

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition接口中定义了五个表示隔离级别的常量:

  1. TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED
  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  3. TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  4. TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  5. TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

脏读

脏读就是读取了“假数据”。例如以下流程,其中事务A的读取就是脏读。

  1. 事务A开始。
  2. 事务B开始,修改记录E的值。
  3. 事务A读取E的值。
  4. 事务B回滚。
  5. ...

不可重复读

不可重复是,在同一个事务中,两次查询同一条记录,但返回结果不相同。例如:

  1. 事务A开始,查询记录E。
  2. 事务B开始,修改记录E的值,并提交事务。
  3. 事务A再次读取E的值。
  4. ...

幻读

幻读是,在同一个事务中,两次执行同一个查询,但返回结果不相同。例如:

  1. 事务A开始,并查询表T的总记录数。
  2. 事务B开始,增加一条数据到表T中,并提交事务。
  3. 事务A再次查询表T的总记录数。
  4. ...

事务超时

事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

事务的只读属性

事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

事务的回滚规则

通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。

声明式事务管理

Spring 的声明式事务管理概述

Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

基于 <tx> 命名空间的声明式事务管理

Spring 2.x 引入了<tx>命名空间,结合使用<aop>命名空间,使配置变得更加简单和灵活。另外,得益于<aop>命名空间的切点表达式支持,声明式事务也变得更加强大。关键的配置信息如下:

<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="find*" propagation="REQUIRED"/>
        <tx:method name="query*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="servicePointcut" expression="execution(* com.wenin819.service.*.*(..))"/>
    <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut"/>
</aop:config>

配置说明:

  1. tx:attribute标签配置是,要进行事务控制的方法名和对应的事务属性配置,其中name可以用*通配符,propagation配置规则如下。
    1. 传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]
    2. 传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。
    3. 传播行为的取值必须以PROPAGATION_开头,具体见上文中传播行为说明。
    4. 隔离级别的取值必须以ISOLATION_开头,具体见上文中隔离级别说明。
    5. 如果事务是只读的,那么我们可以指定只读属性,使用readOnly指定。否则我们不需要设置该属性。
    6. 超时属性的取值必须以TIMEOUT_开头,后面跟一个int类型的值,表示超时时间,单位是秒。
    7. 不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上+。异常的名字可以是类名的一部分。比如+RuntimeException+tion等等。
    8. 导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上-。异常的名字可以是类名的全部或者部分,比如-RuntimeException-tion等等。
    9. 由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器 Bean 的名字取值为transactionManager,则我们可以省略<tx:advice>transaction-manager属性,因为该属性的默认值即为transactionManager
  2. aop:pointcut标签配置是,要进行事务控制类和方法。其中第一个*代表返回值,第二*代表service下子包,第三个*代表方法名,(..)代表方法参数。
  3. aop:advisor标签配置是,把上面配置的事务管理两部分整合起来作为整个事务管理。

基于 @Transactional 的声明式事务管理

除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional标注。@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。注解的核心方法如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    String value() default "";
    Propagation propagation() default Propagation.REQUIRED; // 传播行为
    Isolation isolation() default Isolation.DEFAULT;    // 隔离级别
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;    // 超时属性
    boolean readOnly() default false;   // 只读属性
    Class<? extends Throwable>[] rollbackFor() default {};  // 不影响提交的异常类
    String[] rollbackForClassName() default {}; // 不影响提交的异常类名
    Class<? extends Throwable>[] noRollbackFor() default {};    // 导致回滚的异常类
    String[] noRollbackForClassName() default {};   // 导致回滚的异常类名
}

Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean,配置如下:

<tx:annotation-driven transaction-manager="transactionManager"/>

与前面相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。

虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外,@Transactional注解应该只被应用到public方法上,这是由 Spring AOP 的本质决定的。如果你在protectedprivate或者默认可见性的方法上使用@Transactional注解,这将被忽略,也不会抛出任何异常。

基于<tx>命名空间和基于@Transactional的事务声明方式各有优缺点。基于<tx>的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于@Transactional的方式必须在每一个需要使用事务的方法或者类上用@Transactional标注,尽管可能大多数事务的规则是一致的,但是对@Transactional而言,也无法重用,必须逐个指定。另一方面,基于@Transactional的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。

参考文章

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 1. 关于事务 事务控制就是将一系列操作当成一个不可拆分的逻辑单元,保证这些操作要么都成功,要么都失败。在关系数据...
    七弦桐语阅读 2,876评论 0 14
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,678评论 6 342
  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 4,687评论 2 59
  • 事务有四个特性:ACID 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作...
    jiangmo阅读 1,215评论 0 7