芝法酱学习笔记(1.3)——SpringBoot+mybatis plus+atomikos实现多数据源事务

一、前言

1.1 业务需求

之前我们在讲解注册和登录的时候,有一个重要的技术点忽略了过去。那就是多数据源的事务问题。
按照我们的业务需求,monitor服务可能涉及同时对监控中心数据库和企业中心数据库进行操作,而我们希望这样的操作在一个事务下。并且,企业中心有多个数据库,我们需要一个自动切库的机制。

1.2 多数据源事务技术选型

多个数据库的切库可以使用AbstractRoutingDataSource作为数据源,但要支持事务却没那么简单。多数据源事务,我们最先想到的就是分布式事务,阿里的seata框架可以很好的解决分布式事务问题。所谓分布式事务,就是指一个事务可能分布在不同的服务器上,但需要各服务器同时完成,然后再提交数据库。如果有哪个服务器失败,则一起回滚。
然而,我们这里的需求,仅仅是单一服务器操作多个数据源。如果因此引入seata,还多了一个事务中心的服务器,无疑增加了运维的成本。所以我们打算使用atomikos实现多数据源的事务。

1.3 atomikos介绍

Atomikos 是一个轻量级的 Java 分布式事务管理器。符合XA 和 JTA(Java Transaction API) 规范。有的帖子说atomikos的性能欠佳,因为会上行锁。但我们扪心自问,我们的业务真的有对同一行数据并发的情况么?
当然,我们后面的大章,可能也会介绍seata的方案。不过当前学习阶段,还是先采用Atomikos吧

二、所遇到的挑战

当我们草草的接入atomikos后,会发现AbstractRoutingDataSource数据源下,在一个Transactional注解内,使用AbstractRoutingDataSource作为动态数据源是无法实现切库的。想要探究其原因,需要翻阅源码。
首先,我们按照网上通行的方式,配置一个AbstractRoutingDataSource。使用单步调试,观察其不可切库的原因。

@Slf4j
@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource busyDataSource(){
        MultiDataSource multiDataSource = new MultiDataSource();
        multiDataSource.init();
        return multiDataSource;
    }
    public class MultiDataSource extends AbstractRoutingDataSource{
        public static ThreadLocal<String> curKey = new ThreadLocal<>();
        public void init(){
            HikariDataSource dataSourceMonitor = DataSourceBuilder.create().type(HikariDataSource.class)
                    .driverClassName("com.mysql.cj.jdbc.Driver")
                    .url("jdbc:mysql://192.168.0.64:3306/study2024-class007-monitor?useUnicode=true&characterEncoding=utf-8")
                    .username("dbMgr")
                    .password("???@7")
                    .build();
            HikariDataSource dataSource1 = DataSourceBuilder.create().type(HikariDataSource.class)
                    .driverClassName("com.mysql.cj.jdbc.Driver")
                    .url("jdbc:mysql://192.168.0.64:3306/study2024-class007-busy001?useUnicode=true&characterEncoding=utf-8")
                    .username("dbMgr")
                    .password("???@7")
                    .build();
            HikariDataSource dataSource2 = DataSourceBuilder.create().type(HikariDataSource.class)
                    .driverClassName("com.mysql.cj.jdbc.Driver")
                    .url("jdbc:mysql://192.168.0.64:3306/study2024-class007-busy002?useUnicode=true&characterEncoding=utf-8")
                    .username("dbMgr")
                    .password("???@7")
                    .build();
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put("monitor", dataSourceMonitor);
            targetDataSources.put("busy001", dataSource1);
            targetDataSources.put("busy002", dataSource2);
            setTargetDataSources(targetDataSources);
            setDefaultTargetDataSource(dataSourceMonitor);

            curKey.set("monitor");
        }
        @Override
        protected Object determineCurrentLookupKey() {
            return curKey.get();
        }
        public static void changeDb(String pKey){
            curKey.set(pKey);
        }
    }
}

我们建一个test_table的表,写一段简单的测试逻辑

@RequiredArgsConstructor
@Service
public class TestCurdServiceImpl implements ITestCurdService {

    private final IGenMonitorTestDbService mGenMonitorTestDbService;
    private final IGenBusyTestDbService mGenBusyTestDbService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void mpSave(TestEntityDto pTestDto) {
        GenMonitorTestEntity genMonitorTestEntity = DbDtoEntityUtil.createFromDto(pTestDto,GenMonitorTestEntity.class);
        GenBusyTestEntity genBusyTestEntity = DbDtoEntityUtil.createFromDto(pTestDto,GenBusyTestEntity.class);
        genBusyTestEntity.setEnpId(0L);
        genBusyTestEntity.setEnpCode("sys");
        genMonitorTestEntity.setEnpId(0L);
        genMonitorTestEntity.setEnpCode("sys");

        DataSourceConfig.MultiDataSource.changeDb("monitor");
        mGenMonitorTestDbService.save(genMonitorTestEntity);
        DataSourceConfig.MultiDataSource.changeDb("busy001");
        mGenBusyTestDbService.save(genBusyTestEntity);
        DataSourceConfig.MultiDataSource.changeDb("busy002");
        mGenBusyTestDbService.save(genBusyTestEntity);
    }

}

