根据配置动态加载三方多数据源,支持热加载

场景:项目需要集成多个第三方数据源,数据源配置从主数据库读取,并动态加载,能够支持在springboot+mybatis框架中编写查询mapper和xml,最终实现不同调用不同mapper,查询不同数据源的数据。

前言:查询网上资料多数据源实现都是@Configuration、@MapperScan等注解方式,增加数据源就需要增加配置,不满足要求。因此考虑在springboot加载完毕后,通过代码方式,将多个数据源根据库中配置进行实例化,并将其注入到spring IOC中,在对相关mapper进项手动扫描初始化。此方式即可实现需求,并且支持多数据源热加载。

1.定义多数据源配置参数实体

public class AppDatasource {

    /**
     * 主键
     */
    private Long id;
  
    /**
     * 数据库类型(01 mysql ,02 oracle , 03 postgre)
     */
    private String dbType;

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    /**
     * 数据源dao所在包目录
     */
    private String basePackage;

    /**
     * mapper所在目录
     */
    private String mapperLocations;

    /**
     * 状态 0 正常 1停用 2 删除
     */
    private Integer status;
}

2.定义ApplicationUtil ,用于处理spring容器、Bean注册和查询


@Component
@Lazy(false)
public class ApplicationUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getContext().getBean(clazz);
    }

    /**
     * 同步方法注册bean到ApplicationContext中
     *
     * @param beanName bean名
     * @param clazz 类对象
     * @param original bean的属性值
     */
    public static synchronized void setBean(String beanName, Class<?> clazz, Map<String,Object> original,Object... constructorArg) {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
        if(beanFactory.containsBean(beanName)){
            return;
        }
        //BeanDefinition beanDefinition = new RootBeanDefinition(clazz);
        GenericBeanDefinition definition = new GenericBeanDefinition();
        //类class
        definition.setBeanClass(clazz);
        if (original!=null){
            //属性赋值
            definition.setPropertyValues(new MutablePropertyValues(original));
        }
        if (constructorArg!=null){
            ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
            constructorArgumentValues.addIndexedArgumentValue(0,constructorArg);
            definition.setConstructorArgumentValues(constructorArgumentValues);
        }
        //注册到spring上下文
        beanFactory.registerBeanDefinition(beanName, definition);
    }

    public static synchronized void setBean(String beanName, Class<?> clazz, Map<String,Object> original){
        setBean(beanName,clazz,original,null);
    }

    /**
     * 同步方法注册bean到ApplicationContext中
     *
     * @param beanName bean名
     * @param clazz 类对象
     */
    public static synchronized void setBean(String beanName, Class<?> clazz) {
       setBean(beanName,clazz,null);
    }

}

3.利用@PostConstruct注解实现启动时执行

@Configuration
public class DataSourceConfig {
 
    @Autowired
    ApplicationUtil applicationUtil;

    @PostConstruct
    public void initDataSource() throws Exception {
          // 查询多数据源配置数据
          List<SysOpenapiAppDatasource> appDataSources = new ArrayList<>();
          // 省略appDataSources 赋值逻辑
          for (SysOpenapiAppDatasource appDataSource: appDataSources){
              doInit(appDataSource);
          }
        
    }
    public void doInit(SysOpenapiAppDatasource appDataSource){
        // 初始化数据源...代码见后续步骤
    }
    
}

4.创建DataSource,实例化 sqlSessionFactory和sqlSessionTemplate

        DruidDataSource _dataSource = new DruidDataSource();
        _dataSource.setUrl(appDataSource.getUrl());
        _dataSource.setUsername(appDataSource.getUsername());
        _dataSource.setPassword(DBPasswordUtil.decrypt(appDataSource.getPassword()));  
        _dataSource.setDriverClassName(appDataSource.getDriverClassName());
 
        Long appId = appDataSource.getId();

        Map<String,Object> original = new HashMap<>();
        original.put("dataSource",_dataSource);
        original.put("vfs",SpringBootVFS.class);
        original.put("mapperLocations",new PathMatchingResourcePatternResolver().getResources(appDataSource.getMapperLocations()));
        ApplicationUtil.setBean("sessionFactory"+appId,MybatisSqlSessionFactoryBean.class,original);

        DefaultSqlSessionFactory sessionFactoryBean = (DefaultSqlSessionFactory) ApplicationUtil.getBean("sessionFactory" +appId);

        ApplicationUtil.setBean("sqlSessionTemplate"+appId,
                SqlSessionTemplate.class,null,sessionFactoryBean);

        Map<String,Object> scannerOriginal = new HashMap<>();
        original.put("sqlSessionFactory",sessionFactoryBean);
        //scannerOriginal.put("sqlSessionTemplate",ApplicationUtil.getBean("sqlSessionTemplate"+appId));
        scannerOriginal.put("basePackage",appDataSource.getBasePackage());
        scannerOriginal.put("sqlSessionTemplateBeanName","sqlSessionTemplate"+appId);
        ApplicationUtil.setBean("mapperScanner"+appId,MapperScannerConfigurer.class,scannerOriginal);

5.调用 MapperScannerConfigurer根据配置加载mapper

MapperScannerConfigurer mapperScannerConfigurer = (MapperScannerConfigurer)ApplicationUtil.getBean("mapperScanner"+appId);
        mapperScannerConfigurer.postProcessBeanDefinitionRegistry((DefaultListableBeanFactory)ApplicationUtil.getContext().getAutowireCapableBeanFactory());

到这里核心代码就完成了,接下来,可根据自己mapper和xml配置的路径编写相关代码即可。

原理说明

为什么最后要调用 postProcessBeanDefinitionRegistry方法?

这个方法是MapperScannerConfigurer中用于加载mapper,和@MapperScan的内部处理机制一样。它实现了BeanDefinitionRegistryPostProcessor接口,spring会在Bean初始化完毕调用到这里,用于实例化mapper。而我们在这里必须要手动再次调用一下,才能确保我们的mapper被实例化,否则我们在调用mapper时会找不到的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值