youlai-boot接入动态数据源dynamic-datasource


🚀 有来作者主页

🔥 开源项目

🌺 仓库主页

💖 欢迎点赞、收藏、留言。如有错误,敬请指正!


前言

本篇文章介绍了如何在 youlai-boot 中接入多数据源。
群里小伙伴在使用youlai-boot接入多数据源时,遇到了很多问题,借此机会写一篇博客,介绍一下使用youlai-boot对接多数据源的方法

引入依赖

      <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
            <version>4.3.1</version>
        </dependency>

注意,因为youlai-boot使用的springboot版本是3.3.4,所以引入的依赖必须为spring-boot3。以下是引入的springboot的依赖。

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version> 
        <relativePath/>
    </parent>

接入步骤

1. 创建数据库和表

创建两个库 youlai_bootyoulai_boot1,演示表为 dynamic_datasource_test。两个表结构相同,但数据不同。
在这里插入图片描述
图中分别为youlai_boot和youlai_boot1库,演示表为dynamic_datasource_test。

表结构相同,为了方便演示,这里我手动向两个不同的表塞入了不同的数据。id都为1

youlai_boot库
youlai_boot1库

2. 在配置文件里引入多个数据源,

spring:
  jackson:
    ## 默认序列化时间格式
    date-format: yyyy-MM-dd HH:mm:ss
    ## 默认序列化时区
    time-zone: GMT+8
  datasource:
    dynamic:
      primary: 'master'   # 默认数据源
      strict: false  # 是否严格检查数据源,false 表示不严格检查
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3306/youlai_boot1?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

3. 演示代码

  • DynamicDatasourceModel.java
package com.youlai.boot.modules.demo.model;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * 动态数据源-业务模块演示实体
 *
 * @author Theo
 * @since 2024/11/04
 */
@Data
@TableName("dynamic_datasource_test")
public class DynamicDatasourceModel {

    private Long id;

    private String tempValue;
}
  • DynamicDatasourceController.java
package com.youlai.boot.modules.demo.controller;

import com.youlai.boot.modules.demo.service.DynamicDatasourceService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 动态数据源-业务模块演示Controller
 *
 * @author Theo
 * @since 2024/11/04
 */
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/dynamic/datasource")
public class DynamicDatasourceController {

    private final DynamicDatasourceService dynamicDatasourceService;

    @GetMapping("/test")
    public String test() {
        return dynamicDatasourceService.test();
    }

    @GetMapping("/test1")
    public String test1() {
        return dynamicDatasourceService.test1();
    }
}
  • DynamicDatasourceService.java
package com.youlai.boot.modules.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.modules.demo.model.DynamicDatasourceModel;

/**
 * 动态数据源-业务模块演示Service
 *
 * @author Theo
 * @since 2024/11/04
 */
public interface DynamicDatasourceService extends IService<DynamicDatasourceModel> {
    String test();
    String test1();
}

  • DynamicDatasourceServiceImpl.java
package com.youlai.boot.modules.demo.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.modules.demo.mapper.DynamicDatasourceMapper;
import com.youlai.boot.modules.demo.model.DynamicDatasourceModel;
import com.youlai.boot.modules.demo.service.DynamicDatasourceService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 动态数据源-业务模块演示ServiceImpl
 *
 * @author Theo
 * @since 2024/11/04
 */
@Service
@RequiredArgsConstructor
public class DynamicDatasourceServiceImpl extends ServiceImpl<DynamicDatasourceMapper, DynamicDatasourceModel> implements DynamicDatasourceService {

    @DS("master")
    @Override
    public String test() {
        return this.getById(1L).getTempValue();
    }

    @DS("slave")
    @Override
    public String test1() {
        return this.getById(1L).getTempValue();
    }
}
  • DynamicDatasourceMapper.java
package com.youlai.boot.modules.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.modules.demo.model.DynamicDatasourceModel;
import org.apache.ibatis.annotations.Mapper;

/**
 * 动态数据源-业务模块演示
 *
 * @author Theo
 * @since 2024/11/04
 */
@Mapper
public interface DynamicDatasourceMapper extends BaseMapper<DynamicDatasourceModel> {
}

  • DynamicDatasourceMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.youlai.boot.modules.demo.mapper.DynamicDatasourceMapper">

</mapper>

为了方便演示,我给DynamicDatasourceController的访问地址配置到security的白名单中

