Mybatis-Plus多租戶插件&屬性自動(dòng)賦值方式
1、Mybatis-Plus 多租戶插件
TenantLineInnerInterceptor 是 MyBatis-Plus 提供的一個(gè)插件,用于實(shí)現(xiàn)多租戶的數(shù)據(jù)隔離。通過這個(gè)插件,可以確保每個(gè)租戶只能訪問自己的數(shù)據(jù),從而實(shí)現(xiàn)數(shù)據(jù)的安全隔離。
其實(shí)就是一個(gè)攔截器,用于進(jìn)行sql 增刪改查 時(shí)自動(dòng)添加租戶字段
1.1、屬性介紹
TenantLineInnerInterceptor 的關(guān)鍵屬性是 tenantLineHandler,它是一個(gè) TenantLineHandler 接口的實(shí)例,用于處理租戶相關(guān)的邏輯。
| 屬性名 | 類型 | 默認(rèn)值 | 描述 |
|---|---|---|---|
| tenantLineHandler | TenantLineHandler | 租戶處理器( TenantId 行級(jí) ) |
TenantLineHandler 接口定義了以下方法:
public interface TenantLineHandler {
/**
* 獲取租戶 ID 值表達(dá)式,只支持單個(gè) ID 值
*
* @return 租戶 ID 值表達(dá)式
*/
Expression getTenantId();
/**
* 獲取租戶字段名
* 默認(rèn)字段名叫: tenant_id
*
* @return 租戶字段名
*/
default String getTenantIdColumn() {
return "tenant_id";//默認(rèn)
}
/**
* 根據(jù)表名判斷是否忽略拼接多租戶條件
* 默認(rèn)都要進(jìn)行解析并拼接多租戶條件
*
* @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 definition CREATE TABLE `biz_archive_common` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '數(shù)據(jù)主鍵ID', `title_name` varchar(255) DEFAULT NULL COMMENT '題名', `secrecy_level_id` bigint(20) DEFAULT NULL COMMENT '密級(jí)id', `archive_num` varchar(510) DEFAULT NULL COMMENT '檔號(hào)(照片號(hào))', `roll_num` varchar(31) DEFAULT NULL COMMENT '案卷號(hào)(冊(cè)號(hào)/帶號(hào))', `abandon` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否廢棄 默認(rèn) 0false/1true', `del` varchar(200) NOT NULL DEFAULT '0' COMMENT '是否刪除 默認(rèn) 0false/1true', `create_user` varchar(31) DEFAULT NULL COMMENT '創(chuàng)建者賬戶', `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', `update_user` varchar(31) DEFAULT NULL COMMENT '更新者賬戶', `update_time` datetime DEFAULT NULL COMMENT '更新時(shí)間', `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數(shù)據(jù)源配置
spring:
application:
name: token #項(xiàng)目名
# 數(shù)據(jù)源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security_manager?serverTimezone=GMT%2B8&useUnicode=true&useSSL=false&characterEncoding=utf-8
username: root
password: root
druid:
initial-size: 20
min-idle: 20
max-active: 100
max-wait: 10000
time-between-eviction-0runs-millis: 60000
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: true
test-on-return: true
# mybatis-plus配置
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局邏輯刪除的實(shí)體字段名(since 3.3.0,配置后可以忽略不配置步驟2)
logic-delete-value: 1 # 邏輯已刪除值(默認(rèn)為 1)
logic-not-delete-value: 0 # 邏輯未刪除值(默認(rèn)為 0)
configuration:
map-underscore-to-camel-case: true # 數(shù)據(jù)庫下劃線自動(dòng)轉(zhuǎn)駝峰標(biāo)示關(guān)閉
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志配置
mapper-locations: classpath*:/mapper/**/*.xmlThreadLocalUtil
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 初始化TreadLocal
private static ThreadLocal<Map<String, Object>> RES = new ThreadLocal<Map<String, Object>>() {
/**
* 和繼承ThreadLocal 類一樣,也是一個(gè)方法的復(fù)寫
*/
protected Map<String, Object> initialValue() {
return new HashMap<String, Object>();
}
;
};
/*
* 給線程里面設(shè)置一個(gè)值
*/
public static void set(String name, Object object) {
Map<String, Object> map = RES.get(); // 取出來的map 集合位null
map.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 自動(dòng)回收
}
}
實(shí)現(xiàn) 定義,注入租戶處理器插件
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 {
@Bean
public 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{
/**
* 獲取當(dāng)前租戶 ID。
*/
@Override
public Expression getTenantId() {
Object id = ThreadLocalUtil.get("id");
Long tenantId=Long.valueOf(String.valueOf(id));
// 返回租戶ID的表達(dá)式,LongValue 是 JSQLParser 中表示 bigint 類型的 class
return new LongValue(tenantId);
}
/**
* 表結(jié)構(gòu)中那個(gè)字段用于拼接多租戶條件
*/
@Override
public String getTenantIdColumn() {
return "archive_company_id";
}
/**
* 默認(rèn)返回false:表示所有表都需要拼接多租戶條件
* tableName:表名稱
*/
@Override
public 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 字段名。
*/
@Override
public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
return com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);
}
}
}
測(cè)試
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 {
@Resource
private BizArchiveCommonService bizArchiveCommonService;
@GetMapping("/getAll")
public List<BizArchiveCommon> getAll() {
ThreadLocalUtil.set("id",1645);
List<BizArchiveCommon> archiveCommons = bizArchiveCommonService.list();
return archiveCommons;
}
}
domian
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;
/**
* 創(chuàng)建人
*/
@TableField(fill = FieldFill.INSERT)
private String createUser;
/**
* 創(chuàng)建時(shí)間
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.UPDATE)
private String updateUser;
/**
* 修改時(shí)間
*/
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
/**
* 是否邏輯刪除,true:刪除 false:未刪除
*/
@TableLogic(value = "false", delval = "true")
private Boolean del;
/**
* 全宗單位id
*/
private Long archiveCompanyId;
}
service & ServiceImpl
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
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]

