spring-事务管理
一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID:
原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
- 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。
一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:
使用 begin transaction 命令开始事务。
使用 SQL 查询语句执行各种删除、更新或插入操作。
- 如果所有的操作都成功,则执行提交操作,否则回滚所有操作。
Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。
局部事物 vs. 全局事务
局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。
局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。
全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。
编程式 vs. 声明式
Spring 支持两种类型的事务管理:
编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
- 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。
声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用 Spring AOP 框架的声明式事务管理。
Spring 事务抽象
Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
序号 | 方法 & 描述 |
---|---|
1 |
TransactionStatus getTransaction(TransactionDefinition definition) 根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。 |
2 |
void commit(TransactionStatus status) 该方法提交给定的事务和关于它的状态。 |
3 |
void rollback(TransactionStatus status) 该方法执行一个给定事务的回滚。 |
TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}
序号 | 方法 & 描述 |
---|---|
1 |
int getPropagationBehavior() 该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。 |
2 |
int getIsolationLevel() 该方法返回该事务独立于其他事务的工作的程度。 |
3 |
String getName() 该方法返回该事务的名称。 |
4 |
int getTimeout() 该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。 |
5 |
boolean isReadOnly() 该方法返回该事务是否是只读的。 |
Spring中的隔离级别
:
序号 | 隔离 & 描述 |
---|---|
1 |
TransactionDefinition.ISOLATION_DEFAULT 这是默认的隔离级别。 |
2 |
TransactionDefinition.ISOLATION_READ_COMMITTED 表明能够阻止误读;可以发生不可重复读和虚读。 |
3 |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED 表明可以发生误读、不可重复读和虚读。 |
4 |
TransactionDefinition.ISOLATION_REPEATABLE_READ 表明能够阻止误读和不可重复读;可以发生虚读。 |
5 |
TransactionDefinition.ISOLATION_SERIALIZABLE 表明能够阻止误读、不可重复读和虚读。 |
Spring 事务的传播属性
:
序号 | 传播 & 描述 |
---|---|
1 |
TransactionDefinition.PROPAGATION_MANDATORY 支持当前事务;如果不存在当前事务,则抛出一个异常。 |
2 |
TransactionDefinition.PROPAGATION_NESTED 如果存在当前事务,则在一个嵌套的事务中执行。 |
3 |
TransactionDefinition.PROPAGATION_NEVER 不支持当前事务;如果存在当前事务,则抛出一个异常。 |
4 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED 不支持当前事务;而总是执行非事务性。 |
5 |
TransactionDefinition.PROPAGATION_REQUIRED 支持当前事务;如果不存在事务,则创建一个新的事务。 |
6 |
TransactionDefinition.PROPAGATION_REQUIRES_NEW 创建一个新事务,如果存在一个事务,则把当前事务挂起。 |
7 |
TransactionDefinition.PROPAGATION_SUPPORTS 支持当前事务;如果不存在,则执行非事务性。 |
8 |
TransactionDefinition.TIMEOUT_DEFAULT 使用默认超时的底层事务系统,或者如果不支持超时则没有。 |
TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
序号 | 方法 & 描述 |
---|---|
1 |
boolean hasSavepoint() 该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。 |
2 |
boolean isCompleted() 该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。 |
3 |
boolean isNewTransaction() 在当前事务时新的情况下,该方法返回 true。 |
4 |
boolean isRollbackOnly() 该方法返回该事务是否已标记为 rollback-only。 |
5 |
void setRollbackOnly() 该方法设置该事务为 rollback-only 标记。 |
数据库隔离级别
隔离级别 | 隔离级别的值 | 导致的问题 |
Read-Uncommitted | 0 | 导致脏读 |
Read-Committed | 1 | 避免脏读,允许不可重复读和幻读 |
Repeatable-Read | 2 | 避免脏读,不可重复读,允许幻读 |
Serializable | 3 | 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重 |
脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
总结:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
事务的嵌套
通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。
假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()
PROPAGATION_REQUIRED(spring 默认)
如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。
假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。
PROPAGATION_REQUIRES_NEW
比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。
那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。
他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
PROPAGATION_SUPPORTS
假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。
PROPAGATION_NESTED
现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
a、捕获异常,执行异常分支逻辑
void methodA() { try { ServiceB.methodB(); } catch (SomeException) { // 执行其他业务, 如 ServiceC.methodC(); } }
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback
另外三种事务传播属性基本用不到,在此不做分析。
总结
对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。
spring-事务管理的更多相关文章
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- spring事务管理器设计思想(二)
上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...
- spring事务管理器设计思想(一)
在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...
- 事务管理(下) 配置spring事务管理的几种方式(声明式事务)
配置spring事务管理的几种方式(声明式事务) 概要: Spring对编程式事务的支持与EJB有很大的区别.不像EJB和Java事务API(Java Transaction API, JTA)耦合在 ...
- Spring事务管理器的应对
Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋.像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便:而iBatis则通过模板化技术让你方便地控制SQ ...
- Spring事务管理(转)
1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是 ...
- [Spring框架]Spring 事务管理基础入门总结.
前言:在之前的博客中已经说过了数据库的事务, 不过那里面更多的是说明事务的一些锁机制, 今天来说一下Spring管理事务的一些基础知识. 之前的文章: [数据库事务与锁]详解一: 彻底理解数据库事务一 ...
- Spring 事务管理 01 ——
目录: 参考: 1.Spring 事务管理高级应用难点剖析: 第 1 部分
- Spring 事务管理原理探究
此处先粘贴出Spring事务需要的配置内容: 1.Spring事务管理器的配置文件: 2.一个普通的JPA框架(此处是mybatis)的配置文件: <bean id="sqlSessi ...
- Spring 事务管理高级应用难点剖析--转
第 1 部分 http://www.ibm.com/search/csass/search/?q=%E4%BA%8B%E5%8A%A1&sn=dw&lang=zh&cc=CN& ...
随机推荐
- 思考:用开发移动app的观念来开发网站
首先祝大家新年快乐.万事如意. 开发网站程序也有一些年头了,从最初的静态HTML+ JS,到后来的WebForm,然后过渡到现在的MVC. 由于最近做一些技术调研,也接触了很多移动开发,iOS和And ...
- ORACLE NUMBER类型Scale为0引发的问题
今天遇到了一个很有意思的NUMBER类型Scale引发的问题,我用一个简单的测试用例来展示一下这个案例.假如有个TEST的表,有个字段类型为NUMBER,我插入下面两条数据 CREATE TABLE ...
- web.xml is missing and <failOnMissingWebXml> is set to true 错误解决办法
对web项目的解决方案: 右击项目——>Java EE Tools——>Generate Deployment Descriptor Stub. 然后系统会在src/main/webapp ...
- 实战:考虑性能--Solr索引的schema设计
从 high level 的角度来看,schema.xml 结果如下,这个例子虽然不是一个真实的XML,但是简洁明了的传达了shema的概念. <schema> <types> ...
- Windows 10 安装双系统 CentOS 7
系统环境:Windows 10 && CentOS 7 准备工具: ●CentOS7 官网下载地址:https://wiki.centos.org/Download (注意: i386 ...
- linux中位置参数变量和预定义变量
位置参数变量 预定义变量
- shell parameter expansitions
type testtype -a test math calculate:echo $((1+2*3)) parameter expansition:bash-4 introduced feature ...
- windows下安装并配置mysql
前言:前面三篇文章将django的环境搭建完后,还只能编写静态网页,如果要用到数据库编写动态网页,那么还需要数据库 本章讲解mysql5.6数据库的安装和配置,对于其他版本仅供参考,不一定试用!推荐使 ...
- WPF RichTextbox
WPFTextBoxAutoComplete AvalonEdit WPF SyntaxHighlightBox WinForm 下的 Fast Colored TextBox for Synta ...
- C程序运行计时
在标准的C/C++中最小的时间单位是毫秒ms,下面代码中clock_t是long: 每经过1ms clock()的值就增加1:常量CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元 ...