赞
踩
目录
当不同的租户使用同一套程序,这里就需要考虑一个数据隔离的情况。
数据隔离有三种方案:
Mybatis Plus 提供了一种多租户的解决方案,基于分页插件进行实现,具体实现代码如下:
1、租户配置 // 以下代码都是基于SpringBoot
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
- import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
- import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
- import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
- import net.sf.jsqlparser.expression.Expression;
- import net.sf.jsqlparser.expression.LongValue;
-
- @Configuration
- @MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
- public class MybatisPlusConfig {
-
- /**
- * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
- */
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
- @Override
- public Expression getTenantId() {
- // 设置当前租户ID,实际情况你可以从cookie、或者缓存中拿都行
- return new LongValue(1);
- }
- @Override
- public String getTenantIdColumn() {
- // 对应数据库租户ID的列名
- return "tenant_id";
- }
- // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
- @Override
- public boolean ignoreTable(String tableName) {
- // 只对user表生效
- return !"user".equalsIgnoreCase(tableName);
- }
- }));
- // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
- // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
- // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
- return interceptor;
- }
-
- // @Bean
- // public ConfigurationCustomizer configurationCustomizer() {
- // return configuration -> configuration.setUseDeprecatedExecutor(false);
- // }
- }

2、实体类
- /**
- * 用户实体对应表 user
- */
- @Data
- @Accessors(chain = true) // 链式访问,该注解设置为chain=true,生成setter方法返回this(也就是返回的是对象),代替了默认的返回void
- @TableName("user")
- public class User {
- private Long id;
- /**
- * 租户 ID
- */
- private Long tenantId;
- private String name;
-
- @TableField(exist = false) // 表中不存在字段
- private String addrName;
- }

