1、mybatis-plus 多租户插件
tenantlineinnerinterceptor 是 mybatis-plus 提供的一个插件,用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离。
其实就是一个拦截器,用于进行sql 增删改查 时自动添加租户字段
1.1、属性介绍
tenantlineinnerinterceptor 的关键属性是 tenantlinehandler,它是一个 tenantlinehandler 接口的实例,用于处理租户相关的逻辑。
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| tenantlinehandler | tenantlinehandler | 租户处理器( tenantid 行级 ) |
tenantlinehandler 接口定义了以下方法:
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 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
<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.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/**/*.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 类一样,也是一个方法的复写
*/
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 自动回收
}
}
实现 定义,注入租户处理器插件
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);
}
}
}
测试
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;
/**
* 创建人
*/
@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
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]

测试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语句都需要拼接租户条件,那该如何解决,只需要在相应的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、实体对象的属性自动赋值

比方说表中有这个4个字段,我们在新增,修改的时候能不能自动插入,而不是每次,操作的时候我们给他插入
使用
1. 定义实体类
在实体类中,你需要使用 @tablefield 注解来标记哪些字段需要自动填充,并指定填充的策略。
public class user {
@tablefield(fill = fieldfill.insert)
private string createtime;
@tablefield(fill = fieldfill.update)
private string updatetime;
// 其他字段...
}
2. 实现 metaobjecthandler
创建一个类来实现 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. 配置自动填充处理器
确保你的 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.测试
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;
}
}
新增
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]

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论