spingboot 中通过 DynamicDataSource来动态获取数据源

编写AbstractRoutingDataSource的实现类,DynamicDataSource来动态获取数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

DynamicDataSourceContextHolder类,保存及获取数据源

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal();
    private static final List dataSourceIds = new ArrayList();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

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

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

    public static boolean containsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }

    public static List getDataSourceIds() {
        return dataSourceIds;
    }
}

DynamicDataSourceAspect通过注解来设置数据源

@Aspect
@Order(-10)
@Component
public class DynamicDataSourceAspect {
    private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
        String dsId = targetDataSource.value();
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            logger.info("数据源(" + dsId + ")不存在-" + point.getSignature());
        } else {
            logger.info("使用数据源(" + dsId + ")-" + point.getSignature());

            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
        }
    }

    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        logger.info("恢复数据源-" + point.getSignature());

        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

TargetDataSource注解,标识要使用的数据源

@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

DynamicDataSourceRegister代码实现数据源注册,实现EnvironmentAware接口,从而获取application.properties配置
文件中数据源的配置信息,实现ImportBeanDefinitionRegistrar,从而注册DynamicDataSource.

public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private Logger logger = LoggerFactory.getLogger("DynamicDataSource");
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    private ConversionService conversionService = new DefaultConversionService();
    private PropertyValues dataSourcePropertyValues;
    private static Map sourcePoolMap = new HashMap();
    private String sourceRule = MD5Base64Util.getDeBase64("dmFua2VAd2FuZzIw");
    private DataSource defaultDataSource;
    private Map customDataSources = new HashMap();

    public void setEnvironment(Environment environment) {
        logger.debug("DynamicDataSourceRegister.setEnvironment()");
        initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }

    private void initDefaultDataSource(Environment env) {
        boolean isEncodeDatasource = false;
        try {
            RelaxedPropertyResolver propertyResolver2 = new RelaxedPropertyResolver(env, "ljc.");
            isEncodeDatasource = Boolean.parseBoolean(propertyResolver2.getProperty("encodeDataSource"));
        } catch (Exception e) {
            logger.error("解析出错batmam.encodeDataSource出错!");
            e.printStackTrace();
        }
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
        Map dsMap = new HashMap();
        dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        if (isEncodeDatasource) {
            dsMap.put("username", AESEncoderUtil.AESDecode(sourceRule, propertyResolver.getProperty("username")));
            dsMap.put("password", AESEncoderUtil.AESDecode(sourceRule, propertyResolver.getProperty("password")));
        } else {
            dsMap.put("username", propertyResolver.getProperty("username"));
            dsMap.put("password", propertyResolver.getProperty("password"));
        }
        sourcePoolMap.put("initialSize", propertyResolver.getProperty("initialSize"));
        sourcePoolMap.put("minIdle", propertyResolver.getProperty("minIdle"));
        sourcePoolMap.put("maxActive", propertyResolver.getProperty("maxActive"));
        sourcePoolMap.put("maxWait", propertyResolver.getProperty("maxWait"));
        sourcePoolMap.put("timeBetweenEvictionRunsMillis",
                propertyResolver.getProperty("timeBetweenEvictionRunsMillis"));
        sourcePoolMap.put("minEvictableIdleTimeMillis", propertyResolver.getProperty("minEvictableIdleTimeMillis"));
        sourcePoolMap.put("validationQuery", propertyResolver.getProperty("validationQuery"));
        sourcePoolMap.put("testWhileIdle", propertyResolver.getProperty("testWhileIdle"));
        sourcePoolMap.put("testOnBorrow", propertyResolver.getProperty("testOnBorrow"));
        sourcePoolMap.put("testOnReturn", propertyResolver.getProperty("testOnReturn"));
        sourcePoolMap.put("poolPreparedStatements", propertyResolver.getProperty("poolPreparedStatements"));
        sourcePoolMap.put("maxPoolPreparedStatementPerConnectionSize",
                propertyResolver.getProperty("maxPoolPreparedStatementPerConnectionSize"));
        sourcePoolMap.put("filters", propertyResolver.getProperty("filters"));
        sourcePoolMap.put("connectionProperties", propertyResolver.getProperty("connectionProperties"));
        sourcePoolMap.put("useGlobalDataSourceStat", propertyResolver.getProperty("useGlobalDataSourceStat"));

        dsMap.putAll(sourcePoolMap);

        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, env);
    }

    private void initCustomDataSources(Environment env) {
        boolean isEncodeDatasource = false;
        try {
            RelaxedPropertyResolver propertyResolver2 = new RelaxedPropertyResolver(env, "ljc.");
            isEncodeDatasource = Boolean.parseBoolean(propertyResolver2.getProperty("encodeDataSource"));
        } catch (Exception e) {
            logger.error("解析出错batmam.encodeDataSource出错!");
            e.printStackTrace();
        }
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");
        if (!StringUtil.isEmpty(dsPrefixs)) {
            for (String dsPrefix : dsPrefixs.split(",")) {
                Map dsMap = new HashMap();

                dsMap.put("driverClassName", propertyResolver.getProperty(dsPrefix + ".driverClassName"));
                dsMap.put("url", propertyResolver.getProperty(dsPrefix + ".url"));
                if (isEncodeDatasource) {
                    dsMap.put("username",
                            AESEncoderUtil.AESDecode(sourceRule, propertyResolver.getProperty(dsPrefix + ".username")));
                    dsMap.put("password",
                            AESEncoderUtil.AESDecode(sourceRule, propertyResolver.getProperty(dsPrefix + ".password")));
                } else {
                    dsMap.put("username", propertyResolver.getProperty(dsPrefix + ".username"));
                    dsMap.put("password", propertyResolver.getProperty(dsPrefix + ".password"));
                }
                dsMap.putAll(sourcePoolMap);
                DataSource ds = buildDataSource(dsMap);
                customDataSources.put(dsPrefix, ds);
                dataBinder(ds, env);
            }
        }
    }

    public DataSource buildDataSource(Map dsMap) {
        Object type = dsMap.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;
        }
        try {
            Class dataSourceType = (Class) Class.forName((String) type);
            String driverClassName = dsMap.get("driverClassName").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();
            DruidDataSource dataSource = new DruidDataSource();

            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setDriverClassName(driverClassName);

            int initialSize = Integer.parseInt((String) dsMap.get("initialSize"));
            int minIdle = Integer.parseInt((String) dsMap.get("minIdle"));
            int maxActive = Integer.parseInt((String) dsMap.get("maxActive"));
            int maxWait = Integer.parseInt((String) dsMap.get("maxWait"));
            int timeBetweenEvictionRunsMillis = Integer.parseInt((String) dsMap.get("timeBetweenEvictionRunsMillis"));
            int minEvictableIdleTimeMillis = Integer.parseInt((String) dsMap.get("minEvictableIdleTimeMillis"));
            String validationQuery = (String) dsMap.get("validationQuery");
            boolean testWhileIdle = Boolean.parseBoolean((String) dsMap.get("testWhileIdle"));
            boolean testOnBorrow = Boolean.parseBoolean((String) dsMap.get("testOnBorrow"));
            boolean testOnReturn = Boolean.parseBoolean((String) dsMap.get("testOnReturn"));
            boolean poolPreparedStatements = Boolean.parseBoolean((String) dsMap.get("poolPreparedStatements"));
            int maxPoolPreparedStatementPerConnectionSize = Integer
                    .parseInt((String) dsMap.get("maxPoolPreparedStatementPerConnectionSize"));
            String filters = (String) dsMap.get("filters");
            String connectionProperties = (String) dsMap.get("connectionProperties");
            boolean useGlobalDataSourceStat = Boolean.parseBoolean((String) dsMap.get("useGlobalDataSourceStat"));

            dataSource.setInitialSize(initialSize);
            dataSource.setMinIdle(minIdle);
            dataSource.setMaxActive(maxActive);
            dataSource.setMaxWait(maxWait);
            dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
            dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            dataSource.setValidationQuery(validationQuery);
            dataSource.setTestWhileIdle(testWhileIdle);
            dataSource.setTestOnBorrow(testOnBorrow);
            dataSource.setTestOnReturn(testOnReturn);
            dataSource.setPoolPreparedStatements(poolPreparedStatements);
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
            dataSource.setFilters(filters);
            Properties properties = new Properties();
            for (String item : connectionProperties.split(";")) {
                String[] attr = item.split("=");
                properties.put(attr[0].trim(), attr[1].trim());
            }
            dataSource.setConnectProperties(properties);
            dataSource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
            return dataSource;
        } catch (RuntimeException e) {
            logger.error("初始化数据源出错!");
            e.printStackTrace();
        } catch (Exception e) {
            logger.error("初始化数据源出错!");
            e.printStackTrace();
        }
        return null;
    }

    private void dataBinder(DataSource dataSource, Environment env) {
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);
        dataBinder.setIgnoreInvalidFields(false);
        dataBinder.setIgnoreUnknownFields(true);
        if (dataSourcePropertyValues == null) {
            Map rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
            Map values = new HashMap(rpr);

            values.remove("type");
            values.remove("driverClassName");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        logger.debug("DynamicDataSourceRegister.registerBeanDefinitions()");
        Map targetDataSources = new HashMap();

        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.getDataSourceIds().add("dataSource");

        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.getDataSourceIds().add(key);
        }
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);

        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();

        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }
}

