文章目录
- [1、Mybatis-Plus 多租户插件](#1、Mybatis-Plus 多租户插件)
- 2、实体对象的属性自动赋值
1、Mybatis-Plus 多租户插件
TenantLineInnerInterceptor
是 MyBatis-Plus 提供的一个插件,用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离。其实就是一个拦截器,用于进行sql
增删改查
时自动添加租户字段
1.1、属性介绍
TenantLineInnerInterceptor
的关键属性是 tenantLineHandler
,它是一个 TenantLineHandler
接口的实例,用于处理租户相关的逻辑。
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
tenantLineHandler | TenantLineHandler | 租户处理器( 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
sql
-- security_manager.biz_archive_common definition
CREATE 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
xml
<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
yml
server:
port: 8001
#address: 127.0.0.1
#spring数据源配置
spring:
application:
name: token #项目名
# 数据源
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 # 全局逻辑删除的实体字段名(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 初始化TreadLocal
private 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 集合位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 自动回收
}
}
实现 定义,注入租户处理器插件
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 {
@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{
/**
* 获取当前租户 ID。
*/
@Override
public Expression getTenantId() {
Object id = ThreadLocalUtil.get("id");
Long tenantId=Long.valueOf(String.valueOf(id));
// 返回租户ID的表达式,LongValue 是 JSQLParser 中表示 bigint 类型的 class
return new LongValue(tenantId);
}
/**
* 表结构中那个字段用于拼接多租户条件
*/
@Override
public String getTenantIdColumn() {
return "archive_company_id";
}
/**
* 默认返回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);
}
}
}
测试
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 {
@Resource
private 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> {
}
shell
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
<?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>
java
@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;
}
}
shell
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
接口,并重写 insertFill
和 updateFill
方法。
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 {
@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. 配置自动填充处理器
确保你的 MyMetaObjectHandler
实现类被 Spring 管理,可以通过 @Component
或 @Bean
注解来实现。
注意事项
- 自动填充是直接给实体类的属性设置值。
- 如果属性没有值,入库时会是
null
。 MetaObjectHandler
提供的默认方法策略是:如果属性有值则不覆盖,如果填充值为null
则不填充。- 字段必须声明
@TableField
注解,并设置fill
属性来选择填充策略。 - 填充处理器需要在 Spring Boot 中声明为
@Component
或@Bean
。 - 使用
strictInsertFill
或strictUpdateFill
方法可以根据注解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 {
@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("这是新增的,进行修改!");
bizArchiveCommon.setSecrecyLevelId(123L);
bizArchiveCommon.setArchiveNum("8080-25201-38245");
bizArchiveCommon.setRollNum("25201");
bizArchiveCommon.setAbandon(false);
boolean b = bizArchiveCommonService.updateById(bizArchiveCommon);
return b;
}
}
新增
shell
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]
修改
shell
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]