事务模型

我的未来我决定 提交于 2019-12-26 20:57:10

3种事务模型

本地事务模型

本地事务模型的名称来自于它实际上不是管理事务的框架,而是本地资源管理器。资源管理器是与之通信的数据源的实际提供者。例如,对于数据库,资源管理器是通过数据库驱动程序和DBMS实现的。对于JMS,资源管理器是通过特定的JMS提供程序实现的队列(或主题)连接工厂。使用本地事务模型,开发人员管理连接,而不是事务。实际管理本地事务的是DBMS或JMS提供程序。关于本地事务,事实是事务管理由底层数据库(DBMS)处理,如果是jms,则由底层消息传递提供程序处理。从开发人员的角度来看,我们不管理本地事务模型中的事务,而是管理连接。下面的代码示例演示了使用直接JDBC代码的本地事务模型的使用:

    public void updateTradeOrder(TradeOrderData order)
        throws Exception {
        DataSource ds = (DataSource) (new InitialContext()).lookup(
                "jdbc/MasterDS");
        Connection conn = ds.getConnection();
        conn.setAutoCommit(false);

        Statement stmt = conn.createStatement();
        String sql = "update trade_order ... ";

        try {
            stmt.executeUpdate(sql);
            conn.commit();
        } catch (Exception e) {
            conn.rollback();
            throw e;
        } finally {
            stmt.close();
            conn.close();
        }
    }

注意,在上面的示例中,使用connection.setautoCOMMIT(False)和connection.COMMIT()和connection.ROLLBACK()方法。setautoCOMMIT()方法是整个基于开发人员的连接管理的一个非常重要的部分。auto commit flag

告诉底层DBMS是否应该在执行每个SQL语句后立即提交连接。一个true值告诉DBMS在每个SQL语句执行后立即提交或回滚连接,而一个false值将保持连接活动,并且在执行显式commit()之前不提交更改。默认情况下,此标志通常设置为true。因此,默认情况下,如果我们有多个SQL更新,它们中的每一个都将被独立地执行和提交,并且connection.COMM T()和connection.Rollback()语句将被忽略。

对于Spring框架中的低级jdbc编码,您只需使用org.springframework.jdbc.datasource.datasourceutils,如下面的编码示例所示:

    public void updateTradeOrder(TradeOrderData order)
        throws Exception {
        Connection conn = DataSourceUtils.getConnection(dataSource);
        conn.setAutoCommit(false);

        Statement stmt = conn.createStatement();
        String sql = "update trade_order ... ";

        try {
            stmt.executeUpdate(sql);
            conn.commit();
        } catch (Exception e) {
            conn.rollback();
            throw e;
        } finally {
            stmt.close();
            conn.close();
        }
    }

在Spring中,数据源和相应的业务对象将在Spring配置文件中定义如下:

<bean id="datasource" class="org.springframework.jndi.JndiObjectFactoryBean"> 
 <property name="jndiName" value="jdbc/MasterDS"/>
</bean> 
<bean id="TradingService" class="com.trading.server.TradingService"> 
 <property name="dataSource"> 
     <ref local="datasource"/> 
 </property> 
</bean>

本地事务局限性

对于简单的更新和小的应用程序,本地事务模型工作得很好。然而,一旦我们给我们的应用程序增加了一些复杂性,这个模型就会崩溃。这个模型有几个限制,可能会对您的应用程序体系结构造成严重限制。

本地事务模型的第一个问题是,在编码连接逻辑时,开发人员有足够的空间出错。开发人员必须非常密切地注意自动提交标志设置,特别是在同一方法中进行多次更新时。此外,开发人员必须始终意识到正在调用的方法,以及这些方法是否管理连接。除非您有一个简单的应用程序,大多是单表更新,否则没有有效的方法。

本地事务模型的另一个问题是,当使用xa全局事务协调多个资源时,本地事务不能同时存在(我们将在第5章中更详细地看到这一点)。当协调多个资源时,如数据库和jms目的地(即。队列或主题)您不能使用本地事务模型,并且仍然保持ACID属性。考虑到这些约束和限制,本地事务模型应该只用于简单的基于Web的java应用程序,这些应用程序具有简单的表更新。

考虑到这些约束和限制,本地事务模型应该只用于简单的基于Web的java应用程序,这些应用程序具有简单的表更新。

编程式事务模型

借助javaee定义的统一的api,即JTA来管理事务。避免了由于本地事务模型的多样性(多实现的复杂性)带来的限制,如本地事务中的DB driver的不同实现,DBMS的不同,JMS的多样性实现等等。编程式事务模型和本地事务模型最大的不同就是,编程式事务模型管理的是事务,而不是连接。也即是,编程式封装了连接的逻辑

