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<Organization> getAll() {
        return organizationDao.selectList(new LambdaQueryWrapper<>());
    }
}

它的dao层接口只需要继承BaseMapper<Entity>即可。
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<Organization> {
}

如果一个方法里面使用到了两个数据源的dao层接口的话,需要将使用不同数据源的那段代码抽取出来,在新的方法上增加@DS("slave")来保证这个注解只作用于当前这个操作。
比如

@Service
@AllArgsConstructor
public class OrganizationServiceImpl implements OrganizationService {

    private final OrganizationDao organizationDao;
    
    private final StoryDao storyDao;

    /**
     * 返回组织机构列表
     *
     * @return 组织结构列表
     */
    @Override
    public List<Organization> getAll() {

        int s = storyDao.selectCount(new LambdaQueryWrapper<>());
        System.out.println(s);
        return getOrganizations();
    }
    
    @DS("slave")
    private List<Organization> 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<OrganizationDto> query(Mono<OrganizationDto> mono) {
        Flux<Organization> flux = mono.map(t -> {
            log.debug("query:organizationDto={}", t);
            QueryWrapper<Organization> 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<OrganizationDto> query(Mono<OrganizationDto> mono) {
        Flux<Organization> flux = mono.map(t -> {
            log.debug("query:organizationDto={}", t);
            QueryWrapper<Organization> 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<Organization> getList(QueryWrapper<Organization> wrapper);
}

OrgServiceImpl实现类:

@Service
@AllArgsConstructor
@DS("slave")
public class OrgServiceImpl implements OrgService {

    private final OrganizationDao organizationDao;

    @Override
    public List<Organization> getList(QueryWrapper<Organization> wrapper) {
        return organizationDao.selectList(wrapper);
    }
}

如果这个类中只会使用slave这个数据源的话可以将@DS()注解到类上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容