JTA事务总结

今天在weblogic在对几种JTA的应用场景进行了测试,总结如下:   

测试代码片段:

public void doTest()throws Exception{
        UserTransaction tx = null;
        try{
                Context ctx = new InitialContext();
                tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");                  
                tx.begin();
 
                doResource1();                 
                doResource2();                   
                
                tx.commit();            
            }catch(Exception e){
                e.printStackTrace();
                if(tx != null){
                    tx.rollback();
                }
            } 
    }

    private void doResource1()throws Exception{
            ...
            DataSource ds = (DataSource) ctx.lookup("A|B|C|D|E");                           
            con = ds.getConnection();     
            ...
    }

    private void doResource2()throws Exception{
            ...
            DataSource ds = (DataSource) ctx.lookup("A|B|C|D|E");                           
            con = ds.getConnection();     
            ...
    }

共创建了五个连接池以及对应的数据源

A: OracleLocalDataSource  
B: OracleLocalDataSource2
C: OracleXADataSource
D: OracleXADataSource2
E: MysqlLocalDataSource

A、B:【oracle.jdbc.driver.OracleDriver】
C、D:【oracle.jdbc.xa.client.OracleXADataSource】
E:【org.gjt.mm.mysql.Driver】

Mysql没有支持分布式的XA驱动程序


对于doResource1和doResource2所用不同数据源类型搭配测试情况如下:

[A-A]:运行成功,且weblogic进行了优化,两次操作获得的con其实是同一个连接对象
[A-B]:运行失败,不允许在一次事务中从不同的数据库获取local事务类型的连接
[A-E]:运行失败,不允许在一次事务中从不同的数据库获取local事务类型的连接
[A-D]:在允许Emulate Two-Phase Commit for non-XA Driver的情况下成功,否则失败
[C-C]:运行成功,但是不像(A-A)的情况,两次获得con的对象不是同一个对象,weblogic并为对该情况进行优化
[C-D]:运行成功,这是典型的2PC(two-phase transaction commit process)用于分布式多资源的应用情况

对于C、D的XA类型驱动程序,weblogic默认不允许通过con.setAutoCommit(false)的方式进行事务启动,可以通过允许(Supports Local Transaction)选项启动本地事务。


记得EJB的部署文件的事务属性<trans-attribute>RequiresNew</trans-attribute>的情况,在调用该EJB函数时如果已经存在一个事务进行中,那么要求容器挂起该事务,启动新的事务进行函数调用,直到函数结束后,再恢复原来的事务继续进行。

也许你会想到用以下的方式进行:

UserTransaction tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
UserTransaction tx2 = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");

tx.begin();

tx2.begin();

tx2.commit();

tx.commit();

以上代码会抛出如下异常:
javax.transaction.NotSupportedException: Another transaction is associated with this thread.

查找了sun的 JTA specification 说明如下:
The UserTransaction.begin method starts a global transaction and associates the transaction with the calling thread. The transaction-to-thread association is managed transparently by the Transaction Manager. Support for nested tranactions is not required. The UserTransaction.begin method throws the NotSupportedException when the calling thread is already associated with a transaction and the transaction manager implementation does not support nested transactions.

看来weblogic的Transaction没有实现嵌套功能,那么容器如何RequiresNew的ejb事务情况呢,就得借助于TransactionManager类了

tm = (TransactionManager)ctx.lookup("javax.transaction.TransactionManager");                             
tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
tx.begin();                                     
   ...
transaction = tm.suspend();
doNestedTransaction();                   
tm.resume(transaction);
   ...
tx.commit();             

其实以上问题的顺利解决都归功于sun完整的官方资料,想想从事Java快两年了我的大部分知识都是来源于“东拼西凑”:书籍、论坛、项目...看来是该花点时间好好读读sun网站的各种specification了。

http://java.sun.com/products/jta/index.html


今天对XA和非XA驱动进行了更进一步的研究,终于搞清了以前些模菱两可的问题。

