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

发布时间:2024年01月16日

场景:项目需要集成多个第三方数据源,数据源配置从主数据库读取,并动态加载,能够支持在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时会找不到的。

文章来源:https://blog.csdn.net/xiweiller/article/details/135628257
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。