Java 事务模型和策略
Java中, 对于事务模式, 一般总结为三种。 本地事务, 编程式事务和声明事务。 下面, 我们就分别谈谈这三种事务模式。
事务的ACDI
事务有atomicity, consistancy, isolation and durability.
原子性, 事务在一个单元操作中, 要么提交, 要么回滚。有时候, 我们把它叫做LUW(logic unit of work)或者 SUW(single unit of work).
一致性, 事务在原子操作中, 永远不会处于非一致性的状态。 即在每次的原子事务中的insert, update, or delete, 都会拥有一致性的检查, 尽管事务还没有提交。
隔离性, 它表示不同的事务之间互相作用的程度。 ACDI属性, 决定了怎样保护我更新的那些资源, 而这些资源其它的事务也可以访问。 随着隔离性的提高, 一致性增强了而并发性减弱了。
持久性, 就是提交到数据库, 或者JMS更新到server的数据, 都会持久保存下来。
JTA and JTS
Java 开发中, 有效的管理事务, 并不需要知道 JTA(java transaction service)的后台处理。 但是, 理解java中分布式事务的限制是很重要的。
不管你用什么框架, 很多的企业应用, 都借助JTA(java transaction API)管理事务. JTA是开发者管理事务的接口。 JTS呢, 就是JTA下面的服务, 就像是JDBC和数据库driver之间的关系。 JTA的实现, 可以是商用服务器, 也可以使开源的服务器 (jboss)。 因为JTA必须支持JTS和非JTS实现,
UserTransaction Interface
这个接口, 只在编程式事务中使用, 一般关注它的四个方法:
begin() commit() rollback() getStatus()
这几个方法干嘛的。 就不用说了。
TranscationManager Interface
它主要用于声明式事务中, 在编程事务中, 我们也可以用它做UserTranscation的动作。 但最好在需要使用suspend和resume时, 用TransactionManager.
javax.transaction.TransactionManager.suspend()
在申明式或编程事务中, 把当前线程相关的事务suspend。 这个方法返回当前事务或者null.
javax.transaction.TransactionManager.resume()
它用于恢复上面暂停的事务。
Status Interface
方法UserTransaction.getStatus()可以获得 Status Interface。 它可以从当前事务中获得很多的信息。 它有下面的一些 value:
STATUS_ACTIVE STATUS_COMMITTED STATUS_COMMITTING STATUS_MARKED_ROLLBACK STATUS_NO_TRANSACTION STATUS_PREPARED STATUS_PREPARING STATUS_ROLLEDBACK STATUS_ROLLING_BACK STATUS_UNKNOWN
对于开发人员来说, 特别重要的是STATUS_ACTIVE , STATUS_MARKED_ROLLBACK和STATUS_NO_TRANSACTION。 下面会更加详细的介绍他们。
STATUS_ACTIVE
很多时候, 需要检查当前线程是否绑定了事务, 来进行进一步的查询或处理。 例如:
配置文件中的-Exception, 表示遇到任何Exception都会回滚。 一般我们会指定一个特定的checked异常。
Transaction Attributes
容器, 通过Transaction Attributes, 知道怎样管理JTA事务。 一共有六个属性设置。 前面是EJB, 后面是Spring。
Required PROPAGATION_REQUIRED Mandatory PROPAGATION_MANDATORY RequiresNew PROPAGATION_REQUIRES_NEW Supports PROPAGATION_SUPPORTS NotSupported PROPAGATION_NOT_SUPPORTED Never PROPAGATION_NEVER PROPAGATION_NESTED (only spring. not EJB)
要是使用PROPAGATION_NESTED, 后台的JTS必须能够支持事务的嵌套。
REQUIRED
必须使用, 上下文中没有的话, 需要创建一个新的事务。
MANDATORY
它表示需要一个事务, 但不像REQUIRED, 它不会创建一个新的事务。 如果上下文中没有事务, 就会抛出TransactionRequiredException。 表示需要的事务不存在。
REQUIRESNEW
它表示需要一个新的事务, 如果上下文中已经有了事务。 前面的事务就会挂起。 但这个新的事务终止的时候, 前面的事务会resumed。 这个特性违反ACDI属性 (如果前面存在事务时候)。 如果一个操对于外面的事务, 需要独自先完成时, 它是很有用的。比如, 记录log. 这个log会把前面的操作记录下来, 无论这前面的操作是成功或者失败。 如果不用这个特性, 记录log的操作, 在前面逻辑操作失败的情况下跟着回滚。 这就不符合任何操作都需要记录的理念了。
SUPPORTS
表示可以不需要事务, 但上下文中如果存在的话, 会使用前面的事务。
NOTSUPPORTED
这个表示不使用事务。 如果前面存在事务, 事务会被挂起然后等待这方法完成。这个常在那些方法可能会有异常的地方使用。
NEVER
跟notsupported不同, 如果前面有事务的话, 会抛出异常。
这个属性, 会导致不可预知或runtime exception. 所以慎重使用。
在Spring中,我们可以使用TransactionAttributeSource或者TransactionProxyFactoryBean. 下面分别演示这两种方法:
使用 TransactionAttributeSource
对事务的属性, 一个相关的问题就是,在方法上指定属性, 而不是在类上。但一个好的方法应该是在类上定义属性, 对个别方法进行调整。 也是类上定义的, 是大多数方法都试用的。
例如 :
Required vs. Mandatory
有些情况下, 不知道这两种属性使用哪一种。 这里给出个最佳实践:
如果一个方法,并不负责回滚, 那么这个方法就使用Mandatory
怎么理解呢, 用反推法。 这是因为遵循一个原子, 事务是哪里启动的, 就跟在哪里回滚。 如果这个方法使用了Required。就会创建一个新的事务。 就永远不会回滚了。
事务隔离级别
对开发人员来说, 另外一个对事务的设定,就是隔离级别。 这个属性的设定, 依赖于服务器和数据库。服务器也许支持许多的隔离级别设定, 但数据库必须也要支持才能起效。 它的设定, 需要在一致性与并发性之间取个折中或者看业务需求。 这里介绍EJB和Spring支持的四种隔离级别。
TransactionReadUncommitted TransactionReadCommitted TransactionRepeatableRead TransactionSerializable
TransactionReadUncommitted
这是最低的一个级别, 它允许事务读取另外事务中未提交的更新数据。 这个违反基本的ACID, 许多数据库厂商都不支持(包括Oracle)。
TransactionReadCommitted
这个级别, 允许多个事务访问同一个数据。 但是他们之间的更新, 只有在提交后才能读取。 这个级别, 是默认的设置且大多数厂商都支持的。
TransactionRepeatableRead
可重复读, 既多个事务, 每次查询的结果都是一样的 (自己更改的不算,因为自己拥有读, 写锁)。 例如, 有两个事务, 有一个事务更改了数据并提交了。 另外一个事务, 在自己没有提交钱, 看到的仍然是自己开始取得的那个数据。 直到自己把事务提交后, 才知道林另外一个事务更改了数据。
TransactionSerializable
这个是最高的隔离级别。 表示同时只有一个事务才能访问数据 (但对于oracle, 事实并不是这样的)。 Oracle稍有不同, 一个事务访问数据时, 另一个事务并不会被挂起。但是, 另外一个事务试图去读取同一个数据, Oracle会返回Ora-08177错误消息。
现实中事务隔离级别的设置
在Spring中,隔离级别的设定, 伴随在transaction属性中。 例如:
隔离级别的设定。 必须要数据库也支持, 如果数据不支持, 在数据库中会使用默认设置代替而不会抛出任何exception。 所以开发人员在设定时要注意这一点。 那当然最好是使用TransactionReadCommitted, 除非你有一个必要改变它。
XA Transaction Processing
XA接口在JTA事务中的重要性, 从异构系统中可以看出来。 例如:@TransactionAttribute(TransactionAttributeType.REQUIRED)public void placeFixedIncomeTrade(TradeData trade)throws Exception {try {...Placement placement =placementService.placeTrade(trade);//JMS发送消息placementService.sendPlacementMessage(placement);//数据库执行更细executionService.executeTrade(placement);} catch (TradeExecutionException e) {log.fatal(e);sessionCtx.setRollbackOnly();throw e;}}
在上面的代码中, 如果数据库执行发生exception, 但JMS消息还是会发送出去。尽管我们需要这个消息立刻被释放。 因为JMS是在一个非XA环境下的独立系统,这样就违反我们的ACDI了, 这时候, 我们需要一个全局的事务管理控制JMS和数据库。 使用X/Open XA interface, 我们可以把多个资源维护在ACDI下。 既两阶段提交。
什么时候使用XA
当有多个资源 (数据库和JMS)在同一个事务下时候, 才使用X/Open XA Interface。