SpringBoot实现自定义动态多数据源

SpringBoot实现自定义动态多数据源

最近有个项目,有两种数据库,默认数据库是mysql,另外一种是sqserver,但是开发的过程中,有个很糙蛋的需求,有一个设备的数据存在sqlserver,是会根据年份的增加,会更改存储的数据源,但是表名是一样的表名。例如,今年2025年,这个设备数据就会把数据存到名称为Water2025的sqlserver数据库的device表里,到了2026年,这个设备数据又会存到名称为Water2026的sqlserver数据库的device表里,传统的多数据源就要每到一年手动添加一个数据源,这个很麻烦,所以我要实现个自定义多数据源来处理这个需求。
 

我的想法是,通过自定义注解,加aop代理去修改当前执行线程,所持有的数据源,但是这个修改是在,线程执行到mapper层之前完成的,因为mybatis本身就走了代理,然后我们再去代理的话,拿的对象属性是空。

一、原理

我来简单的讲解下多数据源的实现原理,方便大家理解:

1、springboot中,有个spring-jdbc包是用来创建jdbc连接的,其中有个AbstractRoutingDataSource抽象类,这个类就是实现多数据源的关键核心。
 

2、其中对我们有利用价值的一个是,Map<Object, Object> targetDataSources键值对,这个键值对是用来存储数据源的,key是个当前数据源的唯一标识,也是方便springboot通过这个key,确定当前线程要查询的数据库。而Map<Object, DataSource> resolvedDataSources这个键值对呢,我们往①和②看,可以看到,Map<Object, DataSource> resolvedDataSources键值对,实际上就是Map<Object, Object> targetDataSources。这里提到Map<Object, DataSource> resolvedDataSources键值对,只是为了方便我们下一步讲解。

在这里插入图片描述

 

3、第二个对我们有利用价值的是,determineCurrentLookupKey()抽象方法(ps:见名知意,确定当前查找键)。首先我们再看determineTargetDataSource()方法(ps:见名知意,确定目标数据源),序号①从determineCurrentLookupKey()抽象方法中获取key,获取到key之后到,序号②从Map<Object, DataSource> resolvedDataSources键值对中,获取对应的数据源,返回的dateSource数据源就是,当前线程要查询的数据源。

在这里插入图片描述

 

4、后面就是springboot去执行了,不需要我们再去关心了,相信通过这个3步讲解,大家对原理已经有了大致的了解,现在我们要来一步步开始实现。
 
 
 

二、实现

执行流程:

1、在项目启动时,会执行③自定义多数据源配置,将默认数据源,也就是德鲁伊的数据源配置,和从②中获取targetDataSources键值对,注入到spring中,让spring管理。
 
2、我们定义了个@DataSource自定义注解,和aop切点,当一个请求过来时,会检测方法或者类上是否带有我们自定义@DataSource注解,有得话,会执行⑨中的around()方法,这个方法会解析前端传入得入参,看入参中是否包含startYearFlag值,如果没有,会有个兜底的默认的数据源。
 
3、而后执行②中的setDataSource()方法,修改当前线程的数据源。
 
 
 

①maven依赖和yml配置文件

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.18</version>
	<relativePath/>
</parent>

<properties>
	<maven.compiler.source>11</maven.compiler.source>
	<maven.compiler.target>11</maven.compiler.target>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<spring-boot.version>2.7.18</spring-boot.version>
	<fastjson2.version>2.0.43</fastjson2.version>
	<mybatis-plus.version>3.5.4</mybatis-plus.version>
	<hutool.version>5.8.22</hutool.version>
	<druid.version>1.2.23</druid.version>
	<sqlserver.version>8.2.2.jre8</sqlserver.version>
</properties>

<dependencies>
    <!-- springBoot web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- springBoot test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <!-- Apache Lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>

    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- SQL Server驱动-->
    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <version>${sqlserver.version}</version>
    </dependency>

    <!-- Mysql驱动包 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!-- JSON -->
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>${fastjson2.version}</version>
    </dependency>

    <!--引入druid数据源-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.version}</version>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>

    <!-- hutool 的依赖配置-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>${hutool.version}</version>
    </dependency>

</dependencies>
spring:
  application:
    # 应用名称
    name: @artifactId@
  profiles:
    # 环境配置
    active: @profiles.active@
  output:
    ansi:
      enabled: ALWAYS

logging:
  #log日志文件配置
  file_basePath: ./logs
  file_prefix: ${spring.application.name}


