springboot+mybatis-plus多数据源配置

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
  1. spring下面增加autoconfigure: exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure,这是取消spring自动配置数据源。或者使用简单注解方式也能达到这个效果。就是在启动类上增加@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  2. dynamic下面配置有druidprimarystrictdatasource 四项。
  • 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 响应式编程中使用数据源切换的问题

如果使用响应式编程的话一定要注意,因为FluxMono在操作数据的时候其实底层相当于异步操作,所以一定要将切换数据源的操作单独隔离出去。先看一个错误示范:

@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中,和fluxmono的流式操作完全隔离开,才能正确切换数据源。或者将流式操作丢到controller层也可以处理。主旨就是@DS("slave")的方法和类中不能有fluxmono的操作。
正确操作:
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()注解到类上。

你可能感兴趣的:(springboot+mybatis-plus多数据源配置)