SpringBoot2.0.3整合Mybatis添加动态数据源实现多库查询(DynamicDataSource)

最近由于项目使用了spring boot 2.0.3版本,业务从多个数据查询,必须支持动态数据源,由于2.0.3的版本与之前的版本有了较大的改动其实现上有些不同,再采坑以后在此记录

1、需要Java类

DynamicDataSource.java

package com.a.dynamic;


import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


/**

 * 〈动态数据源〉

 *

 * @author zhoukai7

 * @create 7/27/18

 * @since 1.0.0

 */

public class DynamicDataSource extends AbstractRoutingDataSource {


    @Override

    protected Object determineCurrentLookupKey() {

        return DynamicDataSourceContextHolder.getDataSourceType();

    }


}


DynamicDataSourceAspect.java


package com.a.dynamic;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;


/**

 * 〈一句话功能简述〉<br>

 * 〈动态数据源切面〉

 *

 * @author zhoukai7

 * @create 7/27/18

 * @since 1.0.0

 */

@Aspect

@Order(-1)// 保证该AOP在@Transactional之前执行

@Component

public class DynamicDataSourceAspect {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);


    @Before("@annotation(ds)")

    public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {

        String dsId = ds.name();

        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {

            logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());

        } else {

            logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());

            DynamicDataSourceContextHolder.setDataSourceType(ds.name());

        }

    }


    @After("@annotation(ds)")

    public void restoreDataSource(JoinPoint point, TargetDataSource ds) {

        logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());

        DynamicDataSourceContextHolder.clearDataSourceType();

    }


}

DynamicDataSourceContextHolder.java


package com.a.dynamic;


import java.util.ArrayList;

import java.util.List;


/**

 * 〈一句话功能简述〉<br> 

 * 〈动态数据源句柄〉

 *

 * @author zhoukai7

 * @create 7/27/18

 * @since 1.0.0

 */

public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static List<String> dataSourceIds = new ArrayList<>();


    public static void setDataSourceType(String dataSourceType) {

        contextHolder.set(dataSourceType);

    }


    public static String getDataSourceType() {

        return contextHolder.get();

    }


    public static void clearDataSourceType() {

        contextHolder.remove();

    }


    /**

     * 判断指定DataSrouce当前是否存在

     *

     * @param dataSourceId

     * @author zhoukai7

     * @create 7/27/18

     */

    public static boolean containsDataSource(String dataSourceId){

        return dataSourceIds.contains(dataSourceId);

    }

}

DynamicDataSourceRegister.java


package com.a.dynamic;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.jdbc.DataSourceBuilder;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;


import javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;


/**

 * 〈一句话功能简述〉<br> 

 * 〈注册中心〉

 *

 * @author zhoukai7

 * @create 7/27/18

 * @since 1.0.0

 */

