博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring - Java/J2EE Application Framework 应用框架 第 7 章 事务管理
阅读量:2119 次
发布时间:2019-04-30

本文共 12302 字,大约阅读时间需要 41 分钟。

第 7 章 事务管理

7.1. Spring事务抽象

Spring提供了一致的事务管理抽象。这个抽象是Spring最重要的抽象之一, 它有如下的优点:

  • 为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、iBATIS数据库层 和JDO

  • 提供比大多数事务API更简单的,易于使用的编程式事务管理API

  • 整合Spring数据访问抽象

  • 支持Spring声明式事务管理

传统上,J2EE开发者有两个事务管理的选择: 全局事务或 局部事务。全局事务由应用服务器管理,使用JTA。局部 事务是和资源相关的:例如,一个和JDBC连接关联的事务。这个选择有深刻的含义。 全局事务可以用于多个事务性的资源(需要指出的是多数应用使用单一事务性 的资源)。使用局部事务,应用服务器不需要参与事务管理,并且不能帮助确保 跨越多个资源的事务的正确性。

全局事务有一个显著的不利方面,代码需要使用JTA:一个笨重的API(部分是 因为它的异常模型)。此外,JTA的UserTransaction通常需 要从JNDI获得,这意味着我为了JTA需要同时使用JNDI和JTA。 显然全部使用全局事务限制了应用代码的重用性,因为JTA通常只在应用服 务器的环境中才能使用。

使用全局事务的比较好的方法是通过EJB的CMT (容器管理的事务): 声明式事务管理的一种形式(区别于编程式事务管理 )。EJB的CMT不需要任何和事务相关的JNDI查找,虽然使用EJB本身 肯定需要使用JNDI。它消除大多数——不是全部——书写Java代码控制事务的需求。 显著的缺点是CMT绑定在JTA和应用服务器环境上,并且只有我们选择 使用EJB实现业务逻辑,或者至少处于一个事务化EJB的外观(Facade)后 才能使用它。EJB有如此多的诟病,当存在其它声明式事务管理时, EJB不是一个吸引人的建议。

局部事务容易使用,但也有明显的缺点:它们不能用于多个事务性资 源,并且趋向侵入的编程模型。例如,使用JDBC连接事务管理的代码不能用于 全局的JTA事务中。

Spring解决了这些问题。它使应用开发者能够使用在任何环境 下使用一致的编程模型。你可以只写一次你的代码,这在不同环境 下的不同事务管理策略中很有益处。Spring同时提供声明式和编程式事务管理。

使用编程式事务管理,开发者直接使用Spring事务抽象,这个抽象可以使用在任何 底层事务基础之上。使用首选的声明式模型,开发者通常书写很少的事务相关代 码,因此不依赖Spring或任何其他事务API。

7.2. 事务策略

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;}

这首先是一个SPI接口,虽然它也可以在编码中使用。注意按照Spring的哲学, 这是一个接口。因而如果需要它可以很容易地被模拟和 桩化。它也没有和一个查找策略如JNDI捆绑在一起:PlatformTransactionManager 的实现定义和其他Spring IoC容器中的对象一样。这个好处使得即使使用JTA,也 是一个很有价值的抽象:事务代码可以比直接使用JTA更加容易测试。

继续Spring哲学,TransactionException是unchecked的。 低层的事务失败几乎都是致命。很少情况下应用程序代码可以从它们 中恢复,不过应用开发者依然可以捕获并处理 TransactionException

getTransaction()根据一个类型为 TransactionDefinition的参数返回一个 TransactionStatus对象。返回的 TransactionStatus对象可能代表一个新的或已经存在的事 务(如果在当前调用堆栈有一个符合条件的事务)。

如同J2EE事务上下文一样,一个TransactionStatus也是和执 行的线程关联的。

TransactionDefinition接口指定:

  • 事务隔离:当前事务和其它事务的隔离的程度。 例如,这个事务能否看到其他事务未提交的写数据?

  • 事务传播:通常在一个事务中执行的 所有代码都会在这个事务中运行。但是,如果一个事务上下文已经存在, 有几个选项可以指定一个事务性方法的执行行为:例如,简单地在现有的 事务中运行(大多数情况);或者挂起现有事务,创建一个新的事务。 Spring提供EJB CMT中熟悉的事务传播选项。

  • 事务超时: 事务在超时前能运行多 久(自动被底层的事务基础设施回滚)。

  • 只读状态: 只读事务不修改任何数 据。只读事务在某些情况下(例如当使用Hibernate时)可可是一种非常有用的优化。