SpringBoot中,通过@EnableTransactionManagement,通过@Import(TransactionManagementConfigurationSelector.class)等一系列操作,最终在ProxyTransactionManagementConfiguration文件配置了一个TransactionInterceptor的bean,其基类TransactionAspectSupport就是Spring对事务AOP实现的核心代码了,拦截我们的Transaction注解下方法的函数是invokeWithinTransaction。再进一步阅读跟踪,发现在DataSourceTransactionManager的doBegin函数中,需要获取datasource的connection,并关闭自动提交。获取的connection,会放到ConnectionHolder里。
而每次执行mybatis的命令,实质上执行的是mybatis包下SimpleExecutor的prepareStatement,每次查找前,都会调用transaction.getConnection()。而这个类被实例化时,他的transaction用的是SpringManagedTransaction。其getConnection代码如下:

public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

我们可以看到,如果获取过链接了,就不会再获取了。事务doBegin时获取了一次,所以事务注解内的sql执行不会再获取。
而我们发现,SimpleExecutor的实例化是使用Mybatis的Configuration类中的信息,决定使用哪个transactionManager。
那么,我们自己写一个TransactionManager,处理多数据源下的问题,不就解决了么?
另一个问题,我们传统使用bean注解方式创建bean,并不能实现根据配置动态批量的创建bean。然而,我们希望这套代码可以放到公司项目仓库中,被业务代码引用。这时我们就需要使用Spring的容器装配。

三、代码实现

3.1 pom引用

<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-spring-boot3-starter</artifactId>
    <version>${atomikos.version}</version>
</dependency>
<atomikos.version>6.0.0</atomikos.version>

3.2 MultiDataSourceTransaction

前边提到,之所以无法切库,是Transaction配置的问题。我们为AbstractRoutingDataSource专门写一个Transaction管理器

@Slf4j
public class MultiDataSourceTransaction implements Transaction {

    private ZfRoutingDataSource mZfRoutingDataSource;
    private Map<String,MultiDataSourceConnectionInfo> connectionMap;

    public MultiDataSourceTransaction(DataSource pDataSource) {
        if(pDataSource instanceof ZfRoutingDataSource zfRoutingDataSource){
            mZfRoutingDataSource = zfRoutingDataSource;
            connectionMap = new ConcurrentHashMap<String,MultiDataSourceConnectionInfo>();
        }else{
            throw new ServiceException("传入的DataSource 必须是ZfRoutingDataSource");
        }
    }


    @Override
    public Connection getConnection() throws SQLException {
        String curKey = mZfRoutingDataSource.curKey();
        MultiDataSourceConnectionInfo multiDataSourceConnectionInfo = connectionMap.get(curKey);
        if(null == multiDataSourceConnectionInfo) {
            multiDataSourceConnectionInfo = new MultiDataSourceConnectionInfo();
            DataSource targetDataSource = mZfRoutingDataSource.getTargetDataSource();
            Connection connection = DataSourceUtils.getConnection(targetDataSource);
            multiDataSourceConnectionInfo.setDataSource(targetDataSource);
            multiDataSourceConnectionInfo.setConnection(connection);
            multiDataSourceConnectionInfo.setAutoCommit(connection.getAutoCommit());
            multiDataSourceConnectionInfo.setConnectionTransactional(DataSourceUtils.isConnectionTransactional(connection, targetDataSource));
            connectionMap.put(curKey, multiDataSourceConnectionInfo);
        }
        return multiDataSourceConnectionInfo.getConnection();
    }

    @Override
    public void commit() throws SQLException {
        for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {
            if(!multiDataSourceConnectionInfo.isConnectionTransactional() && !multiDataSourceConnectionInfo.isAutoCommit()){
                Connection targetConnection = multiDataSourceConnectionInfo.getConnection();
                targetConnection.commit();
            }
        }
    }

    @Override
    public void rollback() throws SQLException {
        for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {
            if(!multiDataSourceConnectionInfo.isConnectionTransactional() && !multiDataSourceConnectionInfo.isAutoCommit()){
                Connection targetConnection = multiDataSourceConnectionInfo.getConnection();
                targetConnection.rollback();
            }
        }
    }

    @Override
    public void close() throws SQLException {
        for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {
            DataSourceUtils.releaseConnection(multiDataSourceConnectionInfo.getConnection(), multiDataSourceConnectionInfo.getDataSource());
        }
        connectionMap.clear();
    }

    @Override
    public Integer getTimeout() throws SQLException {
        var holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(mZfRoutingDataSource);
        if (holder != null && holder.hasTimeout()) {
            return holder.getTimeToLiveInSeconds();
        }
        return null;
    }
}
public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {
    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new MultiDataSourceTransaction(dataSource);
    }
}

3.3 mapper的代码结构

