作为Java程序员,你真的了解springboot动态数据源的内幕吗?

做了这么多年的Java程序员,好像使用多数据源的情况非常少,特别是现如今微服务这么火的情况下,不同的业务访问一个数据库是常态,而且Java访问数据源真没有PHP等脚本语言来得那么简单方便,但是在特殊业务情况下,还不得不使用多数据源,今天我们就来讲讲这方面的话题

作为Java程序员,你真的了解springboot动态数据源的内幕吗?

一.应用案例

我们的数据库A为主库,其他数据库配置在主库中,从库B,C,D的数量是不固定的,会根据业务的需要动态的把配置写入到主库中并动态在创建新的数据库,也就是说在项目中我们只需要配置主库的数据源,其他从库都需要从主库中读出配置并动态创建数据源,动态的注入到Spring容器中,在使用的时候动态的切换数据源以实现相应的功能逻辑

二.环境配置

Springboot:2.0.4
Mybatis-plus:3.0.7.1
JDK:1.8

三.方案实践

1.项目启动类修改

在启动类添加@Import({DynamicDataSourceRegister.class})注解用于代替默认的数据源配置

2.代码结构

代码结构如下:
作为Java程序员,你真的了解springboot动态数据源的内幕吗?

@Component
public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

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

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(String name) {
        checkApplicationContext();
        if (applicationContext.containsBean(name)) {
            return (T) applicationContext.getBean(name);
        }
        return null;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }

    private static void checkApplicationContext() {
        if (applicationContext == null)
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");
    }
    public synchronized static void registerSingletonBean(String beanName,Class clzz,Map<String,Object> original) {
        checkApplicationContext();
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory();
        if(beanFactory.containsBean(beanName)){
            removeBean(beanName);
        }
        GenericBeanDefinition definition = new GenericBeanDefinition();
        //类class
        definition.setBeanClass(clzz);
        //属性赋值
        definition.setPropertyValues(new MutablePropertyValues(original));
        //注册到spring上下文
        beanFactory.registerBeanDefinition(beanName, definition);
    }
    public synchronized static void registerSingletonBean(String beanName, Object obj, Map<String,Object> original) {
        checkApplicationContext();
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory();
        if(beanFactory.containsBean(beanName)){
            removeBean(beanName);
        }
        GenericBeanDefinition definition = new GenericBeanDefinition();
        //类class
        definition.setBeanClass(obj.getClass());
        //属性赋值
        definition.setPropertyValues(new MutablePropertyValues(original));
        //注册到spring上下文
        beanFactory.registerBeanDefinition(beanName, definition);
    }
    public synchronized static void registerSingletonBean(String beanName,Object obj) {
        registerSingletonBean(beanName,obj,BeanUtils.transBean2Map(obj));
    }
    /**
     * 删除spring中管理的bean
     * @param beanName
     */
    public static void removeBean(String beanName){
        ApplicationContext ctx = ApplicationContextUtil.getApplicationContext();
        DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
        if(acf.containsBean(beanName)) {
            acf.removeBeanDefinition(beanName);
        }
    }
}
public class BeanUtils {
public static Map<String, Object> transBean2Map(Object obj) {
    if(obj == null){
    return null;
    }
    Map<String, Object> map = new HashMap<>();
    try {
         BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
         PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                // 过滤class属性
                if (!key.equals("class")) {
                // 得到property对应的getter方法
                Method getter = property.getReadMethod();
                Object value = getter.invoke(obj);
                map.put(key, value);
                }
            }
         } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
         }
        return map;
    }
}
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }

    public  void updateTargetDataSource(Map<String,DataSource> customDataSources){
        Map<Object,Object> customDS=new HashMap<Object, Object>();
        customDS.putAll(customDataSources);
        setTargetDataSources(customDS);
        afterPropertiesSet();
    }
}
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    public static List<String> dataSourceIds = new ArrayList<>();

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void setDataSourceType(String dataSourceType) {
        if(!containsDataSource(dataSourceType)){
            DynamicDataSourceRegister.addSlaveDataSource(dataSourceType);
        }
        contextHolder.set(dataSourceType);
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    /**
     * 判断指定DataSrouce当前是否存在
     */
    public static boolean containsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }
}
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

    //默认数据源
    public static DataSource defaultDataSource;
    //用户自定义数据源
    public static Map<String, DataSource> slaveDataSources = new HashMap<>();

    public static BeanDefinitionRegistry beanDefinitionRegistry=null;

    public static String driverName;
    public static String userName;
    public static String password;
    public static String type;
    public static String url;

    @Override
    public  void setEnvironment(Environment environment) {
        initDefaultDataSource(environment);
    }

    private void initDefaultDataSource(Environment env) {
        // 读取主数据源
        driverName=env.getProperty("spring.datasource.driver-class-name");
        userName=env.getProperty("spring.datasource.username");
        password=env.getProperty("spring.datasource.password");
        type=env.getProperty("spring.datasource.type");
        url=env.getProperty("spring.datasource.url");

        Constant.defaultDbName="a";

        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("driver",driverName);
        dsMap.put("url",url);
        dsMap.put("username",userName);
        dsMap.put("password",password);
        dsMap.put("type",type);
        defaultDataSource = buildDataSource(dsMap);
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //添加默认数据源
        targetDataSources.put("dataSource", this.defaultDataSource);

        this.beanDefinitionRegistry=beanDefinitionRegistry;
        beanDefinitionRegistry(defaultDataSource,targetDataSources);
        logger.info("Dynamic DataSource Registry");
    }

    public static void addSlaveDataSource(String dataSourceType){
        BeanDefinition beanDefinition=beanDefinitionRegistry.getBeanDefinition("dataSource");
        PropertyValue propertyValue=beanDefinition.getPropertyValues().getPropertyValue("targetDataSources");
        Map<String,DataSource> oldTargetDataSource=(Map<String,DataSource>) propertyValue.getValue();

        String newUrl=firstStr+dataSourceType+secondStr;

        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("driver",driverName);
        dsMap.put("url",newUrl);
        dsMap.put("username",userName);
        dsMap.put("password",password);
        dsMap.put("type",type);
        DataSource ds = buildDataSource(dsMap);

        oldTargetDataSource.put(dataSourceType,ds);
        DynamicDataSource dynamicDataSource =ApplicationContextUtil.getBean("dataSource");
        dynamicDataSource.updateTargetDataSource(oldTargetDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add(dataSourceType);
    }

    public void beanDefinitionRegistry(DataSource defaultDataSource,Map<Object,Object> targetDataSources){
        //创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        if(targetDataSources.size()>0){
            mpv.addPropertyValue("targetDataSources", targetDataSources);
        }
        //注册 - BeanDefinitionRegistry
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
    }

    public static DataSource buildDataSource(Map<String, Object> dataSourceMap) {
        try {
            Object type = dataSourceMap.get("type");
            Class<? extends DataSource> dataSourceType;
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dataSourceMap.get("driver").toString();
            String url = dataSourceMap.get("url").toString();
            String username = dataSourceMap.get("username").toString();
            String password = dataSourceMap.get("password").toString();
            // 自定义DataSource配置
            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

3.使用实例

DynamicDataSourceContextHolder.setDataSourceType("B");
Integer lProductUv=dataVisitCollectionMapper.getProductUv(dDate);
DynamicDataSourceContextHolder.setDataSourceType(Constant.defaultDbName);

在setDataSourceType的时候判断是否存在此数据源,如果存在就直接切换,不存在就动态创建并加入到Spring容器中,从而实现动态创建数据源的目的

四.总结回顾

本文与众多其他springboot多数据源的文章区别之处主要在于,子库的配置是在使用的时候根据情况动态根据在主库中的配置,链接到各从库,从而实现更加灵活的数据源访问体验,如果你有更好的方法和建议可以私信,我是李正凡,谢谢大家