【Java开源代码栏目提醒】:网学会员在Java开源代码频道为大家收集整理了“java事务设计 - 编程语言“提供大家参考,希望对大家有所帮助!
《
JAVA 事务
设计》读书若干笔记一. 概述1. 事务模型
Java 中有三种可以的事务模型,分别称作本地事务模型(Local Transaction Model) ,编程式事务模型(Programmatic Transaction Model) ,和声明式事务模型(Declarative TransactionModelA. 本地事务模型: 事务是交给本地资源管理器(local resource manager)来管理的。
资源管理器是用于
通信 的、事实上的数据源(datasource)提供者。
举例来说,对于数据库,资源管理器是通过 数据库驱动和数据库
管理系统(Database Management System,DBMS)来实现的。
对于 JMS,所谓资源管理器就是通过特定的 JMS 提供者(provider)实现的队列(queue)或 主题(topic)的连接工厂(connectionfactory)。
经由本地事务模型,开发人员管理的是 ” “连接(connection),而非“事务”。
DBMS 和 JMS 的提供者真正管理了本地事务B. 编程式事务模型: 利用了
Java 事务 API(
Java Transaction API JTA)及其底层事务服务实现的 能量以提供事务支持,突破了“本地事务模型”的种种限制。
通过编程式事务模型,开 发人员的编码对象是 , “连接” 通过使用 javax.transaction.UserTransation 接 “事务” 而非 。
口,开发人员调用 begin方法开始一个事务,调用 commit或 rollback方法去终止这 个事务。
虽然通常情况下不鼓励大量使用编程式事务,然而,在客户端发起的,对远程 无状态会话 Bean(Stateless Session Beans)的 EJB 事务访问的场景下,它还是有可能用 到的C. 声明式事务模型: 在 EJB 的世界中也成为容器托管的事务模型(Container-Managed Transactions),是本书通篇所主要聚焦的事务模型。
在声明式事务模型的环境下,软件 框架或“容器”管理了事务的开始和结束(或者提交,或者回滚) 。
开发人员仅仅需要 告诉
软件框架,碰到应用异常时“去回滚事务”即可,对事务的配置都是通过 EJB 中 的 XML 部署描述文件(例如 ejb-jar.xml)或 Spring 中的 bean 定义文件(例如 ApplicationContext.
xml)来完成的2. JTA 与 JTS我们可以认为 JTA 和 JTS 的关系与 JDBC 和对应的底层数据库驱动类似。
JTA 可以通过商业中间件本身,或通过
开源的事务管理器,例如 JBoss 事务服务(http://www.jboss.com/products/transactions)、JOTM(http://jotm.object
web.org)等实现。
Java 事务服务(JTS)是 CORBA 中 OTS1.1 规范的
Java 版(OTS 是 Object TransactionService 的缩写)。
对于一般开发人员,这并不特别重要——除非您具有打破砂锅问到底的嗜好,或者要对付一次变态的面试。
虽然 JTS 不是 J2EE 规范所要求必须实现的,但对于异构系统实现间的分布式事务交互来说,JTS 是必须的.3. UserTransaction 接口 UserTransaction 接口仅仅用于编程式事务模型,而且主要在 EJB 中使用。
编程人员仅 仅需要 关心其中的如下方法: begin commit rollback getStatus javax.transaction.UserTransaction.begin 在编程式事务模型中,begin方法用于开启一个新的事务,并且将此事务与当前线程相 关联。
如果某个事务已经与当前线程建立过关联,并且底层事务服务不支持嵌套事务, 该方法会抛出一个 NotSupportedException 异常。
javax.transaction.UserTransaction.commit 在编程式事务模型中,commit方法用于提交和当前线程关联的事务,并且终止该事务。
这个方法同时将此事务与当前线程解关联。
在
Java 中,仅仅有一个事务能够与当前线 程 建 立 关 联 。
在 XA 环 境 下 , 这 个 方 法 可 能 抛 出 HeuristicMixedException 或 HeuristicRollbackException, 表示资源管理器做出了独立于事务管理器的决定, 在两阶段 提交过程中回滚或者部分提交了该事务。
javax.transaction.UserTransaction.rollback 在编程式事务模型中,rollback方法用于回滚和当前线程关联的事务,并终止该事务。
这个方法同时将此事务与当前线程解关联。
javax.transaction.UserTransaction.getStatus 在编程式事务模型中,getStatus方法返回一个整型数值,用以表示当前事务的状态。
这 个整型返回值初看是没有什么意义的, 不过我们可以使用 javax.transaction.Status 接口来 确定 getStatus方法返回的值是什么含义。
4. TransactionManager 接口 javax.transaction.TransactionManager 接口主要用于声明式事务模型。
在编程式事务下, 使用 TransactionManager 接口, 您能够做到使用 UserTransaction 接口基本上同样多的事 情。
然而,对大多数方法而言,最好使用 UserTransaction,别去碰 TransactonManager 接 口,除非您需要暂停(suspend)或继续(resume)一个事务。
javax.transaction.TransactionManager.suspend 在声明式或编程式事务模型中,suspend方法用于暂停关联于当前线程的事务。
该方法 返回当前事务的引用;如果没有事务和当前线程关联,则返回 null。
在我们需要暂时停 止当前事务,执行一些不兼容 XA 的
代码或存储过程时,这个方法是相当有用的。
javax.transaction.TransactionManager.resume 在声明式或编程式事务模型中,resume方法用于继续之前暂停的事务。
5. EJBContext 接口 EJBContext 接口使用在 EJB 环境下的声明式事务模型中,对于事务管理,仅仅一个方 法有用,这就是 setRollbackOnly。
javax.ejb.EJBContext.setRollbackOnly 在声明式事务模型下,setRollbackOnly方法用于通知容器:当前事务仅可能产生的后 果便是回滚。
有意思的是这个方法本身在被调用到的时候并不真正立即回滚事务;它只 是 标 记 事 务 是 “ 需 要 回 滚 ” 的 , 如 果 调 用 getStatus 方 法 , 此 时 会 返 回 STATUS_MARKED_ROLLBACK 状态。
使用 TransactionManager.setRollbackOnly也会 产生同样的结果.6. Status 接口 通过 UserTransaction.getStatus来获取事务状态,它有: STATUS_ACTIVE:当前事务和线程关联的状态是有效的,或者说是活跃的。
STATUS_COMMITTED:事务已经提交状态 STATUS_COMMITTING:事务正在提交中。
STATUS_MARKED_ROLLBACK:被标记为回滚状态。
在声明式事务模型下,这个状态常常是有用的。
为了性能优化的需要,我们常 常想要对之前方法调用中被标记为回滚的事务进行跳过处理。
STATUS_NO_TRANSACTION: 当前事务和线程关联的状态是无关联,与 STATUS_ACTIVE 状态相反表述。
(注意我们不能简单地检查状态是否等于 STATUS_ACTIVE 来确定事务上下文的存 在,因为状态不等于 STATUS_ACTIVE 并不意味着事务上下文不存在——事务上下文 也可能处于以上
列表中的任何其他状态) STATUS_PREPARED:已准备状态 STATUS_PREPARING:正准备状态 STATUS_ROLLEDBACK:已经回滚 STATUS_ROLLING_BACK:正在回滚 STATUS_UNKNOWN:未知状态。
二. 本地事务“本地事务”这个术语指的是这样一个事实:事务被底层数据库(DBMS)或在 JMS 中被底层消息服务提供者所管理。
从开发人员的角度来看,在本地事务模型中,我们所管理的并非“事务” ,而是“连接”。
Eg:public void updateTradeOrderTradeOrderData orderthrows Exception DataSource ds DataSourcenew InitialContext.lookupquotjdbc/MasterDSquotConnection conn ds.getConnectionconn.setAutoCommitfalseStatement stmt conn.createStatementString sql quotupdate trade_order ... quottry stmt.executeUpdate
sqlconn.commit catch Exception e conn.rollbackthrow e finally stmt.closeconn.close本地事务模型的缺陷:对于小型的应用环境下简单的更新操作,本地事务模型
工作得很好。
然而,一旦应用的复杂度增加, 这个模型就捉襟见肘了。
本地事务模型本身的限制会对您的应用架构形成相当的掣肘。
第一个问题是一旦使用本地事务模型,开发人员就连接逻辑造成错误
代码的几率是很大的。
开发人员必须非常关注自动提交标志的设置, 特别在同一方法中进行多个更新操作时更是如此第二个
问题:本地事务不能在使用 XA 全局事务协调多个资源时并发地存在, 。
当需要协调多个资源(如数据库和 JMS 目标,如队列或主题)时,您不可能在保证 ACID 特性的前提下使用本地事务模型。
本地事务模型最好只能在单表更新的,最简单的基于 WEB 的
Java 应用中使用三.编程式事务模型1. 编程式事务模型和本地事务模型两者最大区别之一是, 开发人员使用编程式模型, 管理的是事务(transaction) ,而不是连接(connection)。
当在 EJB 中时,编程式事务模型常常被称为 Bean 管理事务(Bean-Managed Transactions) ,或 BMT。
编程式事务模型也可用在 servlet容器之中,能应用于 POJO。
public void updateTradeOrderTradeOrderData orderthrows Exception UserTransaction txn sessionCtx.getUserTransactiontxn.begintry TradeOrderDAO dao new TradeOrderDAOdao.updateTradeOrderordertxn.commit catch Exception e log.fataletxn.rollbackthrow e在上面的例子里,事务上下文(transaction context)被传递到 TradeOrderDAO 对象中,因此不像本地事务模型那样,TradeOrderDAO 并不需要管理连接和事务。
它所需要做的仅仅是从连接,用完后还回去。
在编程式事务模型中,开发人员负责开启和终止事务.2. status.setRollbackOnly 用法样例:public void updateTradeOrderTradeOrderData orderthrows Exception transactionTemplate.executenew TransactionCallbackpublic Object doInTransactionTransactionStatus statustry TradeOrderDAO dao new TradeOrderDAOdao.updateTradeOrderorder catch Exception e status.setRollbackOnlythrow e 注意,当使用此技术的时候,并非要像在 EJB 中那样一定需要显示的调用 begin和commit。
并且,这里通过 TransactionStatus.setRollbackOnly处理回滚,而不像 EJB 那样调用 Transaction.rollback处理之.3. 如何获取获取到 JTA UserTransaction 的引用在使用 EJB 时必须获得 InitialContext,并像下面的
代码一样进行一次 JNDI 查找。
InitialContextctxnewInitialContextUserTransactiontxnUserTransactionctx.lookupquotjavax.transaction.UserTransactionquot ( )以上
代码片段中的查找名称 “javax.transaction.UserTransaction” 是与应用服务器类型相关。
JNDI 是在容器中才能获取,如果是要用 JUnit 那如何测试呢?解 决 这 个 问 题 的 方 法 是 使 用 TransactionManager 接 口 启 动 事 务 。
首 先 , 您 需 要 使 用Class.forName方法装载应用服务器相关的 TransactionManagerFactory 类。
然后,使用反射调用 getTransactionManager方法。
这样,便产生了一个 TransactionManager 实例,以用于开始和结束事务。
下面使用 IBM WebSphere 中的
代码如下://装载 transaction manager 工厂类Class txnClass Class.forNamequotcom.ibm.ejs.jts.jta.TransactionManagerFactoryquot//使用反射调用 getTransactionManager//方法拿到对 transaction manager 的引用TransactionManager txnMgr TransactionManagertxnClass.getMethodquotgetTransactionManagerquotnull.invokenull null//通过 transaction manager 开始事务txnMgr.begin…….对 EJB 来说,您可以在会话 Bean 中使用 SessionContext.getUserTransaction,或在消息驱动 Bean 中使用 MessageDrivenContext.getUserTransaction来获得 UserTransaction.4. 编程式事务中的编码陷阱当使用编程式事务模型时,开发人员必须十二分的警惕异常的处理(exception handling)。
看看下面的
代码,我们仅仅捕获和处理了应用异常(一个被检查异常,checked exception) ,而忽略了运行时异常(runtime exception).因此要求,一旦使用编程式事务,我们开发人员便有责任去管理事务。
一个方法,启动了事务,它就必须负责终止该事务5. 事务上下文问题(Transaction Context Problem)事务上下文不能在编程式事务中传递,但是可以在声明式事务中传递。
6. 编程式事务的使用场景A. 客户端发起事务(cient-initiated transactions)的情形: 如果客户端为一个业务请求做多次远程方法调用,从道理上讲事务必须由客户端开启。
使用JTA 时,就需要使用UserTransaction 接口和编程式事务。
对这样的需求,您必须 在客户端bean 使用编程式事务,而在远程的EJB 使用声明式事务——因为事务上下文 不能被传递给使用编程式事务的EJB。
B. 使用本地的 JTA: 使用 JTA 是一件消耗资源的事情,在有些行为不需要事务,可以不需要,可以放在事 务外执行。
以信用卡处理为例,您也许不会在数据装载,数据校验,数据验证,以及过帐时使用 JTA 事务。
然而,当您需要将 money 从一个账户转向开户银行时,您就需要开启事务 了。
这种情况下,使用声明式事务非常不便,因为它缺乏对事务何时开始,何时终止灵 活的控制。
C. 使用长时间运行(long-running)的 JTA 事务 有时候您想要一个事务跨越对服务器的多次请求。
此时,您可能会在一个有状态会话 Bean 的方法中开启事务,而在另一个有状态会话 Bean 中终止事务。
虽然在技术上这 是可行的,但从设计的观点看这是一个很不好的做法——这样数据库或 JMS 资源在这 个较长的处理时间段内会被很快耗尽。
尽管如此,像本地 JTA 事务一样,如果使用声 明式事务模型,这点是决不可能做到的。
四. 声明式事务模型 在编程式事务模型中, 开发人员必须显示的调用 begin方法去开启事务,调用 commit 或和 rollback去提交或回滚事务。
如果使用声明式事务模型(Declarative Transaction Model),容器将管理事务,这意味着开发人员不用编写任何
代码,便可开始或提交事 务。
在 EJB 中,也被称为容器管理事务(Container-Managed Transactions,CMT) 备注:编码时仅仅只有 setRollbackOnly方法。
该方法告诉容器,当前事务产生的唯一 可能结果便是回滚所有更改,不管后续有什么样的处理逻辑。
声明式事务特点: 1. 通常通过配置,系统就能自动识别事务控制,EJB 和 spring 在配置上会各不相同。
2. 事务属性(Transaction Attributes): 表示是哪种事务,容器如何管理它,事务类型有下面几种:Required(需要):(对应Spring 中的PROPAGATION_REQUIRED 属性) 告诉容器,目标对象方法 需要一个事务。
如果事务上下文已经存在,容器将会使用它;否则,容器将为此方法开启一个 新事务。
Mandatory(强制必须):(对应Spring 中的PROPAGATION_MANDATORY 属性) 容器并不为目标对象开启新的事务。
当使用这个事务属性时,当方法被调用时,一个事先存在 的事务上下文必须存在。
如果此事务上下文不存在,容器将会抛出一个 TransactionRequiredException,宣告它需要一个已存在的事务,但此事务没有找到。
RequiresNew(需要新的):(对应Spring 中的PROPAGATION_REQUIRES_NEW) 告知容器,当对象方法被调用时,“总是”需要开启一个新的事务。
如果先于方法调用前一个 事务已经开启了,此事务将被暂时挂起,容器启动一个新的事务。
当这个新事务随着方法调用 完成终止后,老的事务将会继续。
Supports(支持):对应Spring 中的PROPAGATION_SUPPORTS 属性 告知容器,对象方法并不需要一个事务上下文,但当调用到这个方法而事务上下文碰巧存在时,该方 法会使用它。
e.g. 我们假设这样的场景,一个商家在每天最多只能进行100万股的股票交易。
使用Support 属性,会造 成下面的事情发生: 第一步:当天,迄今已经交易了 90 万股 第二步:事务开始 第三步:商家操作一笔数额为 20 万股的股票交易 第四步:
系统通过使用Supports 事务属性的查询,得知今天的交易总额已达110 万股 第五步:违背交易上限的异常抛出,事务回滚 如果我们使用(下文将要介绍到的)NotSupported 属性,我们便不能得到这个异常。
因为查询不在 此事务的范围之内,并不能够看到试图进行的交易: 第一步:当天,迄今已经交易了 90 万股 第二步:事务开始 第三步:商家操作一笔数额为 20 万股的股票交易 第四步:系统通过使用 NotSupported 事务属性的
查询,得知今天的交易总额还是90 万股 第五步:允许交易,交易提交完成NotSupported(不支持):对应Spring 中的PROPAGATION_NOT_SUPPORTED 告知容器,被调用的对象方法不使用事务。
如果一个事务业已启动,容器会将此事务暂停直至方法调 用结束。
如果调用方法时没有事务存在,容器也不会为此方法开启任何事务。
在方法逻辑中有排斥事 务上下文的
代码存在时,这个属性就非常有用了Never(不用): (对应 Spring 中的 PROPAGATION_NEVER 属性) 告知容器,在调用对象方法时,不允许有事务上下文存在。
3. 异常和回归 原则:我们也必须要记住容器在应用异常时不会自动回滚事务,这是非常重要的。
所以需要告知容器需要在异常发生的时候想要的回归, 比较下面两个例子: 例子一: TransactionAttributeTransactionAttributeType.REQUIRED public void placeFixedIncomeTradeTradeData trade throws Exception try ... Placement placement placementService.placeTradetrade executionService.executeTradeplacement catch TradeExecutionException e log.fatale throw e 例子二: TransactionAttributeTransactionAttributeType.REQUIRED public void placeFixedIncomeTradeTradeData trade throws Exception try ... Placement placement placementService.placeTradetrade executionService.executeTradeplacement catch TradeExecutionException e log.fatale sessionCtx.setRollbackOnly //告知容器需要回归 throw e 在EJB 3.0 中考虑,引入个ApplicationException该标注告知应用容器,当有异 常抛出时是否该自动将事务标记为回滚。
该标注有一个boolean 值的参数。
例如:ApplicationExceptionrollbacktrue 另外在EJB 的世界,setRollbackOnly方法的另一个选择是抛出一个javax.ejb.EJBException 系统异常,强制事务回滚。
4. 使用Required 和Mandatory 事务属性的对比 最佳实践: 如果一个方法需要事务上下文,但不负责将此事务标记为回滚(rollback only)的状态,该方 法应使用Mandatory 事务属性。
5. 事务隔离 事务隔离(Transaction Isolation)是指交织在一起发生的事务之间相互影响的程度。
它决定了在其他事务访问和更新同一份数据时, 一个事务对更新所允许的可见程度。
这样的设置,是数据库和应用服务器实现相依赖的。
应用服务器可能支持多种隔离级别,但对应的数据库必须支持这些同样的隔离级别,这些设置才可能真正生效。
事务隔离同时是数据库并发度(concurrency)和一致性(consistency)的函数。
下面的图示说明了三者之间的关系: 隔离度提升,并发度性能降低,数据一致性就提升。
比如银行金融就适合提升隔离度 反之相应的都反向变化。
EJB 和 spring 都支持下面的隔离度,下面的隔离度从低到高排序: TransactionReadUncommitted 读取未提交:允许事务读取其他事务在提交到 数据库之前产生的未提交更改。
TransactionReadCommitted 读取已提交: 允许多个事务访问同一份数据,但将未提交的数据对其他事务隐藏,直至数 据提交。
TransactionRepeatableRead 可重复读: 这个隔离级别保持了事务彼此隔绝。
这一隔离级别保证, 一旦在某一事务中读取了数据库的一个值集, 在后续的每次查询操作中都读到同样的 值(除非此事务拿到这些数据的读写锁,并自行更改了数据) ,使用这一隔离级别应 该小心,因为在 Repeatable Read 隔离级别下,一个事务如果要更改数据,而这一数 据被其他事务读取时, 此事务需要等待占用数据事务提交的操作(或直接返回失败)。
TransactionSerializable 可序列化: 在这一隔离级别设置下,交错发生的事务被“堆.