首先,我们明确一下需求。我们的数据库分2种,一种是监控中心的库,只有一个数据库实例。而企业中心,却有多个实例。
我们在开发中,可以把监控中心的库的mapper和企业中心的不放在一个包中,分别配置:
在这里插入图片描述
这样,我们可以把监控中心配置为单数据源,企业中心配置为多数据源。

@MapperScans(value = {
        @MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.monitor.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_monitor"),
        @MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.busy.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_busy"),
}
)

3.4 RoutingDataSource

我们针对多数据源,考虑有些项目可能有多组不同的多数据源。比如常见的订单中心,优惠券中心,商品定义中心。写个自己的memo。

public class ZfRoutingDataSource extends AbstractRoutingDataSource {

    ThreadLocal<String> curDbKey = new ThreadLocal<String>();

    @Override
    protected Object determineCurrentLookupKey() {
        return curDbKey.get();
    }

    public String curKey(){
        return curDbKey.get();
    }

    public void set(String pKey) {
        curDbKey.set(pKey);
    }

    public void clear() {
        curDbKey.remove();
    }

    public DataSource getTargetDataSource() {
        return determineTargetDataSource();
    }
}
public class RoutingDataSourceMemo {
    private Map<String,ZfRoutingDataSource> mRoutingDataSource;

    public RoutingDataSourceMemo() {
        mRoutingDataSource = new ConcurrentHashMap<String,ZfRoutingDataSource>();
    }
    public ZfRoutingDataSource createRoutingDataSource(String pKey, Map<Object, Object> pDataSourceMap, DataSource pPrimaryDataSource) {
        ZfRoutingDataSource zfRoutingDataSource = new ZfRoutingDataSource();
        zfRoutingDataSource.setTargetDataSources(pDataSourceMap);
        zfRoutingDataSource.setDefaultTargetDataSource(pPrimaryDataSource);
        zfRoutingDataSource.initialize();
        mRoutingDataSource.put(pKey, zfRoutingDataSource);
        return zfRoutingDataSource;
    }
    public ZfRoutingDataSource get(String pKey) {
        return Optional.<ZfRoutingDataSource>ofNullable(mRoutingDataSource.get(pKey)).
                orElseThrow(()->new ServiceException("没有找到key为"+pKey+"的数据源"));
    }
}
@Configuration
public class RoutingDataSourceMemoConfigure {
    @Bean
    RoutingDataSourceMemo routingDataSourceMemo(){
        return new RoutingDataSourceMemo();
    }
}

3.5 bean装配

3.5.1 常见接口的介绍

ImportBeanDefinitionRegistrar

一般该接口配合@Import使用。
该接口一般用于导入bean,其有2个接口。

void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator);
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

该接口可以读取类上的注解,并依据注解的配置信息,批量注册创建bean的PostProcessor的定义。
该接口允许类继承以下4个接口,用于获取Spring容器的一些关键对象

  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware
BeanDefinitionRegistryPostProcessor

该接口一般用于批量注册实际的bean,其下也有2个接口

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
	@Override
	default void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

第一个接口用于向容器注册bean的定义,第二个接口可以直接向容器注册bean的实例

FactoryBean

.该接口用于延迟化实例化bean。由于许多bean的实例化需要依赖其他bean的创建,那么干脆在该bean被第一次使用到时进行加载。实际上,SpringBoot对Bean的生命周期管理,是基于FactoryBean而非具体的Bean。
该接口的核心接口为:

T getObject() throws Exception;

3.5.2 MultiDataSourceConfigurerRegister

现在,我们开始编写本章需求的代码。这个Register用于向容器注册一个批量注册数据源和SessionFactory的PostProcessor

@Slf4j
public class MultiDataSourceConfigurerRegister implements ImportBeanDefinitionRegistrar,
        EnvironmentAware, ResourceLoaderAware, BeanFactoryAware {

    private Environment mEnvironment;
    private ResourceLoader mResourceLoader;
    private BeanFactory mBeanFactory;

    public MultiDataSourceConfigurerRegister() {
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry,
                                        BeanNameGenerator importBeanNameGenerator) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MultiDataSourceConfigurer.class);
        builder.addConstructorArgValue(mBeanFactory);
        builder.addConstructorArgValue(mResourceLoader);
        builder.addConstructorArgValue(mEnvironment);
        builder.addConstructorArgReference("routingDataSourceMemo");
        registry.registerBeanDefinition(MultiDataSourceConfigurer.class.getName(),builder.getBeanDefinition());
    }

    @Override
    public void setEnvironment(Environment pEnvironment) {
        mEnvironment = pEnvironment;
    }

    @Override
    public void setResourceLoader(ResourceLoader pResourceLoader) {
        mResourceLoader = pResourceLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory pBeanFactory) throws BeansException {
        mBeanFactory = pBeanFactory;
    }
}

3.5.3 MultiDataSourceConfigurerRegister

该类是注册Datasource和SessionFactory的FactoryBean的类

@Slf4j
public class MultiDataSourceConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware {

    private BeanFactory mBeanFactory;
    private ResourceLoader mResourceLoader;
    private Environment mEnvironment;


