Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

事务学习 ( 一 ) 之基本概念

2017-06-05
nivelle

事物

定义:以可控的方式对数据资源进行访问的一组操作。为了保证事物执行前后数据资源所承载的系统始终处于正确状态,事物持有四个特性来实现这个目的:

  • 原子性:事物所包含的全部操作是一个不可分割的整体,这些操作要么全部提交成功,要么只要其中的一个失败,就全部失败。
  • 一致性:事物所包含的操作不能违反数据资源的一致性检查,数据资源在事物执行前处于某个数据一致性状态,那么事物执行之后依然需要保持数据间的一致性状态。
  • 隔离性:事物的隔离性主要规定了各个事物之间相互影响的程度,隔离性的概念主要主要面对数据资源的并发访问,并兼顾影响事物的一致性。当两个事物或者更多事物同时访问同一个数据资源的时候,不同的隔离级别决定了各个事物对该数据资源访问的不同行为。4种类型的隔离级别:

    • Read Uncommitted:最低隔离级别。它最直接的效果就是 ,一个事物可以读取另一个事物没有提交的更新结果。Read Uncommitted是以较低的隔离度来寻求较高的性能,其本身面临以下问题:

    (1)脏读:如果一个事物对数据进行了更新,但事物还没提交,另一事物就可以看到该事物没有提交的更新结果。如果第一个事物回滚,那么第二个事物在此之前读到的数据就是一笔脏数据。

    (2)不可重复读:不可重复读指的是同一个事物在整个事物过程中对同一笔数据进行读取,每次读取的结果都不同。如果事物1在事物2的更新操作之前读过一次数据,在事物2的更新操作之后再读取同一笔数据一次,两次结果是不同的。

    (3)幻读:指的是同样一个查询在整个事物过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下,不管事物2的插入操作是否提交,事物1在插入操作之前和之后执行相同的查询,取得的结果集是不一样的。

    • Read Committed:大部分数据库采取的默认隔离级别,他比Read Uncommitted隔离级别拥有更高级别的限定。一个事物的更新操作结果只有在该事物提交之后,另一个事物才能读到同一笔数据更新后的结果。所以它可以避免脏读问题。
    • Repeatable Read:它保证在事物的整个过程中,对同一笔数据的读取结果是相同的,不管其他事物是否同时在对同一笔数据进行更新,也不管其他事物对同一笔数据的更新提交与否。它能够避免脏读和不可重复读,但是无法避免幻读。
    • Serializable:它是最严格的隔离级别。所有事物都一次执行,可以避免其他隔离级别遇到的所有问题,是最安全的隔离级别。但是我们通常会使用其他隔离级别加上相应的并发锁的机制来控制对数据的访问,这样能保证系统的性能损失不会太大,也能够在一定程度山保证数据一致性。
  • 持久性:一旦整个事物操作成功提交,对数据所做的变更将被记载并不可逆转。

事物中的概念

  • Resource Manager:简称RM.负责存储并管理系统数据资源的状态,比如数据库服务器,JMS消息服务器等。
  • Transaction Processing Monitor:它负责在分布式事物场景中协调包含多个RM的事物处理。J2EE规范中的应用服务器(Application Server)担当的就是这个角色。
  • Transaction Manager:简称TM,它可以使TP Monitor中的核心模块,直接负责多RM之间事物处理的协调工作,并提供事物界定(Transaction Demarcation)、事物上下文传播(Transaction Context Propagation)等功能接口。
  • Application:以独立形式存在的或者运行于容器中的应用程序,可以认为是事物边界的触发点。实际上,并非每个事物的场景中都会出现以上提到的所有参与者,如果根据整个事物中涉及的RM的多寡来区分事物的话,可分为两类:全局事物,局部事物。

(1)全局事物:如果整个事物处理过程中由多个RM参与,那么就需要引入TP Monitor来协调多个RM之间的事物处理处理。TP Monitor将采用两阶段提交(Two-Phase Commit)协议来保证整个事物的ACID属性。这种场景下的事物,就称为全局事物或者分布式事物。

