Spring+Mybatis+Atomikos多数据源事务一致性

1、背景

由于应用多数据源的写操作,一定会涉及到数据的一致性问题,由于mybatis-plus 的局限性,采用spring+mybatis+atomikos+druid实现分布式事务一致性。

2、实现

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tonytaotao</groupId>
    <artifactId>dtsc-job</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>dtsc-job</name>

    <properties>

        <!-- 文件拷贝时的编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- 编译时的编码 -->
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>

        <java.version>1.8</java.version>

        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
        <maven-resources-plugin.version>3.1.0</maven-resources-plugin.version>
        <maven-jar-plugin.version>3.1.0</maven-jar-plugin.version>

        <!-- quartz -->
        <quartz.version>2.3.0</quartz.version>

        <!-- spring -->
        <spring.version>5.1.2.RELEASE</spring.version>
        <aspectjweaver.version>1.9.2</aspectjweaver.version>

        <!-- distribute transaction -->
        <jta.version>1.1</jta.version>
        <transactions-jdbc.version>4.0.6</transactions-jdbc.version>


        <!-- datasource -->
        <spring-redis.version>2.1.2.RELEASE</spring-redis.version>
        <mysql.version>6.0.6</mysql.version>
        <druid.version>1.1.12</druid.version>
        <mybatis-plus.version>3.0.5</mybatis-plus.version>

        <!-- test -->
        <junit.version>4.12</junit.version>

        <!-- log4j2 -->
        <log4j2.version>2.11.1</log4j2.version>
        <slf4j.version>1.7.25</slf4j.version>
        <disruptor.version>3.4.2</disruptor.version>

        <!-- utils -->
        <lombok.version>1.18.4</lombok.version>
        <fastjson.version>1.2.51</fastjson.version>
        <joda-time.version>2.10.1</joda-time.version>
        <guava.version>27.0-jre</guava.version>
        <commons-lang3.version>3.8.1</commons-lang3.version>
        <velocity-engine-core.version>2.0</velocity-engine-core.version>

    </properties>

    <dependencies>

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>${jta.version}</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>${transactions-jdbc.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>${spring-redis.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>${disruptor.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>${joda-time.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>${velocity-engine-core.version}</version>
        </dependency>

    </dependencies>

    <build>

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/test/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <testSource>${java.version}</testSource>
                    <testTarget>${java.version}</testTarget>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>${maven-resources-plugin.version}</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>${maven-jar-plugin.version}</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>test</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                target/classes/lib
                            </outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2.1 JtaDatasourceConfig

package com.tonytaotao.dtsc.common.configuration.datasouce.jta; 

import com.alibaba.druid.pool.xa.DruidXADataSource; 
import com.atomikos.jdbc.AtomikosDataSourceBean; 
import com.tonytaotao.dtsc.common.configuration.datasouce.DBTypeEnum; 
import com.tonytaotao.dtsc.common.configuration.datasouce.DynamicDataSource; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.Primary; 

import javax.sql.DataSource; 
import java.util.HashMap; 
import java.util.Map; 

@Configuration 
public class JtaDatasourceConfig { 

    @Bean("xaMultipleDataSource") 
    @Primary 
    public DynamicDataSource xaMultipleDataSource(@Qualifier("one") DataSource one, @Qualifier("two") DataSource two) { 
        DynamicDataSource dynamicDataSource = new DynamicDataSource(); 
        Map< Object, Object > targetDataSources = new HashMap<>(); 
        targetDataSources.put(DBTypeEnum.ONE.getCode(), one); 
        targetDataSources.put(DBTypeEnum.TWO.getCode(), two); 
        dynamicDataSource.setTargetDataSources(targetDataSources); 
        dynamicDataSource.setDefaultTargetDataSource(one); 
        return dynamicDataSource; 
    } 

    @Bean("one") 
    public DataSource createXADatasourOne() { 

        DruidXADataSource druidXADataSource = createDataSourceBase(); 
        druidXADataSource.setUrl("jdbc:mysql://localhost:3306/dtsc?useSSL=false&useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=UTF8&serverTimezone=GMT"); 
        druidXADataSource.setUsername("root"); 
        druidXADataSource.setPassword("123456"); 

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean(); 
        atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); 
        atomikosDataSourceBean.setUniqueResourceName("one"); 
        atomikosDataSourceBean.setXaDataSource(druidXADataSource); 
        atomikosDataSourceBean.setMaintenanceInterval(30000); 
        atomikosDataSourceBean.setTestQuery("SELECT 1"); 

        return atomikosDataSourceBean; 
    } 

    @Bean("two") 
    public DataSource createXADatasourTwo() { 

        DruidXADataSource druidXADataSource = createDataSourceBase(); 
        druidXADataSource.setUrl("jdbc:mysql://localhost:3306/dtsc_1?useSSL=false&useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=UTF8&serverTimezone=GMT"); 
        druidXADataSource.setUsername("root"); 
        druidXADataSource.setPassword("123456"); 

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean(); 
        atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); 
        atomikosDataSourceBean.setUniqueResourceName("two"); 
        atomikosDataSourceBean.setXaDataSource(druidXADataSource); 
        atomikosDataSourceBean.setMaintenanceInterval(30000); 
        atomikosDataSourceBean.setTestQuery("SELECT 1"); 

        return atomikosDataSourceBean; 
    } 


    private DruidXADataSource createDataSourceBase() { 

        DruidXADataSource druidDataSource = new DruidXADataSource(); 

        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 
        druidDataSource.setInitialSize(50); 
        druidDataSource.setMaxActive(100); 
        druidDataSource.setMinIdle(5); 
        druidDataSource.setMaxWait(1000); 
        druidDataSource.setPoolPreparedStatements(false); 
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(-1); 
        druidDataSource.setValidationQuery("SELECT 1"); 
        druidDataSource.setValidationQueryTimeout(1); 
        druidDataSource.setTestOnBorrow(false); 
        druidDataSource.setTestOnReturn(false); 
        druidDataSource.setTestWhileIdle(true); 
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000); 
        druidDataSource.setMinEvictableIdleTimeMillis(30000); 

        return druidDataSource; 
    } 

}

2.2 JtaTransationConfig

package com.tonytaotao.dtsc.common.configuration.datasouce.jta; 

import com.atomikos.icatch.jta.UserTransactionImp; 
import com.atomikos.icatch.jta.UserTransactionManager; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.EnableAspectJAutoProxy; 
import org.springframework.transaction.annotation.EnableTransactionManagement; 
import org.springframework.transaction.jta.JtaTransactionManager; 

import javax.transaction.SystemException; 

/** 
* @author wujintao 
* 
* Configuration 类似于spring 配置文件,负责注册bean 
* ComponentScan 注解类查找规则定义 <context:component-scan/> 
* EnableAspectJAutoProxy 激活Aspect 自动代理 <aop:aspectj-autoproxy/> 
* proxy-target-class : 默认为false 表示使用JDK 动态代理。如果实现了至少一个接口,Spring 会优先选择使用JDK 动态代理,若果没有实现任何接口,则spring 会选择CGLIB 动态代理,强制使用CGLIB 动态代理,使用以下配置 
* exposeProxy : springAOP 只会拦截public 方法,不会拦截provided 和private 方法,并且不会拦截public 方法内部调用的其他方法,也就是说只会拦截代理对象的方法,即增强的是代理对象,而不是原对象, 设置就可以暴露出代理对象,拦截器会获取代理对象,并且将代理对象转换成原对象。从而对内部调用的方法进行增强 
* EnableTransactionManagement 启用注解式事务管理 <tx:annotation-driven /> 
*/ 
@Configuration 
@EnableTransactionManagement 
@EnableAspectJAutoProxy(exposeProxy = true) 
public class JtaTransationConfig { 

    @Bean("jtaTransactionManager") 
    public JtaTransactionManager jtaTransactionManager() { 

        UserTransactionManager userTransactionManager = new UserTransactionManager(); 
        userTransactionManager.setForceShutdown(true); 

        UserTransactionImp userTransactionImp = new UserTransactionImp(); 
        try { 
            userTransactionImp.setTransactionTimeout(90000); 
        } catch (SystemException e) { 
            e.printStackTrace(); 
        } 

        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(); 
        jtaTransactionManager.setTransactionManager(userTransactionManager); 
        jtaTransactionManager.setUserTransaction(userTransactionImp); 
        jtaTransactionManager.setAllowCustomIsolationLevels(true); 

        return jtaTransactionManager; 
    } 
}

2.3 MTAspect

package com.tonytaotao.dtsc.common.configuration.datasouce.jta; 

import com.tonytaotao.dtsc.common.utils.SpringContextUtil; 
import lombok.extern.slf4j.Slf4j; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.reflect.MethodSignature; 
import org.springframework.stereotype.Component; 
import org.springframework.transaction.jta.JtaTransactionManager; 

import javax.transaction.UserTransaction; 
import java.lang.reflect.Method; 
import java.util.Arrays; 

@Component 
@Aspect 
@Slf4j 
public class MTAspect { 

    @Around(value = "@annotation(com.tonytaotao.dtsc.common.configuration.datasouce.jta.MultiTransactional)") 
    public Object transactional(ProceedingJoinPoint point) throws Exception { 
        String methodName = point.getSignature().getName(); 
        Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes(); 
        UserTransaction userTransaction = null; 
        Object result = null; 
        MultiTransactional multiTransactional = null; 
        try { 
            Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes); 

            if (method.isAnnotationPresent(MultiTransactional.class)) { 
                multiTransactional = method.getAnnotation(MultiTransactional.class); 
                JtaTransactionManager transactionManager = SpringContextUtil.getBean(JtaTransactionManager.class); 
                userTransaction = transactionManager.getUserTransaction(); 
                userTransaction.begin(); 
                log.warn(methodName + ", transaction begin"); 
                result = point.proceed(); 
                userTransaction.commit(); 
                log.warn(methodName + ", transaction commit"); 
            } 

        } catch (Throwable e) { 
            log.error(e.getMessage(), e); 

            if (userTransaction != null) { 
                Class<? extends Throwable>[] rollbackExcptions = multiTransactional.rollbackFor(); 
                Class<? extends Throwable>[] noRollbackExcptions = multiTransactional.noRollbackFor(); 
                boolean rollback = isPresent(e, rollbackExcptions); 
                boolean noRollback = isPresent(e, noRollbackExcptions); 

                if (rollback || !noRollback) { 
                    userTransaction.rollback(); 
                    log.warn(methodName + ", transaction rollback"); 
                } else { 
                    userTransaction.commit(); 
                    log.warn(methodName + ", transaction commit"); 
                } 
            } 
        } 

        return result; 
    } 

    private boolean isPresent(Throwable e, Class<? extends Throwable>[] excptions) { 
        return Arrays.stream(excptions) .filter(exception -> e.getClass().isAssignableFrom(exception) || e.getClass().equals(exception)).findAny().isPresent(); 
    } 
}
2.4 MultiTransactional
package com.tonytaotao.dtsc.common.configuration.datasouce.jta; 

