Spring分布式事务在service中动态切换数据源

项目采用的是struts2+spring+ibatis架构,下面是关键部分代码:

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd" 
		   default-autowire="byName" default-lazy-init="false">
    
	<context:component-scan base-package="com.ssi.*" />
	<!-- 属性文件读入 -->
	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath*:jdbc.properties</value>
			</list>
		</property>
	</bean>
	
	
	<!-- 配置sqlMapclient -->
	<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" />
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<bean id="sqlMapClient1" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" />
		<property name="dataSource" ref="db1" />
	</bean>
	<bean id="sqlMapClient2" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" />
		<property name="dataSource" ref="db2" />
	</bean>
	<bean id="sqlMapClientCenter" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" />
		<property name="dataSource" ref="center" />
	</bean>
	
	<bean id="dynamicSqlMapClientDaoSupport" class="com.ssi.dao.DynamicSqlClientDaoSupport">  
        <property name="targetSqlMapClients">  
            <map>  
                <entry key="db1" value-ref="sqlMapClient1" />  
                <entry key="db2" value-ref="sqlMapClient2" />  
                <entry key="center" value-ref="sqlMapClientCenter" />  
            </map>  
        </property>  
        <property name="defaultSqlMapClient" ref="sqlMapClientCenter" />  
    </bean>  
    <bean id="ibatisDaoSupport" class="com.ssi.dao.IbatisDaoSupport" parent="dynamicSqlMapClientDaoSupport"></bean>
    
    <bean id="userDao" class="com.ssi.dao.impl.UserDaoImpl" parent="ibatisDaoSupport"></bean>

	<!-- 支持 @AspectJ 标记-->
	<aop:aspectj-autoproxy proxy-target-class="true"/>

	<!-- 配置JTA的事务管理器 -->   
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"    init-method="init" destroy-method="close">   
        <property name="forceShutdown" value="true" />   
    </bean>   
    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">   
        <property name="transactionTimeout" value="300" />   
    </bean>   
    <bean id="springTransactionManager"  class="org.springframework.transaction.jta.JtaTransactionManager">   
        <property name="transactionManager" ref="atomikosTransactionManager" />   
        <property name="userTransaction" ref="atomikosUserTransaction" />   
    </bean>
	<!-- 配置通知 -->
	<tx:advice id="txAdvice" transaction-manager="springTransactionManager">
		<tx:attributes>
			 <tx:method name="*" rollback-for="Exception,RuntimeException,com.ssi.exception.SystemException" propagation="REQUIRED" />
		</tx:attributes>
	</tx:advice>
	

	
	<!-- 以AspectJ方式 定义 AOP --> 

	<aop:config>
		<aop:advisor pointcut="execution(* com.ssi.service..*Service*.*(..))" advice-ref="txAdvice" />
	</aop:config>
	
	
	<!-- spring 定时器任务开始 -->
 	<bean name="job" class="org.springframework.scheduling.quartz.JobDetailBean">   
 	     <property name="jobClass">   
 	         <value>com.ssi.action.TimerAction</value>  
 	     </property>   
 	     <property name="jobDataAsMap">  
 	         <map> 
 	              <!-- timeout属性设定了当服务器启动后过10秒钟首次调用你的JobAction -->
 	              <entry key="timeout">   
 	                 <value>10</value>  
 	              </entry>  
 	         </map>   
 	     </property>   
 	</bean>  
 	<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">   
 	     <property name="jobDetail">   
 	         <ref bean="job"/>   
 	     </property>   
 	     <property name="cronExpression">   
 	         <value>0 53 15 ? * MON-FRI</value>  
 	     </property>   
 	</bean>   
 	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" autowire="no">  
 	     <property name="triggers">   
 	         <list>   
 	             <ref local="cronTrigger"/>  
 	         </list>  
 	     </property>   
 	</bean> 
	<!-- spring 定时器任务结束 -->
	
</beans>


