Spring配置多数据源以及基于Atomikos的事务一致性配置

开发生涯第一篇博客,请各位同仁多多指教微笑

    最近项目中需要用到多个数据源,查阅资料后完成了配置,包括多数据源对应的分布式事务的配置,这里用的是Atomikos,Atomikos是一款Java/JTA 事务处理工具,可以在多数据源的环境中进行分布式事务管理。在该博文中只配置2个mysql的数据源作示范,其他的诸如SqlServer,oracle等数据库的配置请查阅相关资料。

    首先,集成Atomikos需用用到的jar包可以到该链接进行下载,请点击此处下载

    集成了Atomikos之后,再在项目的applicationContext.xml文件中作如下配置(这里只给出与数据源和事务相关的配置,其他的基础配置请查阅相关资料):

1、数据源配置(mysql的连接属性这里是另外写了一个.properties配置文件,然后用el表达式访问的):

<bean id="MSDataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" 
        init-method="init" destroy-method="close">
        <!-- Set unique name for this DataSource -->  
        <property name="uniqueResourceName"><value>ms1</value></property>
        <!-- Set XADatasource class name-->  
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="xaProperties">
            <props>
                <prop key="user">${database1.user}</prop>
                <prop key="password">${database1.passwd}</prop>
                <prop key="url">jdbc:mysql://${database1.url}:${database1.port}/${database1.name}</prop>
            </props>
        </property>
        <!-- set properties for datasource connection pool -->  
        <property name="poolSize" value="3" />
        <!-- 管理 Connection 被占用的时间 -->
        <!-- 如果不设置这个值,Atomikos使用默认的300秒(即5分钟),那么在处理大批量数据读取的时候,一旦超过5分钟,就会抛出类似 Resultset is close 的错误 -->
        <property name="reapTimeout"><value>20000</value></property>  
    </bean>
    
    <bean id="MSDataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" 
        init-method="init" destroy-method="close">
        <!-- Set unique name for this DataSource -->  
        <property name="uniqueResourceName"><value>ms2</value></property>
        <!-- Set XADatasource class name-->  
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="xaProperties">
            <props>
                <prop key="user">${database2.user}</prop>
                <prop key="password">${database2.passwd}</prop>
                <prop key="url">jdbc:mysql://${database2.url}:${database2.port}/${database2.name}</prop>
            </props>
        </property>
        <!-- set properties for datasource connection pool -->  
        <property name="poolSize" value="3" />
        <!-- 管理 Connection 被占用的时间 -->
        <!-- 如果不设置这个值,Atomikos使用默认的300秒(即5分钟),那么在处理大批量数据读取的时候,一旦超过5分钟,就会抛出类似 Resultset is close 的错误 -->
        <property name="reapTimeout"><value>20000</value></property>  
    </bean>
2、SessionFactory配置:
<bean id="MSSessionFactory1"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<!-- 				<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
				<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop> -->
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.use_outer_join">true</prop>
				<prop key="hibernate.jdbc.fetch_size">30</prop>
				<prop key="hibernate.jdbc.batch_size">30</prop>
				<prop key="hibernate.jdbc.batch_versioned_data">true</prop>
				<prop key="hibernate.cache.use_second_level_cache">true</prop>
				<prop key="hibernate.cache.use_query_cache">true</prop>
				<prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop>
				<prop key="hibernate.cache.provider_configuration_file_resource_path">/resources/ehcache-cabinet-eip.xml</prop>
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
				</prop>
				<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
				</prop>
				<prop key="javax.persistence.validation.mode">none</prop>
			</props>
		</property>
		<property name="dataSource" ref="MSDataSource1" />
		<property name="packagesToScan">
			<list>
				<value>com.tieline.model</value>
			</list>
		</property>
	</bean>
	
	<bean id="MSSessionFactory2"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
				<!-- <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
				<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop> -->
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.use_outer_join">true</prop>
				<prop key="hibernate.jdbc.fetch_size">30</prop>
				<prop key="hibernate.jdbc.batch_size">30</prop>
				<prop key="hibernate.jdbc.batch_versioned_data">true</prop>
				<prop key="hibernate.cache.use_second_level_cache">true</prop>
				<prop key="hibernate.cache.use_query_cache">true</prop>
				<prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop>
				<prop key="hibernate.cache.provider_configuration_file_resource_path">/resources/ehcache-cabinet-eip.xml</prop>
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
				</prop>
				<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
				</prop>
				<prop key="javax.persistence.validation.mode">none</prop>
			</props>
		</property>
		<property name="dataSource" ref="MSDataSource2" />
		<property name="packagesToScan">
			<list>
				<value>com.tieline.model</value>
			</list>
		</property>
	</bean>