    private ApplicationContext mApplicationContext;

    static final String DATASOURCE_PREFIX = "datasource_";
    static final String SQL_SESSION_FACTORY_PREFIX = "sqlSessionFactory_";
    static final String SQL_SESSION_TEMPLATE_PREFIX = "sqlSessionTemplate_";

    RoutingDataSourceMemo mRoutingDataSourceMemo;

    public MultiDataSourceConfigurer(BeanFactory pBeanFactory,
                                     ResourceLoader pResourceLoader,
                                     Environment pEnvironment,
                                     RoutingDataSourceMemo pRoutingDataSourceMemo) {
        mBeanFactory = pBeanFactory;
        mResourceLoader = pResourceLoader;
        mEnvironment = pEnvironment;
        mRoutingDataSourceMemo = pRoutingDataSourceMemo;
    }


    @Override
    public void afterPropertiesSet() throws Exception {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String profile = mEnvironment.getProperty("spring.profiles.active");
        String url = mEnvironment.getProperty("spring.datasource.url");
        String userName = mEnvironment.getProperty("spring.datasource.username");
        String password = mEnvironment.getProperty("spring.datasource.password");
        SingleConnectionDataSource singleConnectionDataSource = new SingleConnectionDataSource(
                url, userName,password,false);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(singleConnectionDataSource);
        List<Map<String,Object>> res =  jdbcTemplate.queryForList("select * from sys_db where profile = ?",profile);
        List<SysDbEntity> sysDbEntityList = res.stream().map(m-> BeanUtil.toBean(m,SysDbEntity.class)).collect(Collectors.toList());
        Map<String,List<SysDbEntity>> sysDbEntityListMap = sysDbEntityList.stream().collect(Collectors.groupingBy(SysDbEntity::getDatasourceGroup));

        for(Map.Entry<String,List<SysDbEntity>> entry : sysDbEntityListMap.entrySet()){
            String datasourceGroup = entry.getKey();
            List<SysDbEntity> list = entry.getValue();
            SysDbEntity first = list.get(0);
            switch (first.getDatasourceType()){
                case 0 ->{
                    AbstractBeanDefinition singleDataSourceBeanDef = genSingleDataSourceFactorBean(first);
                    registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,singleDataSourceBeanDef);
                    AbstractBeanDefinition singleSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.SINGLE,datasourceGroup);
                    registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,singleSqlSessionFactorBean);
                }
                case 1 ->{
                    AbstractBeanDefinition multiDataSourceBeanDef = genMultiDataSourceFactorBean(datasourceGroup,list,mRoutingDataSourceMemo);
                    registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,multiDataSourceBeanDef);

                    AbstractBeanDefinition multiSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.MULTI,datasourceGroup);
                    registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,multiSqlSessionFactorBean);
                }
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

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

    public AbstractBeanDefinition genSingleDataSourceFactorBean(SysDbEntity pSysDbEntity){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SingleDataSourceFactorBean.class);
        builder.addConstructorArgValue(pSysDbEntity);
        return builder.getBeanDefinition();
    }

    public AbstractBeanDefinition genMultiDataSourceFactorBean(String pKey,List<SysDbEntity> pSysDbEntityList,RoutingDataSourceMemo pRoutingDataSourceMemo){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MultiDataSourceFactorBean.class);
        builder.addConstructorArgValue(pKey);
        builder.addConstructorArgValue(pSysDbEntityList);
        builder.addConstructorArgValue(pRoutingDataSourceMemo);
        return builder.getBeanDefinition();
    }

    public AbstractBeanDefinition genSqlSessionFactorBean(EDataSourceType pDataSourceType, String pDataSourceName){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactorBean.class);

        builder.addConstructorArgValue(pDataSourceType);
        builder.addConstructorArgReference(DATASOURCE_PREFIX + pDataSourceName);
        ObjectProvider<MybatisPlusProperties> mybatisPlusPropertiesProvider = mBeanFactory.getBeanProvider(MybatisPlusProperties.class);
        builder.addConstructorArgValue(mybatisPlusPropertiesProvider);
        builder.addConstructorArgValue(mResourceLoader);
        builder.addConstructorArgValue(mApplicationContext);

        ObjectProvider<Interceptor> interceptorsProvider = mBeanFactory.getBeanProvider(Interceptor.class);
        builder.addConstructorArgValue(interceptorsProvider);

        ObjectProvider<TypeHandler> typeHandlerProvider = mBeanFactory.getBeanProvider(TypeHandler.class);
        builder.addConstructorArgValue(typeHandlerProvider);

        ObjectProvider<LanguageDriver> languageDriverProvider = mBeanFactory.getBeanProvider(LanguageDriver.class);
        builder.addConstructorArgValue(languageDriverProvider);

        ObjectProvider<DatabaseIdProvider> databaseIdProviderProvider = mBeanFactory.getBeanProvider(DatabaseIdProvider.class);
        builder.addConstructorArgValue(databaseIdProviderProvider);

        ResolvableType configurationCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, ConfigurationCustomizer.class);
        ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizerListProvider = mBeanFactory.getBeanProvider(configurationCustomizerTargetType);
        builder.addConstructorArgValue(configurationCustomizerListProvider);

        ResolvableType sqlSessionFactoryBeanCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, SqlSessionFactoryBeanCustomizer.class);
        ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizerListProvider = mBeanFactory.getBeanProvider(sqlSessionFactoryBeanCustomizerTargetType);
        builder.addConstructorArgValue(sqlSessionFactoryBeanCustomizerListProvider);

        ResolvableType mybatisPlusPropertiesCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, MybatisPlusPropertiesCustomizer.class);
        ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider = mBeanFactory.getBeanProvider(mybatisPlusPropertiesCustomizerTargetType);
        builder.addConstructorArgValue(mybatisPlusPropertiesCustomizerProvider);

        // 显式添加配置类的依赖
        //builder.addDependsOn("mybatisPlusInterceptor");

        return builder.getBeanDefinition();
    }
    
}

