Mybatis-Plus 多租户插件属性自动赋值

embedded/2024/11/21 15:40:02/

文章目录

  • 1、Mybatis-Plus 多租户插件
    • 1.1、属性介绍
    • 1.2、使用多租户插件
      • maven
      • yml
      • ThreadLocalUtil
      • 实现 定义,注入租户处理器插件
      • 测试
        • domian
        • service & ServiceImpl
        • mapper
      • 测试mapper.xml 方式
    • 1.3、不使用多租户插件
  • 2、实体对象的属性自动赋值
    • 使用
      • 1. 定义实体类
      • 2. 实现 MetaObjectHandler
      • 3. 配置自动填充处理器
    • 注意事项
      • 4.测试
        • 新增
        • 修改

1、Mybatis-Plus 多租户插件

TenantLineInnerInterceptor 是 MyBatis-Plus 提供的一个插件,用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离。

其实就是一个拦截器,用于进行sql 增删改查 时自动添加租户字段

1.1、属性介绍

TenantLineInnerInterceptor 的关键属性是 tenantLineHandler,它是一个 TenantLineHandler 接口的实例,用于处理租户相关的逻辑。

属性名类型默认值描述
tenantLineHandlerTenantLineHandler租户处理器( TenantId 行级 )

TenantLineHandler 接口定义了以下方法:

java">public interface TenantLineHandler {/*** 获取租户 ID 值表达式,只支持单个 ID 值** @return 租户 ID 值表达式*/Expression getTenantId();/*** 获取租户字段名* 默认字段名叫: tenant_id** @return 租户字段名*/default String getTenantIdColumn() {return "tenant_id";//默认}/*** 根据表名判断是否忽略拼接多租户条件* 默认都要进行解析并拼接多租户条件** @param tableName 表名* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件*/default boolean ignoreTable(String tableName) {return false;}/*** 忽略插入租户字段逻辑** @param columns        插入字段* @param tenantIdColumn 租户 ID 字段* @return*/default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));}
}

1.2、使用多租户插件

比方我有一张表biz_archive_common

-- security_manager.biz_archive_common definitionCREATE TABLE `biz_archive_common` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '数据主键ID',`title_name` varchar(255) DEFAULT NULL COMMENT '题名',`secrecy_level_id` bigint(20) DEFAULT NULL COMMENT '密级id',`archive_num` varchar(510) DEFAULT NULL COMMENT '档号(照片号)',`roll_num` varchar(31) DEFAULT NULL COMMENT '案卷号(册号/带号)',`abandon` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否废弃 默认 0false/1true',`del` varchar(200) NOT NULL DEFAULT '0' COMMENT '是否删除 默认 0false/1true',`create_user` varchar(31) DEFAULT NULL COMMENT '创建者账户',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_user` varchar(31) DEFAULT NULL COMMENT '更新者账户',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`archive_company_id` bigint(20) DEFAULT NULL COMMENT '全宗单位ID',PRIMARY KEY (`id`) USING BTREE,KEY `indexArchiveCompanyId` (`archive_company_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='档案共有信息表';

在这里插入图片描述

maven

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatisplus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.6</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>

yml

server:port: 8001#address: 127.0.0.1
#spring数据源配置
spring:application:name: token #项目名# 数据源datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/security_manager?serverTimezone=GMT%2B8&useUnicode=true&useSSL=false&characterEncoding=utf-8username: rootpassword: rootdruid:initial-size: 20min-idle: 20max-active: 100max-wait: 10000time-between-eviction-0runs-millis: 60000min-evictable-idle-time-millis: 30000validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: truetest-on-return: true# mybatis-plus配置
mybatis-plus:global-config:db-config:logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)configuration:map-underscore-to-camel-case: true # 数据库下划线自动转驼峰标示关闭log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志配置mapper-locations: classpath*:/mapper/**/*.xml

ThreadLocalUtil