mybatis-plus:
  mapper-locations: classpath:com/jyy/**/mapper/mapping/*Mapper.xml
  global-config:
    banner: false

server:
  port: 28000

--- # mysql配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/Water2025?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
      username: root
      password: root

--- # 动态数据源配置
dynamic:
  driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
  url: jdbc:sqlserver://127.0.0.1:1433;
  username: sa
  password: Bwcx66
  urlSuffix: ''

 

②自定义多数据源类

package com.jyy.dynamicdatasource.config;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.jyy.dynamicdatasource.DynamicContextHolder;
import com.jyy.dynamicdatasource.DynamicProperties;
import com.jyy.dynamicdatasource.utils.DynamicDbUtil;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义多数据源
 *
 * @author jyy
 * @data 2025-01-14
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 目标数据源
     */
    public static Map<Object, Object> targetDataSources = new ConcurrentHashMap<>(10);

    /**
     * 确定当前数据源
     *
     * @return 当前数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicContextHolder.peek();
    }
	
    /**
     * 设置数据源
     *
     * @param dbKey             数据源key
     * @param dynamicProperties 数据源配置对象
     */
    public static void setDataSource(String dbKey, DynamicProperties dynamicProperties) {
        // 检查数据源是否存在
        if (!DynamicDataSource.targetDataSources.containsKey(dbKey)) {
            // 获取新的数据源
            DruidDataSource dataSource = DynamicDbUtil.getDbSourceByDbKey(dbKey, dynamicProperties);
            // 添加到targetDataSources中
            DynamicDataSource.targetDataSources.put(dbKey, dataSource);
        }
        // 将数据源放key入ThreadLocal中,
        DynamicContextHolder.push(dbKey);
        // 获取动态数据源Bean
        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringUtil.getBean("dynamicDataSource");
        // 使得修改后的targetDataSources生效
        dynamicDataSource.afterPropertiesSet();
    }

}

 

③自定义多数据源配置

package com.jyy.dynamicdatasource.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

import java.sql.SQLException;

/**
 * 配置多数据源
 *
 * @author jyy
 * @data 2025-01-14
 */
@Slf4j
@Configuration
public class DynamicDataSourceConfig {

    /**
     * 从配置文件中获取配置
     *
     * @return 数据源配置对象
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 默认数据源
     *
     * @param properties 数据源配置对象
     * @return 数据源对象
     */
    @Bean
    public DruidDataSource defaultDataSource(DataSourceProperties properties) {
        // 默认数据源,通过配置获取创建
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getDriverClassName());
        druidDataSource.setUrl(properties.getUrl());
        druidDataSource.setUsername(properties.getUsername());
        druidDataSource.setPassword(properties.getPassword());
        try {
            druidDataSource.init();
        } catch (SQLException e) {
            log.error(e.toString());
        }
        return druidDataSource;
    }

    /**
     * 初始化数据源
     *
     * @param defaultDataSource 默认数据源
     * @return 动态数据源
     */
    @Bean
    @Primary
    @DependsOn({"defaultDataSource"})
    public DynamicDataSource dynamicDataSource(DruidDataSource defaultDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 设置targetDataSources(通过数据库配置获取,首次创建没有数据源)
        dynamicDataSource.setTargetDataSources(DynamicDataSource.targetDataSources);

        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);

        log.info("------- 初始化数据源 -------");
        return dynamicDataSource;
    }

}

 

④多数据源上下文

package com.jyy.dynamicdatasource;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * 多数据源上下文
 *
 * @author jyy
 * @data 2025-01-14
 */
public class DynamicContextHolder {

    private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = ThreadLocal.withInitial(ArrayDeque::new);

    /**
     * 获得当前线程数据源
     *
     * @return 数据源名称
     */
    public static String peek() {
        return CONTEXT_HOLDER.get().peek();
    }

    /**
     * 设置当前线程数据源
     *
     * @param dataSource 数据源名称
     */
    public static void push(String dataSource) {
        CONTEXT_HOLDER.get().push(dataSource);
    }

    /**
     * 清空当前线程数据源
     */
    public static void poll() {
        Deque<String> deque = CONTEXT_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            CONTEXT_HOLDER.remove();
        }
    }

}

 

⑤自定义数据源工具类

package com.jyy.dynamicdatasource.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.jyy.dynamicdatasource.DynamicProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 数据源工具类
 *
 * @author jyy
 * @data 2025-01-14
 */
@Slf4j
public class DynamicDbUtil {

