今天下午开始尝试将项目的transaction交给Glassfish的JTA管理,因为之后会使用到JMS,需要与JDBC组成跨data source的事务。但是不知道是没人这么干过还是大家不屑于将完整的配置过程就下来,JBoss的官方文档、Spring的官方文档、SOF都没有可用的配置建议。经过差不多半天时间的Google和尝试,终于配置成功,在此分享
环境:
- Spring 3.1.1
- Hibernate 4.1.1
- Glassfish 3.1.2
应该都是最新的版本,Spring配置文件如下:
| 01 | ... |
| 02 | <tx:annotation-driven /><!-- 打开Spring的@Transaction声明式事务支持 --> |
| 03 | <tx:jta-transaction-manager /><!-- 配置Spring使用JTA事务 --> |
| 04 | |
| 05 | <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> |
| 06 | ... |
| 07 | <property name="hibernateProperties"> |
| 08 | <value> |
| 09 | ... |
| 10 | hibernate.current_session_context_class=jta<!-- 1 --> |
| 11 | hibernate.transaction.manager_lookup_class=org.hibernate.transaction.SunONETransactionManagerLookup<!-- 2 --> |
| 12 | </value> |
| 13 | </property> |
| 14 | </bean> |
| 15 | ... |
关键配置在1和2处:
根据Hibernate官方文档此处和此处的描述:
When configuring Hibernate’s transaction factory, chooseorg.hibernate.transaction.JTATransactionFactory if you use JTA directly (BMT), and org.hibernate.transaction.CMTTransactionFactory in a CMT session bean. Remember to also set hibernate.transaction.manager_lookup_class. Ensure that your hibernate.current_session_context_class is either unset (backwards compatibility), or is set to "jta".
⋯⋯
See the Javadocs for the org.hibernate.context.spi.CurrentSessionContext interface for a detailed discussion of its contract. It defines a single method, currentSession(), by which the implementation is responsible for tracking the current contextual session. Out-of-the-box, Hibernate comes with three implementations of this interface:
org.hibernate.context.internal.JTASessionContext: current sessions are tracked and scoped by a JTAtransaction. The processing here is exactly the same as in the older JTA-only approach. See the Javadocs for details.
org.hibernate.context.internal.ThreadLocalSessionContext:current sessions are tracked by thread of execution. See the Javadocs for details.
org.hibernate.context.internal.ManagedSessionContext: current sessions are tracked by thread of execution. However, you are responsible to bind and unbind a Session instance with static methods on this class: it does not open, flush, or close a Session.
即需要设置hibernate.current_session_context_class为jta,同时设置hibernate.transaction.manager_lookup_class。但是这里耽误了我半天的就是hibernate.transaction.manager_lookup_class的设置,这里只说“需要设置”,但是没有给出具体的值,虽然文档中给出了一个TransactionManagerLookup接口的实现类列表,但是其中没有Glassfish⋯⋯
又经过了N久的Google,在这里和这里找到了一个神奇的实现类:org.hibernate.transaction.SunONETransactionManagerLookup。根据Javadoc,此类实现了“for Sun ONE Application Server 7 and above”的查找策略,想想GF的前身即是Sun AS,应该可以使用。试了一下,终于work~
这里遗留的一个问题是,我并没有在Hibernate 4.1.1的源代码里找到org.hibernate.transaction.SunONETransactionManagerLookup(上面的Javadoc也是3.6的Javadoc),甚至整个org.hibernate.transaction包下面只有TransactionManagerLookup接口的定义。因此究竟是怎么work的我没有找到最终答案,如果有人知道,希望可以在这里留下答案
2012-4-16补:今天启动服务器时偶然间发现抛出个warning,hibernate.transaction.manager_lookup_class在Hibernate 4中已被deprecated掉,需要使用
| 1 | hibernate.transaction.jta.platform=org.hibernate.service.jta.platform.internal.SunOneJtaPlatform |
替换
2012-4-17补:今天发现Hibernate的自动flush失效,查看GF启动日志后发现一个新的warning:
JTASessionContext being used with JDBCTransactionFactory; auto-flush will not operate correctly with getCurrentSession()
查文档后发现上面的1、2处需要指定TransactionFactory,否则Hibernate将默认使用JDBCTransactionFactory:
| 1 | hibernate.transaction.factory_class=org.hibernate.transaction.JTATransactionFactory |
问题解决。这次的经验同样是,虽然app server启动时一般会输出大量log,但是要让自己养成从中发现warning级别及以上信息的洞察力,这样解决问题将事半功倍
2012-4-22补:今天又遇到了问题,在使用Query.iterate()方法时,Hibernate抛出异常:
org.hibernate.HibernateException: proxy handle is no longer valid
根据这里的讨论,把JTATransactionFactory换成了CMTTransactionFactory,问题解决:
| 1 | hibernate.transaction.factory_class=org.hibernate.transaction.CMTTransactionFactory |