1 文档
mybatis-plus官方文档:https://mp.baomidou.com/guide/dynamic-datasource.html
引入依赖
dependencies {
implementation(
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-data-mongodb",
"com.baomidou:mybatis-plus-boot-starter:$mybatisPlus",
"p6spy:p6spy:$p6spy",
"mysql:mysql-connector-java:$mysql",
"com.alibaba:druid-spring-boot-starter:$druid",
"com.baomidou:dynamic-datasource-spring-boot-starter:$dynamicDatasource"
)
buildscript {
ext {
set('mybatisPlus', "3.4.1")
set('dynamicDatasource', "3.3.2")
set('druid', "1.2.5")
set('p6spy', "3.9.1")
set('mysql', "8.0.22")
}
}
因为我项目中也用到了mongodb,所以引入了mingodb,p6spy可以将sql日志自动回填并显示。
2 数据源配置
spring:
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
data:
mongodb:
uri: mongodb://xxxxxxxxxxxxxxxxxxxxxxxxxx
datasource:
dynamic:
druid:
initialSize: 5
maxActive: 20
minIdle: 5
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filter:
wall:
enabled: true
config:
multiStatementAllow: true
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: true#设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
datasource:
master:
url: jdbc:p6spy:mysql://xx.xx.xx.xx:xx/xxxxx?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true&useSSL=false
username: xxxx
password: xxxx
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
slave:
url: jdbc:p6spy:mysql://yy.yy.yy.yy:yy/yyyy?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true&useSSL=false
username: yyyy
password: yyyy
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
-
spring
下面增加autoconfigure: exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
,这是取消spring自动配置数据源。或者使用简单注解方式也能达到这个效果。就是在启动类上增加@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
。 -
dynamic
下面配置有druid
,primary
,strict
,datasource
四项。
-
druid
是我们自己连接池的配置。 -
primary
设置默认的数据源或者数据源组,默认值即为master -
strict
用来设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源。 -
datasource
下面配置了两个数据源。master和slave,因为我只需要这两个数据源的切换就可以了。
3 切换数据源
在service实现类上或者内部方法上使用@DS("slave")
来切换数据源,默认使用的是主数据源。
这是基本用法:这样getALL()
函数返回的就是slave
的数据。
/**
* 组织机构 实现类
*
* @author 刘鹏
* @date 2021/4/22 15:58
*/
@Service
@AllArgsConstructor
public class OrganizationServiceImpl implements OrganizationService {
private final OrganizationDao organizationDao;
/**
* 返回组织机构列表
*
* @return 组织结构列表
*/
@Override
@DS("slave")
public List getAll() {
return organizationDao.selectList(new LambdaQueryWrapper<>());
}
}
它的dao层接口只需要继承BaseMapper
即可。
OrganizationDao
:
package com.tongxing.media.task.dao.mysql;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tongxing.media.index.entity.po.Organization;
/**
* 组织机构表接口
*
* @author 刘鹏
* @version 2021-04-08 下午5:51
*/
public interface OrganizationDao extends BaseMapper {
}
如果一个方法里面使用到了两个数据源的dao层接口的话,需要将使用不同数据源的那段代码抽取出来,在新的方法上增加@DS("slave")
来保证这个注解只作用于当前这个操作。
比如
@Service
@AllArgsConstructor
public class OrganizationServiceImpl implements OrganizationService {
private final OrganizationDao organizationDao;
private final StoryDao storyDao;
/**
* 返回组织机构列表
*
* @return 组织结构列表
*/
@Override
public List getAll() {
int s = storyDao.selectCount(new LambdaQueryWrapper<>());
System.out.println(s);
return getOrganizations();
}
@DS("slave")
private List getOrganizations() {
return organizationDao.selectList(new LambdaQueryWrapper<>());
}
}
getALL()
函数中storyDao
这个实例是从主数据源获取数据,所以只能把organizationDao.selectList(new LambdaQueryWrapper<>())
这段代码抽出去,并且在方法上增加@DS("slave")
注解。
这样就完全实现了数据源的切换。
4 响应式编程中使用数据源切换的问题
如果使用响应式编程的话一定要注意,因为Flux
和Mono
在操作数据的时候其实底层相当于异步操作,所以一定要将切换数据源的操作单独隔离出去。先看一个错误示范:
@Service
@Slf4j
@AllArgsConstructor
public class OrganizationSerImpl implements OrganizationService {
private final OrganizationDao organizationDao;
/**
* 查询组织机构列表
*
* @param mono 查询参数-父ID
* @return 组织机构列表
*/
@Override
@DS("slave")
public Flux query(Mono mono) {
Flux flux = mono.map(t -> {
log.debug("query:organizationDto={}", t);
QueryWrapper wrapper = new QueryWrapper<>();
if (StringUtils.hasText(t.getParentIscId())) {
wrapper.eq("parent_isc_id", t.getParentIscId());
} else {
wrapper.eq("parent_isc_id", "0");
}
return organizationDao.selectList(wrapper);// 这句划重点
}).flatMapMany(Flux::fromIterable);
return flux.map(t -> {
OrganizationDto organizationDTO = new OrganizationDto();
BeanUtils.copyProperties(t, organizationDTO);
organizationDTO.setName(CommonUtil.decodeBase64(organizationDTO.getName()));
return organizationDTO;
});
}
}
OrganizationDao
是数据源slave
的接口,而像上面操作之后,我们每次读取的数据都是主数据源master
的数据,而不是slave
的数据。一般像这种情况,我们自己单独写一个service
,将这种从数据库获取数据的操作放到单独一个service
中,和flux
和mono
的流式操作完全隔离开,才能正确切换数据源。或者将流式操作丢到controller
层也可以处理。主旨就是@DS("slave")
的方法和类中不能有flux
和mono
的操作。
正确操作:
OrganizationSerImpl
:
@Service
@Slf4j
@AllArgsConstructor
public class OrganizationSerImpl implements OrganizationService {
private final OrgService orgService;
/**
* 查询组织机构列表
*
* @param mono 查询参数-父ID
* @return 组织机构列表
*/
public Flux query(Mono mono) {
Flux flux = mono.map(t -> {
log.debug("query:organizationDto={}", t);
QueryWrapper wrapper = new QueryWrapper<>();
if (StringUtils.hasText(t.getParentIscId())) {
wrapper.eq("parent_isc_id", t.getParentIscId());
} else {
wrapper.eq("parent_isc_id", "0");
}
return orgService.getList(wrapper);
}).flatMapMany(Flux::fromIterable);
return flux.map(t -> {
OrganizationDto organizationDTO = new OrganizationDto();
BeanUtils.copyProperties(t, organizationDTO);
organizationDTO.setName(CommonUtil.decodeBase64(organizationDTO.getName()));
return organizationDTO;
});
}
}
注入OrgService
:
public interface OrgService {
/**
* 查询组织机构
*
* @return 组织机构
*/
List getList(QueryWrapper wrapper);
}
OrgServiceImpl
实现类:
@Service
@AllArgsConstructor
@DS("slave")
public class OrgServiceImpl implements OrgService {
private final OrganizationDao organizationDao;
@Override
public List getList(QueryWrapper wrapper) {
return organizationDao.selectList(wrapper);
}
}
如果这个类中只会使用slave
这个数据源的话可以将@DS()
注解到类上。