    /**
     * 获取数据源
     *
     * @param dbKey             数据源key
     * @param dynamicProperties 数据源配置对象
     * @return DruidDataSource
     */
    public static DruidDataSource getDbSourceByDbKey(final String dbKey, DynamicProperties dynamicProperties) {
        DruidDataSource dataSource = getJdbcDataSource(dbKey, dynamicProperties);
        log.info("-------------------创建数据库[ {} ]DB连接-------------------", dbKey);
        return dataSource;
    }

    /**
     * 获取数据源【最底层方法,不要随便调用】
     *
     * @param dbKey             数据源key
     * @param dynamicProperties 数据源配置对象
     */
    private static DruidDataSource getJdbcDataSource(final String dbKey, DynamicProperties dynamicProperties) {
        DruidDataSource dataSource = new DruidDataSource();
        String driverClassName = dynamicProperties.getDriverClassMame();
        String url = dynamicProperties.getJdbcUrl(dbKey);
        String dbUser = dynamicProperties.getUsername();
        String dbPassword = dynamicProperties.getPassword();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        //dataSource.setValidationQuery("SELECT 1 FROM DUAL");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setBreakAfterAcquireFailure(true);
        dataSource.setConnectionErrorRetryAttempts(0);
        dataSource.setUsername(dbUser);
        dataSource.setMaxWait(60000);
        dataSource.setPassword(dbPassword);

        log.info("******************************************");
        log.info("*                                        *");
        log.info("*====[ {} ]=====Druid连接池已启用 ====*", driverClassName);
        log.info("*                                        *");
        log.info("******************************************");
        return dataSource;
    }

    /**
     * 关闭数据库连接池
     *
     * @param dbKey
     * @return
     */
    public static void closeDbKey(final String dbKey, DynamicProperties dynamicProperties) {
        DruidDataSource dataSource = getDbSourceByDbKey(dbKey, dynamicProperties);
        try {
            if (dataSource != null && !dataSource.isClosed()) {
                dataSource.getConnection().commit();
                dataSource.getConnection().close();
                dataSource.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    private static JdbcTemplate getJdbcTemplate(String dbKey, DynamicProperties dynamicProperties) {
        DruidDataSource dataSource = getDbSourceByDbKey(dbKey, dynamicProperties);
        return new JdbcTemplate(dataSource);
    }

    /**
     * 获取连接
     *
     * @param url
     * @param username
     * @param password
     * @param driverName
     * @return
     */
    public static Connection getConn(String url, String username, String password, String driverName) {
        Connection conn = null;
        try {
            Class.forName(driverName);
            conn = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            throw new RuntimeException("无法连接,问题:" + e.getMessage(), e);
        }

        return conn;
    }

    /**
     * 关闭数据库连接
     *
     * @param
     */
    public static void closeConnection(Connection conn) {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException("close connection failure", e);
        }
    }
}

 

⑥数据源配置类

package com.jyy.dynamicdatasource;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 数据源配置类
 *
 * @author jyy
 * @date 2025-01-13
 */
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "dynamic")
public class DynamicProperties {

    private String url;

    @Value("${dynamic.driver-class-name}")
    private String driverClassMame;

    private String username;

    private String password;

    private String urlSuffix;

    /**
     * 获取数据库连接地址
     *
     * @param databaseName 数据库名称
     * @return 数据库连接地址
     */
    public String getJdbcUrl(String databaseName) {
        // 驱动区分
        switch (driverClassMame) {

            // sqlserver
            case DynamicConstant.SQLSERVER_DRIVE: {
                // jdbc:sqlserver://127.0.0.1:1433;databaseName=MyDatabase;
                return url + "databaseName=" + databaseName + ";";
            }

            // mysql
            case DynamicConstant.MYSQL_DRIVE: {
                // jdbc:mysql://127.0.0.1:3306/jyy-test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
                return url + databaseName + urlSuffix;
            }

            default:
                log.error("数据源类型不存在");
                return null;
        }
    }
}

 

⑦数据源常量

package com.jyy.dynamicdatasource;

/**
 * 数据源常量
 *
 * @author jyy
 * @date 2025-01-13
 */
public final class DynamicConstant {

    private DynamicConstant() {
    }

    public static final String SQLSERVER_PREFIX = "Water";

    /**
     * sqlserver驱动
     */
    public static final String SQLSERVER_DRIVE = "com.microsoft.sqlserver.jdbc.SQLServerDriver";