public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {


    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);


    //指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置)

    //org.apache.tomcat.jdbc.pool.DataSource

    private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";

    //默认数据源

    private DataSource defaultDataSource;

    //用户自定义数据源

    private Map<String, DataSource> slaveDataSources = new HashMap<>();


    @Override

    public void setEnvironment(Environment environment) {

        initDefaultDataSource(environment);

        initslaveDataSources(environment);

    }


    private void initDefaultDataSource(Environment env) {

        // 读取主数据源

        Map<String, Object> dsMap = new HashMap<>();

        dsMap.put("type", env.getProperty("spring.datasource.type"));

        dsMap.put("driver", env.getProperty("spring.datasource.driverClassName"));

        dsMap.put("url", env.getProperty("spring.datasource.url"));

        dsMap.put("username", env.getProperty("spring.datasource.username"));

        dsMap.put("password", env.getProperty("spring.datasource.password"));

        defaultDataSource = buildDataSource(dsMap);

    }



    private void initslaveDataSources(Environment env) {

        // 读取配置文件获取更多数据源

        String dsPrefixs = env.getProperty("slave.datasource.names");

        for (String dsPrefix : dsPrefixs.split(",")) {

            // 多个数据源

            Map<String, Object> dsMap = new HashMap<>();

            dsMap.put("type", env.getProperty("slave.datasource." + dsPrefix + ".type"));

            dsMap.put("driver", env.getProperty("slave.datasource." + dsPrefix + ".driverClassName"));

            dsMap.put("url", env.getProperty("slave.datasource." + dsPrefix + ".url"));

            dsMap.put("username", env.getProperty("slave.datasource." + dsPrefix + ".username"));

            dsMap.put("password", env.getProperty("slave.datasource." + dsPrefix + ".password"));

            DataSource ds = buildDataSource(dsMap);

            slaveDataSources.put(dsPrefix, ds);

        }

    }


    @Override

    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();

        //添加默认数据源

        targetDataSources.put("dataSource", this.defaultDataSource);

        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");

        //添加其他数据源

        targetDataSources.putAll(slaveDataSources);

        for (String key : slaveDataSources.keySet()) {

            DynamicDataSourceContextHolder.dataSourceIds.add(key);

        }


        //创建DynamicDataSource

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

        beanDefinition.setBeanClass(DynamicDataSource.class);

        beanDefinition.setSynthetic(true);

        MutablePropertyValues mpv = beanDefinition.getPropertyValues();

        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);

        mpv.addPropertyValue("targetDataSources", targetDataSources);

        //注册 - BeanDefinitionRegistry

        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);


        logger.info("Dynamic DataSource Registry");

    }


    public DataSource buildDataSource(Map<String, Object> dataSourceMap) {

        try {

            Object type = dataSourceMap.get("type");

            if (type == null) {

                type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

            }

            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;

    }


}

TargetDataSource.java


package com.a.dynamic;


import java.lang.annotation.*;


/**

 * 〈一句话功能简述〉<br> 

 * 〈数据源注解类〉

 *

 * @author zhoukai7

 * @create 7/27/18

 * @since 1.0.0

 */

@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

    String name();

}


2、yml文件配置

application.yml

# Tomcat

server:

    tomcat:

        uri-encoding: UTF-8

        max-threads: 1000

        min-spare-threads: 30

    port: 8012

    servlet:

        session:

            timeout: 1800

        context-path: /


# spring

spring:

    # 环境 dev|test|pro

    profiles:

        active: dev

#    datasource:

#        type: com.alibaba.druid.pool.DruidDataSource

#        driverClassName: com.mysql.jdbc.Driver

    # jackson时间格式化

    jackson:

        time-zone: GMT+8

        date-format: yyyy-MM-dd HH:mm:ss

    # 文件上传

    servlet:

        multipart:

             enabled: true

             max-file-size: 100MB

             max-request-size: 100MB

    # 指定静态资源的路径

    resources:

        static-locations: classpath:/static/,classpath:/views/

   # Spring devtools

    devtools:

         restart:

               enabled: true

    thymeleaf:

        cache: false

# Mybatis配置

#mybatis:

#    mapperLocations: classpath*:mapper/**/*.xml

#    configLocation: classpath:mybatis.xml

#mybatis

mybatis-plus:

  mapper-locations: classpath*:/mapper/**/*.xml

  #实体扫描,多个package用逗号或者分号分隔

  typeAliasesPackage: com.asiainfo.**.entity

  global-config:

    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";

    id-type: 0

    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"

    field-strategy: 2

    #驼峰下划线转换

    db-column-underline: true

    #刷新mapper 调试神器

    refresh-mapper: true

    #数据库大写下划线转换

    #capital-mode: true

    #序列接口实现类配置

    #key-generator: com.baomidou.springboot.xxx

    #逻辑删除配置

    logic-delete-value: 0

    logic-not-delete-value: 1

    #自定义填充策略接口实现

    #meta-object-handler: com.baomidou.springboot.xxx

    #自定义SQL注入器

    #sql-injector: com.baomidou.springboot.xxx

  configuration:

    map-underscore-to-camel-case: true

    cache-enabled: false


application-dev.yml

