多数据源情况下的事务管理,适用于部署到非应用服务器的Web应用和Standalone的应用程序
1. 环境
Spring + Hibernate + JOTM, Oracle Database
2. 场景用例
两个数据库分别存储User信息和Address信息
3. 代码及配置
1) carol.properties
#JNDI调用协议
carol.protocols=jrmp
#不使用CAROL JNDI封装器
carol.start.jndi=false
#不启动命名服务器
carol.start.ns=false
2) 简单的DAO层实现
public class BaseDaoImpl<T> extends HibernateTemplate implements BaseDao<T> { private Class<T> type; public BaseDaoImpl(Class<T> type) { this.type = type; } @Override public void saveEntity(T entity) { this.saveOrUpdate(entity); } }
3) 业务实现类
public class BusinessServiceImpl implements BusinessService{ private BaseDao<User> userDao; private BaseDao<Address> addressDao; @Override public void addUserAdressCombination(User user, Address address) { userDao.saveEntity(user); addressDao.saveEntity(address); } public BaseDao<User> getUserDao() { return userDao; } public void setUserDao(BaseDao<User> userDao) { this.userDao = userDao; } public BaseDao<Address> getAddressDao() { return addressDao; } public void setAddressDao(BaseDao<Address> addressDao) { this.addressDao = addressDao; } }
4) 简单的测试类
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"spring.xml"}); BusinessService bs = (BusinessService)ctx.getBean("businessService"); User user = new User(); user.setName("kevin"); user.setPassword("kevin"); Address address = new Address(); address.setCity("Shanghai"); //address.setStreet("Guo Shou Jing Road"); //Normal situation address.setStreet("Guo Shou Jing Road XXX Company"); // 字符串长度超过数据库中字段的长度 bs.addUserAdressCombination(user, address); } }
5)Spring配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" > <beans> <!-- 1. JOTM本地实例 --> <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" /> <!-- 2. JTA事务管理器 --> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <!-- 2.1:指定userTransaction属性 --> <property name="userTransaction" ref="jotm" /> </bean> <!-- 3. XAPool配置,内部包含了一个XA数据源,对应user数据库 --> <bean id="userDataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <!-- 3.1:内部XA数据源 --> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@192.168.0.33:1521:XE" /> </bean> </property> <property name="user" value="kevin" /> <property name="password" value="kevin" /> </bean> <!-- 4. 配置另一个XAPool,对应address数据库 --> <bean id="addressDataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@192.168.0.8:1521:XE" /> </bean> </property> <property name="user" value="kevin" /> <property name="password" value="kevin" /> </bean> <!-- 5. 配置对应userDataSource数据源的userSessionFactory --> <bean id="userSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref local="userDataSource" /> </property> <property name="mappingDirectoryLocations"> <list> <value>classpath:hbm</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.jdbc.batch_size">50</prop> <prop key="hibernate.cache.use_query_cache">true</prop> <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> </props> </property> <!-- 5.1 这里不要配,否则会报Could not find UserTransaction in JNDI [java:comp/UserTransaction] <property name="jtaTransactionManager"> <ref bean="jotm" /> </property> --> </bean> <!-- 6. 配置对应addressDataSource数据源的addressSessionFactory --> <bean id="addressSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref local="addressDataSource" /> </property> <property name="mappingDirectoryLocations"> <list> <value>classpath:hbm</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.jdbc.batch_size">50</prop> <prop key="hibernate.cache.use_query_cache">true</prop> <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> </props> </property> <!-- 6.1 这里不要配,否则会报Could not find UserTransaction in JNDI [java:comp/UserTransaction] <property name="jtaTransactionManager"> <ref bean="jotm" /> </property> --> </bean> <!-- 7. 对应userSessionFactory数据源的userDao --> <bean id="userDao" class="com.kevin.jotm.dao.impl.BaseDaoImpl"> <constructor-arg> <value>com.kevin.jotm.pojo.User</value> </constructor-arg> <property name="sessionFactory"> <ref bean="userSessionFactory"/> </property> </bean> <!-- 8. 对应addressSessionFactory数据源的addressDao --> <bean id="addressDao" class="com.kevin.jotm.dao.impl.BaseDaoImpl"> <constructor-arg> <value>com.kevin.jotm.pojo.Address</value> </constructor-arg> <property name="sessionFactory"> <ref bean="addressSessionFactory"/> </property> </bean> <!-- 9. 进行跨数据库JTA事务的业务类 --> <bean id="businessService" class="com.kevin.jotm.service.impl.BusinessServiceImpl"> <property name="userDao" ref="userDao" /> <property name="addressDao" ref="addressDao" /> </bean> <!-- 10. 事务拦截器 --> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="txManager" /> <property name="transactionAttributes"> <props> <prop key="*">-Exception</prop> </props> </property> </bean> <!-- 11. 事务增强器 --> <bean id="transactionAdvisor" class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor" /> </bean> <!-- 12. 自动代理 --> <bean id="beanproxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <value>*Service</value> </property> <property name="interceptorNames"> <list> <value>transactionAdvisor</value> </list> </property> </bean> </beans>
以上代码已经过测试可以实现分布式事务管理。 为了对比,曾试过使用BasicDataSource且不使用JOTM,使用org.springframework.orm.hibernate3.HibernateTransactionManager管理多数据源情况下的事务,开始时看到异常情况下也可以同时回滚,但是后来发现是Hibernate缓存造成的假象,原因是:默认情况下,Hibernate 要在事务提交时才将数据的更改同步到数据库中,而事务提交发生在业务方法返回前,如果有异常,方法没有正常返回,User信息没有被同步到数据库而不是被回滚掉。这时需要调用 flush() 方法将数据更改同步到数据库才能比较出两种情况下的不同。