java">package cn.js.util;import java.util.HashMap;
import java.util.Map;/*** Description:** @Author Js* @Create 2024-11-17 14:12* @Version 1.0*/
public class ThreadLocalUtil {//1 初始化TreadLocalprivate static ThreadLocal<Map<String, Object>> RES = new ThreadLocal<Map<String, Object>>() {/*** 和继承ThreadLocal 类一样,也是一个方法的复写*/protected Map<String, Object> initialValue() {return new HashMap<String, Object>();};};/** 给线程里面设置一个值*/public static void set(String name, Object object) {Map<String, Object> map = RES.get(); // 取出来的map 集合位nullmap.put(name, object);}/*** 从线程里面取值*/public static Object get(String name) {Map<String, Object> map = RES.get();if (!map.containsKey(name)) {return null;}return map.get(name);}/*** 清空线程的值*/public static void clear() {Map<String, Object> map = RES.get();map.clear();map = null; // jvm 自动回收}}

实现 定义,注入租户处理器插件

java">package cn.js.config;import cn.js.util.ThreadLocalUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
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.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.ArrayList;
import java.util.List;/*** @Author Js* @Description* @Date 2024-11-15 21:34* @Version 1.0**/
@Configuration
@AutoConfigureBefore(MybatisPlusAutoConfiguration.class)
public class PaginationInterceptorConfig {@Beanpublic MybatisPlusInterceptor addMybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler()));return interceptor;}private class TenantLineHandler implements com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler{/*** 获取当前租户 ID。*/@Overridepublic Expression getTenantId() {Object id = ThreadLocalUtil.get("id");Long tenantId=Long.valueOf(String.valueOf(id));// 返回租户ID的表达式,LongValue 是 JSQLParser 中表示 bigint 类型的 classreturn new LongValue(tenantId);}/*** 表结构中那个字段用于拼接多租户条件*/@Overridepublic String getTenantIdColumn() {return "archive_company_id";}/*** 默认返回false:表示所有表都需要拼接多租户条件* tableName:表名称*/@Overridepublic boolean ignoreTable(String tableName) {//如果那些表不需要拼接多租户条件,List<String> tableList = new ArrayList<>();tableList.add("sql_version");tableList.add("User");tableList.add("Kf");if(tableList.contains(tableName)){//如果不需要添加的表名称在list中,就返回false,不用拼接租户条件return true;}return false;}/*** 获取租户 ID 字段名。*/@Overridepublic boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {return com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);}}}

测试

java">package cn.js.controller;import cn.js.domain.BizArchiveCommon;
import cn.js.service.BizArchiveCommonService;
import cn.js.util.ThreadLocalUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;/*** @Author Js* @Description* @Date 2024-11-15 21:26* @Version 1.0**/
@RequestMapping("/common")
@RestController
public class BizArchiveCommonController {@Resourceprivate BizArchiveCommonService bizArchiveCommonService;@GetMapping("/getAll")public List<BizArchiveCommon> getAll() {ThreadLocalUtil.set("id",1645);List<BizArchiveCommon> archiveCommons = bizArchiveCommonService.list();return archiveCommons;}}
domian
java">package cn.js.domain;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.time.LocalDateTime;/*** @Author Js* @Description* @Date 2024-11-15 21:22* @Version 1.0**/
@Data
@TableName(value="biz_archive_common")
public class BizArchiveCommon {private Long id;private String titleName;private Long secrecyLevelId;private String archiveNum;private String rollNum;@TableLogic(value = "false", delval = "true")private Boolean abandon;/*** 创建人*/@TableField(fill = FieldFill.INSERT)private String createUser;/*** 创建时间*/@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;/*** 修改人*/@TableField(fill = FieldFill.UPDATE)private String updateUser;/*** 修改时间*/@TableField(fill = FieldFill.UPDATE)private LocalDateTime updateTime;/*** 是否逻辑删除,true:删除 false:未删除*/@TableLogic(value = "false", delval = "true")private Boolean del;/*** 全宗单位id*/private Long archiveCompanyId;}
service & ServiceImpl
java">package cn.js.service;import cn.js.domain.BizArchiveCommon;
import com.baomidou.mybatisplus.extension.service.IService;public interface BizArchiveCommonService extends IService<BizArchiveCommon> {
}package cn.js.service.impl;import cn.js.domain.BizArchiveCommon;
import cn.js.mapper.BizArchiveCommonMapper;
import cn.js.service.BizArchiveCommonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** @Author Js* @Description* @Date 2024-11-15 21:27* @Version 1.0**/
@Service
public class BizArchiveCommonServiceImpl extends ServiceImpl<BizArchiveCommonMapper, BizArchiveCommon> implements  BizArchiveCommonService {
}
mapper
java">package cn.js.mapper;import cn.js.domain.BizArchiveCommon;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BizArchiveCommonMapper extends BaseMapper<BizArchiveCommon> {
}
JDBC Connection [HikariProxyConnection@639980080 wrapping com.mysql.cj.jdbc.ConnectionImpl@7c12090] will not be managed by Spring
==>  Preparing: SELECT id, title_name, secrecy_level_id, archive_num, roll_num, abandon, del, create_user, create_time, update_user, update_time, archive_company_id FROM biz_archive_common WHERE archive_company_id = 1645
==> Parameters: 
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38e12bd4]

在这里插入图片描述