spring:

    datasource:

        type: com.alibaba.druid.pool.DruidDataSource

        driverClassName: com.mysql.jdbc.Driver

        # 添加&useSSL=true后会报错

        url: jdbc:mysql://localhost:3309/aas?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8

        username: root

        password: root

        #连接池的配置信息

        initialSize: 10

        minIdle: 10

        maxActive: 100

        # 配置获取连接等待超时的时间

        maxWait: 60000

        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

        timeBetweenEvictionRunsMillis: 60000

        # 配置一个连接在池中最小生存的时间,单位是毫秒

        minEvictableIdleTimeMillis: 300000

        validationQuery: SELECT 1 FROM DUAL

        testWhileIdle: true

        testOnBorrow: false

        testOnReturn: false

        # 打开PSCache,并且指定每个连接上PSCache的大小

        poolPreparedStatements: true

        maxPoolPreparedStatementPerConnectionSize: 20

        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙

        filters: stat,wall,slf4j

        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

slave:

    datasource:

        names: ds1,ds2

        ds1:

            type: com.alibaba.druid.pool.DruidDataSource

            driverClassName: com.mysql.jdbc.Driver

            # 添加&useSSL=true后会报错

            url: jdbc:mysql://localhost:3309/bbs?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10

            username: root

            password: root

            #连接池的配置信息

            initialSize: 10

            minIdle: 10

            maxActive: 100

            # 配置获取连接等待超时的时间

            maxWait: 60000

            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

            timeBetweenEvictionRunsMillis: 60000

            # 配置一个连接在池中最小生存的时间,单位是毫秒

            minEvictableIdleTimeMillis: 300000

            validationQuery: SELECT 1 FROM DUAL

            testWhileIdle: true

            testOnBorrow: false

            testOnReturn: false

            # 打开PSCache,并且指定每个连接上PSCache的大小

            poolPreparedStatements: true

            maxPoolPreparedStatementPerConnectionSize: 20

            # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙

            filters: stat,wall,slf4j

            # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

            connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

        ds2:

            type: com.alibaba.druid.pool.DruidDataSource

            driverClassName: com.mysql.jdbc.Driver

            # 添加&useSSL=true后会报错

            url: jdbc:mysql://localhost:3309/dds?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10

            username: root

            password: root

            #连接池的配置信息

            initialSize: 10

            minIdle: 10

            maxActive: 100

            # 配置获取连接等待超时的时间

            maxWait: 60000

            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

            timeBetweenEvictionRunsMillis: 60000

            # 配置一个连接在池中最小生存的时间,单位是毫秒

            minEvictableIdleTimeMillis: 300000

            validationQuery: SELECT 1 FROM DUAL

            testWhileIdle: true

            testOnBorrow: false

            testOnReturn: false

            # 打开PSCache,并且指定每个连接上PSCache的大小

            poolPreparedStatements: true

            maxPoolPreparedStatementPerConnectionSize: 20

            # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙

            filters: stat,wall,slf4j

            # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

            connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

        # 合并多个DruidDataSource的监控数据

        #useGlobalDataSourceStat: true

#    redis:

#        host: 140.143.23.15

#        port: 6379

#        # Redis服务器连接密码(默认为空)

#        password: redis@root&hp

#        # Redis数据库索引(默认为0)

#        database: 1

#        # 连接超时时间(毫秒)  2.0中需要suffix

#        timeout: 10000

#        jedis:

#            pool:

#               # 连接池中的最大空闲连接

#               max-idle: 50

#               # 连接池中的最小空闲连接

#               min-idle: 8

#               # 连接池最大连接数(使用负值表示没有限制)

#               max-active: 1024

#               # 连接池最大阻塞等待时间(使用负值表示没有限制)

#               max-wait: -1

upload:

    image:

       url: /data/image/release

3、使用方式

   @TargetDataSource(name="ds1")

@Override

public List<Demo> queryList(){

Map<String,Object> map = new HashMap<>();

return demoDao.queryList(map);

}

    @TargetDataSource(name="ds2")

    @Override

    public List<Demo> selectList(){

        Map<String,Object> map = new HashMap<>();

        return demoDao.queryList(map);

    }

说明:@TargetDataSource(name="ds2")只能添加在接口实现类上,而不能添加在接口上,推荐添加在service层的impl实现类上。

如果你的工程使用的mapper接口@Mapper

public interface DemoDao extends BaseMapper<Demo>  则不能在此处使用@TargetDataSource,如果想要在持久层使用,必须有实现类。强烈推荐在service使用。


上一篇:Druid动态数据源配置


下一篇:springboot整合 thymeleaf 案例