spring jta 分布式事物配置

 在项目中需要操作多个数据库,同时,需要保证操作的原子性,保证对多个数据库的操作一致性,而传统的JDBC事务被限定在一个单一的数据库连接中,故引用JTA,即Java Transaction API。JTA事务比JDBC事务更强大,一个JTA事务可以有多个数据源参与者。
 现在,我们用spring+jta+mybatis来完成基本的测试框架。

1 配置多数据源 application.xml

<!-- 两个数据源的功用配置,方便下面直接引用 -->
     <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
             destroy-method="close" abstract="true">
        <property name="poolSize" value="100" />
        <property name="minPoolSize" value="10"/>
        <property name="maxPoolSize" value="300"/>
        <property name="borrowConnectionTimeout" value="60"/>
        <property name="reapTimeout" value="20"/>
        <!-- 最大空闲时间 -->
        <property name="maxIdleTime" value="60"/>
        <property name="maintenanceInterval" value="60" />
        <property name="loginTimeout" value="60"/>
        <property name="logWriter" value="60"/>
        <property name="testQuery">
            <value>select 1</value>
        </property>
        
    </bean> 
    <!-- 配置第一个数据源 -->
    <bean id="dataSource_youlh" parent="abstractXADataSource">
    <!-- value只要两个数据源不同就行,随便取名 -->
        <property name="uniqueResourceName" value="mysql/sitestone" />
        <property name="xaDataSourceClassName"
             value="com.microsoft.sqlserver.jdbc.SQLServerXADataSource" /> 
        <!--<property name="xaDataSourceClassName"
            value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />-->
        <property name="xaProperties">
            <props>
                <prop key="URL">${jdbc.urlYoulh}</prop>
                <prop key="user">${jdbc.usernameYoulh}</prop>
                <prop key="password">${jdbc.passwordYoulh}</prop>
            </props>
        </property>
    </bean>

    <!-- 配置第二个数据源-->
    <bean id="dataSource_erp" parent="abstractXADataSource">
<!-- value只要两个数据源不同就行,随便取名 -->
        <property name="uniqueResourceName" value="mysql/sitesttwo" />
        <property name="xaDataSourceClassName"
            value="com.microsoft.sqlserver.jdbc.SQLServerXADataSource" />
        <property name="xaProperties">
            <props>
               <prop key="URL">${jdbc.urlERP}</prop>
                <prop key="user">${jdbc.username}</prop>
                <prop key="password">${jdbc.password}</prop>
            </props>
        </property>
    </bean>
  

    <bean name="dynamicDatasource" class="com.youlh.dataSource.CustomerDatasource">
        <property name="targetDataSources">
            <map>
                <entry key="ds_youlh" value-ref="dataSource_youlh"/>
                <entry key="ds_erp" value-ref="dataSource_erp"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSource_erp"    />
    </bean>
 
    
<bean id="sqlSessionFactoryYoulh" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource_youlh"/>
         <property name="typeAliasesPackage" value="com.youlh.entity" />
         <!-- mapper和resultmap配置路径 --> 
        <property name="mapperLocations">
            <list>
                <!-- 表示在com.youlh目录下的任意包下的-mapper.xml结尾所有文件 --> 
                <value>classpath:com/youlh/mapper/*Mapper.xml</value>
            </list>
        </property>
        <property name="plugins">
    <array>
      <bean class="com.github.pagehelper.PageHelper">
       </bean>
    </array>
  </property>
    </bean>
    <bean id="sqlSessionFactoryERP" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource_erp"/>
         <property name="typeAliasesPackage" value="com.youlh.entity" />
          <!-- mapper和resultmap配置路径 --> 
        <property name="mapperLocations">
            <list>
                <!-- 表示在com.youlh目录下的任意包下的-mapper.xml结尾所有文件 --> 
                <value>classpath:com/youlh/mapper/*Mapper.xml</value>
            </list>
        </property>
     <property name="plugins">
    <array>
      <bean class="com.github.pagehelper.PageHelper">
       </bean>
    </array>
  </property>
    </bean>

    
    <!-- 配置自定义的SqlSessionTemplate模板,注入相关配置 -->
    <bean id="sqlSessionTemplate" class="com.youlh.dataSource.CustomSqlSessionTemplate" scope="prototype">
        <constructor-arg ref="sqlSessionFactoryERP" />
        <property name="targetSqlSessionFactorys">
            <map>     
                <entry value-ref="sqlSessionFactoryERP" key="ds_erp"/>
                <entry value-ref="sqlSessionFactoryYoulh" key="ds_youlh"/>
            </map> 
        </property>
    </bean>

    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.youlh.mapper" />
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
        <property name="markerInterface" value="com.youlh.mapper.SqlMapper"/>
    </bean>
    
   
 <!-- jta -->
    <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>
 
    <bean id="springTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager">
            <ref bean="atomikosTransactionManager" />
        </property>
        <property name="userTransaction">
            <ref bean="atomikosUserTransaction" />
        </property>
    </bean>
 <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />
  

2 配置注解切换数据源涉及的类
(1)注解类ChooseDataSource.java
package com.youlh.dataSource;

import java.lang.annotation.*;

import org.springframework.stereotype.Component;

/**
 * 注解式数据源,用来进行数据源切换
 * User:Amos.zhou
 * Date: 14-2-27
 * Time: 下午2:34
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChooseDataSource {

    String value() default "";
}

(2)CustomerDatasource.java
package com.youlh.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * User:Amos.zhou
 * Date: 14-3-14
 * Time: 下午5:27
 */