测试mapper.xml 方式

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.js.mapper.BizArchiveCommonMapper"><select id="ones" resultType="cn.js.domain.BizArchiveCommon">select * from biz_archive_common where id=1</select>
</mapper>
java">@RequestMapping("/common")
@RestController
public class BizArchiveCommonController {@Resourceprivate BizArchiveCommonService bizArchiveCommonService;@GetMapping("/getOne")public BizArchiveCommon getOnes() {ThreadLocalUtil.set("id",1645);BizArchiveCommon archiveCommon = bizArchiveCommonService.getones();return archiveCommon;}}
JDBC Connection [HikariProxyConnection@1523711906 wrapping com.mysql.cj.jdbc.ConnectionImpl@6db5719b] will not be managed by Spring
==>  Preparing: SELECT * FROM biz_archive_common WHERE id = 1 AND archive_company_id = 1645
==> Parameters: 
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@70ef05c0]

在这里插入图片描述

1.3、不使用多租户插件

可能我们并不是所有的sql语句都需要拼接租户条件,那该如何解决,只需要在相应的mapper接口上面添加注解

java">@InterceptorIgnore(tenantLine = "true")
java">package cn.js.mapper;import cn.js.domain.BizArchiveCommon;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BizArchiveCommonMapper extends BaseMapper<BizArchiveCommon> {@InterceptorIgnore(tenantLine = "true")BizArchiveCommon ones();
}

在这里插入图片描述


2、实体对象的属性自动赋值

在这里插入图片描述

比方说表中有这个4个字段,我们在新增,修改的时候能不能自动插入,而不是每次,操作的时候我们给他插入

使用

1. 定义实体类

在实体类中,你需要使用 @TableField 注解来标记哪些字段需要自动填充,并指定填充的策略。

java">public class User {@TableField(fill = FieldFill.INSERT)private String createTime;@TableField(fill = FieldFill.UPDATE)private String updateTime;// 其他字段...
}

2. 实现 MetaObjectHandler

创建一个类来实现 MetaObjectHandler 接口,并重写 insertFillupdateFill 方法。

java">package cn.js.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;/*** Description:** @Author Js* @Create 2024-11-17 15:39* @Version 1.0*/
@Component
@Slf4j
public class MetaObjectHandlerConfig implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {log.info("开始插入填充...");this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,"createUser", String.class,"张三");}@Overridepublic void updateFill(MetaObject metaObject) {log.info("开始更新填充...");this.strictUpdateFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now());this.strictUpdateFill(metaObject,"updateUser", String.class,"王五");}
}

3. 配置自动填充处理器

确保你的 MyMetaObjectHandler 实现类被 Spring 管理,可以通过 @Component@Bean 注解来实现。

注意事项

  • 自动填充是直接给实体类的属性设置值。
  • 如果属性没有值,入库时会是 null
  • MetaObjectHandler 提供的默认方法策略是:如果属性有值则不覆盖,如果填充值为 null 则不填充。
  • 字段必须声明 @TableField 注解,并设置 fill 属性来选择填充策略。
  • 填充处理器需要在 Spring Boot 中声明为 @Component@Bean
  • 使用 strictInsertFillstrictUpdateFill 方法可以根据注解 FieldFill.xxx、字段名和字段类型来区分填充逻辑。
  • 如果不需区分,可以使用 fillStrategy 方法。
  • update(T entity, Wrapper<T> updateWrapper) 时,entity 不能为空,否则自动填充失效。
  • update(Wrapper<T> updateWrapper) 时不会自动填充,需要手动赋值字段条件。

4.测试

java">package cn.js.controller;import cn.js.domain.BizArchiveCommon;
import cn.js.service.BizArchiveCommonService;
import cn.js.util.ThreadLocalUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;/*** @Author Js* @Description* @Date 2024-11-15 21:26* @Version 1.0**/
@RequestMapping("/common")
@RestController
public class BizArchiveCommonController {@Resourceprivate BizArchiveCommonService bizArchiveCommonService;@GetMapping("/save")public Boolean save() {ThreadLocalUtil.set("id",1645);BizArchiveCommon bizArchiveCommon = new BizArchiveCommon();bizArchiveCommon.setTitleName("这是新增的");bizArchiveCommon.setSecrecyLevelId(123L);bizArchiveCommon.setArchiveNum("8080-25201-38245");bizArchiveCommon.setRollNum("25201");bizArchiveCommon.setAbandon(false);boolean archiveCommon = bizArchiveCommonService.save(bizArchiveCommon);return archiveCommon;}@GetMapping("/update")public Boolean update() {ThreadLocalUtil.set("id",1645);BizArchiveCommon bizArchiveCommon = new BizArchiveCommon();bizArchiveCommon.setId(1L);bizArchiveCommon.setTitleName("这是新增的,进行修改!");bizArchiveCommon.setSecrecyLevelId(123L);bizArchiveCommon.setArchiveNum("8080-25201-38245");bizArchiveCommon.setRollNum("25201");bizArchiveCommon.setAbandon(false);boolean b = bizArchiveCommonService.updateById(bizArchiveCommon);return b;}}
新增
JDBC Connection [HikariProxyConnection@1186756471 wrapping com.mysql.cj.jdbc.ConnectionImpl@22ef7a2e] will not be managed by Spring
==>  Preparing: INSERT INTO biz_archive_common (id, title_name, secrecy_level_id, archive_num, roll_num, abandon, create_user, create_time, archive_company_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1645)
==> Parameters: 1858059911531872257(Long), 这是新增的(String), 123(Long), 8080-25201-38245(String), 25201(String), false(Boolean), 张三(String), 2024-11-17T16:09:38.650358300(LocalDateTime)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b266740]