(2)局部事物:如果当前事物只有一个RM参与其中,我们就可以称当前事物为局部事物。比如在当前事物中只对一个数据库更新,或者只向一个消息队列中发送消息的情况,都属于局部事物。

Spring事物框架

Spring 事物框架设计理念基本原则是:让事物管理的关注点与数据访问关注点分离。

  • 当在业务层使用事物的抽象API进行事物界定的时候,不需要关心事物将要加诸于上的事物 资源是什么,对不同的事物资源的管理将由相应的框架实现类来操心。
  • 当在数据访问层对可能参与事物的数据资源进行访问时,只需要使用相应的数据访问API进行数据访问,不需要关心当前的事物资源如何参与事物或者是否需要参与事务。这同样将由事物框架类来打理。

以上两个关注点被清晰地分离出来之后,对于开发人员来说,唯一需要关心的,就是通过抽象后的事物管理API对当前事物进行界定而已:

public class FooService
{
     private PlatformTransactionManager transactionManager;
     
     public void serviceMethod()
     {
         TransactionDefinition definition =...;
         
         TransactionStatus txStatus = getTransactionManager().getTransaction(definition);
         try
         {
             //dao1.doDataAccess();
             //dao2.doDataAccess();
         }catch(DataAccessException e)
         {
            getTransactionManager.rollback(txStatus) ;
            throw e;
         }
         getTransactionManager().commit(txStatus);
     }
     
     public PlatformTransactionManager getTransactionManager(){
         return transactionManager;
     }
     
     public void setTransactionmanager(PlatformTransactionManager transactionManager)
     {
         this.transactionmanager=transactionmanager;
     }
}

spring 事物核心接口
  • PlatformTransactionManager:为应用程序提供事物界定的统一方式,是整个事物抽象策略的顶层接口。
public interface PlatformTransactionManager{
    TransactionStatus getTransaction(TransactionDefinition definition) throws Transaction Exception;
    
    void commit(TransactionStatus status) throws TransactionException;
    
    void roolback(TransactionStatus) throws TransactionException;
}

spring 的事物框架针对不同的数据访问方式以及全局事物场景,提供相应的PlafformTransactionManager实现类。例如JDBC数据访问方式的局部事务为例,我们通常将事物管理层放在Service层,而将数据访问逻辑放在DAO层。

因为JDBC的局部事务控制是同一个java.sql.Connection来完成的,所以保证两个DAO的数据访问方法处于同一个事务中,我们就得保证他们使用的是同一个java.sql.Connection.要做到这一点,通常会采用称为connection-passing的方式,即为同一个事务中的各个dao的数据访问方法传递当前事务对应的同一个java.sql.Connection.这样,我们的业务方法以及数据访问方法都得做一定修改:

普通情况下的事物管理代码:

public void serviceMetod()
{
Object txObject = transactionManaget.beginTransaction();
...
dao1.doDataAccess();
dao2.doDataAccess();
...
transactionManager.commitTransaction(txObjct);
}

image

要传递java.sql.Connection,我们可以将整个事物对应的java.sql.Connection实例放到统一的一个地方去,无论是谁,要使用该资源,都从这个地方来获取,这样就解除了事务管理代码和数据访问代码直接通过java.sql.Connection的直接耦合。具体点儿说,我们在事务开始之前取得一个java.sql.Connection,然后将这个Connection绑定到当前的调用线程。之后,数据访问对象在使用Connection进行数据访问的时候,就可以从当前线程上获得这个事务开始的时候绑定的Connection实例。当所有数据访问对象全部 使用这个绑定到当前线程的Connection完成了数据访问工作时,我们就使用这个Connection实例提交或者回滚事务,然后解除它到当前线程的绑定。

Spring事务抽象的三个接口 PlatformTransactionManager、TransactionDefinition以及TransactionStatus
  • TransactionDefinition: 负责定义事务相关属性,包括隔离级别、传播行为。

    • 隔离级别 1.ISLATION_DEFAULT对应数据库默认隔离级别,通常情况下为:Read Committed 2.ISLATION_READ_UNCOMMITTED;对应Read Uncommitted隔离级别 3.ISOLATION_READ_COMMITTED:对应Read Committed隔离级别 4.ISOLATION_REPEATABLE_READ:对应Repeatable read隔离级别

 - 传播行为: 表示整个事务处理过程所跨的业务对象,将以什么样的行为参与事务。

