Spring + Hibernate + JOTM 分布式事宜配置

Spring + Hibernate + JOTM 分布式事务配置

多数据源情况下的事务管理,适用于部署到非应用服务器的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() 方法将数据更改同步到数据库才能比较出两种情况下的不同。