3.5.4 Dadasouce的FactorBean

public class SingleDataSourceFactorBean implements FactoryBean<DataSource> {

    private final SysDbEntity mSysDbEntity;

    public SingleDataSourceFactorBean(SysDbEntity pSysDbEntity) {
        mSysDbEntity = pSysDbEntity;
    }

    @Override
    public DataSource getObject() throws Exception {
        MysqlXADataSource dataSource = DataSourceBuilder.create().type(MysqlXADataSource.class)
                .url(mSysDbEntity.getDbUrl())
                .username(mSysDbEntity.getDbUser())
                .password(mSysDbEntity.getDbPasswd())
                .build();
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setUniqueResourceName(mSysDbEntity.getDbKey());
        atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.cj.jdbc.XADataSource");
        atomikosDataSourceBean.setXaDataSource(dataSource);
        Optional.ofNullable(mSysDbEntity.getMinIdle()).ifPresent(atomikosDataSourceBean::setMinPoolSize);
        Optional.ofNullable(mSysDbEntity.getMaxPoolSize()).ifPresent(atomikosDataSourceBean::setMaxPoolSize);
        return atomikosDataSourceBean;
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }
}
public class MultiDataSourceFactorBean implements FactoryBean<DataSource>  {

    private final String mKey;
    private final List<SysDbEntity> mSysDbEntityList;
    private final RoutingDataSourceMemo mRoutingDataSourceMemo;

    public MultiDataSourceFactorBean(String pKey, List<SysDbEntity> pSysDbEntityList, RoutingDataSourceMemo pRoutingDataSourceMemo) {
        mKey = pKey;
        mSysDbEntityList = pSysDbEntityList;
        mRoutingDataSourceMemo = pRoutingDataSourceMemo;
    }

    @Override
    public DataSource getObject() throws Exception {
        Map<Object, Object> dataSourceMap = new HashMap<>();
        DataSource primaryDataSource = null;
        for (SysDbEntity sysDbEntity : mSysDbEntityList) {
            MysqlXADataSource dataSource = DataSourceBuilder.create().type(MysqlXADataSource.class)
                    .url(sysDbEntity.getDbUrl())
                    .username(sysDbEntity.getDbUser())
                    .password(sysDbEntity.getDbPasswd())
                    .build();
            AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
            atomikosDataSourceBean.setUniqueResourceName(sysDbEntity.getDbKey());
            atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.cj.jdbc.XADataSource");
            atomikosDataSourceBean.setXaDataSource(dataSource);
            Optional.ofNullable(sysDbEntity.getMinIdle()).ifPresent(atomikosDataSourceBean::setMinPoolSize);
            Optional.ofNullable(sysDbEntity.getMaxPoolSize()).ifPresent(atomikosDataSourceBean::setMaxPoolSize);
            dataSourceMap.put(sysDbEntity.getDbKey(), atomikosDataSourceBean);
            if(sysDbEntity.getPrimary()){
                primaryDataSource = atomikosDataSourceBean;
            }
        }
        ZfRoutingDataSource dataSourceRouter = mRoutingDataSourceMemo.createRoutingDataSource(mKey,dataSourceMap,primaryDataSource);
        return dataSourceRouter;
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }
}

3.5.5 Dadasouce的FactorBean

该类需要重点说一下,mybatisplus对mybatis的拓展功能,是依赖SqlSessionFactory实现的。所以单纯的new一个MybatisSqlSessionFactoryBean ,而后getObject,将会使所有mybatis-plus的功能无法使用。我下面的这段代码,是copy并修改mybatis-plus的MybatisPlusAutoConfiguration。

public class SqlSessionFactorBean implements FactoryBean<SqlSessionFactory> {

    private final DataSource mDataSource;

    private final ObjectProvider<MybatisPlusProperties> mPropertiesProvider;
    private final ResourceLoader mResourceLoader;
    private final ApplicationContext mApplicationContext;
    private final EDataSourceType mDataSourceType;

    /* mp用到的一些变量*/

