当前位置:   article > 正文

Mybatis Plus 多租户方案

Mybatis Plus 多租户方案

目录

一、Mybatis Plus 具体实现

二、生产示例


当不同的租户使用同一套程序,这里就需要考虑一个数据隔离的情况。

数据隔离有三种方案:

  1. 独立数据库:简单来说就是一个租户使用一个数据库,这种数据隔离级别最高,安全性最好,但是提高成本。
  2. 共享数据库、隔离数据架构:多租户使用同一个数据裤,但是每个租户对应一个Schema(数据库user)。
  3. 共享数据库、共享数据架构:使用同一个数据库,同一个Schema,但是在表中增加了租户ID的字段,这种共享数据程度最高,隔离级别最低。

一、Mybatis Plus 具体实现

Mybatis Plus 提供了一种多租户的解决方案,基于分页插件进行实现,具体实现代码如下:

1、租户配置 // 以下代码都是基于SpringBoot

  1. import org.mybatis.spring.annotation.MapperScan;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
  5. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  6. import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
  7. import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
  8. import net.sf.jsqlparser.expression.Expression;
  9. import net.sf.jsqlparser.expression.LongValue;
  10. @Configuration
  11. @MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
  12. public class MybatisPlusConfig {
  13. /**
  14. * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
  15. */
  16. @Bean
  17. public MybatisPlusInterceptor mybatisPlusInterceptor() {
  18. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  19. interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
  20. @Override
  21. public Expression getTenantId() {
  22. // 设置当前租户ID,实际情况你可以从cookie、或者缓存中拿都行
  23. return new LongValue(1);
  24. }
  25. @Override
  26. public String getTenantIdColumn() {
  27. // 对应数据库租户ID的列名
  28. return "tenant_id";
  29. }
  30. // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
  31. @Override
  32. public boolean ignoreTable(String tableName) {
  33. // 只对user表生效
  34. return !"user".equalsIgnoreCase(tableName);
  35. }
  36. }));
  37. // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
  38. // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
  39. // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
  40. return interceptor;
  41. }
  42. // @Bean
  43. // public ConfigurationCustomizer configurationCustomizer() {
  44. // return configuration -> configuration.setUseDeprecatedExecutor(false);
  45. // }
  46. }

2、实体类

  1. /**
  2. * 用户实体对应表 user
  3. */
  4. @Data
  5. @Accessors(chain = true) // 链式访问,该注解设置为chain=true,生成setter方法返回this(也就是返回的是对象),代替了默认的返回void
  6. @TableName("user")
  7. public class User {
  8. private Long id;
  9. /**
  10. * 租户 ID
  11. */
  12. private Long tenantId;
  13. private String name;
  14. @TableField(exist = false) // 表中不存在字段
  15. private String addrName;
  16. }

3、数据库层/Mapper层

租户字段会自动拼接

  1. public interface UserMapper extends BaseMapper<User> {
  2. /**
  3. * 自定义SQL:默认也会增加多租户条件
  4. */
  5. Integer myCount();
  6. // 多表也会自动加上
  7. List<User> getAddrAndUser(@Param("name") String name);
  8. }