public class CustomerDatasource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceKeyHolder.getDataSourceKey();
    }
}

(3)自定义CustomSqlSessionTemplate.java
package com.youlh.dataSource;

import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;
import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;
 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import org.aopalliance.intercept.Interceptor;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;



/**
 * TODO: 增加描述
 * 
 * @author Administrator
 * @date 2015年9月22日 下午6:27:47
 * @version 0.1.0 
 * @copyright stnts.com 
 */
public class CustomSqlSessionTemplate extends SqlSessionTemplate {
	 
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;
 
    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
    private SqlSessionFactory defaultTargetSqlSessionFactory;
 
    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
        this.targetSqlSessionFactorys = targetSqlSessionFactorys;
    }
 
    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
    }
 
    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }
 
    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
                .getEnvironment().getDataSource(), true));
    }
 
    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
            PersistenceExceptionTranslator exceptionTranslator) {
 
        super(sqlSessionFactory, executorType, exceptionTranslator);
 
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[] { SqlSession.class }, 
                new SqlSessionInterceptor());
 
        this.defaultTargetSqlSessionFactory = sqlSessionFactory;
    }
 
    @Override
    public SqlSessionFactory getSqlSessionFactory() {
 
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DataSourceKeyHolder.getDataSourceKey());
        if (targetSqlSessionFactory != null) {
            return targetSqlSessionFactory;
        } else if (defaultTargetSqlSessionFactory != null) {
            return defaultTargetSqlSessionFactory;
        } else {
            Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
            Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
        }
        return this.sqlSessionFactory;
    }
 
    @Override
    public Configuration getConfiguration() {
        return this.getSqlSessionFactory().getConfiguration();
    }
 
    public ExecutorType getExecutorType() {
        return this.executorType;
    }
 
    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
    }
 
    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.<T> selectOne(statement);
    }
 
    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.<T> selectOne(statement, parameter);
    }
 
    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
    }
 
    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
    }
 
    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
    }
 
    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.<E> selectList(statement);
    }
 
    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.<E> selectList(statement, parameter);
    }
 
    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
    }
 
    /**
     * {@inheritDoc}
     */
    public void select(String statement, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, handler);
    }
 
    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }
 
    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }
 
    /**
     * {@inheritDoc}
     */
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }
 
    /**
     * {@inheritDoc}
     */
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }
 
    /**
     * {@inheritDoc}
     */
    public int update(String statement) {
        return this.sqlSessionProxy.update(statement);
    }
 
    /**
     * {@inheritDoc}
     */
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }
 
    /**
     * {@inheritDoc}
     */
    public int delete(String statement) {
        return this.sqlSessionProxy.delete(statement);
    }
 
    /**
     * {@inheritDoc}
     */
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }
 
    /**
     * {@inheritDoc}
     */
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
 
    /**
     * {@inheritDoc}
     */
    public void commit() {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }
 
    /**
     * {@inheritDoc}
     */
    public void commit(boolean force) {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }
 
    /**
     * {@inheritDoc}
     */
    public void rollback() {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }
 
    /**
     * {@inheritDoc}
     */
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }
 
    /**
     * {@inheritDoc}
     */
    public void close() {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }
 
    /**
     * {@inheritDoc}
     */
    public void clearCache() {
        this.sqlSessionProxy.clearCache();
    }
 
    /**
     * {@inheritDoc}
     */
    public Connection getConnection() {
        return this.sqlSessionProxy.getConnection();
    }
 
    /**
     * {@inheritDoc}
     * @since 1.0.2
     */
    public List<BatchResult> flushStatements() {
        return this.sqlSessionProxy.flushStatements();
    }
 
    /**
     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
     * the {@code PersistenceExceptionTranslator}.
     */
    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = getSqlSession(
                    CustomSqlSessionTemplate.this.getSqlSessionFactory(),
                    CustomSqlSessionTemplate.this.executorType, 
                    CustomSqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator
                        .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
 
}