    /**
     * mysql驱动
     */
    public static final String MYSQL_DRIVE = "com.mysql.cj.jdbc.Driver";
}

 

⑧自定义注解

package com.jyy.dynamicdatasource.annotation;

import java.lang.annotation.*;

/**
 * 多数据源注解
 *
 * @author jyy
 * @data 2025-01-14
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    
    String value() default "";
    
}

 

⑨aop

package com.jyy.dynamicdatasource.aspect;

import com.jyy.dynamicdatasource.DynamicBaseQuery;
import com.jyy.dynamicdatasource.DynamicConstant;
import com.jyy.dynamicdatasource.DynamicContextHolder;
import com.jyy.dynamicdatasource.DynamicProperties;
import com.jyy.dynamicdatasource.annotation.DataSource;
import com.jyy.dynamicdatasource.config.DynamicDataSource;
import com.jyy.dynamicdatasource.utils.DateUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDate;

/**
 * 多数据源,切面处理类
 *
 * @author jyy
 * @data 2025-01-14
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
@RequiredArgsConstructor
public class DataSourceAspect {

    private final DynamicProperties dynamicProperties;
    
    private static final Pattern YEAR_PATTERN = Pattern.compile("^\\d{4}$");

    /**
     * 定义切点
     * {@code @annotation(com.jyy.dynamicdatasource.annotation.DataSource)}: 如果方法上有com.jyy.dynamicdatasource.DataSource注解,那么这个方法就会被拦截。
     * {@code @within(com.jyy.dynamicdatasource.annotation.DataSource}: 如果方法所在的类上有com.jyy.dynamicdatasource.DataSource注解,那么这个方法也会被拦截。
     */
    @Pointcut("@annotation(com.jyy.dynamicdatasource.annotation.DataSource) || @within(com.jyy.dynamicdatasource.annotation.DataSource)")
    public void dataSourcePointCut() {

    }

    /**
     * 环绕通知
     * {@code @Around("dataSourcePointCut()")}: 这个环绕通知将应用于定义在dataSourcePointCut()中的方法
     *
     * @param point ProceedingJoinPoint对象可以获取方法签名和目标类
     * @return 方法
     * @throws Throwable 抛出异常
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 获取方法信息
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取目标方法
        Method method = signature.getMethod();

        // 获取方法参数名称数组
        String[] parameterNames = signature.getParameterNames();
        // 获取方法参数值数组
        Object[] args = point.getArgs();

        // 用于存储startYearFlag参数的值
        String startYearFlag = null;

        // 遍历参数,找到名为startYearFlag的参数
        for (int i = 0; i < parameterNames.length; i++) {
            // 假设args[i]是DynamicBaseQuery类型,它继承自DynamicBaseQuery
            if (args[i] instanceof DynamicBaseQuery) {
                DynamicBaseQuery query = (DynamicBaseQuery) args[i];
                // 直接获取startYearFlag属性
                startYearFlag = query.getStartYearFlag();
                break;
            }
        }

        // 当前年份
        String year = String.valueOf(LocalDate.now().getYear());

        // 如果找到了startYearFlag参数,则使用它的值
        String value;
        if (isValidYear(startYearFlag)) {
            value = startYearFlag;
        } else {
            // 如果没有找到,保持原来的逻辑
            // 获取目标类上的注解
            DataSource targetDataSource = (DataSource) point.getTarget().getClass().getAnnotation(DataSource.class);
            // 获取方法上的注解
            DataSource methodDataSource = method.getAnnotation(DataSource.class);
            if (null != methodDataSource && StringUtils.isNotEmpty(methodDataSource.value())) {
                value = methodDataSource.value();
            } else if (null != targetDataSource && StringUtils.isNotEmpty(targetDataSource.value())) {
                value = targetDataSource.value();
            } else {
                // 当前年份
                value = year;
            }
        }

        // 判断获取的年份是否大于当前年份
        if (Integer.parseInt(value) > Integer.parseInt(year)) {
            log.error("{}{} 数据源不存在,使用当前{}年份数据源", DynamicConstant.SQLSERVER_PREFIX, value, year);
            value = year;
        } else if (Integer.parseInt(value) < 2023) {
            log.error("{}{} 数据源不存在,使用当前{}年份数据源", DynamicConstant.SQLSERVER_PREFIX, value, year);
            value = year;
        }

        // 根据dbKey动态设置数据源
        String dataBaseName = DynamicConstant.SQLSERVER_PREFIX + value;
        DynamicDataSource.setDataSource(dataBaseName, dynamicProperties);
        log.debug("设置数据源为: {}", dataBaseName);

        try {
            // 执行方法
            return point.proceed();
        } finally {
            DynamicContextHolder.poll();
            log.debug("clean datasource");
        }
    }
    
    /**
     * 校验字符串是否是年份
     *
     * @param year 年份字符串
     * @return true 是年份,false 不是年份
     */
    public static boolean isValidYear(String year) {
        if (StringUtils.isEmpty(year)) {
            return false;
        }
        return YEAR_PATTERN.matcher(year).matches();
    }
}

 

