SpringBoot第30讲:SpringBoot集成MySQL - MyBatis-Plus基于字段隔离的多租户

SpringBoot第30讲:SpringBoot集成MySQL - MyBatis-Plus基于字段隔离的多租户

本文是SpringBoot第30讲,主要介绍 MyBatis-Plus的基于字段隔离的多租户实现,以及MyBatis-Plus的基于字段的隔离方式实践和原理。

文章目录

  • SpringBoot第30讲:SpringBoot集成MySQL - MyBatis-Plus基于字段隔离的多租户
    • 1、知识准备
      • 1.1、什么是多租户?
      • 1.2、多租户在数据存储上有哪些实现方式?
        • 1、DB隔离:独立数据库
        • 2、Schema隔离:共享数据库,隔离数据架构
        • 3、字段隔离:共享数据库,共享数据架构
      • 1.3、MyBatis-Plus的基于字段的隔离方式原理是什么?
    • 2、简单示例
      • 2.1、准备DB和依赖配置
      • 2.2、MyBatis-Plus配置
      • 2.3、定义dao
      • 2.4、定义Service接口和实现类
      • 2.5、controller
      • 2.6、简单测试
    • 3、进一步理解
      • 3.1、来自官方的注意点
      • 3.2、插件的顺序
      • 3.3、封装性实践
    • 4、示例源码

1、知识准备

需要了解多租户及常见的实现方式,以及MyBatis-Plus的基于字段的隔离方式原理。

1.1、什么是多租户?

如下解释来源于百度百科

多租户技术(英语:multi-tenancy technology)或称多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性

多租户简单来说是指一个单独的实例可以为多个组织服务。多租户技术为共用的数据中心内如何以单一系统架构与服务提供多数客户端相同甚至可定制化的服务,并且仍然可以保障客户的数据隔离。一个支持多租户技术的系统需要在设计上对它的数据和配置进行虚拟分区,从而使系统的每个租户或称组织都能够使用一个单独的系统实例,并且每个租户都可以根据自己的需求对租用的系统实例进行个性化配置。

多租户技术可以实现多个租户之间共享系统实例,同时又可以实现租户的系统实例的个性化定制。通过使用多租户技术可以保证系统共性的部分被共享,个性的部分被单独隔离。通过在多个租户之间的资源复用,运营管理维护资源,有效节省开发应用的成本。而且,在租户之间共享应用程序的单个实例,可以实现当应用程序升级时,所有租户可以同时升级。同时,因为多个租户共享一份系统的核心代码,因此当系统升级时,只需要升级相同的核心代码即可。

1.2、多租户在数据存储上有哪些实现方式?

如下解释来源于百度百科

多租户在数据存储上存在三种主要的方案,分别是

1、DB隔离:独立数据库

这是第一种方案,即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本也高。

  • 优点
  1. 为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;
  2. 如果出现故障,恢复数据比较简单。
  • 缺点
  1. 增大了数据库的安装数量,随之带来维护成本和购置成本的增加。
  2. 这种方案与传统的一个客户、一套数据、一套部署类似,差别只在于软件统一部署在运营商那里。如果面对的是银行、医院等需要非常高数据隔离级别的租户,可以选择这种模式,提高租用的定价。如果定价较低,产品走低价路线,这种方案一般对运营商来说是无法承受的。

2、Schema隔离:共享数据库,隔离数据架构

这是第二种方案,即多个或所有租户共享Database,但一个租户(Tenant)一个Schema

  • 优点
  1. 为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可以支持更多的租户数量。
  • 缺点
  1. 如果出现故障,数据恢复比较困难,因为恢复数据库将牵扯到其他租户的数据;
  2. 如果需要跨租户统计数据,存在一定困难。

3、字段隔离:共享数据库,共享数据架构

这是第三种方案,即租户共享同一个Database、同一个Schema,但在表中通过TenantID区分租户的数据。这是共享程度最高、隔离级别最低的模式。

  • 优点
  1. 三种方案比较,第三种方案的维护和购置成本最低,允许每个数据库支持的租户数量最多。
  • 缺点
  1. 隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量;
  2. 数据备份和恢复最困难,需要逐表逐条备份和还原。
  3. 如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本,这种方案最适合。

1.3、MyBatis-Plus的基于字段的隔离方式原理是什么?

这里请看MyBatis的插件机制:MyBatis第八讲:MyBatis插件机制详解与实战

2、简单示例

这里沿用之前的db_user,在表中添加tenant_id,并命名为新的schema db_user_tenant。

2.1、准备DB和依赖配置

创建MySQL的schema db_user_tenant, 导入SQL 文件如下