import org.springframework.core.annotation.AliasFor; 

import java.lang.annotation.*; 

@Target({ElementType.METHOD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
@Documented 
public @interface MultiTransactional { 

    @AliasFor("transactionManager") 
    String value() default ""; 

    @AliasFor("value") 
    String transactionManager() default ""; 

    Class<? extends Throwable>[] rollbackFor() default {}; 

    Class<? extends Throwable>[] noRollbackFor() default {}; 

}

2.5 MybatisConfig

package com.tonytaotao.dtsc.common.configuration.datasouce.jta; 

import com.tonytaotao.dtsc.common.configuration.datasouce.CustomSqlSessionTemplate; 
import com.tonytaotao.dtsc.common.configuration.datasouce.DBTypeEnum; 
import org.apache.ibatis.session.SqlSessionFactory; 
import org.mybatis.spring.SqlSessionFactoryBean; 
import org.mybatis.spring.annotation.MapperScan; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 

import javax.sql.DataSource; 
import java.util.HashMap; 
import java.util.Map; 

@Configuration 
@MapperScan(basePackages = {"com.tonytaotao.dtsc.*.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate") 
public class MybatisConfig { 

    @Bean("sqlSessionTemplate") 
    public CustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("xaSqlSessionFactoryOne") SqlSessionFactory xaSqlSessionFactoryOne, @Qualifier("xaSqlSessionFactoryTwo") SqlSessionFactory xaSqlSessionFactoryTwo) { 

        CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(xaSqlSessionFactoryOne); 

        Map<Object, SqlSessionFactory> targetSqlSessionFactorys = new HashMap<>(); 
        targetSqlSessionFactorys.put(DBTypeEnum.ONE.getCode(), xaSqlSessionFactoryOne); 
        targetSqlSessionFactorys.put(DBTypeEnum.TWO.getCode(), xaSqlSessionFactoryTwo); 

        customSqlSessionTemplate.setTargetSqlSessionFactorys(targetSqlSessionFactorys); 

        return customSqlSessionTemplate; 
    } 

    @Bean("xaSqlSessionFactoryOne") 
    public SqlSessionFactory xaSqlSessionFactoryOne(@Qualifier("one") DataSource one) { 

        try { 
            return createSqlSessionFactory(one); 
        } catch (Exception e) { 
            e.printStackTrace(); 
            return null; 
        } 
    } 

    @Bean("xaSqlSessionFactoryTwo") 
    public SqlSessionFactory xaSqlSessionFactoryTwo(@Qualifier("two") DataSource two) { 

        try { 
            return createSqlSessionFactory(two); 
        } catch (Exception e) { 
            e.printStackTrace(); 
            return null; 
        } 
    } 

    private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{ 

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 
        sqlSessionFactoryBean.setDataSource(dataSource); 
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/tonytaotao/dtsc/*/mapper/xml/*Mapper.xml")); 
        sqlSessionFactoryBean.setTypeAliasesPackage("com.tonytaotao.dtsc.*.entity"); 

        return sqlSessionFactoryBean.getObject(); 
    } 



}

2.6 CustomSqlSessionTemplate

package com.tonytaotao.dtsc.common.configuration.datasouce; 

import org.apache.ibatis.exceptions.PersistenceException; 
import org.apache.ibatis.executor.BatchResult; 
import org.apache.ibatis.reflection.ExceptionUtil; 
import org.apache.ibatis.session.*; 
import org.mybatis.spring.MyBatisExceptionTranslator; 
import org.mybatis.spring.SqlSessionTemplate; 
import org.mybatis.spring.SqlSessionUtils; 
import org.springframework.dao.support.PersistenceExceptionTranslator; 
import org.springframework.util.Assert; 

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

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 Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() { 
        return 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) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); 
        this.defaultTargetSqlSessionFactory = sqlSessionFactory; 
    } 

    @Override 
    public SqlSessionFactory getSqlSessionFactory() { 
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DBContextHolder.getDbType()); 
        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; 
    } 

 
    public <T> T selectOne(String statement) { 
        return this.sqlSessionProxy.<T> selectOne(statement); 
    } 


    public <T> T selectOne(String statement, Object parameter) { 
        return this.sqlSessionProxy.<T> selectOne(statement, parameter); 
    } 

    public <K, V> Map<K, V> selectMap(String statement, String mapKey) { 
        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey); 
    } 


    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { 
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey); 
    } 
 
    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); 
    } 


    public <E> List<E> selectList(String statement) { 
        return this.sqlSessionProxy.<E> selectList(statement); 
    } 


    public <E> List<E> selectList(String statement, Object parameter) { 
        return this.sqlSessionProxy.<E> selectList(statement, parameter); 
    } 


    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 
        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds); 
    } 


    public void select(String statement, ResultHandler handler) { 
        this.sqlSessionProxy.select(statement, handler); 
    } 

    public void select(String statement, Object parameter, ResultHandler handler) { 
        this.sqlSessionProxy.select(statement, parameter, handler); 
    } 


    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { 
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); 
    } 


    public int insert(String statement) { 
        return this.sqlSessionProxy.insert(statement); 
    } 

 
    public int insert(String statement, Object parameter) { 
        return this.sqlSessionProxy.insert(statement, parameter); 
    } 
 
    public int update(String statement) { 
        return this.sqlSessionProxy.update(statement); 
    } 

 
    public int update(String statement, Object parameter) { 
        return this.sqlSessionProxy.update(statement, parameter); 
    } 

 
    public int delete(String statement) { 
        return this.sqlSessionProxy.delete(statement); 
    } 


    public int delete(String statement, Object parameter) { 
        return this.sqlSessionProxy.delete(statement, parameter); 
    } 


    public <T> T getMapper(Class<T> type) { 
        return getConfiguration().getMapper(type, this); 
    } 


    public void commit() { 
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); 
    } 


    public void commit(boolean force) { 
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); 
    } 


    public void rollback() { 
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); 
    } 


    public void rollback(boolean force) { 
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); 
    } 


    public void close() { 
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); 
    } 

 
    public void clearCache() { 
        this.sqlSessionProxy.clearCache(); 
    } 


    public Connection getConnection() { 
        return this.sqlSessionProxy.getConnection(); 
    } 


    public List<BatchResult> flushStatements() { 
        return this.sqlSessionProxy.flushStatements(); 
    } 


    private class SqlSessionInterceptor implements InvocationHandler { 
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
            final SqlSession sqlSession = SqlSessionUtils.getSqlSession(CustomSqlSessionTemplate.this.getSqlSessionFactory(), 
            CustomSqlSessionTemplate.this.executorType, CustomSqlSessionTemplate.this.exceptionTranslator); 
            try { 
                Object result = method.invoke(sqlSession, args); 
                if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) { 
                    sqlSession.commit(true); 
                } 
                return result; 
            } catch (Throwable t) { 
                Throwable unwrapped = ExceptionUtil.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 { 
                SqlSessionUtils.closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory()); 
            } 
        } 
    } 
}

2.7  复制  https://my.oschina.net/TonyTaotao/blog/2980518  中的DBTypeEnum 、DBAnnotation 、DBAspect 、DBContextHolder 、DynamicDataSource 的配置。

2.8 jta.properties

com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory

2.9 SpringContextUtil

package com.tonytaotao.dtsc.common.utils;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static <T> T getBean(String name) throws BeansException {
        return (T)applicationContext.getBean(name);
    }

    public static <T> T getBean(String name, Class<?> requiredType) throws BeansException {
        return applicationContext.getBean(name, (Class<T>)requiredType);
    }

    public static <T> T getBean(Class<?> requiredType) throws BeansException {
        String[] names = applicationContext.getBeanNamesForType(requiredType);
        if (names == null || names.length == 0) {
            throw new IllegalArgumentException("没有找到Bean,类型:" + requiredType.getName());
        }
        return (T)applicationContext.getBean(names[0]);
    }

    public static <T> Collection<T> getBeans(String... names) throws BeansException {
        Collection<T> beans = new ArrayList<>();
        for (String name : names) {
            beans.add((T)applicationContext.getBean(name));
        }
        return beans;
    }

    public static <T> Map<Object, T> getBeans(Class<?> requiredType) throws BeansException {
        Map<Object, T> beans = new HashMap<>();
        String[] names = applicationContext.getBeanNamesForType(requiredType);
        for (String name : names) {
            beans.put(name, (T)applicationContext.getBean(name));
        }
        return beans;
    }

    public static <T> Map<Object, T> getBeans(Class<?> requiredType, Class<?>... excludeTypes) throws BeansException {
        Map<Object, T> beans = new HashMap<>();
        String[] names = applicationContext.getBeanNamesForType(requiredType);
        for (String name : names) {
            Class type = applicationContext.getType(name);
            if (excludeTypes != null) {
                boolean flag = Arrays.stream(excludeTypes)
                        .filter(excludeType -> excludeType.equals(type))
                        .findAny()
                        .isPresent();

                if (flag) {
                    continue;
                }
            }
            beans.put(name, (T)applicationContext.getBean(name));
        }
        return beans;
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return applicationContext.isSingleton(name);
    }

    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return applicationContext.getType(name);
    }

    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return applicationContext.getAliases(name);
    }
}

3、使用方法

在需要跨库写操作的方法上加上 @MultiTransactional 注解,即可保证分布式事务的一致性

11181648_Yj6i.png

转载于:https://my.oschina.net/TonyTaotao/blog/2980521

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值