public void updateTradeOrder(TradeOrderData order) 
 throws Exception { 
 UserTransaction txn = sessionCtx.getUserTransaction(); 
 txn.begin(); 
 try { 
 TradeOrderDAO dao = new TradeOrderDAO(); 
 dao.updateTradeOrder(order); 
 txn.commit(); 
 } catch (Exception e) { 
 log.fatal(e); 
 txn.rollback(); 
 throw e; 
 } 
}

借助于编程式事务,开发者管理的是事务,而不是连接。开发者使用javax.transaction.UserTransaction接口的begin()方法来创建关联当前线程的事务,使用commit()和rollback()来提交和回滚

在使用编程事务时,开发人员必须密切注意事务管理,因为它涉及异常处理开发人员必须确保事务始终在启动事务的方法中终止。这往往比听起来的困难多一倍,特别是对于具有复杂异常处理的大型、复杂的应用程序。 

只有当您有很好的理由使用编程事务模型时,才应该使用该事务模型。有三个很好的原因是客户端发起的事务、本地化的JTA事务和长期运行的事务。除了这些场景之外,您应该使用声明性事务模型。

声明式事务模型

我们在编程事务模型中看到,开发人员必须使用BEGIN()、COMMIT()和ROLLBACK()方法显式启动事务并提交或回滚事务。

使用声明性事务模型,容器管理事务,这意味着开发人员不必编写java代码来启动或提交事务。但是,开发人员必须告诉容器如何管理事务。这是通过EJB-jar.xml部署描述符中的XML配置设置和特定于每个应用服务器(EJB)的扩展部署描述符,或者在applicationcontext.xml bean配置文件(Spring)中完成的。

声明性事务模型,也称为EJB世界中的容器管理事务(或CMT)。使用声明式模型,框架或容器管理事务的开始和停止(即提交或回滚)。开发人员只需要告诉框架什么时候在应用程序异常上回滚事务,并通过EJB中的XML部署描述符(例如EJB-jar.xml)或spring的bean定义文件(例如applicationcontext.xml)中的配置参数配置事务。开发者负责事务的开始和结束,在EJB中,这是通过UserTransactionManager接口完成的。使用start()方法启动事务,使用commit()或rollback()方法终止事务。在spring中,这是通过使用TransactionTemplate或通过位于org.springframework.transaction包中的平台事务管理器来完成的。

此图是根据springframework api整理

不管您使用的是什么框架,大多数企业级java应用程序都会利用java事务api(jta)进行事务管理。JTA是开发人员用来管理事务的接口。另一方面,java事务服务(J TS)是实现JTA的底层事务服务,被大多数商业和开放源应用服务器使用(请注意,除了可以使用的JTS之外,市场上还有其他事务服务)。认为JTA和JTS之间的关系类似于JDBC与相应的底层数据库驱动程序之间的关系;JTA对JDBC就像JTS对数据库驱动程序一样。该JTA可以通过商业应用服务器或开源事务管理器实现。

Java事务服务(Jts)是CORBA ots1.1规范(对象事务服务)的Java语言映射。作为一名开发人员,这并不是非常重要的,除非你是在玩一些古怪版本的琐碎追求或在一个真正艰难的工作面试。虽然J2EE没有强制使用jts,但对于异构实现之间的分布式事务的互操作性来说,jts是强制性的。由于JTA必须同时支持jts和非jts实现,所以仅仅通过查看JTA接口就很难知道实现所支持的确切功能。例如,尽管jts规范对嵌套事务具有可选的支持,但J2EE不支持此特性。

幸运的是,开发人员在处理事务时需要关注的接口不多。例如,在使用编程事务时,我们需要使用的惟一接口是javax.transaction.userTransaction接口。此接口允许我们以编程方式开始事务、提交事务、回滚事务并获取事务状态。在EJB中使用声明性事务时,我们主要关注ejbtext接口中的setrollbackonly()方法。

对于javax.Transaction包提供的接口,我们可以做更多的工作,但是开发人员不会经常使用这些接口。例如,我们将在本书中进一步看到,有时可能需要直接访问事务管理器。当我们使用声明性事务时需要手动挂起或恢复事务时,就会出现这些情况。我们还可以使用TransactionManager接口启动、提交和回滚事务。

用户事务接口仅在编程事务模型中使用,主要在EJB中。这个接口中开发人员需要关注的惟一方法是:

·BEGIN()

·COMMIT()

·ROLLBACK()

·getStatus()

