mybati-plus切换数据源的两种方式

在工作中我们在一个springboot项目中,经常会有不同数据源的场景,如果我们项目中用的是mybatis-plus,那就很方便去配置,如果用的是mybatis可以切换成mybatis-plus,也可以自己实现一个动态切换数据源(spring已经给我们提供了一个接口)

首先引入依赖



com.baomidou
dynamic-datasource-spring-boot-starter
3.0.0

一.一般使用情况下使用@DS("数据源名称"),我们进行切换数据源

@DS("slave")
public List list(String areaCode) {
List list = slaveMapper.list(areaCode);
return list;
}

只需要在application.yml添加一组dataSource就可以了

dynamic:
primary: master
datasource:

master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master
username: root
password: 123456

slave:
url: jdbc:mysql://127.0.0.1:3306/slave
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver

二.特殊情况@DS("")不能满足我们的所有需求,比如我们在slave数据源的service方法中要使用master中的数据,例如:我数据在slave字典在master中,于是我写成了以下这样(这样写是错误的,并不能达到我们想要的效果)

@DS("slave")
public List list(String areaCode) {
List list = slaveMapper.list(areaCode);
List educationes = listDict("education");
...后续操作...
}

@DS("master")
public List listDict(String dictType) {
List list = dictMapper.list(dictType);
return list;
}

原因:mybatis-plus数据源是利用spring-aop实现的,对于aop而言它是以每次请求为单位的,简单的说,虽然我们使用了两个方法,分别配置了两个@DS("),但其实第二个并不会生效。

解决办法,对于一次请求两个数据源在把第二个数据源改为手动修改,下面的手动修改配置:

public List listDict(String dictType) {
DynamicDataSourceContextHolder.push("master");
List list = dictMapper.list(dictType);
DynamicDataSourceContextHolder.poll();
return list;
}

原理:我们看下源码,找到mybatis-plus中DynamicRoutingDataSource类:

public DataSource determineDataSource() {
return this.getDataSource(DynamicDataSourceContextHolder.peek());
}

public DataSource getDataSource(String ds) {
if (StringUtils.isEmpty(ds)) {
return this.determinePrimaryDataSource();
} else if (!this.groupDataSources.isEmpty() && this.groupDataSources.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return ((DynamicGroupDataSource) this.groupDataSources.get(ds)).determineDataSource();
} else if (this.dataSourceMap.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return (DataSource) this.dataSourceMap.get(ds);
} else if (this.strict) {
throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
} else {
return this.determinePrimaryDataSource();
}
}

主要有两个方法,determineDataSource()会在使用数据源时调用,determineDataSource()方法中使用DynamicDataSourceContextHolder.peek()获得一个字符串调用getDataSource()获取数据源:

private static final ThreadLocal<Deque> LOOKUP_KEY_HOLDER = new ThreadLocal() {
protected Object initialValue() {
return new ArrayDeque();
}
};

private DynamicDataSourceContextHolder() {
}

public static String peek() {
return (String)((Deque)LOOKUP_KEY_HOLDER.get()).peek();
}

public static void push(String ds) {
((Deque)LOOKUP_KEY_HOLDER.get()).push(StringUtils.isEmpty(ds) ? "" : ds);
}

public static void poll() {
Deque deque = (Deque)LOOKUP_KEY_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
}

}

在DynamicDataSourceContextHolder中定义了一个双端队列LOOKUP_KEY_HOLDER,它的peek()方法每次取得时当前线程的首个,所以我们在手动却换的过程中,加入一个我们想要的datasource然后通过poll()方法删除就能达到我们手动切换的目的。

posted @ 2021-03-30 14:05  drott  阅读(8227)  评论(0)    收藏  举报