As a Java programmer, do you really know the inside story of the springboot dynamic data source?

Keywords: Java Database Spring Attribute

Java programmers who have been working for many years seem to use very few multiple data sources, especially in the current situation of microservices being so hot, it is normal for different businesses to access a database, and Java access to data sources is not as simple as PHP and other scripting languages, but in special business situations, we have to use multiple data sources. Today we will talk about it Talk about this topic

1, Application cases

Our database A is the main database, and other databases are configured in the main database. The number of slave databases B, C and D is not fixed. We will dynamically write the configuration to the main database according to the business needs and dynamically create A new database. That is to say, in the project, we only need to configure the data source of the main database, and other slave databases need to read the configuration from the main database and dynamically create the data source, which is dynamic Inject into the Spring container, dynamically switch the data source when using to realize the corresponding functional logic

2, Environment configuration

Springboot:2.0.4
Mybatis-plus:3.0.7.1
JDK:1.8

3, Program practice

1. Modification of project startup class

Add @ Import({DynamicDataSourceRegister.class}) annotation in the startup class to replace the default data source configuration

2. Code structure

The code structure is as follows:

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

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

    /**
     * Gets the ApplicationContext stored in the static variable
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * Get the Bean from the static variable ApplicationContext and automatically transform it to the type of the assigned object
     */
    public static <T> T getBean(String name) {
        checkApplicationContext();
        if (applicationContext.containsBean(name)) {
            return (T) applicationContext.getBean(name);
        }
        return null;
    }

    /**
     * Get the Bean from the static variable ApplicationContext and automatically transform it to the type of the assigned object
     */
    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 Uninjected,Please be there. applicationContext.xml Definition in 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 class
        definition.setBeanClass(clzz);
        //Attribute assignment
        definition.setPropertyValues(new MutablePropertyValues(original));
        //Register to spring context
        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 class
        definition.setBeanClass(obj.getClass());
        //Attribute assignment
        definition.setPropertyValues(new MutablePropertyValues(original));
        //Register to spring context
        beanFactory.registerBeanDefinition(beanName, definition);
    }
    public synchronized static void registerSingletonBean(String beanName,Object obj) {
        registerSingletonBean(beanName,obj,BeanUtils.transBean2Map(obj));
    }
    /**
     * Delete bean s managed in spring
     * @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();
                // Filter class attribute
                if (!key.equals("class")) {
                // Get the getter method corresponding to property
                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();
    }

    /**
     * Determine whether the specified datasroute currently exists
     */
    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);

    //Default data source
    public static DataSource defaultDataSource;
    //User defined data source
    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) {
        // Read master data source
        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>();
        //Add default data source
        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){
        //Create 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);
        }
        //Registration - 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();
            // Custom DataSource configuration
            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. Examples

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

When setting datasourcetype, judge whether this data source exists. If it exists, switch it directly. If it does not exist, create it dynamically and add it to the Spring container, so as to realize the purpose of creating data source dynamically

4, Summary and review

The main difference between this article and many other spring boot multi data source articles is that the configuration of the sub database is dynamically linked to each slave database according to the configuration in the main database when using, so as to realize a more flexible data source access experience. If you have better methods and suggestions, you can private mail. I'm Li Zhengfan. Thank you

Posted by john-iom on Sun, 12 Jan 2020 05:28:16 -0800