前段时间写了完成基于 AbstractRoutingDataSource 接口的办法来完成多数据源的动态切换,参考:链接。

可是此种办法没有确保业务,所以今天来整合 Atomiks 来确保动态多数据源的业务。

引入的 jar

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

装备

第一个程序注解:AopContext 相关,和包扫描

@Configuration
//表明经过 aop 框架暴露改代理目标,AopContext 能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = "com.practice.thinkindynamicsource.多数据源动态完成.mapper" , sqlSessionTemplateRef = "sqlSessionTemplate")
public class ApplicationConfig {
}

AtomikosConfig

JTA 业务装备类:Java Transaction API。

界说了 PlatformTransactionManager 实际是 JtaTransactionManager。

@Configuration
public class AtomikosConfig {
    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }
    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }
    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager transactionManager() throws SystemException {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

DruidConfig

  1. 首先界说了两个数据源Bean(这里有几个数据源,就界说几个Bean)
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.master.";
        return getDataSource(env, prefix, MASTER);
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.slave.";
        return getDataSource(env, prefix, SLAVE);
    }
    protected DataSource getDataSource(Environment env, String prefix, String dataSourceName)
    {
        Properties prop = build(env, prefix);//获取装备信息
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName(dataSourceName);
        ds.setXaProperties(prop);
        return ds;
    }
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(MASTER, masterDataSource);
        setDataSource(targetDataSources, SLAVE, "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
//下面是结合 AbstractRoutingDataSource 完成多数据源的代码,详细参考源码
    /**
     * 设置数据源
     *
     * @param targetDataSources 备选数据源调集
     * @param sourceName 数据源称号
     * @param beanName bean称号
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }

MybatisConfig

    public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception
    {
//        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.config-location");
//        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
//        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }
    @Bean(name = "sqlSessionFactoryMaster")
    public SqlSessionFactory sqlSessionFactoryMaster(Environment env, @Qualifier("masterDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }
    @Bean(name = "sqlSessionFactorySlave")
    public SqlSessionFactory sqlSessionFactorySlave(Environment env, @Qualifier("slaveDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }
    @Bean(name = "sqlSessionTemplate")
    public DynamicSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster,
                                                        @Qualifier("sqlSessionFactorySlave") SqlSessionFactory factorySlave) throws Exception
    {
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
        sqlSessionFactoryMap.put(DruidConfig.SLAVE, factorySlave);
        DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

自界说SqlSessionTemplate

这里初始化会结合 MybatisConfig :有几个数据源就会初始化几个 SqlSessionFactory 详细履行句子的时分就会运用详细的 SqlSessionFactory 去履行。

public class DynamicSqlSessionTemplate 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;
    @Override
    public SqlSessionFactory getSqlSessionFactory()
    {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys
                .get(DynamicDataSourceContextHolder.getDataSourceType());
        if (targetSqlSessionFactory != null)
        {
            return targetSqlSessionFactory;
        }
        else if (defaultTargetSqlSessionFactory != null)
        {
            return defaultTargetSqlSessionFactory;
        }
        return this.sqlSessionFactory;
    }
    private class SqlSessionInterceptor implements InvocationHandler
    {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                    DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
            try
            {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()))
                {
                    sqlSession.commit(true);
                }
                return result;
            }
            catch (Throwable t)
            {
                Throwable unwrapped = unwrapThrowable(t);
                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null
                        && unwrapped instanceof PersistenceException)
                {
                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null)
                    {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            }
            finally
            {
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
}

多数据源装备

查看源码,或参考上一篇文章:/post/719582…

  • DynamicDataSource 完成 AbstractRoutingDataSource# determineCurrentLookupKey();
  • DynamicDataSourceContextHolder 动态切换数据源处理
  • DataSource、DataSourceAspect

测试

写一个接口,在完成类中完成多数据源切换而且确保业务

    @Override
    @Transactional
    public Map testDataSource() {
        SpringUtils.getAopProxy(this).insertA();
        SpringUtils.getAopProxy(this).insertB();
        int i=10/0;
        return new HashMap();
    }
    @DataSource(value = DataSourceType.slave)
    public void insertA(){
        dynamicTestMapper.dynamicTestA();
    }
    @DataSource(value = DataSourceType.master)
    public void insertB(){
        dynamicTestMapper.dynamicTestB();
    }

总结

相似指定完成那样:/post/720220…,履行详细数据源的时分,指定SqlSessionFactory。

  1. 运用 Atomikos 创立 transactionManager
  2. 自界说 SqlSessionFactory 来自界说 SqlSessionTemplate ,当切换数据源的时分,SqlSessionTemplate 也随之切换。
  3. 运用工具类获取当时 AOP代理目标去履行 mapper 办法:AopContext.currentProxy();才会收效。

悉数代码库房:gitee.com/ncharming/k…