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
很多时候, 需要检查当前线程是否绑定了事务, 来进行进一步的查询或处理。 例如:

Java代码   收藏代码
  1. ...  
  2. if  (txn.getStatus() == Status.STATUS_ACTIVE)  
  3. logger.info("Transaction active in query operation" );  
  4. ...  



STATUS_MARKED_ROLLBACK
在申明事务中, 这个状态很有用。 为了性能的考虑, 我们也许需要跳过一些处理, 如果事务被前面的方法回滚后。

Java代码   收藏代码
  1. ...  
  2. if  (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK)  
  3. throw   new  Exception(  
  4. "Further Processing Halted Due To Txn Rollback" );  
  5. ...  



STATUS_NO_TRANSACTION
这是唯一的一个方法确定上下文中没有事务。

Java代码   收藏代码
  1. ...  
  2. if  (txn.getStatus() == Status.STATUS_NO_TRANSACTION)  
  3. throw   new  Exception(  
  4. "Transaction needed but none exists" );  
  5. ...  


我们不能用STATUS_ACTIVE去判断是否没有事务。 事务还可能是其他的状态。

本地事务
本地事务, 其实就是DBMS或者JMS提供的事务管理。对于开发人员, 在本地事务中, 我们不用管理transactions, 而是 connections. 下面的代码说明了这个问题:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. DataSource ds = (DataSource)  
  4. (new  InitialContext()).lookup( "jdbc/MasterDS" );  
  5. Connection conn = ds.getConnection();  
  6. conn.setAutoCommit(false );  
  7. Statement stmt = conn.createStatement();  
  8. String sql = "update trade_order ... " ;  
  9. try  {  
  10. stmt.executeUpdate(sql);  
  11. conn.commit();  
  12. catch  (Exception e) {  
  13. conn.rollback();  
  14. throw  e;  
  15. finally  {  
  16. stmt.close();  
  17. conn.close();  
  18. }  
  19. }  



在上面的例子中, 我们使用connection的提交, 回滚。 那个自动提交开关, 告诉DBMS是否在执行每条SQL后立刻提交。 这个开关, 默认是true.

在spring 框架中, 用底层的JDBC的话,可以使用org.springframework.jdbc.datasource.DataSourceUtils. 例如:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. Connection conn = DataSourceUtils  
  4. .getConnection(dataSource);  
  5. conn.setAutoCommit(false );  
  6. Statement stmt = conn.createStatement();  
  7. String sql = "update trade_order ... " ;  
  8. try  {  
  9. stmt.executeUpdate(sql);  
  10. conn.commit();  
  11. catch  (Exception e) {  
  12. conn.rollback();  
  13. throw  e;  
  14. finally  {  
  15. stmt.close();  
  16. conn.close();  
  17. }  
  18. }  



在spring 中, datasource 及相关的类, 可以在配置文件中定义为:

Java代码   收藏代码
  1. <bean id= "datasource"   
  2. class = "org.springframework.jndi.JndiObjectFactoryBean" >  
  3. <property name="jndiName"  value= "jdbc/MasterDS" />  
  4. </bean>  
  5. <bean id="TradingService"   
  6. class = "com.trading.server.TradingService" >  
  7. <property name="dataSource" >  
  8. <ref local="datasource" />  
  9. </property>  
  10. </bean>  




Auto Commit and Connection Management
自动提交, 在本地事务中是很重要的。 通过collection 传递,还是可以处理复杂点的逻辑的, 例如:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. DataSource ds = (DataSource)  
  4. (new  InitialContext()).lookup( "jdbc/MasterDS" );  
  5. Connection conn = ds.getConnection();  
  6. conn.setAutoCommit(false );  
  7. OrderDAO orderDao = new  OrderDAO();  
  8. TradeDAO tradeDao = new  TradeDAO();  
  9. try  {  
  10. //SQL and Connection Logic in DAO Classes   
  11. orderDao.update(order, conn);  
  12. tradeDao.update(order, conn);  
  13. conn.commit();  
  14. catch  (Exception e) {  
  15. logger.fatal(e);  
  16. conn.rollback();  
  17. throw  e;  
  18. finally  {  
  19. conn.close();  
  20. }  
  21. }  




这种方式, 在大多数情况下是可以工作的, 但这不是一个好的事务策略, 它需要大量的代码维护, 如果你需要类似上面的代码逻辑的话, 那你就应该使用编程事务或者申明事务了。

本地事务的考虑和限制
本地事务, 只能处理简单的逻辑, 如果系统需要复杂的处理, 对于应用架构, 这个模式会有一些危险的限制。

第一个地方, 就是在coding时候, 开发人员在很多地方容易犯错。开发人员需要时刻注意关掉自动提交功能。 在次, 如果许多方法有管理连接, 我们在调用这些方法的时候, 也要格外小心。 除非我们的应用基本上都是单表操作。 否则, 没有办法保证所以的SQL都在同一个事务中。

还有一个问题, 在使用XA global transaction时候, 本地事务不能存在并发。 当协调多个资源时候, 比如数据库和JMS destination的时候, 我们不能使用本地事务和确保ACDI属性。

基于上面的限制, 本地事务只能在简单的应用和单表操作中使用。

编程事务模型
编程事务于本地事务的最大不同, 就是开发者管理 transactions, 而不是 connections. 下面是个EJB中使用的场景:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. UserTransaction txn = sessionCtx.getUserTransaction();  
  4. txn.begin();  
  5. try  {  
  6. TradeOrderDAO dao = new  TradeOrderDAO();  
  7. dao.updateTradeOrder(order);  
  8. txn.commit();  
  9. catch  (Exception e) {  
  10. log.fatal(e);  
  11. txn.rollback();  
  12. throw  e;  
  13. }  
  14. }  



从上面的代码我们可以看到, 事务被传播到TradeOrderDAO对象中, 但不同于本地模型, TradeOrderDAO不再需要管理transcations 或者 connections. 它所需要做的, 就是从连接池中拿出连接并释放。

在编程事务中, 开发者需要开启和终于一个事务。 在EJB中, 这个通过UserTransaction接口实现, 在spring 中, 通过TransactionTemplate或者PlatformTransactionManager实现。

然后, 调用begin(), commit() 或者rollback()方法。

尽管编程事务模型常常不被鼓励使用, 但在一些场合下, 它确实挺有用的。下面的例子是在spring中使用情形:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. transactionTemplate.execute(new  TransactionCallback()  
  4. {  
  5. public  Object doInTransaction(  
  6. TransactionStatus status)  
  7. {  
  8. try  {  
  9. TradeOrderDAO dao = new  TradeOrderDAO();  
  10. dao.updateTradeOrder(order);  
  11. catch  (Exception e) {  
  12. status.setRollbackOnly();  
  13. throw  e;  
  14. }  
  15. }  
  16. } );  
  17. }  
  18. <bean id="transactionTemplate"   
  19. class ="org.springframework.transaction.support.  
  20. TransactionTemplate">  
  21. <property name="transactionManager" >  
  22. <ref local="transactionManager" />  
  23. </property>  
  24. </bean>  
  25. <bean id="tradingService"   
  26. class = "com.trading.server.TradingService" >  
  27. <property name="transactionTemplate" >  
  28. <ref local="transactionTemplate" />  
  29. </property>  
  30. </bean>  



从上面的例子看出, 使用框架, 并不需要调用begin(), commit()方法。

Obtaining a Reference to the JTA UserTransaction

在基于web应用中, 需要使用JNDI查找获得。例如:

Java代码   收藏代码
  1. ...  
  2. InitialContext ctx = new  InitialContext()  
  3. UserTransaction txn = (UserTransaction)  
  4. ctx.lookup("javax.transaction.UserTransaction" )  
  5. ...  



说到这, 这情况变得有点复杂了。 我们知道, 上面的事务通过jndiI绑定到服务器上。 有写服务器在外部不能获得JNDI resouce ( web container. like tomcat). 但有些是可以的(Jboss). 下面的列出一些常见的绑定的transaction.

JBoss: "UserTransaction"
WebLogic: "javax.transaction.UserTransaction"
WebSphere v5+: "java:comp/UserTransaction"
WebSphere v4: "jta/usertransaction"
SunONE: "java:comp/UserTransaction"
JRun4: "java:comp/UserTransaction"
Resin: "java:comp/UserTransaction"
Orion: "java:comp/UserTransaction"
JOnAS: "java:comp/UserTransaction"


上面的UserTransaction, 在容器外,往往不能获得,但在单元测试或者基于Swing的应用中, 我们怎么解决这个问题呢。 这时候, 我们需要通过Class.forName()方法加载服务端指定的TransactionManagerFactory类, 这样, 才能启动和停止一个事务。 下面的代码演示使用IBM Websphere TransactionManagerFactory:

Java代码   收藏代码
  1. ...  
  2. //load the transaction manager factory class   
  3. Class txnClass = Class.forName(  
  4. "com.ibm.ejs.jts.jta.TransactionManagerFactory" );  
  5. //using reflection invoke the getTransactionManager()   
  6. //method to get a reference to the transaction manager   
  7. TransactionManager txnMgr = (TransactionManager)  
  8. txnClass.getMethod("getTransactionManager" , null )  
  9. .invoke(null null );  
  10. //start the transaction via the transaction manager   
  11. txnMgr.begin();  
  12. ...  



我们可以用任何服务器的TransactionManagerFactory类替换掉上面的object. 这样我们可以在容器外拿到事务管理实例了。

在编程事务中,我们要特别注意异常处理。 看看下面的代码, 我们只处理checked 异常:

Java代码   收藏代码
  1. public   void  updateTradeOrder(TradeOrderData order)  
  2. throws  Exception {  
  3. UserTransaction txn = sessionCtx.getUserTransaction();  
  4. txn.begin();  
  5. try  {  
  6. TradeOrderDAO dao = new  TradeOrderDAO();  
  7. dao.updateTradeOrder(order);  
  8. txn.commit();  
  9. catch  (ApplicationException e) {  
  10. log.fatal(e);  
  11. txn.rollback();  
  12. throw  e;  
  13. }  
  14. }  



编程事务, 说明开发者要自己处理异常。特别注意且确保要关掉事务 (这在大大复杂项目中, 是件很难的事情:)。

编程事务使用场景
一般, 不鼓励使用编程事务, 但在一些场合中, 编程事务还是很有用的。 特别是事务是客户端发起的。 客户端需要调用多个 EJBbean时候,这是就需要在客户端使用它。 但要注意, ejb端需要使用申明事务, 因为编程事务不能在不同的bean之间传递。

另外一个场景就是, 因为JTA事务是比较耗资源的, 为了性能的需求, 我们需要在一些地方关掉事务, 比如银行系统。 只在转账的时候打开事务, 在加载, 验证数据的时 候关掉事务。 而这个在申明事务中很难做到, 因为申明事务没有编程控制, 不知道什么时候事务开始和结束。 

编程事务, 只有在你有好的理由需要使用的时候才使用它。 否则的话, 你就要用到申明事务。

申明事务模型
经过前面的介绍, 我们知道, 编程事务需要开发人员启动, 提交和回滚事务。 那么, 申明事务,容器管理着事务, 就不需要开发人员写Java代码去启动, 提交一个事务了。但是, 开发人员必须告诉容器, 怎样管理事务 。 在spring 中, 通过ApplicationContext.xml bean配置文件实现。

在Spring中, 我们通过TransactionProxyFactoryBean代理使用申明事务。 例如:

Java代码   收藏代码
  1. <bean id= "tradingServiceTarget"   
  2. class = "com.trading.server.TradingServiceBean" >  
  3. ...  
  4. </bean>  
  5. <bean id="tradingService"   
  6. [b]class ="org.springframework.transaction.interceptor.  
  7. TransactionProxyFactoryBean"[/b]>  
  8. <property name="transactionManager"  ref= "txnMgr" />  
  9. <property name="target"  ref= "tradingServiceTarget" />  
  10. <property name="transactionAttributes" >  
  11. <props>  
  12. <prop key="*" >PROPAGATION_SUPPORTS</prop>  
  13. <prop key="update*" >  
  14. PROPAGATION_REQUIRED,[b]-Exception[/b]  
  15. </prop>  
  16. </props>  
  17. </property>  
  18. </bean>  



配置文件中的-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

Java代码   收藏代码
  1. <bean id= "transactionAttributeSource"   
  2. class ="org.springframework.transaction.interceptor.  
  3. NameMatchTransactionAttributeSource">  
  4. <property name="properties" >  
  5. <props>  
  6. <prop key="*" >PROPAGATION_MANDATORY</prop>  
  7. </props>  
  8. </property>  
  9. </bean>  
  10. <bean id="tradingService"   
  11. class ="org.springframework.transaction.interceptor.  
  12. TransactionProxyFactoryBean">  
  13. <property name="transactionAttributeSource" >  
  14. <ref local="transactionAttributeSource" >  
  15. </property>  
  16. ...  
  17. </bean>  



使用 TransactionProxyFactoryBean

Java代码   收藏代码
  1. <bean id= "tradingService"   
  2. class ="org.springframework.transaction.interceptor.  
  3. TransactionProxyFactoryBean">  
  4. <property name="transactionManager"  ref= "txnMgr" />  
  5. <property name="target"  ref= "tradingServiceTarget" />  
  6. <property name="transactionAttributes" >  
  7. <props>  
  8. <prop key="*" >PROPAGATION_MANDATORY</prop>  
  9. </props>  
  10. </property>  
  11. </bean>  



对事务的属性, 一个相关的问题就是,在方法上指定属性, 而不是在类上。但一个好的方法应该是在类上定义属性, 对个别方法进行调整。  也是类上定义的, 是大多数方法都试用的。

例如 :

Java代码   收藏代码
  1. <bean id= "tradingService"  ...>  
  2. ...  
  3. <property name="transactionAttributes" >  
  4. <props>  
  5. [b]<prop key="*" >PROPAGATION_MANDATORY</prop>  
  6. <prop key="getAllTradersByID" >  
  7. PROPAGATION_SUPPORTS  
  8. </prop>[/b]  
  9. </props>  
  10. </property>  
  11. </bean>  




Required vs. Mandatory
有些情况下, 不知道这两种属性使用哪一种。 这里给出个最佳实践:
如果一个方法,并不负责回滚, 那么这个方法就使用Mandatory

怎么理解呢, 用反推法。 这是因为遵循一个原子, 事务是哪里启动的, 就跟在哪里回滚。 如果这个方法使用了Required。就会创建一个新的事务。 就永远不会回滚了。

事务隔离级别

对开发人员来说, 另外一个对事务的设定,就是隔离级别。 这个属性的设定, 依赖于服务器和数据库。服务器也许支持许多的隔离级别设定, 但数据库必须也要支持才能起效。  它的设定, 需要在一致性与并发性之间取个折中或者看业务需求。 这里介绍EJB和Spring支持的四种隔离级别。

  •   TransactionReadUncommitted
  •   TransactionReadCommitted  
  •   TransactionRepeatableRead
  •   TransactionSerializable 



TransactionReadUncommitted
这是最低的一个级别, 它允许事务读取另外事务中未提交的更新数据。 这个违反基本的ACID, 许多数据库厂商都不支持(包括Oracle)。

TransactionReadCommitted
这个级别, 允许多个事务访问同一个数据。 但是他们之间的更新, 只有在提交后才能读取。 这个级别, 是默认的设置且大多数厂商都支持的。

TransactionRepeatableRead
可重复读, 既多个事务, 每次查询的结果都是一样的 (自己更改的不算,因为自己拥有读, 写锁)。 例如, 有两个事务, 有一个事务更改了 数据并提交了。 另外一个事务, 在自己没有提交钱, 看到的仍然是自己开始取得的那个数据。 直到自己把事务提交后, 才知道林另外一个事务更改了数 据。

TransactionSerializable
这个是最高的隔离级别。 表示同时只有一个事务才能访问数据 (但对于oracle, 事实并不是这样的)。 Oracle稍有不同, 一个事务访问数据时, 另一个事务并不会被挂起。但是, 另外一个事务试图去读取同一个数据, Oracle会返回Ora-08177错误消息。

现实中事务隔离级别的设置
在Spring中,隔离级别的设定, 伴随在transaction属性中。 例如:

Java代码   收藏代码
  1. <bean id= "tradingService"  ...>  
  2. ...  
  3. <property name="transactionAttributes" >  
  4. <props>  
  5. <prop key="placeTrade" >  
  6. PROPAGATION_MANDATORY,ISOLATION_SERIALIZABLE  
  7. </prop>  
  8. </props>  
  9. </property>  
  10. </bean>  



隔离级别的设定。 必须要数据库也支持, 如果数据不支持, 在数据库中会使用默认设置代替而不会抛出任何exception。 所以开发人员在设定时要注意这一点。 那当然最好是使用TransactionReadCommitted, 除非你有一个必要改变它。

XA Transaction Processing
XA接口在JTA事务中的重要性, 从异构系统中可以看出来。  例如:

Java代码   收藏代码
  1. @TransactionAttribute (TransactionAttributeType.REQUIRED)  
  2. public   void  placeFixedIncomeTrade(TradeData trade)  
  3. throws  Exception {  
  4. try  {  
  5. ...  
  6. Placement placement =  
  7. placementService.placeTrade(trade);  
  8. //JMS发送消息   
  9. placementService.sendPlacementMessage(placement);  
  10. //数据库执行更细   
  11. executionService.executeTrade(placement);  
  12. catch  (TradeExecutionException e) {  
  13. log.fatal(e);  
  14. sessionCtx.setRollbackOnly();  
  15. throw  e;  
  16. }  
  17. }  



在上面的代码中, 如果数据库执行发生exception, 但JMS消息还是会发送出去。尽管我们需要这个消息立刻被释放。 因为JMS是在一个非XA环境下的独立系统,这样就违反我们的ACDI了, 这时候, 我们需要一个全局的事务管理控制JMS和数据库。 使用X/Open XA interface, 我们可以把多个资源维护在ACDI下。 既两阶段提交。

什么时候使用XA
当有多个资源 (数据库和JMS)在同一个事务下 时候, 才使用X/Open XA Interface。

你可能感兴趣的:(java)