JTA事务总结
言归正传,让我们回到今天的主题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);进行手工设置。
实际使用中发现,如果用weblogic的JTA的话,需要用weblogic的datasource的Connection,否则update操作不会成功。
Context ctx = new InitialContext();
Session session = sessionFactory.openSession(((DataSource)ctx.lookup("mydatasource")).getConnection));