Spring 事务管理

事务简介

        事务是逻辑上的一组操作,它们被当作一个单独的工作单元,要么都执行,要么都不执行;事务管理是企业级应用程序开发中必不可少的技术,其实就是按照给定的事务规则来执行提交或者回滚操作用来确保数据的完整性和一致性。

事务的四个关键属性(ACID)
        数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作。在关系数据库中,一个事务由一组SQL语句组成。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

原子性(Atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。
一致性(Consistent),事务在完成时,必须使所有的数据都保持一致状态,事务的中间状态不能被观察到的。
隔离性(Insulation),由并发事务所作的修改必须与任何其它并发事务所作的修改隔离,防止数据损坏,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)
持久性(Duration),事务完成之后,它对数据库中数据的改变的影响是永久性的,即使数据库发生故障也不应该对其有任何影响。

 任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能都很好的满足,也要考虑支持到什么程度。

并发事务带来的问题
       如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据修改、读”脏”数据(脏读)、不可重复读、产生幽灵数据;假设数据库中有如下一条记录:

idnamesexagebirthdaycardId
19880523100806gray.zM301988-05-23 10:08:061234567890X

● 第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事务更新同一条数据,某一事务异常终止,回滚造成第一个完成的更新也同时丢失。

 事务1事务1
T1开启事务 
T2 开启事务
T3查询数据 age = 30 
T4 查询数据 age = 30
T5更新数据 age = 20 
T6 更新数据 age = 25 并提交事务
T7回滚事务 

T1时刻开启了事务1;T2时刻开启了事务2;T3时刻事务1从数据库中取出了id="19880523100806"的数据;T4时刻事务2取出了同一条数据;T5时刻事务1将age字段值更新为20,T6时刻事务2更新age为25并提交了数据;但是T7事务1回滚了事务age最后的值依然为30,事务2的更新丢失了,这种情况就叫做"第一类丢失更新(lost update)"。

● 脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。

 事务1事务1
T1开启事务 
T2 开启事务
T3查询数据 age = 30 
T4更新数据 age = 20 
T5 查询数据 age = 20


在T1时刻开启了事务1,T2时刻开启了事务2,在T3时刻事务1从数据库中取出了id="19880523100806"的数据,在T4时刻事务1将age的值更新为20,但是事务还未提交,T5时刻事务2读取同一条记录,获得age的值为20,但是事务1还未提交,若在T6时刻事务1回滚了事务2的数据就是错误的数据(脏数据),这种情况叫做" 脏读(dirty read)"。

● 虚读/幻读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。

 事务1事务1
T1开启事务 
T2 开启事务
T3查询数据 age = 30 
T4 插入一条数据
T5 提交事务
T6查询数据,有两条记录 

在T1时刻开启了事务1,T2时刻开启了事务2,T3时刻事务1从数据库中查询所有记录,记录总共有一条,T4时刻事务2向数据库中插入一条记录,T5时刻事务2提交事务。T6事务1再次查询数据数据时,记录变成两条了。这种情况是"虚读(phantom read)"。


● 不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

 事务1事务1
T1开启事务 
T2 开启事务
T3查询数据 age = 30 
T4 查询数据 age = 30
T5 更新数据 age = 20
T6 提交事务
T7查询数据 age = 20 


在T1时刻开启了事务1,T2时刻开启了事务2,在T3时刻事务1从数据库中取出了id="19880523100806"的数据,此时age=30,T4时刻事务2查询同一条数据,T5事务2更新数据age=20,T6时刻事务2提交事务,T7事务1查询同一条数据,发现数据与第一次不一致。这种情况就是"不可重复读(unrepeated read)"。


● 第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。

 事务1事务1
T1开启事务 
T2 开启事务
T3更新数据 age = 25 
T4 更新数据 age = 20
T5提交事务 
T6 提交事务


在T1时刻开启了事务1,T2时刻开启了事务2,T3时刻事务1更新数据age=25,T4时刻事务2更新数据age=20,T5时刻事务1提交事务,T6时刻事务2提交事务,把事务1的更新覆盖了。这种情况就是"第二类丢失更新(second lost updates)"。

