Transaction 注解
@Transaction 参考 Transaction事务属性 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法
Spring 中 @Transactional 配置
引入命名空间
在 Spring 配置文件中 引入 <tx:>
命名空间
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd"
default-lazy-init="true">
重点关注
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
添加事务管理器
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
@Transactional的属性
属性 |
类型 |
描述 |
---|---|---|
value |
String |
可选的限定描述符,指定使用的事务管理器 |
propagation |
enum: Propagation |
可选的事务传播行为设置 |
isolation |
enum: Isolation |
可选的事务隔离级别设置 |
readOnly |
boolean |
读写或只读事务,默认读写 |
timeout |
int (in seconds granularity) |
事务超时时间设置 |
rollbackFor |
Class对象数组,必须继承自Throwable |
导致事务回滚的异常类数组 |
rollbackForClassName |
类名数组,必须继承自Throwable |
导致事务回滚的异常类名字数组 |
noRollbackFor |
Class对象数组,必须继承自Throwable |
不会导致事务回滚的异常类数组 |
noRollbackForClassName |
类名数组,必须继承自Throwable |
不会导致事务回滚的异常类名字数组 |
Spring 中 @Transactional 的传播行为和隔离级别
不同的位置使用
- 标注在类前:表示类中的所有方法都进行事务处理
- 标注在接口、实现类的方法前:表示方法进行事务处理
事务传播行为
注释 |
作用 |
---|---|
@Transactional(propagation=Propagation.REQUIRED) |
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下) |
@Transactional(propagation=Propagation.NOT_SUPPORTED) |
容器不为这个方法开启事务 |
@Transactional(propagation=Propagation.REQUIRES_NEW) |
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 |
@Transactional(propagation=Propagation.MANDATORY) |
必须在一个已有的事务中执行,否则抛出异常 |
@Transactional(propagation=Propagation.NEVER) |
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) |
@Transactional(propagation=Propagation.SUPPORTS) |
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务 |
事务超时设置
@Transactional(timeout=30) //30秒超时
事务隔离级别
注释 |
作用 |
---|---|
@Transactional(isolation = Isolation.READ_UNCOMMITTED) |
读取未提交数据(会出现脏读, 不可重复读) 基本不使用 |
@Transactional(isolation = Isolation.READ_COMMITTED) |
读取已提交数据(会出现不可重复读和幻读) |
@Transactional(isolation = Isolation.REPEATABLE_READ) |
可重复读(会出现幻读) |
@Transactional(isolation = Isolation.SERIALIZABLE) |
串行化 |
- 脏读 : 一个事务读取到另一事务未提交的更新数据。
- 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据。
- 可重复读 : 在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据。
- 幻读 : 一个事务读到另一个事务已提交的insert数据。
REQUIRED 与 REQUIRED_NEW 解读
在上面的事务传播行为的六种情况中,最难以理解的,并且容易在transaction设计时出现问题的是 REQUIRED
和 REQURED_NEW
这两者的区别。当程序在某些情况下抛出异常时,如果对于这两者不够了解,就可能很难发现而且解决问题。
场景一
ServiceA.java:
public class ServiceA {
@Transactional
public void callB() {
serviceB.doSomething();
}
}
ServiceB.java
public class ServiceB {
@Transactional
public void doSomething() {
throw new RuntimeException("B throw exception");
}
}
这种情况下,我们只需要在调用 ServiceA.callB
时捕获 ServiceB
中抛出的运行时异常,则transaction就会正常的rollback。
场景二
在保持场景一中 ServiceB
不变,在 ServiceA
中调用 ServiceB
的 doSomething
时去捕获这个异常,如下:
public class ServiceA {
@Transactional
public void callB() {
try {
serviceB.doSomething();
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
}
这个时候,我们再调用 ServiceA
的 callB
。程序会抛出这样一个异常信息
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
原因是什么呢?
因为在 ServiceA
和 ServiceB
中的 @Transactional
。
propagation都采用的默认值:REQUREID
。
根据我们前面讲过的REQUIRED特性,当 ServiceA
调用 ServiceB
的时候,他们是处于同一个transaction中。
当ServiceB
中抛出了一个异常以后,ServiceB
会把当前的transaction标记为需要rollback。但是ServiceA
中捕获了这个异常,并进行了处理,认为当前transaction应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException
。
场景三
在保持场景二中ServiceA
不变,修改ServiceB
中方法的propagation配置为REQUIRES_NEW
,如下:
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() {
throw new RuntimeException("B throw exception");
}
}
此时,程序可以正常的退出了,也没有抛出UnexpectedRollbackException
。原因是因为当ServiceA
调用ServiceB
时,serviceB
的doSomething
是在一个新的transaction中执行的。
所以,当 doSomething
抛出异常以后,仅仅是把新创建的transaction rollback了,而不会影响到ServiceA
的transaction。ServiceA
就可以正常的进行commit。
当然这里把ServiceA
和ServiceB
放在两个独立的transaction是否成立,还需要再多多考虑你的业务需求。
- 表单控件的副产品——查询控件
- 表单控件续(1)——应用接口来简化和分散代码
- 通过自定义配置实现插件式设计
- 让IoC动态解析自定义配置(提供基于Unity的实现)
- 如何让ASP.NET默认的资源编程方式支持非.ResX资源存储
- 在VS中通过建立依赖关系使文件结构更清晰
- 一个关于ConfigurationManager.GetSecion方法的小问题
- 追踪记录每笔业务操作数据改变的利器——SQLCDC
- 一个完整的用于追踪数据改变的解决方案
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- 通过内存分析工具来证明字符串驻留机制
- 如果在BackgroundWorker运行过程中关闭窗体…
- 从数据到代码——基于T4的代码生成方式
- 解决T4模板的程序集引用的五种方案
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python面试题搜集:史上最全python面试题详解(一)
- 一日一技:在 Golang 中如何快速判断字符串是否在一个数组中
- 面试问题之操作系统
- 软技能提升:转转中后台规范落地实践
- python面试题搜集(三)
- 什么是计算机网络?为什么需要网络通信?如何进行网络编程?
- Python中好用又高效的Collections 模块
- 正确创建Python二维数组
- 深入理解Python内存管理与垃圾回收,再也不怕问了(一)
- 走进面向“对象”编程的理想国(一)——深入理解Python中的一切皆对象
- 深入理解Python内存管理与垃圾回收,再也不怕问了(二)
- 如何利用Python实现二分查找(迭代和递归)
- 详解排序算法(Python实现)
- Python垃圾回收机制
- iOS 开发:『Crash 防护系统』(二)KVO 防护