• springboot+mybatis-plus实现多数据源(从数据库加载多数据源)


    springboot+mybatis-plus配置多数据源的方式网上有很多,但是都是把数据源配置在yml或者properties中,由于本人所在项目需要从数据库加载数据源,所以本文介绍本人实现的方法是从数据库加载数据源。
    1.实现原理
    如果数据源是配置文件配置的,在项目启动时就会自动加载所以所有数据源并且实例化成相应的bean。但是数据库配置时,需要先加载一个主数据源,读取数据库表,把表里面配置数据库源再加载为bean。
    2.实现步骤
    1.由于在MyBatisPlusConfig中配置的地方需要配置一个DataSource去查询数据加载多数据源。代码如下

    @Bean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSourceSwitch) throws Exception
        {
            String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
            String mapperLocations = env.getProperty("mybatis.mapperLocations");
            typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
            VFS.addImplClass(SpringBootVFS.class);
    
            MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
            mybatisPlus.setDataSource(dataSourceSwitch);
            mybatisPlus.setVfs(SpringBootVFS.class);
            String configLocation = this.properties.getConfigLocation();
            if(StrUtil.isNotBlank(configLocation)) {
                mybatisPlus.setConfigLocation(this.resourceLoader.getResource(configLocation));
            }
            mybatisPlus.setConfiguration(properties.getConfiguration());
            mybatisPlus.setPlugins(this.interceptors);
            MybatisConfiguration mc = new MybatisConfiguration();
            mc.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
            mc.setMapUnderscoreToCamelCase(true);// 数据库和java都是驼峰,就不需要
            mybatisPlus.setConfiguration(mc);
            if (this.databaseIdProvider != null) {
                mybatisPlus.setDatabaseIdProvider(this.databaseIdProvider);
            }
            mybatisPlus.setTypeAliasesPackage(typeAliasesPackage);
            mybatisPlus.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
            mybatisPlus.setMapperLocations(this.properties.resolveMapperLocations());
            mybatisPlus.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
            return mybatisPlus.getObject();
    
    //        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    //        sessionFactory.setDataSource(dataSource);
    //        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
    //        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
    //        return sessionFactory.getObject();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.mybatisPlus.setDataSource(dataSourceSwitch); 这句代码,但是如果在这里直接配置主数据源,那么后面在加载多个数据源之后的重新配置比较麻烦(其实自己不会实现)。所以在这里我直接配置一个动态数据源对象,这个对象继承了AbstractRoutingDataSource对象。这个对象的相关源码中有两个属性targetDataSources(目标数据源是一个map,也就是我们配置的多数据源)和defaultTargetDataSource(默认的数据源,如果从map获取为null则默认设置的数据源)。所以我们初始化的时候就需要配置这两个属性,默认数据源就是主数据源。但是我们初始化的时候数据源对象还没生成并没有读取数据库,那我们的数据源怎么加载了,这里我用到了一个全局的hashMap。提前设置到targetDataSources上,在之后读取的时候王这个hashMap里面添加即可。所以在初始化的我实现了一下BeanPostProcessor(spring提供的一个可以在bean实例化之前和之后调用的方法可以动态修改我们的bean),代码如下:

    @Slf4j
    @Configuration
    public class DataSourceBeanPostProcessor implements BeanPostProcessor, BeanDefinitionRegistryPostProcessor {
    
        /**
         * 拦截dataSource初始化之后。将其装换为DataSourceSwitch,便于后续从数据库读取其他数据源注入
         * 这个地方需要在bean初始化之前执行.否则不会执行 {@link AbstractRoutingDataSource#afterPropertiesSet()}. 会提示数据源未初始化
         * @param bean:
         * @param beanName:
         * @date 2022/2/14 8:57
         * @return {@link Object}
         */
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (Objects.equals(beanName, DataSourceEnum.DATA_SOURCE_PRIMARY.getName())) {
                log.info("replace dataSource");
                DataSourceSwitch dataSourceSwitch = new DataSourceSwitch();
                DataSourceContextHolder.DATA_SOURCE_MAP.put(DataSourceEnum.DATA_SOURCE_PRIMARY.getName(), bean);
                dataSourceSwitch.setTargetDataSources(DataSourceContextHolder.DATA_SOURCE_MAP);
                dataSourceSwitch.setDefaultTargetDataSource(bean);
                return dataSourceSwitch;
            }
            return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
        }
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            String[] beanDefinitionNames = registry.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                if (Objects.equals(beanDefinitionName, "sqlSessionTemplate")) {
                    log.info("sqlSessionTemplate is primary");
                    BeanDefinition sqlSessionTemplate = registry.getBeanDefinition("sqlSessionTemplate");
                    sqlSessionTemplate.setPrimary(true);
                    registry.registerBeanDefinition("sqlSessionTemplate", sqlSessionTemplate);
                }
            }
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
        }
    }
    
    
    /**
     * @className DataSourceContextHolder
     * @date 2022/2/12 16:46
     **/
    public class DataSourceContextHolder {
    
        /**数据源名称**/
        private static final ThreadLocal databaseHolder = new ThreadLocal<>();
    
        public static final Map DATA_SOURCE_MAP = new ConcurrentHashMap<>(8);
    
        public static void setDatabaseHolder(String dataSourceName) {
            databaseHolder.set(dataSourceName);
        }
    
        /**
         * 取得当前数据源
         *
         * @return
         */
        public static String getDatabaseHolder() {
            return databaseHolder.get();
        }
    
        /**
         * 清除上下文数据
         */
        public static void clear() {
            databaseHolder.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    3.postProcessBeforeInitialization (bean初始化之前)。该方法中,我们拦截到dataSource这个对象,beanName就是dataSource。然后将dataSource对象转换为dataSourceSwitch我们声明的数据源对象,并且设置对象相关属性targetDataSourcesdefaultTargetDataSource。全局的hashMap就是DataSourceContextHolder 类中的 DATA_SOURCE_MAP。这里说一下targetDataSources这个map获取数据源的方法。在DataSourceSwitch继承AbstractRoutingDataSource 后,需要重写一个方法determineCurrentLookupKey方法,这个方法返回一个对象就是targetDataSources这个map中的key。所以这个key我配置到了ThreadLocal中databaseHolder

    @Slf4j
    public class DataSourceSwitch extends AbstractRoutingDataSource {
    
        /**
         * 根据{@link AbstractRoutingDataSource#targetDataSources} 获取当前数据源。如果为null,则获取默认 {@link AbstractRoutingDataSource#defaultTargetDataSource}
         * @param
         * @author xiatie
         * @date 2022/2/14 9:40
         * @return {@link Object}
         */
        @Override
        protected Object determineCurrentLookupKey() {
            String dataSourceName = DataSourceContextHolder.getDatabaseHolder();
            log.info("----------------get dataSource {}----------------", dataSourceName);
            return dataSourceName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.经过上面的,主数据源就已经配置完成了,并且提供的一个可以随时添加数据源的map。然后在自己写的DataSourceConfig中查询数据源注入到容器中并且添加到全局数据源map里面

    @Slf4j
    @Configuration
    public class DataSourceConfig implements InitializingBean {
    
        @Resource
        private DataSourceMapper dataSourceMapper;
    
        @Autowired
        private SpringUtil springUtil;
    
        @Override
        public void afterPropertiesSet() {
            try {
                // 1.查询所有数据源
                QueryWrapper queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("status", Const.VALID);
                List list = dataSourceMapper.selectList(queryWrapper);
                // 2.为每个数据源生成bean
                createBeanByDataSource(list);
            }catch (Exception e) {
                log.info("数据源加载失败{}", e.getMessage(), e);
            }
        }
    
    
        /**
         * 为每个数据源生成bean
         * 初始化配置文件主数据源 ---> 通过主数据源查询获取表中配置的数据源 ---> 将所有数据源配置成bean --->再将所有数据源从新注册到容器中
         * @param list:
         * @author xiatie
         * @date 2022/2/12 9:09
         */
        private void createBeanByDataSource(List list) throws Exception {
            if (CollectionUtil.isEmpty(list)) {
                log.info("数据源为空");
                return;
            }
            for (DataSourceEntity dataSourceEntity : list) {
                log.info("----------------start register dataSource{}----------------", dataSourceEntity.getDataSourceName());
                DruidDataSource dataSource = new DruidDataSource();
    
                dataSource.setDbType(dataSourceEntity.getType());
                dataSource.setUrl(dataSourceEntity.getUrl());
                dataSource.setUsername(dataSourceEntity.getUserName());
                dataSource.setPassword(dataSourceEntity.getPassWord());
                dataSource.setDriverClassName(dataSourceEntity.getDriverClassName());
                dataSource.setInitialSize(dataSourceEntity.getPoolInitialSize());
                dataSource.setMinIdle(dataSourceEntity.getPoolMinIdle());
                dataSource.setMaxActive(dataSourceEntity.getPoolMaxActive());
                dataSource.setMaxWait(dataSourceEntity.getPoolMaxWait());
                dataSource.setTimeBetweenEvictionRunsMillis(dataSourceEntity.getPoolTimeBetweenEvictionRunsMillis());
                dataSource.setMinEvictableIdleTimeMillis(dataSourceEntity.getPoolMinEvictableIdleTimeMillis());
                dataSource.setValidationQuery(dataSourceEntity.getPoolValidationQuery());
    
                springUtil.registerBean(dataSourceEntity.getDataSourceName(), dataSource);
                // 获取bean看是否注册成功
                Object registerBean = springUtil.getBean(dataSourceEntity.getDataSourceName());
                if (Objects.isNull(registerBean)) {
                    log.info("{}数据源注册失败", dataSourceEntity.getDataSourceName());
                    continue;
                }
                DataSourceContextHolder.DATA_SOURCE_MAP.put(dataSourceEntity.getDataSourceName(), registerBean);
    
                // 1.为每个数据源注册SqlSessionFactory
                SqlSessionFactory sqlSessionFactory = createSqlSessionFactoryByDataSource(((DruidDataSource) registerBean), dataSourceEntity.getDataSourceName());
                // 2.注册事务管理器
                createDataSourceTransactionManager(((DruidDataSource) registerBean), dataSourceEntity.getDataSourceName());
                // 3.注册sqlSessionTemplate
                createSqlSessionTemplate(sqlSessionFactory, dataSourceEntity.getDataSourceName());
                // 4.这个时候动态数据源中DataSourceSwitch已经存在相应数据,需要从新加载到DataSourceSwitch父类的resolvedDataSources属性中
                ((DataSourceSwitch) springUtil.getBean(DataSourceEnum.DATA_SOURCE_PRIMARY.getName())).afterPropertiesSet();
            }
        }
    
        /**
         * 注册sqlSessionFactory
         * @param druidDataSource:
         * @param dataSourceName:
         * @author xiatie
         * @date 2022/2/12 10:02
         */
        private SqlSessionFactory createSqlSessionFactoryByDataSource(DruidDataSource druidDataSource, String dataSourceName){
            try {
                MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
                sqlSessionFactoryBean.setDataSource((druidDataSource));
                MybatisConfiguration configuration = new MybatisConfiguration();
                configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
                configuration.setJdbcTypeForNull(JdbcType.NULL);
                sqlSessionFactoryBean.setConfiguration(configuration);
    
                sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().
                        getResources("classpath*:com/sevalo/data/statistics/**/*.xml"));
                sqlSessionFactoryBean.setPlugins(new MybatisPlusInterceptor());
                sqlSessionFactoryBean.setGlobalConfig(new GlobalConfig().setBanner(false));
    
                SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
    
                springUtil.registerBean(Const.SQL_SESSION_FACTORY + dataSourceName, sqlSessionFactory);
                // 获取是否注册成功
                Object bean = springUtil.getBean(Const.SQL_SESSION_FACTORY + dataSourceName);
                if (Objects.isNull(bean)) {
                    log.error("sqlSessionFactory注册失败");
                    return null;
                }
                return (SqlSessionFactory) bean;
            }catch (Exception e) {
                log.error("sqlSessionFactory注册失败{}", e.getMessage(), e);
            }
            return null;
        }
    
        /**
         * 注册事务管理器
         * @param druidDataSource:
         * @param dataSourceName:
         * @author xiatie
         * @date 2022/2/12 10:05
         */
        private void createDataSourceTransactionManager(DruidDataSource druidDataSource, String dataSourceName){
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(druidDataSource);
    
            springUtil.registerBean(Const.TRANSACTION_MANAGER + dataSourceName, transactionManager);
        }
    
        /**
         * 注册sqlSessionTemplate
         * @param sqlSessionFactory:
         * @param dataSourceName:
         * @author xiatie
         * @date 2022/2/12 10:17
         */
        private void createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, String dataSourceName){
            SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
    
            springUtil.registerBean(Const.SQL_SESSION_TEMPLATE + dataSourceName, sqlSessionTemplate);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137

    5.由于AbstractRoutingDataSource 真正保存数据源使用的是resolvedDataSources 这个map。所以需要重新执行一遍加载方法。这个地方获取的bean就是刚才拦截的dataSource,也就是自定义的DataSourceSwitch对象

    ((DataSourceSwitch) springUtil.getBean(DataSourceEnum.DATA_SOURCE_PRIMARY.getName())).afterPropertiesSet();
    
    • 1

    6.具体启动执行的时候注入其他的mapper的时候会报找到多个sqlSessionTemplate实例,spring不知道使用哪一个,我们需要指定主sqlSessionTemplate。所以上面代码从除了实现BeanPostProcessor,还实现了一个BeanDefinitionRegistryPostProcessor。这个和BeanPostProcessor类似,只不过这个在beanDefinition(描述spring中bean对象的一个对象)注册前后执行的。

    @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            String[] beanDefinitionNames = registry.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                if (Objects.equals(beanDefinitionName, "sqlSessionTemplate")) {
                    log.info("sqlSessionTemplate is primary");
                    BeanDefinition sqlSessionTemplate = registry.getBeanDefinition("sqlSessionTemplate");
                    sqlSessionTemplate.setPrimary(true);
                    registry.registerBeanDefinition("sqlSessionTemplate", sqlSessionTemplate);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    设置一个主要的sqlsessionTemplate.为什么在dataSourceMapper中不会报多个sqlsessionTemplate异常,因为在实例化主数据源的时候,加载的多数据源还没来得及实例化完毕

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    Self -Attention、Cross-Attention?
    Web自动化---解决登录页面随机验证码问题
    工具: MarkDown学习
    1、Netty适合场景
    MySQL索引下推
    Stable Diffusion代码简介
    深度学习推荐系统(六)DeepFM模型及其在Criteo数据集上的应用
    uos服务器操作系统源码安装rabbitmq
    uView安装部署
    docker命令实例(举例子学习)
  • 原文地址:https://blog.csdn.net/web18224617243/article/details/126114331