通过测试得知,在JTA事务中(包括JDBC事务以及非事务环境下),应用程序操作的connection其实都是weblogic的包装类

[A、B]:

weblogic.jdbc.wrapper.JTSConnection_weblogic_jdbc_oracle_OracleConnection

[C、D]:

weblogic.jdbc.wrapper.JTAConnection_weblogic_jdbc_wrapper_XAConnection_weblogic_jdbcx_base_BaseConnectionWrapper

[E]:

weblogic.jdbc.wrapper.JTSConnection_com_mysql_jdbc_Connection

 

由于XA驱动实现了XAResource的接口,所以能参与完全的2PC协议,保证多资源参与的事务的原子操作。但是非XA驱动没有实现XAResource的接口的接口,无法完全参与2PC协议,所以在分布式事务(即JTA事务)中weblogic实现了非XA驱动的XAResource包装(wrapper),用来模拟(或者说欺骗TM^_^)两步提交,在...\bea\weblogic81\server\lib\weblogic.jar类包中,通过反编译weblogic.jdbc.wrapper.JTSConnection和weblogic.jdbc.wrapperJTSXAResourceImpl类得到如下代码片段:

 

JTSXAResourceImpl源码片段:
    public int prepare(Xid xid)throws XAException
    {
        if(!jtsConn.getEnable2PC())
            throw new XAException("JDBC driver does not support XA ... );
        else
            return 0;
    }

    public void commit(Xid xid, boolean flag)throws XAException
    {
        ...
        jtsConn.internalCommit();
        ...
    }
    public void rollback(Xid xid)throws XAException
    {
        ...
        jtsConn.internalRollback();
        ...
    }

 

JTSConnection源码片段:
    public synchronized void internalRollback() throws SQLException
    {
        ...
        connection.rollback();
        ...
        internalClose();
        ...
    }

    public void internalCommit() throws SQLException
    {
        ...
        connection.commit();
        ...
        internalClose();
        ...
    }

 

可知如果非XA驱动允许两步提交时(enableTwoPhaseCommit),当TransactionManager调用prepare时XAResource包装类只是简单的返回XA_OK(0),当TransactionManager调用commit或rollback时XAResource包装类将代理调用非XA驱动的JDBC connection的commit或rollback,所以如果在commit或rollback出现异常后,应用程序的数据将有可能处于不一致的状态(其实如果XA驱动在TM调用XAResource的commit或者rollback时出错,系统数据也会处于不一致的状态,不过出现这种情况的概率是微乎其微的,如果想搞三步、四步...提交的话,就会进入鸡生蛋、蛋生鸡的无休止讨论的问题了^_^)。

 

接下来让我们研究一下连接关闭的问题,细心的人也许早已发现JTA与JDBC的事务对于应用层的操作有点“自相矛盾”,JDBC的先获得con在setAutoCommit(false)启动事务,然后关闭con,在commit或者rollback事务,然而JTA的顺序正好相反,先tx.begin(),再获取con,然后关闭con,最后再tx.commit或者rollback。(这里有句话您看完一下内容后会认同的:For both non-XA and XA driver, you can close the connection after the distributed transaction is completed.)

当应用程序调用Connection.close时容器通过注册的ConnectionEventListener事件通知TransactionManager,以便TransactionManager结束Connection对应的XAResource对象的事务分支(end函数调用),对于XA驱动的连接此时在Connection.close之后即可将该连接返回XA连接池供其他业务使用。

所以JTA spec中的提示到:

A distributed transaction may still be active after a participating Connection object is closed. This is not true for local transactions。

但是对于非XA驱动调用Connection.close后的情况将有所有区别,由于XA驱动需要用connection进行最后的commit或rollback,所以应用程序调用Connection.close之后只是对与应用不能再使用包装的Connection,但容器并没有真正将连接返回连接池,而是在调用XAResource包装类的commit和rollback时,进而调用JTSConnection的internalCommit和internalRollback,最终再JTSConnection的这两个函数中internalClose将非XA的连接释放到连接池中。

所以weblogic的资料(http://edocs.bea.com/wls/docs60/faq/transactions.html)中说:
the number of active distributed transactions using the non-XA
connection pool is limited by the maximum capacity of the JDBC connection pool

When you use an XA driver, the connection management is more scalable. WLS does not hold on to the same physical XA connection until the transaction is committed or rolled back. In fact, in most cases, the XA connection as only held for the duration of a method invocation. WLS JDBC wrappers intercept all JDBC calls and enlist the XAResource associated with the XA connection on demand. When the method invocation returns to the caller, or when it makes another call to another server, WLS delists the XAResource associated with the XA connection.

WLS also returns the XA connection to the connection pool on delistment if there are no open result sets. Also, during commit processing, any XAResource object can be used to commit any number of distributed transactions in parallel. As a result, neither the number of active distributed transactions using the XA connection pool nor the number of concurrent commit/rollbacks is limited by the maximum capacity of the connection pool. Only the number of concurrent database access connections is limited by the maximum capacity of the connection pool.

对于以上XA驱动的con在关闭后,不必等待事务结束即释放回连接池的推论,我在weblogic上对oracle做了些好像没有效果,weblogic中有个 Keep XA Connection Till Transaction Complete 的选项我也没有选中啊。不知是我对JTA的理解有误,还是说与我用的数据库驱动或者是weblogic的实现有关,要是有谁测出了效果还请指点指点小弟。

最后请注意:除非将XA驱动的连接池设置允许【Supports Local Transaction】选项,否则在非事务的环境下对con进行的操作将抛出如下错误,甚至是getAutoCommit()操作

刚看完《hibernate in action》,前段时间hiberante3又发布了3.0.2版,对于hibernate的这种更新路线我还是比较喜欢的,2.x的版本继续更新发展这对于已经在项目中应用hibernate的人来说是再庆幸不过的了(不过这也许是废话,如果GAVIN KING不继续发展2.x谁还敢用3.x,因为...4.x...),3版本是不兼容2的,整体包名都进行了彻底的变动,如果想移植的话其实工作量也不大很多接口都是一样的,配置文件也尽量保持以前的规范,而且3会有更多令人兴奋的新特性,这种走不同版本分支、不拖泥带水的革新,至少让我感觉比一味坚持要兼容老版本的EJB3来得爽!

 

言归正传,让我们回到今天的主题JTA,当然开头白并非废话,因为今天讲的是hibernate中的JTA事务应用。

////////////// 以下为hibernate中的经典事务操作步骤  //////////////

Session sess = factory.openSession();
Transaction tx = null;
try {
        tx = sess.beginTransaction();
        // do some work
        ...
        tx.commit();
}
catch (RuntimeException e) {
        if (tx != null) tx.rollback();
        throw e; // or display error message
}
finally {
        sess.close();
}

hibernate2中只有JDBCTransaction和JTATransaction两种事务类,对于使用JTA事务的应用,
应该说JTATransaction适用于绝大部分情况,但是CMT情况将存在一些例外:

////////// begin片段 //////////
        newTransaction = ut.getStatus() == Status.STATUS_NO_TRANSACTION;
        if (newTransaction) {
                ut.begin();
                log.debug("Began a new JTA transaction");
        }
////////// commit片段 //////////
        if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush();
        if (newTransaction) {
                try {
                        log.debug("Committing UserTransaction started by Hibernate");
                        ut.commit();
                }
////////// rollback片段 //////////
        if (newTransaction) {
                if (!commitFailed) ut.rollback();
        }
        else {
                ut.setRollbackOnly();
        }

通过以上JTATransaction源码可知,如果调用session.beginTransaction()时已经处于事务状态,hibernate只是简单的加入这个事务,这对于Requires和RequiresNew等情况是没有问题的,但是如果调用session.beginTransaction()时不处于事务环境,那么JTATransaction将启动事务。这对于一个通过容器管理(CMT)事务的且不需要启事务的EJB调用将会存在矛盾。当然对于CMT的情况你可以不调用任何hibernate的Transaction事务函数,只通过session进行CRUD(create、read、update、delete)操作,避开这个例外的情况,但是这存在一个新问题:一般的hibernate操作代码并不直接手工调用session.flush,而是通过tx.commit时由hibernate内部自动进行flush,所以如果想用以上的小伎俩,那么请记得在操作最后手工添加session.flush函数。这样CMT中的代码片段可以改造如下:

Session sess = factory.openSession();
try {
        // do some work
        ...
        session.flush();
}
catch (RuntimeException e) {
        context.setRollbackOnly();
        throw e; // or display error message
}
finally {
        sess.close();
}


为了解决以上问题hibernate3进行了改进,增加了CMTTransaction类,以及两个新属性:
1:【hibernate.transaction.flush_before_completion】
If enabled, the session will be automatically flushed during the before completion
phase of the transaction. (Very useful when using Hibernate with CMT.)

2:【hibernate.transaction.auto_close_session】
If enabled, the session will be automatically closed during the before completion
phase of the transaction.(Very useful when using Hibernate with CMT.)

所以对于CMT的情况,只要将以上两个参数设置为true,hibernate自动会在事务提交时进行flush和close,具体实现细节可以看看org.hibernate.transaction.CacheSynchronization的实现和
JDBCContext中的registerSynchronizationIfPossible函数。由于CMT情况下容器对于RuntimeException的异常将进行事务回滚,所以可以通过在需要回滚事务时抛出RuntimeException类型的异常,那么甚至可以完全不用操作hibernate的Transaction类的任何API。

对于JTA的配置问题注意以下几点:

tx = (UserTransaction) ctx.lookup("javax.transaction.UserTransaction");
tx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");

对于weblogic由于以上两种方式都能找到JTA事务对象,而hibernate默认的查找名如下为【java:comp/UserTransaction】,所以在weblogic中设置启用JTA方式,只需要配置【hibernate.transaction.factory_class】属性为【org.hibernate.transaction.JTATransactionFactory】,【hibernate.transaction.manager_lookup_class】属性不配也行。

还有一种方式通过配置如下属性,可以指定UserTransaction的JNDI名
<property name="jta.UserTransaction">javax.transaction.UserTransaction</property>

另外还有一个JTA调用超时问题,对于weblogic默认是30秒,可以通过控制台进行动态配置,如果事务处理操作配置的超时时间对于weblogic的情况,容器将会自动调用internalRollback()。

最后一点需要注意的是数据库事务隔离级别的设置:

----------------------------------------------------------------
■ Read uncommitted:
   这个级别是不安全的,事务中的查询会读到当前其它事务正在设置当时还未提交的数据

■ Read committed:
   这个级别相对比较安全,事务中读到的数据都是已经得到提交的数据,但是如果两次读取同一个条记录,但是在两次读取的过程中有另外的事务更改了改记录并成功提交的话,则会出现同一事务中两次读取同一条记录数据不一致的情况。这种情况很少出现,因为一般同一事务的程序不会重复读取同一条记录,如果用hibernate就更安全了,hibernate的一级缓存不会让程序向数据库两次读取同一条记录。

■ Repeatable read:
   这个级别解决了同一事务两次读取数据结果不同的情况,这个级别也是很多数据库的默认事务级别(如mysql)

■ Serializable:
   这个级别会使所有事务串行化工作,每个事务只能排队进行数据库操作,类似单线程的Servlet的工作机制,这样在并非量较高的数据库访问时,数据库操作效率将极其底下,应该避免使用
----------------------------------------------------------------
hibernate中通过【hibernate.connection.isolation】属性进行设置,但是如果hibernate的数据库连接是通过数据源获得的话,hibernate则不再对事务隔离级别进行设置,所以对于数据源的到的数据库连接只能通过设置应用服务器配置数据库隔离级别,当然也可以通过Connection con = session.connection();con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);进行手工设置。



转载于:https://my.oschina.net/pangzhuzhu/blog/318136

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值