applicationContext-datasource.xml 
 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
	<!--指定Spring配置中用到的属性文件-->
	<bean id="propertyConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:jdbc.properties</value>
			</list>
		</property>
	</bean>
	<!-- JTA 数据源配置 -->
	<bean id="center" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/center</value>
		</property>
		<property name="xaDataSourceClassName">
			<value>${jta.driver.className}</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="url">${center.jdbc.driver.url}</prop>
				<prop key="user">${center.sql.user.name}</prop>
				<prop key="password">${center.sql.user.password}</prop>
			</props>
		</property>
		<property name="testQuery" value="select 1" />
		<property name="poolSize">
			<value>${poolsize}</value>
		</property>
		<property name="maxPoolSize">
			<value>${maxPoolSize}</value>
		</property>
		<property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property>
	</bean>
	
	<bean id="db1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/db1</value>
		</property>
		<property name="xaDataSourceClassName">
			<value>${jta.driver.className}</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="url">${db1.jdbc.driver.url}</prop>
				<prop key="user">${company.sql.user.name}</prop>
				<prop key="password">${company.sql.user.password}</prop>
			</props>
		</property>
		<property name="testQuery" value="select 1" />
		<property name="poolSize">
			<value>${poolsize}</value>
		</property>
		<property name="maxPoolSize">
			<value>${maxPoolSize}</value>
		</property>
		<property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property>
	</bean>
	<bean id="db2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
		<property name="uniqueResourceName">
			<value>mysql/db2</value>
		</property>
		<property name="xaDataSourceClassName">
			<value>${jta.driver.className}</value>
		</property>
		<property name="xaProperties">
			<props>
				<prop key="url">${db2.jdbc.driver.url}</prop>
				<prop key="user">${company.sql.user.name}</prop>
				<prop key="password">${company.sql.user.password}</prop>
			</props>
		</property>
		<property name="testQuery" value="select 1" />
		<property name="poolSize">
			<value>${poolsize}</value>
		</property>
		<property name="maxPoolSize">
			<value>${maxPoolSize}</value>
		</property>
		<property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property>
	</bean>
	
	<bean id="dataSource" class="com.ssi.datasource.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="db1" value-ref="db1" />
				<entry key="db2" value-ref="db2" />
				<entry key="center" value-ref="center" />
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="center" />
	</bean>
</beans>


DynamicSqlClientDaoSupport.java

package com.ssi.dao;

import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.support.DaoSupport;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.util.Assert;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ssi.datasource.DbContextHolder;

public class DynamicSqlClientDaoSupport extends DaoSupport implements InitializingBean{

	private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate();
	private Map<String,SqlMapClient> targetSqlMapClients;
	private SqlMapClient defaultSqlMapClient;
	private boolean externalTemplate = false;

	
	/**
	 * Set the JDBC DataSource to be used by this DAO.
	 * Not required: The SqlMapClient might carry a shared DataSource.
	 * @see #setSqlMapClient
	 */
	public final void setDataSource(DataSource dataSource) {
		if (!this.externalTemplate) {
	  	this.sqlMapClientTemplate.setDataSource(dataSource);
		}
	}

	/**
	 * Return the JDBC DataSource used by this DAO.
	 */
	public final DataSource getDataSource() {
		return this.sqlMapClientTemplate.getDataSource();
	}

	/**
	 * Set the iBATIS Database Layer SqlMapClient to work with.
	 * Either this or a "sqlMapClientTemplate" is required.
	 * @see #setSqlMapClientTemplate
	 */
	public final void setSqlMapClient(SqlMapClient sqlMapClient) {
		if (!this.externalTemplate) {
			this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
		}
	}

	/**
	 * Return the iBATIS Database Layer SqlMapClient that this template works with.
	 */
	public final SqlMapClient getSqlMapClient() {
		return this.sqlMapClientTemplate.getSqlMapClient();
	}

	/**
	 * Set the SqlMapClientTemplate for this DAO explicitly,
	 * as an alternative to specifying a SqlMapClient.
	 * @see #setSqlMapClient
	 */
	public final void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) {
		Assert.notNull(sqlMapClientTemplate, "SqlMapClientTemplate must not be null");
		this.sqlMapClientTemplate = sqlMapClientTemplate;
		this.externalTemplate = true;
	}

	/**
	 * Return the SqlMapClientTemplate for this DAO,
	 * pre-initialized with the SqlMapClient or set explicitly.
	 */
	public final SqlMapClientTemplate getSqlMapClientTemplate() {
	  String dbtype = DbContextHolder.getDbType();
	  if(targetSqlMapClients!=null&&targetSqlMapClients.containsKey(dbtype)){
		  SqlMapClient sqlMapClient = targetSqlMapClients.get(dbtype);
		  sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient);
	  }
	  return this.sqlMapClientTemplate;
	}

	@Override
	protected final void checkDaoConfig() {
		if (!this.externalTemplate) {
			this.sqlMapClientTemplate.afterPropertiesSet();
		}
	}

	public Map<String, SqlMapClient> getTargetSqlMapClients() {
		return targetSqlMapClients;
	}

	public void setTargetSqlMapClients(Map<String, SqlMapClient> targetSqlMapClients) {
		this.targetSqlMapClients = targetSqlMapClients;
	}

	public SqlMapClient getDefaultSqlMapClient() {
		return defaultSqlMapClient;
	}

	public void setDefaultSqlMapClient(SqlMapClient defaultSqlMapClient) {
		this.defaultSqlMapClient = defaultSqlMapClient;
	}
}
IbatisDaoSupport.java

package com.ssi.dao;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.ibatis.SqlMapClientCallback;