这些设置反映了标准概念。如果需要,请查阅讨论事务隔离层次和其他核心事 务概念的资源:理解这些概念在使用Spring和其他事务管理解决方案时是非常关键的。

TransactionStatus接口为处理事务的代码提供一个简单 的控制事务执行和查询事务状态的方法。这个概念应该是熟悉的,因为它们在所 有的事务API中是相同的:

public interface TransactionStatus {    boolean isNewTransaction();    void setRollbackOnly();    boolean isRollbackOnly();}

但是使用Spring事务管理时,定义 PlatformTransactionManager的实现是基本方式。在好的Spring 风格中,这个重要定义使用IoC实现。

PlatformTransactionManager实现通常需要了解它们工作 的环境:JDBC、JTA、Hibernate等等。

下面来自Spring范例jPetstore中的 dataAccessContext-local.xml,它展示了一个局部 PlatformTransactionManager实现是如何定义的。它将和JDBC一起工作。

我们必须定义JDBC数据源,然后使用DataSourceTransactionManager,提供给 它的一个数据源引用。

${jdbc.driverClassName}
${jdbc.url}
${jdbc.username}
${jdbc.password}

PlatformTransactionManager定义如下:

如果我们使用JTA,如同范例中dataAccessContext-jta.xml, 我们需要使用通过JNDI获得的容器数据源,和一个JtaTransactionManager实 现。JtaTransactionManager不需要知道数据源,或任何其他特定资源,因为它将 使用容器的全局事务管理。

jdbc/jpetstore

我们可以很容易地使用Hibernate局部事务,如同下面Spring的PetClinic 示例应用中的例子的一样。

在这种情况下,我们需要定义一个Hibernate的LocalSessionFactory,应用程 序将使用它获得Hibernate的会话。

数据源bean定义和上面例子类似,这里不再罗列(如果这是容器数据源,它应该是非事务的,因为Spring会管理事务, 而不是容器)。

这种情况下,“transactionManager” bean的类型是HibernateTransactionManager。 和DataSourceTransactionManager拥有一个数据源的引用一样, HibernateTransactionManager需要一个SessionFactory的引用。

org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml
${hibernate.dialect}

使用Hibernate和JTA事务,我们可以简单地使用JtaTransactionManager, 就象JDBC或任何其他资源策略一样。

注意任何资源的JTA配置都是这样的,因为它们都是全局事务。

在所有这些情况下,应用程序代码不需要任何更改。我们可以仅仅更改配置 来更改管理事务的方式,即使这些更改意味这从局部事务转换到全局事务或者相反 的转换。

如果不使用全局事务,你需要采用一个特定的编码规范。幸运的是它非常简单 。你需要以一个特殊的方式获得连接资源或者会话资源,允许相关的 PlatformTransactionManager实现跟踪连接的使用,并且当需要时应用事务管理。

例如,如果使用JDBC,你不应该调用一个数据源的 getConnection()方法,而必须使用Spring的 org.springframework.jdbc.datasource.DataSourceUtils类,如下:

Connection conn = DataSourceUtils.getConnection(dataSource);

这将提供额外的好处,任何SQLException都被Spring的 CannotGetJdbcConnectionException封装起来,它属于Spring的unchecked 的DataAccessException的类层次。这给你比 简单地从SQLException获得更多的信息,并且确保跨数据 库,甚至跨越不同持久化技术的可移植性。

没有Spring事务管理的情况下,这也能很好地工作,因此无论使用 Spring事务管理与否,你都可以使用它。

当然,一旦你使用Spring的JDBC支持或Hibernate支持,你将不想使用 DataSourceUtils或其他帮助类,因为与直接使用相关API相比,你将更乐意使用Spring的抽象。 例如,如果你使用Spring的JdbcTemplate或jdbc.object包来简化使用JDBC, 正确的数据库连接将自动取得,你不需要书写任何特殊代码。

7.3. 编程式事务管理

Spring提供两种方式的编程式事务管理

  • 使用TransactionTemplate

  • 直接使用一个PlatformTransactionManager实现

我们通常推荐使用第一种方式。

第二种方式类似使用JTA UserTransaction API (虽然异常处理少一点麻烦)。

7.3.1. 使用TransactionTemplate