    private final ObjectProvider<Interceptor> mInterceptorsProvider;
    private final ObjectProvider<TypeHandler> mTypeHandlersProvider;
    private final ObjectProvider<LanguageDriver> mLanguageDriversProvider;
    private final ObjectProvider<DatabaseIdProvider> mDatabaseIdProviderProvider;
    private final ObjectProvider<List<ConfigurationCustomizer>> mConfigurationCustomizersProvider;
    private final ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> mSqlSessionFactoryBeanCustomizersProvider;
    private final ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mMybatisPlusPropertiesCustomizersProvider;


    private Interceptor[] mInterceptors;
    private TypeHandler[] mTypeHandlers;
    private LanguageDriver[] mLanguageDrivers;
    private DatabaseIdProvider mDatabaseIdProvider;
    private List<ConfigurationCustomizer> mConfigurationCustomizers;
    private List<SqlSessionFactoryBeanCustomizer> mSqlSessionFactoryBeanCustomizers;
    private List<MybatisPlusPropertiesCustomizer> mMybatisPlusPropertiesCustomizers;
    private MybatisPlusProperties mMybatisPlusProperties;



    public SqlSessionFactorBean(EDataSourceType pDataSourceType,
                                DataSource pDataSource,
                                ObjectProvider<MybatisPlusProperties> propertiesProvider,
                                ResourceLoader resourceLoader,
                                ApplicationContext applicationContext,

                                ObjectProvider<Interceptor> interceptorsProvider,
                                ObjectProvider<TypeHandler> typeHandlersProvider,
                                ObjectProvider<LanguageDriver> languageDriversProvider,
                                ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
                                ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers,
                                ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider) {
        mDataSourceType = pDataSourceType;
        mDataSource = pDataSource;
        this.mPropertiesProvider = propertiesProvider;
        this.mInterceptorsProvider = interceptorsProvider;
        this.mTypeHandlersProvider = typeHandlersProvider;
        this.mLanguageDriversProvider = languageDriversProvider;
        this.mResourceLoader = resourceLoader;
        this.mDatabaseIdProviderProvider = databaseIdProvider;
        this.mConfigurationCustomizersProvider = configurationCustomizersProvider;
        this.mSqlSessionFactoryBeanCustomizersProvider = sqlSessionFactoryBeanCustomizers;
        this.mMybatisPlusPropertiesCustomizersProvider = mybatisPlusPropertiesCustomizerProvider;
        this.mApplicationContext = applicationContext;
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        getMpBeans();
        switch (mDataSourceType){
            case MULTI -> {
                MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                MultiDataSourceTransactionFactory transactionFactory = new MultiDataSourceTransactionFactory();
                sqlSessionFactory(bean,mDataSource);
                bean.setTransactionFactory(transactionFactory);
                return bean.getObject();
            }
            case SINGLE -> {
                MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
                sqlSessionFactory(bean,mDataSource);
                return bean.getObject();
            }
        }
        return null;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    protected void getMpBeans(){
        mInterceptors = mInterceptorsProvider.stream().toArray(Interceptor[]::new);
        mTypeHandlers = mTypeHandlersProvider.stream().toArray(TypeHandler[]::new);
        mLanguageDrivers = mLanguageDriversProvider.stream().toArray(LanguageDriver[]::new);
        mDatabaseIdProvider = mDatabaseIdProviderProvider.getIfAvailable();
        mConfigurationCustomizers = mConfigurationCustomizersProvider.getIfAvailable();
        mSqlSessionFactoryBeanCustomizers = mSqlSessionFactoryBeanCustomizersProvider.getIfAvailable();
        mMybatisPlusPropertiesCustomizers = mMybatisPlusPropertiesCustomizersProvider.getIfAvailable();
        mMybatisPlusProperties = mPropertiesProvider.getIfAvailable();
    }


    public MybatisSqlSessionFactoryBean sqlSessionFactory(MybatisSqlSessionFactoryBean pMybatisSqlSessionFactoryBean ,DataSource dataSource) throws Exception {

        MybatisSqlSessionFactoryBean factory = pMybatisSqlSessionFactoryBean;
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {
            factory.setConfigLocation(this.mResourceLoader.getResource(this.mMybatisPlusProperties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.mMybatisPlusProperties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.mMybatisPlusProperties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.mInterceptors)) {
            factory.setPlugins(this.mInterceptors);
        }
        if (this.mDatabaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.mDatabaseIdProvider);
        }
        if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.mMybatisPlusProperties.getTypeAliasesPackage());
        }
        if (this.mMybatisPlusProperties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.mMybatisPlusProperties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.mMybatisPlusProperties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.mTypeHandlers)) {
            factory.setTypeHandlers(this.mTypeHandlers);
        }
        if (!ObjectUtils.isEmpty(this.mMybatisPlusProperties.resolveMapperLocations())) {
            factory.setMapperLocations(this.mMybatisPlusProperties.resolveMapperLocations());
        }

        Class<? extends LanguageDriver> defaultLanguageDriver = this.mMybatisPlusProperties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.mLanguageDrivers)) {
            factory.setScriptingLanguageDrivers(this.mLanguageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

        applySqlSessionFactoryBeanCustomizers(factory);

        GlobalConfig globalConfig = this.mMybatisPlusProperties.getGlobalConfig();
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        this.getBeanThen(AnnotationHandler.class, globalConfig::setAnnotationHandler);
        this.getBeanThen(PostInitTableInfoHandler.class, globalConfig::setPostInitTableInfoHandler);
        this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        factory.setGlobalConfig(globalConfig);
        return factory;
    }


    private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
        if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            consumer.accept(this.mApplicationContext.getBean(clazz));
        }
    }

    private <T> void getBeansThen(Class<T> clazz, Consumer<List<T>> consumer) {
        if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
            final Map<String, T> beansOfType = this.mApplicationContext.getBeansOfType(clazz);
            List<T> clazzList = new ArrayList<>();
            beansOfType.forEach((k, v) -> clazzList.add(v));
            consumer.accept(clazzList);
        }
    }

    private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
        MybatisPlusProperties.CoreConfiguration coreConfiguration = this.mMybatisPlusProperties.getConfiguration();
        MybatisConfiguration configuration = null;
        if (coreConfiguration != null || !StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {
            configuration = new MybatisConfiguration();
        }
        if (configuration != null && coreConfiguration != null) {
            coreConfiguration.applyTo(configuration);
        }
        if (configuration != null && !CollectionUtils.isEmpty(this.mConfigurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.mConfigurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
    }

    private void applySqlSessionFactoryBeanCustomizers(MybatisSqlSessionFactoryBean factory) {
        if (!CollectionUtils.isEmpty(this.mSqlSessionFactoryBeanCustomizers)) {
            for (SqlSessionFactoryBeanCustomizer customizer : this.mSqlSessionFactoryBeanCustomizers) {
                customizer.customize(factory);
            }
        }
    }
}

四、代码展示

在主类上,添加如下注解,就可以使用多数据源事务了

@EnableZfMultiDataSource
@MapperScans(value = {
        @MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.monitor.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_monitor"),
        @MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.busy.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_busy"),
}
)

具体代码请移步我的 码云

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/933952.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Centos服务器如何访问windows的共享目录

CentOS服务器访问Windows的共享目录通常需要使用SMB/CIFS&#xff08;Server Message Block/Common Internet File System&#xff09;协议。以下是详细的步骤&#xff1a; 1、Windows端设置共享文件夹 1&#xff09;右键要共享的文件夹&#xff0c;点击属性-->在“共享”选…

JVM, JRE 和 JDK

JRE: Java Runtime Environment, Java 运行环境. JDK: Java Development Kit, Java 开发工具包. JRE JVM 核心类库 运行工具 JDK JVM 核心类库 开发工具 JVM: Java Virtual Machine, Java 虚拟机. 核心类库: Java 已经写好的东西, 直接拿来用即可. 开发工具: 包括 …

图数据库 | 13、图数据库架构设计——高性能计算架构再续

书接上文 图数据库 | 12、图数据库架构设计——高性能计算架构​​​​​​。昨天老夫就图数据库架构设计中的 实时图计算系统架构、图数据库模式与数据模型、核心引擎如何处理不同的数据类型、图计算引擎中的数据结构 这四块内容进行了展开讲解&#xff0c;今儿继续往下、往深…

Linux Cgroup学习笔记

文章目录 Cgroup(Control Group)引言简介Cgroup v1通用接口文件blkio子系统cpu子系统cpuacct子系统cpuset子系统devices子系统freezer子系统hugetlb子系统memory子系统net_cls子系统net_prio子系统perf_event子系统pids子系统misc子系统 Cgroup V2基础操作组织进程和线程popula…

R语言 | 峰峦图 / 山脊图

目的&#xff1a;为展示不同数据分布的差异。 1. ggplot2 实现 # 准备数据 datmtcars[, c("mpg", "cyl")] colnames(dat)c("value", "type") head(dat) # value type #Mazda RX4 21.0 6 #Mazda RX4 Wag …

java+ssm+mysql收纳培训网

项目介绍&#xff1a; 使用javassmmysql开发的收纳视频培训网&#xff0c;系统包含超级管理员&#xff0c;系统管理员、培训师、用户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;用户管理&#xff08;培训师、用户&#xff09;&#…

【教程】创建NVIDIA Docker共享使用主机的GPU

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 这套是我跑完整理的。直接上干货&#xff0c;复制粘贴即可&#xff01; # 先安装toolkit sudo apt-get update sudo apt-get install -y ca-certifica…

【全攻略】React Native与环信UIKit:Expo项目从创建到云打包完整指南

前言 在当今快速发展的移动应用领域&#xff0c;React Native 因其跨平台开发能力和高效的开发周期而受到开发者的青睐。而 Expo&#xff0c;作为一个基于 React Native 的框架&#xff0c;进一步简化了开发流程&#xff0c;提供了一套完整的工具链&#xff0c;使得开发者能够…

新浪财经-数据中心-基金重仓GU-多页数据批量获取

拉到底部&#xff0c;可以看到一共有6页。 import pandas as pd dfpd.DataFrame() url_strhttp://vip.stock.finance.sina.com.cn/q/go.php/vComStockHold/kind/jjzc/index.phtml?p for i in range(6): urlstr(url_str)str(i1) df pd.concat([df,pd.read_html(url)…

从爱尔兰歌曲到莎士比亚:LSTM文本生成模型的优化之旅

上一篇&#xff1a;《再用RNN神经网络架构设计生成式语言模型》 序言&#xff1a;本文探讨了如何通过多种方法改进模型的输出&#xff0c;包括扩展数据集、调整模型架构、优化训练数据的窗口设置&#xff0c;以及采用字符级编码。这些方法旨在提高生成文本的准确性和合理性&am…

ElasticSearch常见的索引_集群的备份与恢复方案

方案一&#xff1a;使用Elasticsearch的快照和恢复功能进行备份和恢复。该方案适用于集群整体备份与迁移&#xff0c;包括全量、增量备份和恢复。 方案二&#xff1a;通过reindex操作在集群内或跨集群同步数据。该方案适用于相同集群但不同索引层面的迁移&#xff0c;或者跨集…

软件工程复习记录

基本概念 软件工程三要素&#xff1a;方法、工具、过程 软件开发方法&#xff1a;软件开发所遵循的办法和步骤&#xff0c;以保证所得到的运行系统和支持的文档满足质量要求。 软件开发过程管理 软件生命周期&#xff1a;可行性研究、需求分析、概要设计、详细设计、编码、测…

快速了解 Aurora DSQL

上周在 AWS re:Invent大会&#xff08;类似于阿里云的云栖大会&#xff09;上推出了新的产品 Aurora DSQL[1] &#xff0c;在数据库层面提供了多区域、多点一致性写入的能力&#xff0c;兼容 PostgreSQL。并声称&#xff0c;在多语句跨区域的场景下&#xff0c;延迟只有Google …

差异基因富集分析(R语言——GOKEGGGSEA)

接着上次的内容&#xff0c;上篇内容给大家分享了基因表达量怎么做分组差异分析&#xff0c;从而获得差异基因集&#xff0c;想了解的可以去看一下&#xff0c;这篇主要给大家分享一下得到显著差异基因集后怎么做一下通路富集。 1.准备差异基因集 我就直接把上次分享的拿到这…

运维排错系列:Excel上传失败,在剪切板有大量信息。是否保存其内容...

问题点 在导入 Excel 数据到 SAP 的时候&#xff0c;某些时刻系统会出现如下的弹窗。 上载 excel 文件时&#xff0c;您会收到错误&#xff1a;“剪贴板上有大量信息。XXX” Microsoft Office Excel 的弹出窗口显示以下信息&#xff1a; 剪贴板上存在大量信息。是否保留其内容…

Linux系统下常用资源查看

一、查看CPU使用率 top 命令 top命令可以看到总体的系统运行状态和cpu的使用率 。 %us&#xff1a;表示用户空间程序的cpu使用率&#xff08;没有通过nice调度&#xff09; %sy&#xff1a;表示系统空间的cpu使用率&#xff0c;主要是内核程序。 %ni&#xff1a;表示用户空间且…

关于一些游戏需要转区的方法

当玩非国区游戏时有时会出现乱码导致无法启动&#xff0c;此时多半需要转区来进行解决 1.下载转区软件 【转区工具】Locale Emulator 下载链接&#xff1a;Locale.Emulator.2.5.0.1.zip - 蓝奏云 用此软件可以解决大部分问题。 2.进行系统转区 首先打开控制面板选择时间与…

《探索视频数字人:开启未来视界的钥匙》

一、引言 1.1视频数字人技术的崛起 在当今科技飞速发展的时代&#xff0c;视频数字人技术如一颗璀璨的新星&#xff0c;正逐渐成为各领域瞩目的焦点。它的出现&#xff0c;犹如一场科技风暴&#xff0c;彻底改变了传统的视频制作方式&#xff0c;为各个行业带来了前所未有的机…

clipchamp制作视频文字转语音音频

一.准备工作&#xff1a; 1.在浏览器打开 https://app.clipchamp.com/首次打开需要登录&#xff0c;未登录用户注册登录 2.点击右上角头像到Settings页面&#xff0c;点击Language切换到中文&#xff08;英文水平好的可以忽略此步骤&#xff09;因中文英文界面有微小差异&…

MaxEnt模型在物种分布模拟中如何应用?R语言+MaxEnt模型融合物种分布模拟、参数优化方法、结果分析制图与论文写作

目录 第一章 以问题导入的方式&#xff0c;深入掌握原理基础 第二章 常用数据检索与R语言自动化下载及可视化方法 第三章 R语言数据清洗与特征变量筛选 第四章 基于ArcGIS、R数据处理与进阶 第五章 基于Maxent的物种分布建模与预测 第六章 基于R语言的模型参数优化 第七…