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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 最近由于项目使用了spring boot 2.0.3版本,业务从多个数据查询,必须支持动态数据源,由于2.0.3的版本与之前的版本有了较大的改动其实现上有些不同,再采坑以后在此记录

最近由于项目使用了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使用。


相关文章
|
4月前
|
SQL XML Java
MyBatis Mapper中使用limit参数的查询问题
总结而言,MyBatis中使用 `limit`参数的查询可以高度定制并且灵活,基于方法签名和XML映射文件的组合来达成多样化的查询需求。通过参数化查询和动态SQL,MyBatis可以有效地处理各种复杂情境下的数据库操作,并且将SQL语句的维护与业务代码的编写相分离,提升代码的可维护性和可阅读性。
450 13
|
8月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
443 0
|
5月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
986 1
Spring boot 使用mybatis generator 自动生成代码插件
|
5月前
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
225 1
|
4月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
152 0
|
5月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
233 1
|
10月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
537 29
|
8月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
673 0
|
8月前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
1094 0
|
10月前
|
XML Java 数据库连接
Mybatis一对一,一对多关联查询
## MyBatis一对一、一对多关联查询详解 MyBatis是一款优秀的持久层框架,提供了灵活的SQL映射功能,支持复杂的数据库操作。本文将详细介绍MyBatis中一对一和一对多关联查询的实现。 ### 一对一关联查询 一对一关联关系指的是一个表中的一条记录与另一个表中的一条记录相关联。例如,一个用户有一个地址信息。 #### 数据库表设计 假设有两个表:`user`和 `address`。 ``` CREATE TABLE user ( id INT PRIMARY KEY, name VARCHAR(50) ); CREATE TABLE address
294 18