3、事务配置

	<!-- atomikos事务管理器 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" 
        init-method="init" destroy-method="close">
        <property name="forceShutdown">
            <value>true</value>
        </property>
    </bean>

    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300" />
    </bean>

    <!-- spring 事务管理器 -->
    <bean id="springTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction" />
        <property name="allowCustomIsolationLevels" value="true"/> 
    </bean>

    <!-- 使用annotation定义事务,对于要加入事物的类,只需对该类加 @Transactional  -->
    <tx:annotation-driven transaction-manager="springTransactionManager" />

接下来,我们写两个与上述两个数据源相对应的dao,获取访问这两个数据源的session对象,代码如下(接口省略):

package core.dao
import java.lang.reflect.ParameterizedType;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import core.dao.BaseDao;

@Repository
public class BaseDaoImpl<E> implements BaseDao<E> {

	private SessionFactory sessionFactory;
	protected Class<E> entityClass;
	
	@SuppressWarnings("unchecked")
	public BaseDaoHisImpl() {
		ParameterizedType superClass = (ParameterizedType) this.getClass().getGenericSuperclass();
		this.entityClass = (Class<E>) superClass.getActualTypeArguments()[0];
	}

	public SessionFactory getSessionFactory() {
		return this.sessionFactory;
	}

	@Resource(name = "MSSessionFactory1")
	public void setSF(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

}

package core.dao
import java.lang.reflect.ParameterizedType;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import core.dao.BaseDao;

@Repository
public class BaseDaoHisImpl<E> implements BaseDaoHis<E> {

	private SessionFactory sessionFactory;
	protected Class<E> entityClass;
	
	@SuppressWarnings("unchecked")
	public BaseDaoHisImpl() {
		ParameterizedType superClass = (ParameterizedType) this.getClass().getGenericSuperclass();
		this.entityClass = (Class<E>) superClass.getActualTypeArguments()[0];
	}

	public SessionFactory getSessionFactory() {
		return this.sessionFactory;
	}

	@Resource(name = "MSSessionFactory2")
	public void setSF(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

}


有了上述两个基础类,我们就可以在业务类中调用getSession()方法来获取session对象来访问数据库了,当然了,也可以在这两个基类里封装一些公用性的增删改查的方法,供给业务类调用,代码编写完成之后,测试如下:在同一个service方法里通过这两个基类去访问那两个数据源,且在方法上打上@Transactional来开启事务,先在第一个dao里对第一个库做了一个正常的插入,然后在第二个dao里对第二个库做了一个插入,且另外写了一个1/0的表达式来制造一个异常,结果发现对第一个库的插入同样并没有生效,发生了回滚,也就是说,对两个库的操作保持了事务一致性。

    最后,还有一个问题,按上述步骤启动项目没有任何问题,但是在浏览器打开时报了找不到sessionFactory这个bean的异常,这个是OpenSessionInViewFilter的问题。这个类中有个属性叫sessionFactoryBeanName默认值为sessionFactory,而我在上述配置中的SessionFactory不叫这个名字,查看API有如下解释:
Looks up the SessionFactory in Spring's root web application context. Supports a "sessionFactoryBeanName"
 filter init-param in web.xml; the default bean name is "sessionFactory". Looks up the SessionFactory on each request,
 to avoid initialization order issues (when using ContextLoaderServlet, the root application context will get initialized after this filter).
 问题就出在这里,我们在web.xml中配置OpenSessionInViewFilter这个过滤器的时候,需要指定一个默认的SessionFactory的名字,要不然它就默认为sessionFactory,我们上面的配置中取的名字是MSSessionFactory1和MSSessionFactory2,随便取一个,放在OpenSessionInViewFilter过滤器的参数那里就可以了,代码如下:

<filter>
    <filter-name>openSessionInView</filter-name>
    <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
    <async-supported>true</async-supported>
    <init-param>  
        <param-name>sessionFactoryBeanName</param-name>  
        <param-value>MSSessionFactory1</param-value>  
    </init-param> 
  </filter>
  <filter-mapping>
    <filter-name>openSessionInView</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
至此,spring多数据源以及事务的配置就全部结束了,欢迎各位道友沟通交流微笑





展开阅读全文

没有更多推荐了,返回首页