3、数据库层/Mapper层
租户字段会自动拼接
- public interface UserMapper extends BaseMapper<User> {
- /**
- * 自定义SQL:默认也会增加多租户条件
- */
- Integer myCount();
-
- // 多表也会自动加上
- List<User> getAddrAndUser(@Param("name") String name);
- }
自定义 sql 的 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.baomidou.mybatisplus.samples.tenant.mapper.UserMapper">
-
- <-- <resultMap id="UserMap" type="com.ms.project.entity.User">
- <id property="id" column="id"/>
- <result property="name" column="global_id"/>
- <result property="gender" column="eqp_code"/>
- <result property="age" column="mp_code"/>
- <result property="telPhone" column="mp_name"/>
- <result property="registerMode" column="measure_parameter"/>
- <result property="thirdPartyId" column="measure_unit"/>
- </resultMap> -->
-
- <select id="myCount" resultType="java.lang.Integer">
- select count(1) from user
- </select>
-
- <select id="getAddrAndUser" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
- select a.name as addr_name, u.id, u.name
- from user_addr a
- left join user u on u.id=a.user_id
- <where>
- <if test="name!=null">
- a.name like concat(concat('%',#{name}),'%')
- </if>
- </where>
- </select>
- </mapper>

4、数据源配置和数据库表
application.yml 文件
- # DataSource Config
- spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/mybatis-plus?useSSL=false
- username: root
- password: root
-
- # Logger Config
- logging:
- level:
- com.baomidou.mybatisplus.samples: debug
数据库脚本
- DROP TABLE IF EXISTS user;
-
- CREATE TABLE user
- (
- id BIGINT(20) NOT NULL COMMENT '主键ID',
- tenant_id BIGINT(20) NOT NULL COMMENT '租户ID',
- name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
- PRIMARY KEY (id)
- );
-
- DROP TABLE IF EXISTS user_addr;
-
- CREATE TABLE USER_ADDR
- (
- id BIGINT(20) NOT NULL COMMENT '主键ID',
- user_id BIGINT(20) NOT NULL COMMENT 'user.id',
- name VARCHAR(30) NULL DEFAULT NULL COMMENT '地址名称',
- PRIMARY KEY (id)
- );
-
- -- 添加数据
- DELETE FROM user;
-
- INSERT INTO user (id, tenant_id, name) VALUES
- (1, 1, 'Jone'),(2, 1, 'Jack'),(3, 1, 'Tom'),
- (4, 0, 'Sandy'),(5, 0, 'Billie');
-
- INSERT INTO user_addr (id, USER_ID, name) VALUES
- (1, 1, 'addr1'),(2,1,'addr2');

补充依赖
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <!-- mybatis-plus 依赖 -->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- </dependency>
- <!-- MySQL 连接驱动依赖 -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- </dependencies>

5、单元测试类
- import com.baomidou.mybatisplus.samples.tenant.entity.User;
- import com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper;
- import org.junit.jupiter.api.Assertions;
- import org.junit.jupiter.api.Test;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import javax.annotation.Resource;
- import java.util.List;
-
- /**
- * 多租户 Tenant 演示
- */
- @SpringBootTest
- public class TenantTest {
- @Resource
- private UserMapper mapper;
-
- // INSERT INTO user (id, name, tenant_id) VALUES (?, ?, 1)
- // SELECT id, tenant_id, name FROM user WHERE id = ? AND user.tenant_id = 1
- @Test
- public void aInsert() { // 添加
- User user = new User();
- user.setName("一一");
- Assertions.assertTrue(mapper.insert(user) > 0);
- user = mapper.selectById(user.getId());
- Assertions.assertTrue(1 == user.getTenantId());
- }
-
- @Test
- public void bDelete() { // 删除
- Assertions.assertTrue(mapper.deleteById(3L) > 0);
- }
-
- @Test
- public void cUpdate() { // 修改
- Assertions.assertTrue(mapper.updateById(new User().setId(1L).setName("mp")) > 0);
- }
-
- @Test
- public void dSelect() { // 查询
- List<User> userList = mapper.selectList(null);
- userList.forEach(u -> Assertions.assertTrue(1 == u.getTenantId()));
- }
-
- /**
- * 自定义SQL:默认也会增加多租户条件
- * 参考打印的SQL: SELECT count(1) FROM user WHERE user.tenant_id = 1
- */
- @Test
- public void manualSqlTenantFilterTest() {
- System.out.println(mapper.myCount());
- }
-
- // 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('%', ?), '%')
- @Test
- public void testTenantFilter(){
- mapper.getAddrAndUser(null).forEach(System.out::println);
- mapper.getAddrAndUser("add").forEach(System.out::println);
- }
- }

官方完整的代码地址:mybatis-plus-samples: MyBatis-Plus Samples 文档
在实际开发环境中,可以更加灵活的处理
自定义 TenantLineHandler
- import cn.hutool.core.util.StrUtil;
- import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
- import lombok.extern.slf4j.Slf4j;
- import net.sf.jsqlparser.expression.Expression;
- import net.sf.jsqlparser.expression.NullValue;
- import net.sf.jsqlparser.expression.StringValue;
- import org.springframework.beans.factory.annotation.Autowired;
-
- /**
- * 租户维护处理器
- */
- @Slf4j
- public class MyTenantHandler implements TenantLineHandler {
-
- @Autowired
- private MyTenantConfigProperties properties;
-
- /**
- * 获取租户 ID 值表达式,只支持单个 ID 值
- * @return 租户 ID 值表达式
- */
- @Override
- public Expression getTenantId() {
- String tenantId = TenantContextHolder.getTenantId();
- log.debug("当前租户为 >> {}", tenantId);
- if (StrUtil.isBlank(tenantId)) {
- return new NullValue(); // 数据库的值
- }
- return new StringValue(tenantId);
- }
-
- /**
- * 获取租户字段名
- * @return 租户字段名
- */
- @Override
- public String getTenantIdColumn() {
- return properties.getColumn();
- }
-
- /**
- * 根据表名判断是否忽略拼接多租户条件
- * <p>
- * 默认都要进行解析并拼接多租户条件
- * @param tableName 表名
- * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
- */
- @Override
- public boolean ignoreTable(String tableName) {
- String tenantId = TenantContextHolder.getTenantId();
- // 租户中ID 为空,查询全部,不进行过滤
- if (StrUtil.isBlank(tenantId)) {
- return Boolean.TRUE;
- }
- return !properties.getTables().contains(tableName);
- }
- }

使用 Properties 进行灵活的配置
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.context.annotation.Configuration;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * 多租户配置
- */
- @Data
- @RefreshScope
- @Configuration
- @ConfigurationProperties(prefix = "my.mro.tenant")
- public class MyTenantConfigProperties {
- /**
- * 维护租户列名称
- */
- private String column = "tenant_id";
-
- /**
- * 多租户的数据表集合
- */
- private List<String> tables = new ArrayList<>();
- }

然后租户表可以在配置文件中进行维护,application.yml 文件维护示例如下:
- # 租户表维护
- my:
- mro:
- tenant:
- column: tenant_code
- tables:
- - fault_info
- - component_info
- - demand_acceptance
- - demand_detail
至此,租户方案完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。