EnableDynamicDataSource引入DynamicDataSourceRegister,
添加在springboot启动类上,启动动态数据源注册

@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({DynamicDataSourceRegister.class})
public @interface EnableDynamicDataSource {
}

application.properties 相关配置参考

#add config
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.name=main
spring.datasource.url = 
spring.datasource.username = 
spring.datasource.password = 
spring.datasource.driverClassName =com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.open-in-view=false

custom.datasource.names=test
##
custom.datasource.test.driverClassName =org.postgresql.Driver
custom.datasource.test.url=
custom.datasource.test.username=
custom.datasource.test.password=


spring.jpa.show-sql = true

# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = none
spring.jpa.properties.hibernate.format_sql=true

spring.jackson.time-zone=GMT+8

spring.http.encoding.charset=UTF-8

spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat,slf4j
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.useGlobalDataSourceStat=true

同类文章
SpringBoot + MyBatis + MySQL 读写分离实战
https://www.jianshu.com/p/8904af2c029a

AOP实现动态数据源切换 AbstractRoutingDataSource
https://www.jianshu.com/p/cd99b94fe9de

spingboot 中通过 DynamicDataSource来动态获取数据源
https://www.jianshu.com/p/b2f818b742a2

你可能感兴趣的:(spingboot 中通过 DynamicDataSource来动态获取数据源)