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

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

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

DynamicDataSourceContextHolder类,保存及获取数据源

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal();
    private static final List<String> 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<String> 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<String, DataSource> 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<String, Object> 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<String, Object> 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<String, Object> dsMap) {
        Object type = dsMap.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;
        }
        try {
            Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>) 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<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
            Map<String, Object> 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<Object, Object> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容