Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource
Posted 拽着尾巴的鱼儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource相关的知识,希望对你有一定的参考价值。
前言:对于同一个系统,不同的租户需要自己独立分隔的数据库(每个数据库的表结构可以是相同的),同时也要支持跨数据源的查询;并且支持分布式事务,如果这里不使用分库分表插件,需要怎样实现?本文采用MyBatis-Plus下的dynamic-datasource 进行实现。
MyBatis-Plus的dynamic-datasource 官网;
开始整合:
1 spring-cloud 整合多数据源:
1.1 maven pom jar包,如果启动发生问题则需要排除版本jar 包冲突的问题:
<!-- mybatis 多数据源切换依赖jar -->
<!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis plus 方便后续业务开发 -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.2</version>
</dependency>
<!-- mysql的连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mysql的分页插件便于业务查询分页处理 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
<exclusions>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>3.1</version>
</dependency>
<!-- druid的连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- xxl job 定时任务 便于后续定时任务的扩展,如果不需要可以手动去除 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.0</version>
</dependency>
<!-- web jar 便于hppt 通信 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- feign 接口 便于服务之间的通信,如果不需要可以手动去除 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- lombok 自动生成get set 等方法便于开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
1.2 bootstrap.yml 文件配置数据源信息:
server:
port: 8010
spring:
autoconfigure:
# 排除原有的连接池装配
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
# dynamic-datasource-spring-boot-starter 动态数据源的配置内容
datasource:
type: com.alibaba.druid.pool.DruidDataSource
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/master
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave1:
url: jdbc:mysql://localhost:3306/slaveone
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
slave2:
url: jdbc:mysql://localhost:3306/slavetwo?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
# druid 公共配置 参数: https://www.jianshu.com/p/f8b720737b20
druid:
# 连接池初始化大小
initialSize: 5
# 最小空闲连接数
minIdle: 5
# 最大连接数
maxActive: 30
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 'x'
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
# 如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn: false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,
# 比如说oracle。在mysql下建议关闭。
poolPreparedStatements: true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
maxPoolPreparedStatementPerConnectionSize: 20
# 属性类型是字符串,通过别名的方式配置扩展插件,监控统计用的filter:stat;
# 日志用的filter:log4j防御sql注入的filter:wall
filters: stat,wall,slf4j,config
# 公用监控数据
useGlobalDataSourceStat: true
# 慢日志输出
stat:
log-slow-sql: true
merge-sql: true
# 10 秒
slow-sql-millis: 10000
# mybatis mapper 包扫描路径
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
# xxl job 任务地址
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin
accessToken: lgx123456
executor:
appname: dev-bluegrass-dynamic-service
logpath:
logretentiondays: 10
ip: localhost
port: 9087
1.3 启动类增加路径扫描:
package org.lgx.bluegrass.bluegrassdynamic;
import org.lgx.bluegrass.api.constant.BaseConstant;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
// Feign 路径扫描
@EnableFeignClients("xxxx")
// mappper 包路径扫描
@MapperScan(basePackages ="xxx.bluegrassdynamic.mapper")
public class BluegrassDynamicApplication
public static ConfigurableApplicationContext applicationContext;
public static void main(String[] args)
applicationContext = SpringApplication.run(BluegrassDynamicApplication.class, args);
2 多数据源主动切换:
考虑方法:虽然使用@Ds 可以在类中进行切换,但是需要在每个类或者方法上增加改注解,如果增加错误或者漏加,就会切错数据库或者默认访问到主库中;那么是否可以有一个切面或者拦截器,可以在http 请求进入到方法之前,收到完成一次切换;
2.1 定义拦截器根据当前登录的用户的便签完成数据源的切换:
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class HttpRequestDynamic extends HandlerInterceptorAdapter
final static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
if (!(handler instanceof HandlerMethod))
// 不是 httpreuqest 请求直接放行
return true;
// 当前用户
String userDb = request.getHeader("userDb");
if (StringUtils.hasText(userDb))
// 获取用户所属数据源
String dbName = getDbName(userDb);
if (StringUtils.hasText(dbName))
// 切换数据源
DynamicDataSourceContextHolder.push(dbName);
threadLocal.set(true);
return true;
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
// 在方法执行完毕或者执行报错后,移除数据源
if (null != threadLocal.get() && threadLocal.get())
DynamicDataSourceContextHolder.clear();
threadLocal.remove();
private String getDbName(String userDb)
// 此处根据用户便签动态映射配置的数据库
return userDb;
2.2 将拦截器交由spring 管理:
import org.lgx.bluegrass.bluegrassdynamic.interceptor.HttpRequestDynamic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer
@Autowired
private HttpRequestDynamic httpRequestDynamic;
/**
* 拦截器配置
*
* @param registry 注册类
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
// 除了请求报错和请求静态资源外拦截一切http 请求
registry.addInterceptor(httpRequestDynamic).addPathPatterns("/**")
.excludePathPatterns(
"/file/get/*",
"/image/view/*",
"/**/error"
);
2.3 此处扩展,对xxl job 定时任务进行aop拦截:
import com.xxl.job.core.context.XxlJobHelper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class XxlJobHandlerMonitor
@Pointcut("@annotation(com.xxl.job.core.handler.annotation.XxlJob)")
public void xxlJobCut()
@Before(value = "xxlJobCut()")
public void permissionCheck(JoinPoint joinPoint)
@Around(value = "xxlJobCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable
String param = XxlJobHelper.getJobParam();
// 解析参数
// 根据参数所带的目标表数据库,可以进行
System.out.println("\\"123\\" = " + "123");
return joinPoint.proceed();
2.4 此处扩展,对feign 进行拦截(可以将user的便签信息设置到header中,同各个服务的拦截器协同作用):
方式1 在feign 的http 请求发送之前,构建header:
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.lgx.bluegrass.api.constant.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @Description
* @Date 2021/4/16 15:39
* @Author lgx
* @Version 1.0
*/
@Configuration
public class FeignConfiguration implements RequestInterceptor
private Logger logger = LoggerFactory.getLogger(FeignConfiguration.class);
@Override
public void apply(RequestTemplate requestTemplate)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
if (attributes != null)
String authorization = attributes.getRequest().getHeader(Constant.X_AUTHORIZATION);
if (null != authorization)
requestTemplate.header(Constant.X_AUTHORIZATION, authorization);
String dbName = attributes.getRequest().getHeader(Constant.dbName);
if (null != dbName)
requestTemplate.header(Constant.dbName, dbName);
String userDb = attributes.getRequest().getHeader(Constant.userDb);
if (null != userDb)
requestTemplate.header(Constant.userDb, userDb);
Constant.java:
public class Constant
public static final String BASIC_AUTH_TOKEN_PREFIX = "Basic";
public static final String X_AUTHORIZATION = "X-Authorization";
public static final String dbName = "dbName";
public static final String userDb = "userDb";
方式二,feign 方法中增加header 参数:
feign 接口定义:
import orgidea maven聚合项目创建子项目出现异常?
情况是这样的,当我创建完后,会出现一般情况下的pom文件(1图)。但是过了一会,idea配置完成之后,pom文件变成了2图。然后父类的继承没了。。。没就没把,但是每次都要手动改就很麻烦。。。我很迷惑,这是为什么。。
参考技术A
第一步:看pom.xml中有没有引入相关jar包的坐标;
第二步:执行mvn clean compile 命令,看看有没有自动把jar包下到本地maven仓库;
第三部:如果下载不成功,重新配置maven镜像,重新执行第二部的命令
参考技术B
你是不是父项目中的pom文件没有指定子模块呀,你这个子模块中只写parent是不行的
<modules>
</modules>追问有的,后来又尝试了一下,不选择骨架直接创建没这个问题,似乎就是后续的骨架自动配置出现问题。
然后我怀疑是本地库jar包缺失,整个库删掉重下还是出现这个问题。接着换eclipse再来一遍,一切正常。。。我心态有些炸了。。
参考技术C
聚合了一些项目,创建来电的项目也是出现了一场。
以上是关于Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource的主要内容,如果未能解决你的问题,请参考以下文章
[Spring-Cloud][Maven][Docker]Feign接口应该放在FeignClients还是EurekaClients?