1.PROPAGATION_REQUIRED:如果当前存在一个事物,则加入当前事物。如果当前不存在事务,则创建一个新的事物。保证在一个事物中运行。通常作为默认的事物传播特性。

2.PROPAGATION_REQUIRES_NEW:不管当前是否存在事务,都会创建新的事物。如果当前存在事务,会将当前事务挂起。适用于某个业务对象所做的事情不想影响到外层事物的情形。比如假设当前的业务方法需要向数据库中更新某些日志信息,但即使这些日志信息更新失败,我们也不想因为该业务方法的事务回滚,而影响到外层事物的成功提交。

3.PROPAGATION_SUPPORTS:如果当前存在一个事物,则加入当前事物。如果当前不存在事务,则直接执行。尤其适用一些查询方法。如果当前方法直接执行,那么不需要事物的支持。如果当前方法被其他方法调用,而其他方法启动了一个事务,使用PROPAGATION_SUPPORTS可以保证当前方法能够加入当前事物,并洞察当前事物对数据资源所做的更新。例如:A.service()会首先更新数据库,然后和调用B.service()进行查询,那么,如果B.service()是PROPAGATION_SUPPORTS的传播行为,就可以读取A.service()之前所做的最新更新结果。

4.PROPAGATION_MANDATORY:强制要求当前存在一个事物,如果不存在,则抛出异常。如果某个方法需要事物支持,但是自身又不管理事物提交或者回滚,那么比较适合这种传播行为。

5.PROPAGATION_NOT_SUPPORTED:不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务的话,当前事务原则上将被挂起,但这要看对应的PlatformTransactionManager实现类是否支持事物的挂起。

6.PROPAGATION_NEVER:永远不需要当前事物,如果存在当前事务,则抛出异常。

7.PROPAGATION_NESTED:如果存在当前事物,则在当前事物的给嵌套事物中执行,否则与PROPAGATION_REQUIRED的行为类型,即创建新的事物,在新创建的事物中执行。