測(cè)試mapper.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="cn.js.mapper.BizArchiveCommonMapper">
<select id="ones" resultType="cn.js.domain.BizArchiveCommon">
select * from biz_archive_common where id=1
</select>
</mapper>@RequestMapping("/common")
@RestController
public class BizArchiveCommonController {
@Resource
private 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語句都需要拼接租戶條件,那該如何解決,只需要在相應(yīng)的mapper接口上面添加注解
@InterceptorIgnore(tenantLine = "true")
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、實(shí)體對(duì)象的屬性自動(dòng)賦值

比方說表中有這個(gè)4個(gè)字段,我們?cè)谛略?,修改的時(shí)候能不能自動(dòng)插入,而不是每次,操作的時(shí)候我們給他插入
使用
1. 定義實(shí)體類
在實(shí)體類中,你需要使用 @TableField 注解來標(biāo)記哪些字段需要自動(dòng)填充,并指定填充的策略。
public class User {
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.UPDATE)
private String updateTime;
// 其他字段...
}
2. 實(shí)現(xiàn) MetaObjectHandler
創(chuàng)建一個(gè)類來實(shí)現(xiàn) MetaObjectHandler 接口,并重寫 insertFill 和 updateFill 方法。
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 {
@Override
public void insertFill(MetaObject metaObject) {
log.info("開始插入填充...");
this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());
this.strictInsertFill(metaObject,"createUser", String.class,"張三");
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("開始更新填充...");
this.strictUpdateFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now());
this.strictUpdateFill(metaObject,"updateUser", String.class,"王五");
}
}
3. 配置自動(dòng)填充處理器
確保你的 MyMetaObjectHandler 實(shí)現(xiàn)類被 Spring 管理,可以通過 @Component 或 @Bean 注解來實(shí)現(xiàn)。
注意事項(xiàng)
- 自動(dòng)填充是直接給實(shí)體類的屬性設(shè)置值。
- 如果屬性沒有值,入庫時(shí)會(huì)是
null。 MetaObjectHandler提供的默認(rèn)方法策略是:如果屬性有值則不覆蓋,如果填充值為null則不填充。- 字段必須聲明
@TableField注解,并設(shè)置fill屬性來選擇填充策略。 - 填充處理器需要在 Spring Boot 中聲明為
@Component或@Bean。 - 使用
strictInsertFill或strictUpdateFill方法可以根據(jù)注解FieldFill.xxx、字段名和字段類型來區(qū)分填充邏輯。 - 如果不需區(qū)分,可以使用
fillStrategy方法。 - 在
update(T entity, Wrapper<T> updateWrapper)時(shí),entity不能為空,否則自動(dòng)填充失效。 - 在
update(Wrapper<T> updateWrapper)時(shí)不會(huì)自動(dòng)填充,需要手動(dòng)賦值字段條件。
4.測(cè)試
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 {
@Resource
private 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("這是新增的,進(jìn)行修改!");
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: 這是新增的,進(jìn)行修改!(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]

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解HTTP請(qǐng)求與響應(yīng)基礎(chǔ)及實(shí)例
這篇文章主要介紹了詳解HTTP請(qǐng)求與響應(yīng)基礎(chǔ)及實(shí)例的相關(guān)資料,這里對(duì)http的請(qǐng)求和響應(yīng)進(jìn)行詳細(xì)分析并附有實(shí)現(xiàn)實(shí)例,需要的朋友可以參考下2017-07-07
SpringBoot2.2.X用Freemarker出現(xiàn)404的解決
這篇文章主要介紹了SpringBoot2.2.X用Freemarker出現(xiàn)404的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java實(shí)現(xiàn)圖片轉(zhuǎn)base64完整代碼示例
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)圖片轉(zhuǎn)base64的相關(guān)資料,Base64是網(wǎng)絡(luò)上最常見的用于傳輸8Bit字節(jié)碼的編碼方式之一,Base64就是一種基于64個(gè)可打印字符來表示二進(jìn)制數(shù)據(jù)的方法,需要的朋友可以參考下2023-12-12
SpringBoot項(xiàng)目中實(shí)現(xiàn)訪問HTML頁面
文章介紹了Spring Boot項(xiàng)目中首頁的配置方法,包括將`index.html`作為首頁面、使用自定義`xxx.html`作為首頁面以及通過Controller控制層返回任意HTML頁面,同時(shí),文章還強(qiáng)調(diào)了在`pom.xml`中添加依賴的重要性,并詳細(xì)說明了使用`@Controller`注解來渲染頁面2025-11-11
SpringBoot實(shí)現(xiàn)敏感配置信息加密與解密
SpringBoot支持多種配置加載方式,不管用哪種方式,敏感信息(比如數(shù)據(jù)庫密碼、Redis密碼、第三方API密鑰)如果明文存儲(chǔ),都會(huì)有泄露風(fēng)險(xiǎn),下面我們就來聊聊如何對(duì)敏感配置信息加密與解密吧2025-07-07