忽略演示地址的请求

4. 启动项目

在自动项目之后,日志显示两个数据源已成功初始化。
启动日志显示数据源初始化成功

5. 访问两个演示地址

访问 /dynamic/datasource/test/dynamic/datasource/test1

演示结果
至此,动态数据源配置已经完全结束


总结

  1. 依赖引入:根据项目的 Spring Boot 版本,选择适合的依赖。
    如果所使用的Springboot版本小于等于2.7.3,则引入的依赖为
	<dependency>
	    <groupId>com.baomidou</groupId>
	    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
	</dependency>

当前文章引入dynamic-datasource-spring-boot3-starter是因为使用的为youlai-boot项目

  1. 使用注解 @DS:切换数据源时使用。
	@DS("slave")
	@Override
	public String test1() {
	    return this.getById(1L).getTempValue();
	}
  1. 代码切换数据源
 	@Override
    public String test1() {
        try {
            DynamicDataSourceContextHolder.push("slave");
            return this.getById(1L).getTempValue();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }

根据DynamicDataSourceContextHolder的源码我们可以看到

	/**
     * 设置当前线程数据源
     * <p>
     * 如非必要不要手动调用,调用后确保最终清除
     * </p>
     *
     * @param ds 数据源名称
     * @return 数据源名称
     */
    public static String push(String ds) {
        String dataSourceStr = DsStrUtils.isEmpty(ds) ? "" : ds;
        LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
        return dataSourceStr;
    }

所以我们在手动更改数据源之后,尽可能的保证执行poll方法

  1. 事务管理
  • 使用 @DSTransactional 注解来保证多个数据源下的事务一致性。

如果使用了多个数据源进行了增删改操作,则我们不能再使用@Transactional注解来保证事物的一致性了,因为已经涉及到两个数据库。
改用@DSTransactional注解

DSTransactional注解
对应的切面文件在这里
DSTransactional注解切面实现
可以看到,实现方式为通过不同的线程来执行不同库的操作,最终确定是否提交事物

  • 请注意数据库表的存储引擎。MyISAM不支持事物。

动态添加数据源

需求描述:
当你项目的数据量特别大,已经达到了千万甚至更高的级别,这种情况下,领导让你根据这些用户的手机号进行取模,根据得到的结果,给不同的用户分别放到不同的库里。这样的好处是,如果我有更多的用户,我可以根据手机号对不同的数字取模,就可以分更多的库。这种情况下,你如果把这些数据库的配置都写到配置文件里,显然是非常不合适的。

解决方法
在这里插入图片描述
在这里插入图片描述

可以创建一个表,分别给这些数据源起好名,例如number_1number_2,分别对应哪个数据库,在启动的时候可以将这些数据源读出来,使用DynamicRoutingDataSource的addDataSource方法,给这些数据源加载进这些动态数据源里。

LocalContextAware.java

package com.youlai.boot.config;


import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.youlai.boot.system.model.entity.DynamicDs;
import com.youlai.boot.system.service.DynamicDsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 项目启动时运行的类 会执行setApplicationContext方法
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LocalContextAware implements ApplicationContextAware {
    private final DynamicDsService dynamicDsService;
    private final DynamicRoutingDataSource dynamicRoutingDataSource;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        try {
            List<DynamicDs> list = dynamicDsService.list();
            int i = 0;
            for (DynamicDs dynamicDs : list) {
                DruidDataSource druidDataSource = new DruidDataSource();
                druidDataSource.setUrl(dynamicDs.getDbUrl());
                druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
                druidDataSource.setUsername(dynamicDs.getDbUser());
                druidDataSource.setPassword(dynamicDs.getDbPassword());
                druidDataSource.setMaxActive(20);
                druidDataSource.setMaxWait(60000L);
                druidDataSource.setMinIdle(5);
                druidDataSource.setMaxCreateTaskCount(5);
                druidDataSource.setBreakAfterAcquireFailure(true);
                druidDataSource.setOnFatalErrorMaxActive(3);
                druidDataSource.init();
                dynamicRoutingDataSource.addDataSource(dynamicDs.getDbName(),druidDataSource);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

代码中DynamicDsService只是为了读取dynamic_ds表中的数据源信息,这里就不再过多展示了

这样,数据库就动态的添加到了我们的项目中,使用方式和演示代码部分没有任何区别,事物的方式也是一致的。下面是我测试的部分代码和结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值