不可重复度和幻读区别:幻读的重点在于插入与删除,即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,而不可重复读重点在于修改,即第二次查询会发现查询结果比第一次查询结果不一致,即第一次结果已经不可重现了。解决不可重复读的问题只需锁住满足条件的行(行级锁),解决幻读需要锁表(表级锁,最高隔离级别Serializable

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
 

  • 数据库分为本地事务跟全局事务
  1. 本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID。
  2. 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
  • Java事务类型分为JDBC事务跟JTA事务
  1. JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理。
  2. JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务。JTA的真正强大之处在于它能够在单个事务中管理多个资源(如数据库,消息服务)。在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现。如果想让JTA管理多台数据库操作的分布式事务,需要XA支持,Open Group设计的X / Open分布式事务处理定义了一种标准通信架构。允许多个应用程序共享多个资源管理器提供的资源,并允许其工作协调到全局事务中。该XA接口使资源管理者参加事务,执行2PC(两个阶段提交),并在发生故障之后恢复。JTA + XA能够实现分布式事务2PC 。

Spring的事务管理。

        作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层(AbstractPlatformTransactionManager)。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。

根据用于管理事务的底层实现,Spring中的事务策略可以分为两个主要部分:

单连接器策略(相当于本地事务管理器) : 底层技术使用单连接器。例如,JDBC使用连接级事务、Hibernate以及JDO使用会话级事务。可以应用使用AOP和拦截器的声明式事务管理。
多连接器策略(相当于全局事务管理器) : 底层技术具有使用多个连接器的能力。当有这方面需求时,JTA是最好的选择。此策略需要启用JTA的数据源实例。JBossTS、Atomikos、Bitronix都是开源的JTA实现。

Spring既支持编程式事务管理(也称编码式事务),也支持声明式的事务管理:

编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式事务中,必须在每个业务操作中包含额外的事务管理代码

声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring AOP(AspectJ 动态代理)框架支持声明式事务管理。

        Spring并不直接管理事务,而是提供了多种事务管理器,它们将事务管理的职责委托给JTA或其他持久化机制所提供的平台相关的事务实现。每个事务管理器都会充当某一特定平台的事务实现的门面(Facade),这使得用户在Spring中使用事务时,几乎不用关注实际的事务实现是什么;Spring事务管理涉及的接口API的联系如下:

  • PlatformTransactionManager: (平台)事务管理器接口,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
  1. DataSourceTransactionManager数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于支持本地事务,包括Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理,事实上,其内部也是通过操作java.sql.Connection来开启、提交和回滚事务。
  2. JpaTransactionManager提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理;
  3. HibernateTransactionManager提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
  4. JtaTransactionManager提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器,用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server's transaction coordinator),也可以不依赖容器,配置一个本地的JTA provider
  5. OC4JjtaTransactionManagerSpring提供的对OC4J10.1.3+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  6. WebSphereUowTransactionManagerSpring提供的对WebSphere 6.0+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  7. WebLogicJtaTransactionManagerSpring提供的对WebLogic 8.1+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
  • TransactionDefinition: PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition类 ,这个类就定义了一些基本的事务属性(隔离级别、传播行为、事务超时、是否只读、回滚规则总共5个方面),事务属性描述了事务策略如何应用到方法上,声明式事务是通过事务属性来定义的。

(1)五大隔离级别

隔离级别定义了一个事务可能受其他并发事务影响的程度。在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,可是会导致脏读、幻读,不可重复读,更新丢失问题。为了解决数据库事务并发运行时的各种问题数据库系统提供四种事务隔离级别,每一个隔离级别用一个整数表示:
● Serializable 行化串(8)二进制值0001
● Repeatable Read 可重复读(4)二进制值0010 MySql默认隔离级别
● Read Commited 可读已提交(2)二进制值0100 Oracle默认级别
● Read Uncommited 可读未提交(1)二进制值1000     

每一个隔离级别可以解决的问题:

隔离级别第一类丢失更新脏读幻读不可重复读第二类丢失更新
串行化不可能不可能不可能不可能不可能
可重复读不可能不可能可能不可能不可能
可读已提交不可能不可能可能可能可能
可读未提交不可能可能可能可能

可能

Spring 中的隔离级别是通过 TransactionDefinition 接口中定义的五个表示隔离级别的常量来表示的:

  1. ISOLATION_DEFAULT: PlatfromTransactionManager默认的隔离级别,使用数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  2. ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  3. ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  4. ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  5. ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

 (2) 七大传播行为(为了解决业务层方法之间互相调用的事务问题)

         当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

AttributeDescription
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。

支持当前事务的情况:

  1. PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,Spring默认的传播行为。
  2. PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

不支持当前事务的情况:

  1. PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  2. PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  1. PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

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

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

(5) 回滚规则(定义事务回滚规则)
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
 

  • TransactionStatus: 接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

TransactionStatus接口接口内容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
} 

    spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。最后,需要特别强调的是,spring的事务是和线程相关的,事务管理是通过ThreadLocal和线程绑定的,在Service类中使用多线程请务必确保多线程中的代码受事务管理器控制,比如在Service中开启多线程调用本Service类中的方法就不受Spring事务控制,另外,多线程不能复用原来的Connection,极容易耗尽数据库连接池资源导致系统异常,使用时务必注意!


MySql的隔离级别和锁的关系

https://www.cnblogs.com/zsychanpin/p/7395635.html

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值