TransactionTemplate采用和其他Spring模板 ,如JdbcTemplate和 HibernateTemplate,一样的方法。它使用回调方法,把应用 程序代码从处理取得和释放资源中解脱出来(不再有try/catch/finally)。如同 其他模板,TransactionTemplate是线程安全的。

必须在事务上下文中执行的应用代码看起来像这样,注意使用 TransactionCallback可以返回一个值:

Object result = tt.execute(new TransactionCallback() {    public Object doInTransaction(TransactionStatus status) {        updateOperation1();        return resultOfUpdateOperation2();    }});

如果没有返回值,使用TransactionCallbackWithoutResult, 如下:

tt.execute(new TransactionCallbackWithoutResult() {    protected void doInTransactionWithoutResult(TransactionStatus status) {        updateOperation1();        updateOperation2();    }});

回调中的代码可以调用TransactionStatus对象的 setRollbackOnly()方法来回滚事务。

想要使用TransactionTemplate的应用类必须能访问一 个PlatformTransactionManager:通常通过一个JavaBean属 性或构造函数参数暴露出来。

使用模拟或桩化的PlatformTransactionManager,单元测试 这些类很简单。没有JNDI查找和静态魔术代码:它只是一个简单的接口。和平常一样, 你可以使用Spring简化单元测试。

7.3.2. 使用PlatformTransactionManager