(4) 数据源切换类DataSourceKeyHolder.java
package com.youlh.dataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Created with IntelliJ IDEA.
 * User: vic
 * Date: 13-5-18
 * Time: 下午4:35
 * 将数据源的键存入ThreadLocal中
 */
public class DataSourceKeyHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    private static Logger logger = LoggerFactory.getLogger(DataSourceKeyHolder.class);

    public static void setDataSourceKey(String dataSourceKey) {
    	String key = contextHolder.get();
    	if(key==null||!key.equals(dataSourceKey)){
    		logger.debug("Thread:"+Thread.currentThread().getName()+"dataSource key is "+ dataSourceKey);
    	       
    		contextHolder.set(dataSourceKey);
    	}
        
    }

    public static String getDataSourceKey() {
        String key = contextHolder.get();
        return key;
    }

    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
}
(5) 切面类DataSourceAspect.java
package com.youlh.dataSource;


import java.lang.reflect.Method;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;






public class DataSourceAspect {


    /**
     * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
     * 
     * @param point
     * @throws Exception
     */
    public void intercept(JoinPoint point) throws Exception {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
        for (Class<?> clazz : target.getInterfaces()) {
            resolveDataSource(clazz, signature.getMethod());
        }
        resolveDataSource(target, signature.getMethod());
    }


    /**
     * 提取目标对象方法注解和类型注解中的数据源标识
     * 
     * @param clazz
     * @param method
     */
    private void resolveDataSource(Class<?> clazz, Method method) {
        try {
            Class<?>[] types = method.getParameterTypes();
            // 默认使用类注解
            if (clazz.isAnnotationPresent(ChooseDataSource.class)) {
            	ChooseDataSource source = clazz.getAnnotation(ChooseDataSource.class);
            	DataSourceKeyHolder.setDataSourceKey(source.value());
            }
            // 方法注解可以覆盖类注解
            Method m = clazz.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(ChooseDataSource.class)) {
            	ChooseDataSource source = m.getAnnotation(ChooseDataSource.class);
            	DataSourceKeyHolder.setDataSourceKey(source.value());
            }
        } catch (Exception e) {
            System.out.println(clazz + ":" + e.getMessage());
        }
    }


}

3 切面配置,拦截Dao层方法
 
 <!-- 启用CGliB-->
    <aop:aspectj-autoproxy/>
<bean id="dataSourceAspect" class="com.youlh.dataSource.DataSourceAspect" />
   <aop:config>
        <aop:aspect ref="dataSourceAspect">
            <!-- 拦截所有Dao方法 -->
            <aop:pointcut id="dataSourcePointcut" expression="execution(* com.youlh.dao.*.*(..))"/>
            <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
        </aop:aspect>
    </aop:config>

4 Dao层举例
@ChooseDataSource("ds_youlh")
	public Long findMessageUnhandle() {
		// TODO Auto-generated method stub
		return messageMapper.findMessageUnhandle();
	}
	@ChooseDataSource("ds_youlh")
	public void updateUserPassword(Manager user) {
		// TODO Auto-generated method stub
		managerMapper.updateUserPassword(user);
	}


也可以加在类上
@Repository
@ChooseDataSource("ds_erp")
public class UserDao {
	@Autowired
	private StoreCustomersMapper storeCustomeMapper;
	
	@Autowired
	private NoticeMapper noticeMapper;


5 service层事务
@Transactional(rollbackFor=Exception.class)
	public void createOrder(BaseBean bean,String sourceDevice,SalesOrder order, 
			JSONArray orderItemList, List<SalesOrderAdditional> arrayAddtion,
			String dayNum,String startCity) {
		//操作1 ,调用dao层方法
		//操作2,调用dao层方法
	}
		


6 主要的jar
<!-- 分布式事物,多数据源 -->
		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>atomikos-util</artifactId>
			<version>3.9.3</version>
		</dependency>

		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions</artifactId>
			<version>3.9.3</version>
		</dependency>

		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions-jta</artifactId>
			<version>3.9.3</version>
		</dependency>
		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions-jdbc</artifactId>
			<version>3.9.3</version>
		</dependency>
		<dependency>
			<groupId>javax.transaction</groupId>
			<artifactId>jta</artifactId>
			<version>1.1</version>
		</dependency>
<!-- 分布式事物,多数据源 -->


注意service层方法,逻辑放到service层,数据库切换根据Dao层的注解,可以加在方法上,也可以加在类上。
参考文章:http://blog.csdn.net/MassiveStars/article/details/54025236
http://www.cnblogs.com/digdeep/p/4624998.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值