自定义 sql 的 xml 文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper">
  4. <-- <resultMap id="UserMap" type="com.ms.project.entity.User">
  5. <id property="id" column="id"/>
  6. <result property="name" column="global_id"/>
  7. <result property="gender" column="eqp_code"/>
  8. <result property="age" column="mp_code"/>
  9. <result property="telPhone" column="mp_name"/>
  10. <result property="registerMode" column="measure_parameter"/>
  11. <result property="thirdPartyId" column="measure_unit"/>
  12. </resultMap> -->
  13. <select id="myCount" resultType="java.lang.Integer">
  14. select count(1) from user
  15. </select>
  16. <select id="getAddrAndUser" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
  17. select a.name as addr_name, u.id, u.name
  18. from user_addr a
  19. left join user u on u.id=a.user_id
  20. <where>
  21. <if test="name!=null">
  22. a.name like concat(concat('%',#{name}),'%')
  23. </if>
  24. </where>
  25. </select>
  26. </mapper>

4、数据源配置和数据库表

application.yml 文件

  1. # DataSource Config
  2. spring:
  3. datasource:
  4. url: jdbc:mysql://127.0.0.1:3306/mybatis-plus?useSSL=false
  5. username: root
  6. password: root
  7. # Logger Config
  8. logging:
  9. level:
  10. com.baomidou.mybatisplus.samples: debug

数据库脚本

  1. DROP TABLE IF EXISTS user;
  2. CREATE TABLE user
  3. (
  4. id BIGINT(20) NOT NULL COMMENT '主键ID',
  5. tenant_id BIGINT(20) NOT NULL COMMENT '租户ID',
  6. name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
  7. PRIMARY KEY (id)
  8. );
  9. DROP TABLE IF EXISTS user_addr;
  10. CREATE TABLE USER_ADDR
  11. (
  12. id BIGINT(20) NOT NULL COMMENT '主键ID',
  13. user_id BIGINT(20) NOT NULL COMMENT 'user.id',
  14. name VARCHAR(30) NULL DEFAULT NULL COMMENT '地址名称',
  15. PRIMARY KEY (id)
  16. );
  17. -- 添加数据
  18. DELETE FROM user;
  19. INSERT INTO user (id, tenant_id, name) VALUES
  20. (1, 1, 'Jone'),(2, 1, 'Jack'),(3, 1, 'Tom'),
  21. (4, 0, 'Sandy'),(5, 0, 'Billie');
  22. INSERT INTO user_addr (id, USER_ID, name) VALUES
  23. (1, 1, 'addr1'),(2,1,'addr2');

补充依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. </dependency>
  6. <!-- mybatis-plus 依赖 -->
  7. <dependency>
  8. <groupId>com.baomidou</groupId>
  9. <artifactId>mybatis-plus-boot-starter</artifactId>
  10. </dependency>
  11. <!-- MySQL 连接驱动依赖 -->
  12. <dependency>
  13. <groupId>mysql</groupId>
  14. <artifactId>mysql-connector-java</artifactId>
  15. </dependency>
  16. </dependencies>

5、单元测试类

  1. import com.baomidou.mybatisplus.samples.tenant.entity.User;
  2. import com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper;
  3. import org.junit.jupiter.api.Assertions;
  4. import org.junit.jupiter.api.Test;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import javax.annotation.Resource;
  7. import java.util.List;
  8. /**
  9. * 多租户 Tenant 演示
  10. */
  11. @SpringBootTest
  12. public class TenantTest {
  13. @Resource
  14. private UserMapper mapper;
  15. // INSERT INTO user (id, name, tenant_id) VALUES (?, ?, 1)
  16. // SELECT id, tenant_id, name FROM user WHERE id = ? AND user.tenant_id = 1
  17. @Test
  18. public void aInsert() { // 添加
  19. User user = new User();
  20. user.setName("一一");
  21. Assertions.assertTrue(mapper.insert(user) > 0);
  22. user = mapper.selectById(user.getId());
  23. Assertions.assertTrue(1 == user.getTenantId());
  24. }
  25. @Test
  26. public void bDelete() { // 删除
  27. Assertions.assertTrue(mapper.deleteById(3L) > 0);
  28. }
  29. @Test
  30. public void cUpdate() { // 修改
  31. Assertions.assertTrue(mapper.updateById(new User().setId(1L).setName("mp")) > 0);
  32. }
  33. @Test
  34. public void dSelect() { // 查询
  35. List<User> userList = mapper.selectList(null);
  36. userList.forEach(u -> Assertions.assertTrue(1 == u.getTenantId()));
  37. }
  38. /**
  39. * 自定义SQL:默认也会增加多租户条件
  40. * 参考打印的SQL: SELECT count(1) FROM user WHERE user.tenant_id = 1
  41. */
  42. @Test
  43. public void manualSqlTenantFilterTest() {
  44. System.out.println(mapper.myCount());
  45. }
  46. // SELECT a.name AS addr_name, u.id, u.name FROM user_addr a LEFT JOIN user u ON u.id = a.user_id AND u.tenant_id = 1 WHERE a.name LIKE concat(concat('%', ?), '%')
  47. @Test
  48. public void testTenantFilter(){
  49. mapper.getAddrAndUser(null).forEach(System.out::println);
  50. mapper.getAddrAndUser("add").forEach(System.out::println);
  51. }
  52. }

官方完整的代码地址:mybatis-plus-samples: MyBatis-Plus Samples 文档

二、生产示例

在实际开发环境中,可以更加灵活的处理

自定义 TenantLineHandler

  1. import cn.hutool.core.util.StrUtil;
  2. import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
  3. import lombok.extern.slf4j.Slf4j;
  4. import net.sf.jsqlparser.expression.Expression;
  5. import net.sf.jsqlparser.expression.NullValue;
  6. import net.sf.jsqlparser.expression.StringValue;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. /**
  9. * 租户维护处理器
  10. */
  11. @Slf4j
  12. public class MyTenantHandler implements TenantLineHandler {
  13. @Autowired
  14. private MyTenantConfigProperties properties;
  15. /**
  16. * 获取租户 ID 值表达式,只支持单个 ID 值
  17. * @return 租户 ID 值表达式
  18. */
  19. @Override
  20. public Expression getTenantId() {
  21. String tenantId = TenantContextHolder.getTenantId();
  22. log.debug("当前租户为 >> {}", tenantId);
  23. if (StrUtil.isBlank(tenantId)) {
  24. return new NullValue(); // 数据库的值
  25. }
  26. return new StringValue(tenantId);
  27. }
  28. /**
  29. * 获取租户字段名
  30. * @return 租户字段名
  31. */
  32. @Override
  33. public String getTenantIdColumn() {
  34. return properties.getColumn();
  35. }
  36. /**
  37. * 根据表名判断是否忽略拼接多租户条件
  38. * <p>
  39. * 默认都要进行解析并拼接多租户条件
  40. * @param tableName 表名
  41. * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
  42. */
  43. @Override
  44. public boolean ignoreTable(String tableName) {
  45. String tenantId = TenantContextHolder.getTenantId();
  46. // 租户中ID 为空,查询全部,不进行过滤
  47. if (StrUtil.isBlank(tenantId)) {
  48. return Boolean.TRUE;
  49. }
  50. return !properties.getTables().contains(tableName);
  51. }
  52. }

使用 Properties 进行灵活的配置

  1. import lombok.Data;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.cloud.context.config.annotation.RefreshScope;
  4. import org.springframework.context.annotation.Configuration;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. /**
  8. * 多租户配置
  9. */
  10. @Data
  11. @RefreshScope
  12. @Configuration
  13. @ConfigurationProperties(prefix = "my.mro.tenant")
  14. public class MyTenantConfigProperties {
  15. /**
  16. * 维护租户列名称
  17. */
  18. private String column = "tenant_id";
  19. /**
  20. * 多租户的数据表集合
  21. */
  22. private List<String> tables = new ArrayList<>();
  23. }

然后租户表可以在配置文件中进行维护,application.yml 文件维护示例如下:

  1. # 租户表维护
  2. my:
  3. mro:
  4. tenant:
  5. column: tenant_code
  6. tables:
  7. - fault_info
  8. - component_info
  9. - demand_acceptance
  10. - demand_detail

至此,租户方案完成。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号