




已阅读5页,还剩7页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
2009 年 7 月 14 日混淆事务模型与事务策略是一个常见的错误。本系列关于事务策略的第二篇文章将概述 Java 平台支持的三种事务模型,并介绍使用这些模型的四种主要事务策略。通过使用 Spring Framework 和 Enterprise JavaBeans (EJB) 3.0 规范中的示例,Mark Richards 将解释事务模型的运行原理以及它们如何形成开发各种事务策略(从基本的事务处理到高速事务处理系统)的基础。开发人员、设计人员和架构师经常会混淆事务模型与事务策略。我经常会让与客户接触的架构师和技术总监描述他们项目的事务策略。我通常会获得三种回应。有时,他们会说 “我们实际上并未在应用程序中使用事务。”另一些时候,我会听到迷惑的回答:“我不明白你的意思。”但是,我也会遇到非常自信的回答:“我们使用声明式事务。”在本文中,术语声明式事务描述的是一个事务模型,但它绝不是一种事务策略。关于本系列事务将改善您数据的质量、完整性以及一致性,并且让您的应用程序更加可靠。在 Java 应用程序中实现成功的事务处理并不是一项轻松的任务,它是一项与编写代码相关的设计工作。 在这个新的系列中,Mark Richards 将指导您设计一个有效的事务策略,它适用于各种用例,从简单的应用程序到高级性能事务处理。Java 平台支持的三种事务模型包括: Local Transaction 模型 Programmatic Transaction 模型 Declarative Transaction 模型这些模型描述事务在 Java 平台中的基本运行方式,以及它们是如何实现的。但是,它们仅提供了事务处理的规则和语义。如何应用事务模型则完全由您决定。举例来说,应该如何在REQUIRED和MANDATORY事务属性之间做出选择?您应该在何时何种情况下指定事务回滚指令?您应该在何时考虑 Programmatic Transaction 模型与 Declarative Transaction 模型的优劣?您应该如何优化高性能系统的事务?事务模型本身无法回答这些问题。您必须通过开发自己的事务策略或采用本文介绍的四种主要事务策略之一来解决它们。如本系列的第一篇文章所述,许多常见的事务陷阱都会影响到事务行为,并且由此会降低数据的完整性和一致性。同样,缺乏有效的(或任何)事务策略将对您数据的完整性和一致性造成负面影响。本文所描述的事务模型是开发有效事务策略的基本元素。理解这些模型之间的差异以及它们的运行方式对于理解使用它们的事务策略非常重要。在介绍完三种事务模型之后,我将讨论适用于大多数业务应用程序(从简单的 Web 应用程序到大型的高速事务处理系统)的四种事务策略。事务策略系列的后续文章将详细讨论这些策略。Local Transaction 模型在 Local Transaction 模型中,事务由底层数据库资源管理程序(而非应用程序所在的容器或框架)管理,这便是它得名的原因。在这个模型中,您将管理连接,而不是事务。从 “了解事务陷阱” 一文可知,在使用对象关系映射框架,如 Hibernate、TopLink 或 the Java Persistence API (JPA),执行数据库更新时,您不能使用 Local Transaction 模型。在使用数据访问对象(DAO)或基于 JDBC 的框架和数据库存储过程时,您可以使用它。您可以采用以下两种方式来使用 Local Transaction 模型:让数据库来管理连接,或者以编程的方式管理连接。要让数据库管理连接,您需要将 JDBCConnection对象上的autoCommit属性设置为true(默认值),这将通知底层数据库管理系统(DBMS)在完成插入、更新或删除操作之后提交事务,或者在操作失败时返回任务。清单 1 展示了这种技巧,它在TRADE表中插入了一条股票交易命令:清单 1. 包括一个更新的本地事务public class TradingServiceImpl public void processTrade(TradeData trade) throws Exception Connection dbConnection = null; try DataSource ds = (DataSource) (new InitialContext().lookup(jdbc/MasterDS); dbConnection = ds.getConnection(); dbConnection.setAutoCommit(true); Statement sql = dbConnection.createStatement(); String stmt = insert into TRADE .; sql.executeUpdate(stmt1); finally if (dbConnection != null) dbConnection.close(); 注意,在清单 1 中,autoCommit值设置为true,这将指示 DBMS 应该在各数据库语句之后提交本地事务。如果在逻辑工作单元(LUW)中维护一个数据库活动,那么这一技巧将非常有用。但是,假设清单 1 中的processTrade()方法还将更新ACCT表中的余额以反映交易订单的值。在本例中,两个数据库操作是相互独立的,针对TRADE表的插入操作将在更新ACCT表之后提交给数据库。若ACCT表更新失败,没有任何机制可以回滚到对TRADE表的更新操作,从而造成数据库中的数据不一致。此场景又引出了第二个技巧:以编程的方式管理连接。在此技巧中,您将Connection对象上的autoCommit属性设置为false,并手动提交或回滚连接。清单 2 演示了此技巧:清单 2. 包括多个更新的本地事务public class TradingServiceImpl public void processTrade(TradeData trade) throws Exception Connection dbConnection = null; try DataSource ds = (DataSource) (new InitialContext().lookup(jdbc/MasterDS); dbConnection = ds.getConnection(); dbConnection.setAutoCommit(false); Statement sql = dbConnection.createStatement(); String stmt1 = insert into TRADE .; sql.executeUpdate(stmt1); String stmt2 = update ACCT set balance.; sql.executeUpdate(stmt2); dbCmit(); catch (Exception up) dbConnection.rollback(); throw up; finally if (dbConnection != null) dbConnection.close(); 注意,在清单 2 中,autoCommit属性设置为false,这将通知底层 DBMS 在代码而非数据库中管理连接。在本例中,如果一切正常,您必须调用Connection对象上的commit()方法;否则,如果出现异常,则调用rollback()方法。通过这种方式,您可以在相同的工作单元中协调两个数据库活动。虽然 Local Transaction 模型现在看来有些过时,但它是本文结束部分介绍的一个主要事务策略的重要元素。回页首Programmatic Transaction 模型Programmatic Transaction 模型的名称来自这样一个事实:开发人负责管理事务。在 Programmatic Transaction 模型中,与 Local Transaction 模型不同,您将管理事务,并且将与底层数据库连接相分离。与清单 2中的示例相似,借助此模型,开发人员将负责从事务管理程序获取一个事务,启动该事务,提交事务,以及(如果出现异常)回滚到事务。您可能已经猜到,这会产生大量易于出错的代码,从而对应用程序中的业务逻辑造成影响。但是,一些事务策略需要使用 Programmatic Transaction 模型。虽然概念是相同的是,但 Programmatic Transaction 模型的实现在 Spring Framework 和 EJB 3.0 规范中是不同的。我将先使用 EJB 3.0 来演示这个模型的实现,然后使用 Spring Framework 来展示相同的数据库更新。使用 EJB 3.0 实现编程事务在 EJB 3.0 中,您将从事务管理程序(换句话说,容器)获取一个事务,方法是对javax.transaction.UserTransaction执行 Java Naming and Directory Interface (JNDI) 查找。获取UserTransaction之后,您可以调用begin()方法来启动事务,调用commit()方法来提交事务,并且调用rollback()方法以便在出现错误时回滚事务。在此模型中,容器不会自动提交或回滚事务;开发人员需要自己在执行数据库更新的方法编写此行为。清单 3 通过 JPA 展示了使用 EJB 3.0 的 Programmatic Transaction 模型:清单 3. 使用 EJB 3.0 实现的编程事务StatelessTransactionManagement(TransactionManagementType.BEAN)public class TradingServiceImpl implements TradingService PersistenceContext(unitName=trading) EntityManager em; public void processTrade(TradeData trade) throws Exception InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx.lookup(UserTransaction); try txn.begin(); em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId(); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(BUY) acct.setBalance(currentBalance - tradeValue); else acct.setBalance(currentBalance + tradeValue); mit(); catch (Exception up) txn.rollback(); throw up; 在带无状态会话 bean 的 Java EE 容器环境中使用 Programmatic Transaction 模型时,您必须通知容器您正在使用编程事务。为此,您需要使用TransactionManagement注释并将事务类型设置为BEAN。如果您未使用此注释,则容器将假定您使用声明式事务管理(CONTAINER),它是 EJB 3.0 的默认事务类型。在无状态会话 bean 上下文外部的客户机层中使用编程事务时,您不需要设置事务类型。使用 Spring 实现编程事务Spring Framework 提供了两种实现 Programmatic Transaction 模型的方法。一种是通过 SpringTransactionTemplate来实现,另一种是直接使用 Spring平台事务管理程序。由于我并不热衷于编写匿名内部类和难以理解的代码,因此我将使用第二种技巧来演示 Spring 中的 Programmatic Transaction 模型。Spring 至少有九种平台事务管理程序。最常用的包括DataSourceTransactionManager、HibernateTransactionManager、JpaTransactionManager和JtaTransactionManager。我的代码示例使用的是 JPA,因此我将展示JpaTransactionManager的配置。要在 Spring 中配置JpaTransactionManager,只需要使用org.springframework.orm.jpa.JpaTransactionManager类在应用程序上下文 XML 文件中定义 bean,并添加一个到 JPA Entity Manager Factory bean 的引用。然后,假定包含应用程序逻辑的类是由 Spring 管理的,将事务管理程序注入到 bean 中,如清单 4 所示:清单 4. 定义 Spring JPA 事务管理程序 如果 Spring 未管理应用程序类,那么您可以在您的方法中引用事务管理程序,方法是在 Spring 上下文中使用getBean()方法。在源代码中,您现在可以使用平台管理程序获取一个事务。执行了所有更新之后,您可以调用commit()方法来提交事务,或者调用rollback()方法来回滚事务。清单 5 展示了此技巧:清单 5. 使用 Spring JPA 事务管理程序public class TradingServiceImpl PersistenceContext(unitName=trading) EntityManager em; JpaTransactionManager txnManager = null; public void setTxnManager(JpaTransactionManager mgr) txnManager = mgr; public void processTrade(TradeData trade) throws Exception TransactionStatus status = txnManager.getTransaction(new DefaultTransactionDefinition(); try em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId(); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(BUY) acct.setBalance(currentBalance - tradeValue); else acct.setBalance(currentBalance + tradeValue); txnMmit(status); catch (Exception up) txnManager.rollback(status); throw up; 注意清单 5 中 Spring Framework 与 EJB 3.0 之间的差异。在 Spring 中,获取事务(随后启动它)的方法是在平台事务管理程序上调用getTransaction()。匿名DefaultTransactionDefinition类包含关于事务及其行为的详细信息,包括事务名称、隔离级别、传播模式(事务属性)和事务超时(如果存在)。在本例中,我可以仅使用默认值,其名称是空字符串,底层 DBMS 的默认的隔离级别通常为READ_COMMITTED,事务属性是PROPAGATION_REQUIRED,以及 DBMS 的默认超时。还需注意,commit()和rollback()方法是使用平台事务管理程序调用的,而不是事务(与 DJB 的情况相同)。回页首Declarative Transaction 模型Declarative Transaction 模型,又称作Container Managed Transactions(CMT),是 Java 平台中最常用的事务模型。在该模型中,容器环境负责启动、提交和回滚事务。开发人员仅负责指定事务的行为。本系列的第一篇文章所讨论的大多数事务陷阱都与 Declarative Transaction 模型相关。Spring Framework 和 EJB 3.0 都利用注释来指定事务行为。Spring 使用Transactional注释,而 EJB 3.0 使用TransactionAttribute注释。在使用 Declarative Transaction 模型时,容器将不会针对检测到的异常自动回滚事务。开发人员必须指定出现异常时在何处以及何时回滚事务。在 Spring Framework 中,您通过使用Transactional注释上的rollbackFor属性来指定它。在 EJB 中,您通过调用SessionContext上的setRollbackOnly()方法来指定它。清单 6 展示了 Declarative Transaction 模型的 EJB 应用:清单 6. 使用 EJB 3.0 的声明式事务Statelesspublic class TradingServiceImpl implements TradingService PersistenceContext(unitName=trading) EntityManager em; Resource SessionContext ctx; TransactionAttribute(TransactionAttributeType.REQUIRED) public void processTrade(TradeData trade) throws Exception try em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId(); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(BUY) acct.setBalance(currentBalance - tradeValue); else acct.setBalance(currentBalance + tradeValue); catch (Exception up) ctx.setRollbackOnly(); throw up; 清单 7 演示了 Declarative Transaction 模型的 Spring Framework 应用:清单 7. 使用 Spring 的声明式事务public class TradingServiceImpl PersistenceContext(unitName=trading) EntityManager em; Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class) public void processTrade(TradeData trade) throws Exception em.persist(trade); AcctData acct = em.find(AcctData.class, trade.getAcctId(); double tradeValue = trade.getPrice() * trade.getShares(); double currentBalance = acct.getBalance(); if (trade.getAction().equals(BUY) acct.setBalance(currentBalance - tradeValue); else acct.setBalance(currentBalance + tradeValue); 事务属性除了回滚指令之外,您还必须指定事务属性,这将定义事务的行为。Java 平台支持六种事务属性,而与您使用的是 EJB 还是 Spring Framework 无关: Required Mandatory RequiresNew Supports NotSupported Never在描述这些事务属性时,我将使用一个假想的methodA()方法,事务属性将应用于这个方法。如果为methodA()指定了Required事务属性,并且在已有事务作用域内调用了methodA(),那么将使用已有的事务作用域。否则,methodA()将启动一个新的事务。如果事务是由methodA()启动的,则它也必须由methodA()来终止(提交或回滚)。这是最常用的事务属性,并且是 EJB 3.0 和 Spring 的默认属性。遗憾的是,它的使用并不正确,从而造成了数据完整性和一致性问题。对于本系列后续文章将要讨论的各事务策略,我将更加详细地阐述这个事务属性。如果为methodA()指定了Mandatory事务属性,并且在已有事务作用域内调用了methodA(),那么将使用已有的事务作用域。但是,如果调用methodA()时没有事务上下文,则会抛出一个TransactionRequiredException,指示必须在调用methodA()之前呈现事务。这个事务属性应用于本文下一部分将要讨论的Client Orchestration事务策略。RequiresNew事务属性非常有趣。许多时候,我会发现这个属性被误用或误解。methodA()指定了RequiresNew属性,并且在有或没有事务上下文的情况下调用了methodA(),那么新事务将始终由methodA()启动(和终止)。这意味着,如果在另一个事务(比如说Transaction1)的上下文中调用了methodA(),那么Transaction1将暂停,同时会启动一个新的事务(Transaction2)。当methodA()结束时,Transaction2将提交或回滚,并且Transaction1将恢复。这显然违背了事务的 ACID(atomicity、consistency、isolation 和 durability)属性(特别是atomicity属性)。换句话说,所有数据库更新都不再包含在一个单一的工作单元中。如果Transaction1将进行回滚,则由Transaction2提交的更改将仍然被提交。如果是这种情况,那么事务属性有什么好处呢?如本系列第一篇文章所述,这个事务属性仅用于独立于底层事务的数据库操作(比如审计或记录,在本例中为Transaction1)。Supports事务属性也是我发现大多数开发人员未完全理解或掌握的一个地方。如果为methodA()指定了Supports事务属性,并且在已有事务的作用域中调用了methodA(),则methodA()将在该事务的作用域中执行。但是,如果在没有事务上下文的情况下调用了methodA(),则不会启动任何事务。此属性主要用于针对数据库的只读操作。如果是这种情况,为何不指定NotSupported事务属性(在下一段中讨论)呢?毕竟,该属性将确保方法在没有事务的情况下运行。答案非常简单。在已有事务的上下文中调用查询操作会导致从数据库事务日志中读取数据(换句话说,已更新的数据),而不在事务作用域中运行会导致查询从表中读取未修改的数据。举例来说,如果您插入一个新的交易订单到TRADE表中,然后(在相同的事务中)获取所有交易订单的列表,则未提交的交易将出现在列表中。但是,如果您使用的是NotSupported事务属性,那么它会造成数据库查询从表中而不是从事务日志中读取数据。因此,在上一个示例中,您将不会看到未提交的交易。这并不一定是一件坏事;这将由您的用例和业务逻辑决定。NotSupported事务属性指定被调用的方法将不使用或启动事务,无论是否呈现了事务。如果为methodA()指定了NotSupported事务属性,并且在某个事务的上下文中调用了methodA(),则该事务将暂停直到methodA()结束。当methodA()结束时,原始事务将被恢复。这个事务属性只有少许用例,并且它们主要涉及数据库存储过程。如果您尝试在已有事务上下文的作用域中调用数据库存储过程,并且数据库存储过程包含一个BEGIN TRANS,或者对于 Sybase 的情况,在未连接模式中运行,则会抛出一个异常,指示已经存在一个事务无法启动新事务。(换句话说,内嵌事务不受支持)。几乎所有的容器都使用 Java Transaction Service (JTS) 作为默认的 JTA 事务实现。不支持内嵌事务的是 JTS 而不是 Java 平台。如果您无法修改数据库存储过程,那么您可以使用NotSupported属性来暂停已有事务上下文,以避免这种致命的异常。但是,其影响是您不能在相同的 LUW 中对数据库执行原子更新。这是一种权衡,但有时可以让您迅速脱离困境。Never或许是最有趣的事务属性。它的行为类似于NotSupported事务属性,但存在一个重要差异:如果使用Never事务属性调用方法时已经存在某个事务上下文,则会抛出一个异常,指示您在调用该方法时不允许使用这个事务。我能想到的针对此事务属性的唯一一个用例就是用于测试。它提供了一种简单的方法,用于验证当您调用特定的方法时某个事务是否存在。如果您在调用相应的方法时使用了Never事务属性并且接收到了一个异常,则知道事务已经呈现。如果允许执行方法,则您知道事务未被呈现。这是确保事务策略坚固的理想方法。回页首事务策略本文描述的事务模型形成了即将介绍的事务策略的基础。在开始构建事务策略之前,完全理解这些模型之间的差异以及它们的运行原理是非常重要的。可以在大多数业务应用程序场景中使用的主要事务策略包括: Client Orchestration 事务策略 API Layer 事务策略 High Concurrency 事务策略 High-Speed Processing 事务策略我将在此处简要介绍这些策略,并将在本系列的后续文章中详细讨论它们。当来自客户机层的基于多服务器或基于模型的调用完成单一工作单元时,将使用Client Orchestration事务策略。此处的客户机层可以表示来自 Web 框架、门户应用程序、桌面系统或工作流产品或业务流程管理(BPM)组件的调用。从本质上说,客户机层拥有处理流以及完成特定请求所需的 “步骤”。举例来说,要添加一个交易订单,假定您需要将交易插入到数据库中,然后更新客户的帐户余额以反映交易的值。如果应用程序的 API 层非常细粒度化,那么您需要调用客户机层中的两个方法。在此场景中,事务工作单元必须位于客户机层中以确保自动化的工作单元。当粗粒度方法充当后端功能的主要入口点时,将使用API Layer事务策略。(如果愿意,可以将它们称作services)。在此场景中,客户机(基于 Web、基于 Web 服务、基于消息或者甚至桌面)向后端发起单个调用以执行特定的请求。使用上文提到的交易订单场景,在本例中您将拥有一个入口点方法(比如说processTrade())由客户机层调用。然后,这个方法将包含插入交易订单和更新帐户所需的业务流程。我将这个策略指定为这个名称的原因是,在大多数情况下,后端处理功能会通过使用接口或 API 向客户应用程序公开。这是最常用的事务策略之一。High Concurrency事务策
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 行政办公物资采购模板
- 时间极限课件
- 销售订单与合同管理模板
- 梦境里的魔法450字15篇范文
- 企业文化建设方案落地执行清单
- 蓝色商务风跨部门沟通协作企业培训
- 东非野生动物大迁徙之角马的旅程150字8篇范文
- 状物作文我喜欢的蔬菜450字(15篇)
- 纪念白求恩课文课件
- 商业合作保密协议书
- 2025广西公需科目考试答案(3套涵盖95-试题)一区两地一园一通道建设人工智能时代的机遇与挑战
- 2025至2030中国公路养护行业项目调研及市场前景预测评估报告
- 沉淀池安全操作规程
- 职业规划杨彬课件
- 护理人员行为规范
- 2025版安全生产法全文
- 2025年中国护腰垫数据监测报告
- 2025年中远海运集团招聘笔试备考题库(带答案详解)
- 车间现场品质培训
- 新教师职业素养提升培训
- 2025年高考英语全国一卷听力评析及备考建议
评论
0/150
提交评论