Spring的编程式事务管理和声明式事务管理

  事务管理对于企业应用特别是金融产品而言至关重要,它确保用户的每一次数据操作都是可靠的,即便出现了异常情况,也不至于破坏后台数据的完整性。

在Spring中,事务是通过TransactionDefinition接口来定义的:

public interface TransactionDefinition {
    //事务的传播行为
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    //事务的隔离级别
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    //获取属性的方法(事务属性的设置完全由程序员控制)
    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    String getName();
}

事务的隔离级别
在数据库操作中,为了有效保证并发读取数据的正确性,而提出的事务隔离级别。
并发读取数据时,可能会发生以下几种情况:
·脏读:当一个事务正在访问数据库,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
·不可重复读:在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,
·幻读:当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。

隔离级别
ISOLATION_DEFAULT :默认值,表示使用底层数据库的默认隔离级别。对于大多数主流数据库而言,通常是ISOLATION_READ_UNCOMMITTED。
ISOLATION_READ_UNCOMMITTED :表示一个事务可以读取另一个事务修改但还没有提交的数据。不能防止脏读和不可重复读。
ISOLATION_READ_COMMITTED :表示一个事务只能读取另一个事务已经提交的数据。可以防止脏读,大多数主流数据库的默认事务隔离级别。
ISOLATION_REPEATABLE_READ :表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的数据也会被忽略。可以防止脏读和不可重复读,但是带来了更多的性能损失。
ISOLATION_SERIALIZABLE :表示所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。可以防止脏读、不可重复读和幻读,但是严重影响程序的性能。

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

事务超时

一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。

事务的只读属性

对事务性资源(指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等)进行只读操作以提高事务处理的性能。

事务的回滚规则

通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。但是,也可以人为控制事务在抛出某些未检查异常时仍然提交事务,或者在抛出某些已检查异常时回滚事务。

Spring事务管理API

所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。

“给定的事务规则”-->TransactionDefinition

“执行提交或者回滚操作”-->PlatformTransaction

事务运行时的状态-->TransactionStatus

TransactionDefinition

该接口开篇已经介绍过,用于定义一个事务。它包含了事务的静态属性。Spring提供了一个默认的实现DefaultTransactionDefinition,该类适用于大多数情况,如果它不能满足需求,可以通过TransactionDefinition接口来实现自己的事务定义。

PlatformTransactionManager

该接口用于执行具体的事务操作。

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
    void commit(TransactionStatus var1) throws TransactionException;
    void rollback(TransactionStatus var1) throws TransactionException;
}

根据底层所使用的不同的持久化API或框架,PlatformTransactionManager的主要实现类大致如下:

DataSourceTransactionManager:适用于JDBC和iBatis

HibernateTractionManager:适用于Hibernate

JpaTransactionManager:适用于JPA

这些事务管理器在定义时需要提供底层的数据源作为其属性:

DataSourceTransactionManager-->DataSource

HibernateTractionManager-->SessionFactory

JpaTransactionManager-->EntityManagerFactory

TransactionStatus

platformTransactionManager.getTransaction(transactionDefinition)方法返回一个TransactionStatus对象。返回的TransactionStatus对象可能代表一个新的或者已经存在的事务。TransactionStatus接口提供了一个简单的控制事务执行和查看事务状态的方法。

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}

Spring编程式事务管理

通过 Spring 提供的事务管理 API,我们可以在代码中灵活控制事务的执行。

基于底层API的事务管理

示例代码:

public class BankServiceImpl implements BankService {
  private BankDao bankDao;
  private TransactionDefinition txDefinition;
  private PlatformTransactionManager txManager;
  public boolean transfer(Long fromId, Long toId, double amount) {
    TransactionStatus txStatus = txManager.getTransaction(txDefinition);
    boolean result = false;
    Try {
      result = bankDao.transfer(fromId, toId, amount);
      txManager.commit(txStatus);
    } catch (Exception e) {
      result = false;
      txManager.rollback(txStatus);
    }
    return result;
  }
}

示例配置:


  
  
  
    
      
    
  

如果方法需要实施事务管理,首先需要在方法执行前启动一个事务,调用PlatformTransactionManager的getTransaction(transactionDefinition)方法,启动事务后,便可以编写业务逻辑代码,然后在适当地方执行事务的提交或者回滚操作。

基于TransactionTemplate的事务管理

上个示例中事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。幸好,Spring 也意识到了这点,并提供了简化的方法,这就是 Spring 在数据访问层非常常见的模板回调模式。

示例代码:

public class BankServiceImpl implements BankService {
  private BankDao bankDao;
  private TransactionTemplate transactionTemplate;
  public boolean transfer(final Long fromId, final Long toId, final double amount) {
    return (Boolean) transactionTemplate.execute(new TransactionCallback(){
      public Object doInTransaction(TransactionStatus status) {
        Object result;
        try {
          result = bankDao.transfer(fromId, toId, amount);
        } catch (Exception e) {
          status.setRollbackOnly();
          result = false;
        }
        return result;
      }
   });
  }
}

示例配置:


  
  

TransactionTemplate 的 execute()方法有一个 TransactionCallback类型的参数,该接口中定义了一个 doInTransaction()方法,通常以匿名内部类的方式实现TransactionCallback接口,并在其 doInTransaction()方法中编写业务逻辑代码。这里可以使用默认的事务提交和回滚规则,这样在业务代码中就不需要显式调用任何事务管理的 API。doInTransaction()方法有一个TransactionStatus类型的参数,可以在方法的任何位置调用该参数的 setRollbackOnly()方法将事务标识为回滚的,以执行事务回滚。

根据默认规则,如果在执行回调方法的过程中抛出了Runtime异常,或者显式调用了TransacationStatus.setRollbackOnly()方法,则回滚事务;如果事务执行完成或者抛出了Checked类型的异常,则提交事务。

Spring声明式事务管理

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

声明式事务最大的优势就是不需要通过编程的方法管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中做相关的事务规则说明(或通过等价的基于注解的方式),便可以将事务规则应用到业务逻辑中。

建议在开发中使用声明式事务,不仅因为它简单,更主要因为这样使得纯业务代码不被污染,极大方便后期的代码维护。

与编程式事务相比,声明式事务唯一不足的地方是,后者的最细颗粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

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

Spring2.x引入了命名空间,结合命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于命名空间的切点表达式支持,声明式事务也变得更加强大。

示例配置:


  


  


  
  

由于使用了切点表达式,就不需要针对每一个业务类创建一个代理对象了。

基于@Transactional命名空间的声明式事务管理

示例代码:

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId,Long toId,double amount) {
  return bankDao.transfer(fromId,toId,amount);
}

示例配置:

虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是Spring小组建议不要在接口或者接口方法上使用该注解,只有在使用基于接口的代理时它才会生效。另外,@Transactional注解只被应用到public方法上。

基于命名空间和基于@Transactional的事务声明方式各有优缺点。基于的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于@Transactional的方式必须在每个需要使用事务的方法或者类上用@Transactional注解。尽管可能大多数事务的规则是一致的,但是对@Transactional而言,无法重用,必须逐个指定。







你可能感兴趣的:(框架)