首页 MyBatis-Plus 多数据源
文章
取消

MyBatis-Plus 多数据源

多数据源的配置方式有多种,既可以通过注解(@DS),也可以通过拦截器的方式处理。不同的方式解决的业务问题领域不一致而已。 本文介绍的方式是使用 混合配置 的方式实现,原理是:通过拦截器,依据类所属的包名动态切换数据源!

1. 引入必备依赖

1
2
3
4
5
6
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <!-- 选择与项目中 baomidou 框架对应的版本-->
  <version>${version}</version>
</dependency>

2. Yaml 文件配置

提醒:可以把此 yaml 文件抽离为公共配置文件,在其他的配置文件中显性引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 自定义配置。方便
test:
  datasource:
    alias:
      applet: &AliasApplet applet
      system: &AliasSystem system
    # 数据库的用户名和密码
    account:
      username: test
      password: root
    url:
      applet: jdbc:mysql://127.0.0.1:3306/test_applet?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
      system: jdbc:mysql://127.0.0.1:3306/test_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    # 配置顺序依据优先原则, 小范围配置在前。如果不配置则使用默认数据源
    relationship:
      mapping:
        # key:指定的datasource别名,value:需要切换的类路径 包名
        *AliasApplet : "com.test.app"
        *AliasSystem : "com.test.system"

spring:
  datasource:
    dynamic:
      # 默认数据源
      primary: ${test.datasource.alias.applet}
      #  启用严格匹配数据源,未匹配到指定数据源时抛异常
      strict: true
      datasource:
        # 譬如:小程序专用库
        *AliasApplet :
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: ${test.datasource.url.applet}
          username: ${test.datasource.account.username}
          password: ${test.datasource.account.password}
        # 譬如:后台管理专用库
        *AliasSystem :
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: ${test.datasource.url.system}
          username: ${test.datasource.account.username}
          password: ${test.datasource.account.password}

3. Spring 拦截器配置

  • 读取 yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.test.component;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 数据源与包关系配置类, 配置有顺序要求, 依据小范围优先配置原则
 *
 * @author Oriental Ming
 * @date 2021/7/15
 */
@Data
@Component
@ConfigurationProperties(prefix = "test.datasource.relationship")
public class DataSourceProperty {

    /**
     * <pre>
     *     包名和数据源的对应关系
     *     key: 数据源名, value: 包名
     * </pre>
     */
    private Map<String, String> mapping;

}

  • 拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.test.config.impl;

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.test.component.DataSourceProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源切换拦截器
 *
 * @author Oriental Ming
 * @date 2021/7/15
 */
@Configuration
public class DataSourceInterceptor implements HandlerInterceptor {

    private final Map<String, String> PACK_MAP_DATASOURCE;
    private final Map<Object, String> BEAN_MAP_PACK = new HashMap<>();

    public DataSourceInterceptor(DataSourceProperty dataSourceProperty) {
        // 如果没有配置数据源关系, 则使用默认数据源
        PACK_MAP_DATASOURCE = dataSourceProperty.getMapping();
    }

    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        String packageName = getPackageName((HandlerMethod) handler);
        // 依据包路径动态配置数据源, 如果没有匹配的则使用默认数据源
        for (Map.Entry<String, String> entry : PACK_MAP_DATASOURCE.entrySet()) {
            if (packageName.contains(entry.getValue())) {
                DynamicDataSourceContextHolder.push(entry.getKey());
                break;
            }
        }

        return true;
    }

    @Override
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
        // 请求完成,及时清理数据源配置
        DynamicDataSourceContextHolder.clear();
    }

    /**
      * 依据方法名称获取其包的全名
      *
      * @param method 受理的方法
      * @return 方法所在类的包全名
      */
    private String getPackageName(HandlerMethod method) {
        Object bean = method.getBean();
        if (BEAN_MAP_PACK.containsKey(bean)) {
            return BEAN_MAP_PACK.get(bean);
        }

        String packageName = method.getMethod().getDeclaringClass().getName();
        BEAN_MAP_PACK.put(bean, packageName);
        return packageName;
    }
}

  • 配置 WebMvcConfigurer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.test.config;

import com.test.config.impl.DataSourceInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 公众号端MVC全局配置
 *
 * @author Oriental Ming
 * @date 2021/7/15
 */
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final DataSourceInterceptor dataSourceInterceptor;

    /**
     * 数据源切换
     *
     * @param registry 拦截器注册类
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
    }
}

完结撒花 😂 ! 制作不易,如引用原文,必须附此原文链接,否则违者必究!😈


本文由作者按照 CC BY 4.0 进行授权