javax.transaction.usertransaction.start()在编程事务模型中使用BEGIN()方法启动一个新事务并将事务与当前线程关联。如果在事务已经是8│的java事务设计策略支持作者时调用该方法,则此方法将引发NotSupportedException。

javax.transaction.transactionManager接口主要用于声明性事务模型中。UserTransactionManager接口能做的一切,transactionManager接口也能做。但是,对于大多数方法,最好使用usertransaction接口,除非需要暂停或恢复事务,否则不要使用transactionManager接口。

javax.transaction.TransactionManager.suspend()

在声明性事务模型或编程事务模型中,使用悬空()方法来挂起与当前线程关联的事务。此方法返回对当前事务的引用,如果没有与当前线程关联的事务,则返回NULL。如果需要挂起当前事务以执行与xa不兼容的代码或存储过程,则此方法非常有用。我们将在书的第五章中看到使用这种方法的一个例子。

javax.transaction.TransactionManager.resume()

在声明性或编程性事务模型中使用resume()方法来恢复先前挂起的事务。它以对先前挂起的事务的引用作为参数,将该事务与当前线程关联,然后继续该事务。

在声明性事务模型中使用setrollbackonly()方法通知容器,当前事务的唯一可能结果是回滚。这个方法的有趣之处在于,它实际上不会在调用时回滚事务;它只会标记事务回滚,此结果是在调用getStatus()时会返回一个STATUS_MARKED_ROLLBACK状态。

同样的结果也可以通过TransactionManager.setRollbackOnly()方法来实现,但是由于我们已经有了SessionContext或MessageDriveContext,所以使用这个方法是有意义的。

Status Interface

正如我们在上一节中所看到的,我们可以通过javax.transaction.Status接口获得事务的状态,这是从usertransaction.getstatus()方法获得的值的结果。我专门用一节来讨论事务状态,因为首先,它有点酷,其次,它为我们提供了许多关于当前事务状态的有用信息。以下值包含在状态接口中:

• STATUS_ACTIVE
• STATUS_COMMITTED
• STATUS_COMMITTING
• STATUS_MARKED_ROLLBACK
• STATUS_NO_TRANSACTION
• STATUS_PREPARED
• STATUS_PREPARING
• STATUS_ROLLEDBACK
• STATUS_ROLLING_BACK
• STATUS_UNKNOWN
在上面列出的所有状态值中,在大多数主流java业务应用程序中,唯一对开发人员真正有用的是status_active、status_mark_rollback和status_no_transaction。
STATUS_ACTIVE

there may be times when it is important to see if a current transaction is associated with the thread.

有时,查看当前事务是否与线程关联很重要。例如,出于调试和调优的目的,我们可能需要添加一个切面或拦截器来检查在查询操作期间是否存在一个事务。

使用这个切面和状态值,我们可以检测到对事务设计策略的可能优化。其他情况下,如果需要挂起当前事务或执行可能导致应用程序失败的代码(例如,执行xa下包含DDL代码的存储过程),则可能需要使用此状态。下面的代码片段显示了Status_Active Status值的用法:

...
if (txn.getStatus() ==
Status.STATUS_ACTIVE
)
 
logger.info("Transaction active in query operation");
...

在此示例中,如果在查询方法中有事务,我们将在日志文件中创建条目。这可能表示我们在不需要交易时进行交易,从而确定可能的优化机会或整个交易设计策略的问题。

STATUS_MARKED_ROLLBACK

在使用声明性事务模型时,此状态可能很有用。出于性能优化的原因,如果事务在以前的方法调用中被标记为回滚,我们可能希望跳过处理。因此,如果我们想查看事务是否被标记为回滚,我们可以检查它,如下面的代码示例所示:

...
if (txn.getStatus() ==
Status.STATUS_MARKED_ROLLBACK
)
throw new Exception(
"Further Processing Halted Due To Txn Rollback");
...
 
STATUS_NO_TRANSACTION
 
这种状态是有用的,因为它是确定是否真正没有事务上下文的唯一方法。就像status_active一样,这也可以用于调试或优化目的,以检查应该有事务的事务的存在。作为切面或拦截器(或内联代码),我们可以为那些需要更新的方法检测事务设计策略中可能的漏洞。下面的代码示例显示status_no_transaction值的使用:
 
...
if (txn.getStatus() ==
Status.STATUS_NO_TRANSACTION
)
throw new Exception(
"Transaction needed but none exists");
...
 

请注意,我们不能简单地检查状态是否等于STATUS_ACTIVE状态,因为缺少此状态并不表示缺少事务上下文。在上面列出的其他状态中可能有一个事务上下文。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!