![image](http://7xpuj1.com1.z0.glb.clouddn.com/%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA.png)
  • 事务的超时时间(Timeout)

  • 是否只读(ReadOnly)

TransactionDefinition相关实现:DefaultTransactionDefinition是TransactionDefinition接口的默认实现类,它提供了各事务属性的默认值,并通过它的setter方法,我们可以更改这些默认设置。

  • PlatformTransactionManager: 将参照TransactionDefinition的属性定义来开启相关事务

  • TransactionStatus:事务开启之后到事务结束的状态,对事务进行有限的控制

    1.propagationBehavior=PROPAGATION_REQUIRED

    2.isolationlevel = ISOLATION_DEFAULT

    3.timeout = TIMEOUT_DEFAULT

    4.readOnly = false

image

TransactionTemplate是Spring提供的进行编程式事务管理的模板方法类,它直接继承了DefaultTransactionDefinition。

TansationAttribute是继承自TransactionDefinition的接口定义,主要面向使用Spring AOP进行申明式事务管理场合。他在TransactionDefinition定义的基础上添加了一个rollbackOn方法。实现了当异常类型是unchecked exception的情况下将事务回滚。

TransactionDefinition 有两个实现类,RuleBasedTransactionAttribute和DelegatingTransactionAttribute。RuleBasedTransactionAttribute允许我们同时制定多个回滚规则,这些规则以包含org.springframwork.transaction.interceptor.RollbackRuleAttribute或者org.springframewok.transaction.interceptor.NoRollbackRuleAttribute的List形式提供。RuleBaseTransactionAttribute的rollbackOn将使用传入的异常类型与这些回滚规则进行匹配,然后再决定是否要回滚事务。

  • TransationcStatus

定义:表示整个事物处理过程中的事物状态,更多时候,我们在编程式事务中使用该接口。

  • 使用TransactionStatus提供的相应方法查询事物状态

  • 通过setRollbackOnly()方法标记当前事物以使其回滚

  • 如果相应的PlatformTransactionManager支持Savepoint,可以通过TransactionStatus在当前事物中创建内部嵌套事物。

image

Spring事务框架内的各个TransactionManager的实现,大都借助于DefaultTransactionStatus来记载事务状态信息。

  • PlatformTransactionManager

    PlatformTransactionManager 的整个抽象体系基于Strategy模式,由PlatformTransactionManager对事务界定进行统一抽象,而具体的界定策略的实现则交由具体的实现类。

    (1)面向局部事物的PlatformTransactionManager实现类

    image

    有了这些实现类,我们在使用Spring的事物抽象框架进行事物管理的时候,只需要跟进当前使用的数据访问技术,选择对应的PlatformTransactionManager实现类即可。

    (2)面向全局事物的PlatformTranactionManager实现类

    org.springframework.ransaction.jta.JtaTransactionManager是Spring提供的支持分布式事物的PlatformTransac-tionManager实现。JtaTransactionManager对各种JTA实现提供的分布式事务支持进行统一封装,只不过它的事务管理操作,最终都会委派给具体的JTAs实现来完成。

PlatformTransactionManager 的实现类DataSourceTransactionManager

基本概念:

  • transaction object: 承载了当前事务的必要信息,PlatformTransactionManager实现类可以根据transaction object所提供的信息来决定如何处理当前事物。它类似于JTA规范中的javax.transaction.Transaction定义。

  • TransactionSynchronization: 它是可以注册到事务处理过程中的回调接口。它就像事物处理的事件监听器,当事物处理的某些规定时点发生时,会调用TransactionSynchronization上的一些方法来执行相应的回调逻辑,如在事物完成后清理相应的系统资源等操作。Spring事物抽象框架所定义的TransactionSynchronization类似于JTA规范的javax.transaction.Synchronization,但比JTA的Synchronization提供了更多回调方法,允许我们对事务的处理添加更多的回调逻辑。

  • TransactionSynchronizationManager:类似于JTA规范中的javax.transaction.TransactionSynchronizationRegistry,我们通过TransactionSynchronizationManager来管理TransactionSynchronization,当前事务状态以及具体的事务资源。具体的事务资源,比如java.sql.Connection或者Hibernate.Session绑定到线程,TransactionSynchronizationManager就是这些资源绑定的目的地。

org.springframework.transaction.support.AbstractPlatformTransactionManager作为DataSourceTransactionManager的父类,以模板方法的形式封装了固定的事务处理逻辑,而只将与事务资源相关的操作以protected或者abstract方法的形式留给DataSourceTransactionManager来实现。作为模板方法父类,AbstractPlatformTransactionManager替各子类实现了一下固定的事务内部处理逻辑:

  • 判定是否存在当前事务,然后根据判断结果执行不同的处理逻辑

  • 结合是否存在当前事务的情况,根据TransactionDefinition中只对的传播行为的不同语义执行后续逻辑

  • 根据情况挂起或者恢复事务

  • 提交事务之前坚持readOnly字段是否被设置,如果是的话,以事务回滚的代替事务的提交

  • 在事物回滚的情况下,清理并恢复事务状态

  • 如果事务的Synchonization处于active状态,在事务处理的规定时点出发注册的Synchonization回调接口

模板方法:

  • getTransaction(TransactonDefinition)开始。getTransaction(TransactionDefinition)的主要目的是开启一个事务,但需要在此之前判断一下之前是否存在一个事务。如果存在,则根据TransactionDefinition中的传播行为决定是挂起当前事务还是抛出异常。同样的,不存在事务的情况下,也需要根据传播行为的具体语义来决定如何处理。

该方法的处理逻辑,基本上按照下面的流程执行:

(1) 获取transaction object以判断是否存在当前事务。

Object transaction = doGetTransaction();

  • DataSourceTransactionManager会返回DataSourceTransacionManager.DataSourceTransactionObject类型实例,HibernateTransactionManager会返回HibernateTransactionManager.HibernateTransactionObject类型的实例。

  • doGetTransaction()是getTransaction(TransactionDefinition)模板方法公开给子类实现的abstract类型方法。DataSourceTransactionManager在实现doGetTransaction()方法逻辑时,会从TransactionsynchronizationManager获取绑定的资源,然后添加到DataSourceTransactionObejct之后返回。

(2)获取Log类的debug信息,避免之后代码重复

boolean debugEnabled = logger.isDebugEnabled();
if(debugEnabled){
  logger.debug("Using tansaction obejct["+transaction+"]");
}

(3)检查TransactionDefinition参数合法性。

if(definition==null){
  definition = new DefaultTransactionDefinition();
}

(4)根据先前获得的transaction object判断是否存在当前事务,根据判定结果采取不同的处理方式,如下

if(isExistingTransaction(transaction)){
  return handleExistingTransaction(definition,transaction,debugEnabled);
}

默认情况下isExistingTransaction返回false,该方法需要具体子类根据情况进行覆写。对于DatasourceTransactionManager来说,它会根据传入的Transaction所记载的信息进行判断:

DatasourceTransactionObject txObject = (DatasourceTransactionObject) transaction;
return (txObject.getConnectionHolder()!=null&&txObejct.getConnectionHolder().isTransactionActive())

对于HibernateTransactionManager来说,则会将transaction强制转型为HibernateTransactionObject,然后根据HibernateTransactionObject所记载的信息来判断之前是否存在一个事务,其他具体实现类对isExistingTransaction的处理也是如此。

不管isExistingTransaction(transaction)返回结果如何,下面处理主体上都是以TransactionDefinition中的传播行为为中心进行的。比如同样是PROPAGATION_REQUIRED,在存在当前事务与不存在当前事务两种情况下的处理是不同的,前者会使用之前的事物,后者则会创建新的事物,其他的传播行为的处理也是按照不同的场景分别处理。

(a) 如果isExistingTransaction(transaction)方法返回true,即存在当前事物情况下,由handleExistingTransaction()方法处理存在当前事务,应该如何创建事务对应的Transactionstatus实例并返回。

如果definition定义的传播行为是PROPAGATION_NEVER,抛出异常并退出。如下:

if(definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER){
  throw new IllegalTransactionstateException(
    "Existing transaction found for transaction marked with prpagation 'never'"
  );
}

如果definition定义的传播行为是PROPAGATION_NOT_SUPPORTED,则挂起当前事物,然后返回。如下:

if(definition.getPropagationBehavior()==TransactionDefiniton.PROPAGATION_NOT_SUPPORT)
{
if(debugEnabled){
  logger.dubug("suspending current transaction");
 }
 Obejct suspendedResources = suspend(transaction);
 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
 return newTransactionStatus(definition ,null,false,newSynchronization,debugEnabled,suspendedResources);
}


newTransactionStatus()方法返回一个DefaultTransactionStatus实例,因为我们挂起了当前事务。而PROPAGATION_NOT_SUPPORTED不需要事务,所以,返回的DefaultTransactionStatus不包含transaction object的信息。

如果definition定义的传播行为是PROPAGATION_REQUIRES_NEW,则同样挂起当前事务,并开始一个新的事务并返回。如下:

image

AbstractPlatformTransactionManager首先将当前事务挂起,然后调用doBegin()方法开始新的事务。如果开始事务过程中出现异常,那么恢复之前挂起的事物。doBegin(transaction,definition)方法会首先检查传入的transaction,以提取必要信息判断之前是否存在绑定的connection信息。如果没有,则从DataSource中获取新的connection,然后将其AutoCommit状态改为false,并绑定到TransactionSynchronizationManager。当然,这期间也会涉及事物定义的应用以及条件检查等逻辑。在所有一切搞定之后,newTransactionStatus会创建一个包含definition、transaction object以及挂起的事物信息和其他状态信息的DefaultTransactionStatus实例并返回。


内容来自《Spring揭秘》 作者:王福强


评论