你也可以使用 org.springframework.transaction.PlatformTransactionManager 直接管理事务。简单地通过一个bean引用给你的bean传递一个你使用的 PlatformTransactionManager实现。然后, 使用TransactionDefinition和 TransactionStatus对象就可以发起事务,回滚和提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition()def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = transactionManager.getTransactionDefinition(def);try {    // execute your business logic here} catch (MyException ex) {    transactionManager.rollback(status);    throw ex;}transactionManager.commit(status);

7.4. 声明式事务管理

Spring也提供了声明式事务管理。这是通过Spring AOP实现的。

大多数Spring用户选择声明式事务管理。这是最少影响应用代码的选择, 因而这是和非侵入性的轻量级容器的观念是一致的。

从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。 它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上 下文调用setRollbackOnly()方法。不同之处如下:

  • 不象EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。 只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作

  • Spring可以使声明式事务管理应用到普通Java对象,不仅仅是特殊的类,如EJB

  • Spring提供声明式回滚规则:EJB没有对应的特性, 我们将在下面讨论这个特性。回滚可以声明式控制,不仅仅是编程式的

  • Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务 回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用 EJB CMT,除了使用setRollbackOnly(),你没有办法能 够影响容器的事务管理

  • Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如 果你需要这些特性,我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我 们并不希望事务跨越远程调用

回滚规则的概念是很重要的:它们使得我们可以指定哪些异常应该发起自 动回滚。我们在配置文件中,而不是Java代码中,以声明的方式指定。因此,虽然我们仍 然可以编程调用TransactionStatus对象的 setRollbackOnly()方法来回滚当前事务,多数时候我们可以 指定规则,如MyApplicationException应该导致回滚。 这有显著的优点,业务对象不需要依赖事务基础设施。例如,它们通常不需要引 入任何Spring API,事务或其他任何东西。

EJB的默认行为是遇到系统异常(通常是运行时异常), EJB容器自动回滚事务。EJB CMT遇到应用程序异常 (除了java.rmi.RemoteException外的checked异常)时不 会自动回滚事务。虽然Spring声明式事务管理沿用EJB的约定(遇到unchecked 异常自动回滚事务),但是这是可以定制的。

按照我们的测试,Spring声明式事务管理的性能要胜过EJB CMT。

通常通过TransactionProxyFactoryBean设置Spring事务代理。我们需 要一个目标对象包装在事务代理中。这个目标对象一般是一个普通Java对象的bean。当我 们定义TransactionProxyFactoryBean时,必须提供一个相关的 PlatformTransactionManager的引用和事务属性。 事务属性含有上面描述的事务定义。

PROPAGATION_REQUIRED,-MyCheckedException
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED,readOnly

事务代理会实现目标对象的接口:这里是id为petStoreTarget的bean。(使用 CGLIB也可以实现具体类的代理。只要设置proxyTargetClass属性为true就可以。 如果目标对象没有实现任何接口,这将自动设置该属性为true。通常,我们希望面向接口而不是 类编程。)使用proxyInterfaces属性来限定事务代理来代 理指定接口也是可以的(一般来说是个好想法)。也可以通过从 org.springframework.aop.framework.ProxyConfig继承或所有AOP代理工厂共享 的属性来定制TransactionProxyFactoryBean的行为。

这里的transactionAttributes属性定义在 org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource 中的属性格式来设置。这个包括通配符的方法名称映射是很直观的。注意 insert*的映射的值包括回滚规则。添加的-MyCheckedException 指定如果方法抛出MyCheckedException或它的子类,事务将 会自动回滚。可以用逗号分隔定义多个回滚规则。-前缀强制回滚,+前缀指定提 交(这允许即使抛出unchecked异常时也可以提交事务,当然你自己要明白自己 在做什么)。

TransactionProxyFactoryBean允许你通过 “preInterceptors”和“postInterceptors”属性设置“前”或“后”通知来提供额外的 拦截行为。可以设置任意数量的“前”和“后”通知,它们的类型可以是 Advisor(可以包含一个切入点), MethodInterceptor或被当前Spring配置支持的通知类型 (例如ThrowAdvice, AfterReturningtAdviceBeforeAdvice, 这些都是默认支持的)。这些通知必须支持实例共享模式。如果你需要高级AOP特 性来使用事务,如有状态的maxin,那最好使用通用的 org.springframework.aop.framework.ProxyFactoryBean, 而不是TransactionProxyFactoryBean实用代理创建者。

也可以设置自动代理:配置AOP框架,不需要单独的代理定义类就可以生成类的 代理。

更多信息和实例请参阅AOP章节。

无论你是是否是AOP专家,都可以更有效地使用Spring的 声明式事务管理。但是,如果你想成为Spring AOP的高级用户,你会发现整合声明 式事务管理和强大的AOP性能是非常容易的。

7.4.1. BeanNameAutoProxyCreator,另一种声明方式

TransactionProxyFactoryBean非常有用,当事 务代理包装对象时,它使你可以完全控制代理。如果你需要用一致的方式(例如,一个 样板文件,“使所有的方法事务化”)包装大量的bean,使用一个 BeanFactoryPostProcessor的一个实现, BeanNameAutoProxyCreator,可以提供另外一种方法, 这个方法在这种简单的情况下更加简单。

重述一下,一旦ApplicationContext读完它的初始化信息,它将初始化所有实 现BeanPostProcessor接口的bean,并且让它们后处理 ApplicationContext中所有其他的bean。所以使用这种机制,一个正 确配置的BeanNameAutoProxyCreator可以用来后处 理所有ApplicationContext中所有其他的bean(通过名称来识别),并且把它 们用事务代理包装起来。真正生成的事务代理和使用 TransactionProxyFactoryBean生成的基本一致,这里不再 讨论。

让我们看下面的配置示例:

PROPAGATION_REQUIRED

假设我们在ApplicationContext中已经有一个TransactionManager的实例 ,我们首先要做的使创建一个 TransactionInterceptor实例。 根据通过属性传递的TransactionAttributeSource接口的一个实现, ransactionInterceptor决定哪个 方法被拦截。这个例子中,我们希望处理匹配 所有方法这种最简单的情况。这个不是最有效的方式,但设置非常迅速,因为 我可以使用预先定义的匹配所有方法的 MatchAlwaysTransactionAttributeSource类。如果我 们需要特定的方式,可以使用 MethodMapTransactionAttributeSource, NameMatchTransactionAttributeSourceAttributesTransactionAttributeSource

现在我们已经有了事务拦截器,我们只需把它交给我们定义的 BeanNameAutoProxyCreator实例中,这样AppliactonContext中 定义的6个bean以同样的方式被封装。你可以看到,这 比用TransactionProxyFactoryBean以一种方式单独封装6个bean简洁很 多。封装第7个bean只需添加一行配置。

你也许注意到可以应用多个拦截器。在这个例子中,我们还应用了一个 前面定义的HibernateInterceptor (bean id=hibInterceptor),它为我们管理 Hibernare的会话。

有一件事值得注意,就是在TransactionProxyFactoryBean, 和BeanNameAutoProxyCreator切换时bean的命名。 第一种情况,你只需给你想要包装的bean一个类似myServiceTarget的id, 给代理对象一个类似myService的id,然后所有代理对象的用户只需引用代理对象, 如myService(这些是通用命名规范, 要点是目标对象要有和代理对象不同的名称,并且它们都要在ApplicationContext中可用)。然而, 使用BeanNameAutoProxyCreator时, 你得命名目标对象为myService。 然后当BeanNameAutoProxyCreator后处理目标对象 并生成代理时,它使得代理以和原始对象的名称被插入到 ApplicationContext中。从这一点看,只有代理(含有被包装的对象)在ApplicationContext中可用。

7.5. 编程式还是声明式事务管理

如果你只有很少的事务操作,使用编程式事务管理才是个好主意。例如, 如果你有一个WEB应用需要为某些更新操作提供事务,你可能不想用Spring或其 他技术设置一个事务代理。使用 TransactionTemplate可能是个很好的方法。

另一方面,如果你的应用有大量的事务操作,声明式事务管理就很有价值。它使得事务管理从业务逻辑分离, 并且Spring中配置也不困难。使用Spring,而不是EJB CMT,声明式事务管理配置的成本极大地降低。

7.6. 你需要应用服务器管理事务吗?

Spring的事务管理能力--尤其声明式事务管理--极大地改变了J2EE应用程序需要 应用服务器的传统想法。

尤其,你不需要应用服务器仅仅为了通过EJB声明事务。事实上,即使你拥 有强大JTA支持的应用服务器,你也可以决定使用Spring声明式事务管理提供比 EJB CMT更强大更高效的编程模型。

只有需要支持多个事务资源时,你才需要应用服务器的JTA支持。许多应用没有 这个需求。例如许多高端应用使用单一的,具有高度扩展性的数据库,如Oracle 9i RAC。

当然也许你需要应用服务器的其它功能,如JMS和JCA。但是如果你只需使用 JTA,你可以考虑开源的JTA实现,如JOTM(Spring整合了JOTM)。但是, 2004年早期,高端的应用服务器提供更健壮的XA资源支持。

最重要一点,使用Spring,你可以选择何时将你的应用迁移到完整 应用服务器。使用EJB CMT或JTA都必须书写代码使用局部事务, 例如JDBC连接的事务,如果以前需要全局的容器管理的事务,还要面临着繁重 的改写代码的过程,这些日子一去不复返了。使用Spring只有配置需要改变,你的代码 不需要修改。

7.7. 公共问题

开发着需要按照需求仔细的使用正确的 PlatformTransactionManager实现。

理解Spring事务抽象时如何和JTA全局事务一起工作是非常重要的。使用得当, 就不会有任何冲突:Spring仅仅提供一个简单的,可以移植的抽象层。

如果你使用全局事务,你必须为你的所有事务操作使用Spring的 org.springframework.transaction.jta.JtaTransactionManager。 否则Spring将试图在象容器数据源这样的资源上执行局部事务。这样的局部事务没有任何 意义,好的应用服务器会把这作为一个错误。

from: http://docs.huihoo.com/spring/zh-cn/transaction.html

转载地址:http://waref.baihongyu.com/

你可能感兴趣的文章
Leetcode C++《热题 Hot 100-13》234.回文链表
查看>>
Leetcode C++《热题 Hot 100-14》283.移动零
查看>>
Leetcode C++《热题 Hot 100-15》437.路径总和III
查看>>
Leetcode C++《热题 Hot 100-17》461.汉明距离
查看>>
Leetcode C++《热题 Hot 100-18》538.把二叉搜索树转换为累加树
查看>>
Leetcode C++《热题 Hot 100-19》543.二叉树的直径
查看>>
Leetcode C++《热题 Hot 100-21》581.最短无序连续子数组
查看>>
Leetcode C++《热题 Hot 100-22》2.两数相加
查看>>
Leetcode C++《热题 Hot 100-23》3.无重复字符的最长子串
查看>>
Leetcode C++《热题 Hot 100-24》5.最长回文子串
查看>>
Leetcode C++《热题 Hot 100-26》15.三数之和
查看>>
Leetcode C++《热题 Hot 100-27》17.电话号码的字母组合
查看>>
Leetcode C++《热题 Hot 100-28》19.删除链表的倒数第N个节点
查看>>
Leetcode C++《热题 Hot 100-29》22.括号生成
查看>>
Leetcode C++《热题 Hot 100-40》64.最小路径和
查看>>
Leetcode C++《热题 Hot 100-41》75.颜色分类
查看>>
Leetcode C++《热题 Hot 100-42》78.子集
查看>>
Leetcode C++《热题 Hot 100-43》94.二叉树的中序遍历
查看>>
Leetcode C++ 《第175场周赛-1 》5332.检查整数及其两倍数是否存在
查看>>
Leetcode C++ 《第175场周赛-2 》5333.制造字母异位词的最小步骤数
查看>>