⑩接收前端固定入参

package com.jyy.dynamicdatasource;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 动态数据源基类 query
 *
 * @author jyy
 * @date 2025-01-15
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicBaseQuery implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 开始年份标识
     */
    private String startYearFlag;

    /**
     * 结束年份标识
     */
    private String endYearFlag;
}

 
 
 

三、测试代码

我分别在sqlserver中,新建了Water2023、Water2025、Water2026三个数据源,每个数据源中都新建了,device表,device表中有id和name字段,name的值分别是2023、2025、2026。在mysql中新建了Water2025库,里面也是device表,name的值是mysql2025。

在这里插入图片描述

 

①controller

package com.jyy.demo.controller;

import com.jyy.demo.serivce.DemoService;
import com.jyy.utils.dynamicdatasource.DynamicBaseQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * demo
 *
 * @author jyy
 * @date 2025-02-05
 */
@Slf4j
@RestController
@RequestMapping("/demo")
@RequiredArgsConstructor
public class DemoController {

    private final DemoService demoService;

    /**
     * 测试多数据源
     *
     * @param query 入参
     * @return String
     */
    @GetMapping("/testDynamic")
    public String testDynamic(DynamicBaseQuery query) {
        // 测试多数据源
        String name = demoService.testDynamic(query);
        log.info("name:{}", name);
        return name;
    }
}

 

②service

package com.jyy.demo.serivce;

import com.jyy.utils.dynamicdatasource.DynamicBaseQuery;

/**
 * demo
 *
 * @author jyy
 * @date 2025-02-05
 */
public interface DemoService {

    /**
     * 测试多数据源
     *
     * @param query 入参
     * @return String
     */
    String testDynamic(DynamicBaseQuery query);
}

 

③serviceImpl

package com.jyy.demo.serivce.impl;

import com.jyy.demo.mapper.DemoMapper;
import com.jyy.demo.serivce.DemoService;
import com.jyy.utils.dynamicdatasource.DynamicBaseQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * demo
 *
 * @author jyy
 * @date 2025-02-05
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {

    private final DemoMapper demoMapper;

    /**
     * 测试多数据源
     *
     * @param query 入参
     * @return String
     */
    @Override
    public String testDynamic(DynamicBaseQuery query) {
        // 测试多数据源
        return demoMapper.testDynamic();
    }
}

 

④mapper

package com.jyy.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

/**
 * demo
 *
 * @author jyy
 * @date 2025-02-05
 */
@Mapper
public interface DemoMapper {

    /**
     * 测试多数据源
     *
     * @return String
     */
    String testDynamic();
}

 

⑤mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jyy.demo.mapper.DemoMapper">

    <select id="testDynamic" resultType="java.lang.String">
        select name from device
    </select>
</mapper>

 
 
 

四、测试结果

注意:自定义注解可以加在controller、serivce、serviceImpl的类上或者方法上,但是不能加在mapper层,因为mybatis本来就走了代理,我们再去代理的话,代理的对象属性是空的。

 

项目启动成功,并且多数据源也初始化成功了

在这里插入图片描述

 

①没加自定义注解,查询默认mysql数据源

在这里插入图片描述

在这里插入图片描述

能查询查出,证明默认数据源没问题。

 

②加自定义注解,默认查询sqlserver,今年2025年数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

 

③加自定义注解,加入参,查询sqlserver,2023年历史数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

 

④加自定义注解,加入参,查询2026数据,因为项目需求,我这里做了校验,大于本年度和小于2023年的,在项目中都是没有真实数据源的,没有兜底会报错。这里2026年还没到,所以没有这个数据源,兜底

在这里插入图片描述

在这里插入图片描述
 
 
 

五、总结

我们处理动态多数据源的核心,不管是兜底还是数据源逻辑处理,其实还是在⑨aop中,这个是我在项目开发过程中遇到的需求,大家可以借鉴下,其实还可以用redis来缓存,增加性能,但是我这个项目查询的逻辑很单一,没必要用到redis。
 

修改数据源的逻辑在,第二节的序号⑨aop中的,环绕通知方法,因为我这个项目的修改数据源的需求比较简单,也没有使用到自定义注解的value值,但是传value也是可以被接收的,大家可以根据自身的需求,修改aop中的逻辑

 

写的很粗略,大家将就着看看吧。哈哈。
 
 
 

六、源码地址

 
gitee: https://gitee.com/jiyaya/jyy-dynamic.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值