DROP TABLE IF EXISTS `tb_role`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tb_role` (`id` int NOT NULL AUTO_INCREMENT,`tenant_id` int DEFAULT NULL,`name` varchar(255) NOT NULL,`role_key` varchar(255) NOT NULL,`description` varchar(255) DEFAULT NULL,`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;--
-- Dumping data for table `tb_role`
--LOCK TABLES `tb_role` WRITE;
/*!40000 ALTER TABLE `tb_role` DISABLE KEYS */;
INSERT INTO `tb_role` VALUES (1,1,'admin','admin','admin','2021-09-08 17:09:15','2021-09-08 17:09:15');
/*!40000 ALTER TABLE `tb_role` ENABLE KEYS */;
UNLOCK TABLES;--
-- Table structure for table `tb_user`
--DROP TABLE IF EXISTS `tb_user`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tb_user` (`id` int NOT NULL AUTO_INCREMENT,`tenant_id` int DEFAULT NULL,`user_name` varchar(45) NOT NULL,`password` varchar(45) NOT NULL,`email` varchar(45) DEFAULT NULL,`phone_number` int DEFAULT NULL,`description` varchar(255) DEFAULT NULL,`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;--
-- Dumping data for table `tb_user`
--LOCK TABLES `tb_user` WRITE;
/*!40000 ALTER TABLE `tb_user` DISABLE KEYS */;
INSERT INTO `tb_user` VALUES (1,1,'qiwenjie','qwj930828','1172814226@qq.com',1212121213,'afsdfsaf','2021-09-08 17:09:15','2021-09-08 17:09:15');
/*!40000 ALTER TABLE `tb_user` ENABLE KEYS */;
UNLOCK TABLES;--
-- Table structure for table `tb_user_role`
--DROP TABLE IF EXISTS `tb_user_role`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tb_user_role` (`user_id` int NOT NULL,`role_id` int NOT NULL,`tenant_id` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;--
-- Dumping data for table `tb_user_role`
--LOCK TABLES `tb_user_role` WRITE;
/*!40000 ALTER TABLE `tb_user_role` DISABLE KEYS */;
INSERT INTO `tb_user_role` VALUES (1,1,1);
/*!40000 ALTER TABLE `tb_user_role` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;-- Dump completed on 2022-04-02 12:50:14

引入maven依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version>
</dependency>

增加yml配置

spring:datasource:url: jdbc:mysql://localhost:3306/db_user_tenant?useSSL=false&autoReconnect=true&characterEncoding=utf8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: qwj930828mybatis-plus:configuration:# 开启二级缓存cache-enabled: trueuse-generated-keys: truedefault-executor-type: REUSEuse-actual-param-name: true# 输出SQL log 方便 debuglog-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.2、MyBatis-Plus配置

通过添加 TenantLineInnerInterceptor 来完成。

package springboot.mysql.mybatisplus.tenant.config;import java.util.List;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.schema.Column;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** MyBatis-plus configuration, add pagination interceptor.** @author qiwenjie*/
@Configuration
public class MyBatisConfig {/*** inject pagination interceptor.** @return pagination*/@Beanpublic PaginationInnerInterceptor paginationInnerInterceptor() {return new PaginationInnerInterceptor();}/*** add interceptor.** @return MybatisPlusInterceptor*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// TenantLineInnerInterceptorinterceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 实际可以将TenantId放在threadLocal中(比如xxxxContext中),并获取。return new LongValue(1);}@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic boolean ignoreTable(String tableName) {return false;}@Overridepublic boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);}}));// 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor,防止分页失效interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
}

2.3、定义dao

RoleDao

package springboot.mysql.mybatisplus.tenant.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import springboot.mysql.mybatisplus.tenant.entity.Role;/*** @author qiwenjie*/
public interface IRoleDao extends BaseMapper<Role> {
}

UserDao

package springboot.mysql.mybatisplus.tenant.dao;import java.util.List;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import springboot.mysql.mybatisplus.tenant.entity.User;
import springboot.mysql.mybatisplus.tenant.entity.query.UserQueryBean;/*** @author qiwenjie*/
public interface IUserDao extends BaseMapper<User> {List<User> findList(UserQueryBean userQueryBean);
}

这里你也同时可以支持BaseMapper方式和自己定义的xml的方法(比较适用于关联查询),比如findList是自定义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="springboot.mysql.mybatisplus.tenant.dao.IUserDao"><resultMap type="springboot.mysql.mybatisplus.tenant.entity.User" id="UserResult"><id     property="id"       	column="id"      		/><result property="userName"     column="user_name"    	/><result property="password"     column="password"    	/><result property="email"        column="email"        	/><result property="phoneNumber"  column="phone_number"  	/><result property="description"  column="description"  	/><result property="createTime"   column="create_time"  	/><result property="updateTime"   column="update_time"  	/><collection property="roles" ofType="springboot.mysql.mybatisplus.tenant.entity.Role"><result property="id" column="id"  /><result property="name" column="name"  /><result property="roleKey" column="role_key"  /><result property="description" column="description"  /><result property="createTime"   column="create_time"  	/><result property="updateTime"   column="update_time"  	/></collection></resultMap><sql id="selectUserSql">select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time, r.name, r.role_key, r.description, r.create_time, r.update_timefrom tb_user uleft join tb_user_role ur on u.id=ur.user_idinner join tb_role r on ur.role_id=r.id</sql><select id="findList" parameterType="springboot.mysql.mybatisplus.tenant.entity.query.UserQueryBean" resultMap="UserResult"><include refid="selectUserSql"/>where u.id != 0<if test="userName != null and userName != ''">AND u.user_name like concat('%', #{user_name}, '%')</if><if test="description != null and description != ''">AND u.description like concat('%', #{description}, '%')</if><if test="phoneNumber != null and phoneNumber != ''">AND u.phone_number like concat('%', #{phoneNumber}, '%')</if><if test="email != null and email != ''">AND u.email like concat('%', #{email}, '%')</if></select>
</mapper> 

2.4、定义Service接口和实现类

UserService接口

package springboot.mysql.mybatisplus.tenant.service;import java.util.List;import com.baomidou.mybatisplus.extension.service.IService;
import springboot.mysql.mybatisplus.tenant.entity.User;
import springboot.mysql.mybatisplus.tenant.entity.query.UserQueryBean;/*** @author qiwenjie*/
public interface IUserService extends IService<User> {List<User> findList(UserQueryBean userQueryBean);
}

User Service的实现类

package springboot.mysql.mybatisplus.tenant.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import tech.pdai.springboot.mysql8.mybatisplus.tenant.dao.IUserDao;
import tech.pdai.springboot.mysql8.mybatisplus.tenant.entity.User;
import tech.pdai.springboot.mysql8.mybatisplus.tenant.entity.query.UserQueryBean;
import tech.pdai.springboot.mysql8.mybatisplus.tenant.service.IUserService;
import java.util.List;// 实现ServiceImpl接口,可以使得代码复用
@Service
public class UserDoServiceImpl extends ServiceImpl<IUserDao, User> implements IUserService {@Overridepublic List<User> findList(UserQueryBean userQueryBean) {return baseMapper.findList(userQueryBean);}
}

Role Service 接口

package springboot.mysql.mybatisplus.tenant.service;import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import springboot.mysql.mybatisplus.tenant.entity.Role;
import springboot.mysql.mybatisplus.tenant.entity.query.RoleQueryBean;public interface IRoleService extends IService<Role> {List<Role> findList(RoleQueryBean roleQueryBean);
}

Role Service 实现类

package springboot.mysql.mybatisplus.tenant.service.impl;import java.util.List;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import springboot.mysql.mybatisplus.tenant.dao.IRoleDao;
import springboot.mysql.mybatisplus.tenant.entity.Role;
import springboot.mysql.mybatisplus.tenant.entity.query.RoleQueryBean;
import springboot.mysql.mybatisplus.tenant.service.IRoleService;@Service
public class RoleDoServiceImpl extends ServiceImpl<IRoleDao, Role> implements IRoleService {@Overridepublic List<Role> findList(RoleQueryBean roleQueryBean) {return lambdaQuery().like(StringUtils.isNotEmpty(roleQueryBean.getName()), Role::getName, roleQueryBean.getName()).like(StringUtils.isNotEmpty(roleQueryBean.getDescription()), Role::getDescription, roleQueryBean.getDescription()).like(StringUtils.isNotEmpty(roleQueryBean.getRoleKey()), Role::getRoleKey, roleQueryBean.getRoleKey()).list();}
}

2.5、controller

User Controller

package springboot.mysql.mybatisplus.tenant.controller;import java.time.LocalDateTime;
import java.util.List;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springboot.mysql.mybatisplus.tenant.entity.User;
import springboot.mysql.mybatisplus.tenant.entity.query.UserQueryBean;
import springboot.mysql.mybatisplus.tenant.entity.response.ResponseResult;
import springboot.mysql.mybatisplus.tenant.service.IUserService;/*** @author qiwenjie*/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;/*** @param user user param* @return user*/@ApiOperation("Add/Edit User")@PostMapping("add")public ResponseResult<User> add(User user) {if (user.getId()==null) {user.setCreateTime(LocalDateTime.now());}user.setUpdateTime(LocalDateTime.now());userService.save(user);return ResponseResult.success(userService.getById(user.getId()));}/*** @return user list*/@ApiOperation("Query User One")@GetMapping("edit/{userId}")public ResponseResult<User> edit(@PathVariable("userId") Long userId) {return ResponseResult.success(userService.getById(userId));}/*** @return user list*/@ApiOperation("Query User List")@GetMapping("list")public ResponseResult<List<User>> list(UserQueryBean userQueryBean) {return ResponseResult.success(userService.findList(userQueryBean));}
}

Role Controller

package springboot.mysql.mybatisplus.tenant.controller;import java.util.List;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springboot.mysql.mybatisplus.tenant.entity.Role;
import springboot.mysql.mybatisplus.tenant.entity.query.RoleQueryBean;
import springboot.mysql.mybatisplus.tenant.entity.response.ResponseResult;
import springboot.mysql.mybatisplus.tenant.service.IRoleService;/*** @author qiwenjie*/
@RestController
@RequestMapping("/role")
public class RoleController {@Autowiredprivate IRoleService roleService;/*** @return role list*/@ApiOperation("Query Role List")@GetMapping("list")public ResponseResult<List<Role>> list(RoleQueryBean roleQueryBean) {return ResponseResult.success(roleService.findList(roleQueryBean));}
}

2.6、简单测试

访问页面:http://localhost:8080/doc.html在这里插入图片描述

拦截之前的SQL

original SQL: select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time, r.name, r.role_key, r.description, r.create_time, r.update_timefrom tb_user uleft join tb_user_role ur on u.id=ur.user_idinner join tb_role r on ur.role_id=r.id  where u.id != 0

最后执行的SQL中,对联表查询的每个表都加了:tenant_id

023-08-03 11:36:37.519  INFO 10091 --- [nio-8080-exec-5] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1372159021 wrapping com.mysql.cj.jdbc.ConnectionImpl@681a68fa] will not be managed by Spring
==>  Preparing: SELECT u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time, r.id rid, r.name rname, r.role_key, r.description rdescription, r.create_time rcreate_time, r.update_time rupdate_time FROM tb_user u LEFT JOIN tb_user_role ur ON u.id = ur.user_id AND ur.tenant_id = 1 INNER JOIN tb_role r ON ur.role_id = r.id AND u.tenant_id = 1 AND r.tenant_id = 1 WHERE u.id != 0
==> Parameters: 
<==    Columns: id, password, user_name, email, phone_number, description, create_time, update_time, rid, rname, role_key, rdescription, rcreate_time, rupdate_time
<==        Row: 1, qwj930828, qiwenjie, 1172814226@qq.com, 1212121213, afsdfsaf, 2021-09-08 17:09:15, 2021-09-08 17:09:15, 1, admin, admin, admin, 2021-09-08 17:09:15, 2021-09-08 17:09:15
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@78cf36be]

3、进一步理解

在实际使用字段进行多租户隔离时有哪些注意点呢?

3.1、来自官方的注意点

相关建议

  1. 多租户 != 权限过滤, 不要乱用, 租户之间是完全隔离的!!!
  2. 启用多租户后所有执行的method的sql都会进行处理.
  3. 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)
    1. 不推荐使用官方的,对于权限过滤可以自行魔改,参考这篇项目实战:todo

3.2、插件的顺序

MyBatis-Plus使用多个功能插件需要注意顺序关系

MyBatis-Plus基于字段的多租户是通过插件机制拦截实现的,因为还有很多其它的拦截器,比如:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

所以需要注意顺序: 使用多个功能需要注意顺序关系,建议使用如下顺序

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入

3.3、封装性实践

实际项目中还需要对配置进行封装。

回看如下的处理, 我们看下可以封装的点:

// TenantLineInnerInterceptor
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 实际可以将TenantId放在threadLocal中(比如xxxxContext中),并获取。// 在实际项目中,我们会从网关上下文中获取用户信息return new LongValue(1);}@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic boolean ignoreTable(String tableName) {return false;}@Overridepublic boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);}
}));

1、对于配置

  • 相关配置可以封装到yml, 然后注入进来。

2、对于TenantId

  • 实际可以将TenantId放在threadLocal中(比如xxxxContext中),并获取。

3、对于ignoreTable

  • 比如有些表不要自动进行拦截的,可以在yml中配置并重写ignoreTable方法。

4、对于ignoreInsert

  • 对于插入数据是否需要携带TenantId,可以通过重写ignoreInsert方法。

4、示例源码

todo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/55574.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bash的特性(二)IO重定向与管道

bash的I/O重定向及管道 一、概述 在shell中&#xff0c;最常使用的fd(file descriptor)有三个&#xff0c;标准输入&#xff0c;标准输出&#xff0c;错误输出。进程用文件描述符来管理打开的文件。 名称 文件描述符 标准输入&#xff08;stdin) 0 键盘&#xff0c;也可以…

pom文件---maven

027-Maven 命令行-实验四-生成 Web 工程-执行生成_ev_哔哩哔哩_bilibili 27节.后续补充 一.maven下载安装及配置 1)maven下载 2) settings文件配置本地仓库 3)settings配置远程仓库地址 4)配置maven工程的基础JDK版本 5)确认JDK环境变量配置没问题,配置maven的环境变量 验证…

K8s实战入门(三)

文章目录 3. 实战入门3.1 Namespace3.1.1 测试两个不同的名称空间之间的 Pod 是否连通性 3.2 Pod3.3 Label3.4 Deployment3.5 Service 3. 实战入门 本章节将介绍如何在kubernetes集群中部署一个nginx服务&#xff0c;并且能够对其进行访问。 3.1 Namespace Namespace是kuber…

flask-----蓝图

1.引入蓝图 flask都写在一个文件中&#xff0c;项目这样肯定不行&#xff0c;会导致循环导入的问题&#xff0c;分目录&#xff0c;分包&#xff0c;使用蓝图划分目录。 2.使用蓝图 步骤如下&#xff1a; -1 实例化得到一个蓝图对象-order_blueBlueprint(order,__name__,tem…

uniapp echarts 点击失效

这个问题网上搜了一堆&#xff0c;有的让你降版本&#xff0c;有的让你改源码。。。都不太符合预期&#xff0c;目前我的方法可以用最新的echarts。 这个方法就是由npm安装转为CDN&#xff0c;当然你可能会质疑用CDN这样会不稳定&#xff0c;那如果CDN的地址是本地呢&#xff1…

Button按钮(antd-design组件库)简单使用

1.Button按钮 按钮用于开始一个即时操作。 2.何时使用 标记了一个&#xff08;或封装一组&#xff09;操作命令&#xff0c;响应用户点击行为&#xff0c;触发相应的业务逻辑。 在 Ant Design 中我们提供了五种按钮。 主按钮&#xff1a;用于主行动点&#xff0c;一个操作区域只…

无涯教程-Lua - nested语句函数

Lua编程语言允许在另一个循环中使用一个循环。以下部分显示了一些示例来说明这一概念。 nested loops - 语法 Lua中嵌套for循环语句的语法如下- for init,max/min value, increment dofor init,max/min value, incrementdostatement(s)endstatement(s) end Lua编程语言中的…

Kernel Exception导致手机重启案例分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、高温触发 Kernel Exception 重启问题二、解决方案三、提高电池温度方案 一、 高温触发 Kernel Exception 重启问题 手机 电池温度 默认60度以上高温…

MySQL索引3——Explain关键字和索引使用规则(SQL提示、索引失效、最左前缀法则)

目录 Explain关键字 索引性能分析 Id ——select的查询序列号 Select_type——select查询的类型 Table——表名称 Type——select的连接类型 Possible_key ——显示可能应用在这张表的索引 Key——实际用到的索引 Key_len——实际索引使用到的字节数 Ref ——索引命…

【BASH】回顾与知识点梳理(七)

【BASH】回顾与知识点梳理 七 七. 前六章知识点总结及练习7.1 总结7.2 练习 该系列目录 --> 【BASH】回顾与知识点梳理&#xff08;目录&#xff09; 七. 前六章知识点总结及练习 7.1 总结 由于核心在内存中是受保护的区块&#xff0c;因此我们必须要透过『 Shell 』将我…

【java】【maven】【基础】MAVEN安装配置介绍

目录 1 下载 2 安装-windows为例 3 配置环境变量 3.1 JAVA_HOME 3.2 MAVEN_HOME 3.3 PATH 3.4 验证 4 MAVEN基础概念 4.1 仓库概念 4.2 坐标概念 4.2.1 打开网址 4.2.2 输入搜索内容junit 4.2.3 找到对应API名称点击 4.2.4 点击对应版本 4.2.5 复制MAVEN坐标 4.3 配置…

CentOS7---部署Tomcat和安装Jpress

总览需求 1. 简述静态网页和动态网页的区别。 2. 简述 Webl.0 和 Web2.0 的区别。 3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。1、简述静态网页和动态网页的区别 静态网页&#xff1a; 请求响应信息&#xff0c;发给客户端进行处理&#xff0c…