import com.ibatis.sqlmap.client.SqlMapExecutor;
@SuppressWarnings("unchecked")
public class IbatisDaoSupport<Entity> extends DynamicSqlClientDaoSupport implements IEntityDao<Entity> {

	protected final Log log = LogFactory.getLog(getClass());


	public Entity get(String sqlId, Serializable id) {
		return (Entity) getSqlMapClientTemplate().queryForObject(sqlId, id);
	}
	public Entity getByParamMap(String sqlId, Object param) {
		return (Entity) getSqlMapClientTemplate().queryForObject(sqlId, param);
	}
	public Object save(String sqlId, Object o) {
		return getSqlMapClientTemplate().insert(sqlId, o);
	}
	public Object batchSave(final String sqlId,final List<Entity> entityList) throws Exception{   
	    // 执行回调   
		return getSqlMapClientTemplate().execute(new SqlMapClientCallback() {   
	        // 实现回调接口   
	        public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException{   
	            // 开始批处理   
	            executor.startBatch();   
	            for (Entity entity : entityList) {   
	                executor.insert(sqlId, entity);   
	            }   
	            return executor.executeBatch();   
	        }

	    });   
	  
	} 
	public Integer remove(String sqlId, Object o) {
		return getSqlMapClientTemplate().delete(sqlId, o);

	}

	public Integer removeById(String sqlId, Serializable id) {
		return getSqlMapClientTemplate().delete(sqlId, id);
	}

	public Integer update(String sqlId, Object o) {
		return getSqlMapClientTemplate().update(sqlId, o);

	}
	public Long totalCount(String sqlId, Object o){
		return (Long) getSqlMapClientTemplate().queryForObject(sqlId, o);
	}
	public List<Entity> pagedList(String sqlId, Map<String, Object> map,int pageSize, int pageNum) {
		int start = (pageNum - 1) * pageSize;
		map.put("start", start);
		map.put("pageSize", pageSize);
		List<Entity> list = getSqlMapClientTemplate().queryForList(sqlId, map);
		return list;
	}
	public List<Entity> list(String sqlId,Object o){
		return getSqlMapClientTemplate().queryForList(sqlId,o);
	}
	public List<Entity> list(String sqlId){
		return getSqlMapClientTemplate().queryForList(sqlId);
	}

}




UserDaoImpl.java:

package com.ssi.dao.impl;

import org.springframework.stereotype.Repository;


import com.ssi.dao.IUserDao;
import com.ssi.dao.IbatisDaoSupport;
import com.ssi.model.User;
@Repository("userDao")
public class UserDaoImpl extends IbatisDaoSupport<User> implements IUserDao {
	public Integer addUser(User user) throws Exception{
		return (Integer) this.save("User.insert", user);
	}
}


 

UserServiceImpl.java

package com.ssi.service.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.ssi.dao.IUserDao;
import com.ssi.datasource.DbContextHolder;
import com.ssi.model.User;
import com.ssi.service.IUserService;


@Service("userService")
public class UserServiceImpl implements IUserService {
	@Resource private IUserDao userDao;
	/**
	 * 测试在service中切换数据源 异常是否回滚
	 */
	public void addUser(User user) throws Exception{
		DbContextHolder.setDbType("db1");   
		userDao.addUser(user);
		DbContextHolder.setDbType("db2");  
		user.setUserName("user2");
		userDao.addUser(user);
		DbContextHolder.setDbType("center");
		user.setUserName("user3");
		userDao.addUser(user);
		//System.out.println(1/0);
	}
}


DynamicDataSource.java:

public class DynamicDataSource extends AbstractRoutingDataSource {

	static Logger log = Logger.getLogger(DynamicDataSource.class);

	protected Object determineCurrentLookupKey() {
		return DbContextHolder.getDbType();
	}

}
DbContextHolder.java:

public class DbContextHolder {
	private static final ThreadLocal contextHolder = new ThreadLocal();

	public static void setDbType(String dbType) {
		contextHolder.set(dbType);
	}

	public static String getDbType() {
		return (String) contextHolder.get();
	}

	public static void clearDbType() {
		contextHolder.remove();
	}

}


三个数据库:dbcenter、db1、db2 表结构均相同 

脚本:

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userName` varchar(20) DEFAULT NULL,
  `password` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

单元测试:

public class JunitTest{  
	public ApplicationContext cxt;
	@Test
	public void init() throws Exception{
		cxt = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml","applicationContext-datasource.xml"});
		testInsertUser();

	}
	
	
	private void testInsertUser() throws Exception{
		IUserService userService = (IUserService)cxt.getBean("userService");
		User user = new User();
		user.setUserName("user1");
		user.setPassword("0");
		userService.addUser(user);
	}
	
	private void testInsertUser2() throws Exception{
	
}  





你可能感兴趣的:(Spring分布式事务在service中动态切换数据源)