在这里插入图片描述


修改
JDBC Connection [HikariProxyConnection@724111921 wrapping com.mysql.cj.jdbc.ConnectionImpl@5db6c17a] will not be managed by Spring
==>  Preparing: UPDATE biz_archive_common SET title_name = ?, secrecy_level_id = ?, archive_num = ?, roll_num = ?, abandon = ?, update_user = ?, update_time = ? WHERE id = ? AND del = false AND archive_company_id = 1645
==> Parameters: 这是新增的,进行修改!(String), 123(Long), 8080-25201-38245(String), 25201(String), false(Boolean), 王五(String), 2024-11-17T16:22:59.641094300(LocalDateTime), 1(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5390448d]

在这里插入图片描述


http://www.ppmy.cn/embedded/139372.html

相关文章

【WRF-Urban】URBPARM_LCZ.TBL 查找表解释及内容

【WRF-Urban】URBPARM_LCZ.TBL 查找表解释及内容 URBPARM_LCZ.TBL 文件的作用URBPARM_LCZ.TBL 文件中的参数URBPARM_LCZ.TBL 的使用URBPARM_LCZ.TBL 文件内容如何调整或扩展 URBPARM_LCZ.TBL参考URBPARM_LCZ.TBL 文件是 WRF(天气研究与预报模型) 中用于处理 局地气候区(Loca…

很能体现FPGA硬件思维的一道面试题

在面试的时候&#xff0c;要在短时间检查一个人的水平&#xff0c;需要面试官有针对性的问些问题&#xff0c;这里举例说一个很能体现FPGA硬件思维的一道面试题。 if-else及case语句条件判断的优先级 这里先列出几个例子&#xff0c;大家可以先自行判断以下&#xff1a; exampl…

探索Linux软件包管理的进化之路:从源码编译到Snap包的飞跃

目录 引言一、Linux软件包管理的发展过程1、从源代码安装软件2、使用软件包安装软件3、高级软件包管理工具 二、Deb软件包管理1、查看2、安装3、卸载 三、 使用APT工具管理软件包1、基本功能2、解决依赖关系3、软件包更新4、查询软件包5、安装软件包6、卸载软件包7、升级软件包…

前端三剑客(二):CSS

目录 1. CSS 基础 1.1 什么是 CSS 1.2 语法格式 1.3 引入方式 1.3.1 行内样式 1.3.2 内部样式 1.3.3 外部样式 1.4 CSS 编码规范 2. 选择器 2.1 标签选择器 2.2 id 选择器 2.3 class 选择器(类选择器) 2.4 复合选择器 2.5 通配符选择器 3. 常用 CSS 样式 3.1 c…

03 —— Webpack 自动生成 html 文件

HtmlWebpackPlugin | webpack 中文文档 | webpack中文文档 | webpack中文网 安装 npm install --save-dev html-webpack-plugin 下载html-webpack-plugin本地软件包 npm i html-webpack-plugin --save-dev 配置webpack.config.js让webpack拥有插件功能 const HtmlWebpack…

【MATLAB代码】基于IMM(Interacting Multiple Model)算法的目标跟踪,所用模型:CV、CA、CT

文章目录 3个模型的IMM&#xff08;代码简介&#xff09;源代码运行结果代码介绍总结 3个模型的IMM&#xff08;代码简介&#xff09; 本MATLAB代码实现了基于IMM&#xff08;Interacting Multiple Model&#xff09;算法的目标跟踪。它使用三种不同的运动模型&#xff08;匀速…

优选算法(双指针)

1.双指针介绍 双指针算法是一种常用的算法思想&#xff0c;特别适用于处理涉及阵列、链表或字符串等线性数据结构的问题。通过操作两个一个指针来进行导航或操作数据结构&#xff0c;双指针可以最大程度优化解决方案的效率。提高效率并减少空间复杂度。 在Java中使用双指针的核…

【蓝桥杯C/C++】翻转游戏:多种实现与解法解析

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: 蓝桥杯C/C 文章目录 &#x1f4af;题目&#x1f4af;问题分析解法一&#xff1a;减法法解法二&#xff1a;位运算解法解法三&#xff1a;逻辑非解法解法四&#xff1a;条件运算符解法解法五&#xff1a;数组映射法不同解法的比较…