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 注解,即可保证分布式事务的一致性