mybatisplus多租户插件学习

学习链接

mybatis源码运行详细流程 - 自己的链接

mybatisplus官网#多租户插件介绍
mybatisplus官方样例代码 gitee地址

Mybatis-Plus入门系列(4)- MybatisPlus之多租户插件TenantLineInnerInterceptor
Mybatis-Plus入门系列(17)-多租户插件TenantLineInnerInterceptor源码解析

【分享】Mybatis-plus多租户插件实现数据隔离方案分享

SpringCloud微服务实战——搭建企业级开发框架(二十二):基于MybatisPlus插件TenantLineInnerInterceptor实现多租户功能

前言

之前以为mybatisplus就只是提供了一些方便调用操作数据库的api而已,没想到里面还这么多玩法的?感觉是学习mybatis源码应用的一个很好的实践。

之前项目中也是有疑问,对于不同的接口,虽然接口可以对用户角色进行授权,但是并不能控制用户的传参,如果系统中存在多个用户,那么可以肯定的是:一个普通用户他不能操作到其它用户的数据,若仅仅对接口层面进行控制,是达不到要求的,那么势必就要自己在几乎所有的sql中都要添加上用户的限制条件。这时候的这个用户,就可以把它当作租户来看待,使用mybatisplus的多租户插件就可以“自动”的在sql中拼接上用户作为查询条件,达到在多个租户之间数据隔离的效果。在一些比较复杂的sql中,可以禁用掉多租户插件,自己拼接。

案例

数据库准备

建表

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	tenant_id BIGINT(20) NOT NULL COMMENT '租户ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	PRIMARY KEY (id)
);

DROP TABLE IF EXISTS user_addr;

CREATE TABLE USER_ADDR
(
  id BIGINT(20) NOT NULL COMMENT '主键ID',
  user_id BIGINT(20) NOT NULL COMMENT 'user.id',
  name VARCHAR(30) NULL DEFAULT NULL COMMENT '地址名称',
  PRIMARY KEY (id)
);
插入数据
DELETE FROM user;

INSERT INTO user (id, tenant_id, name) VALUES
(1, 1, 'Jone'),(2, 1, 'Jack'),(3, 1, 'Tom'),
(4, 0, 'Sandy'),(5, 0, 'Billie');

INSERT INTO user_addr (id, USER_ID, name) VALUES
(1, 1, 'addr1'),(2,1,'addr2');

环境准备

User
@Data
@Accessors(chain = true)
public class User {
    private Long id;
    /**
     * 租户 ID
     */
    private Long tenantId;
    private String name;

    @TableField(exist = false)
    private String addrName;

}
UserMapper
public interface UserMapper extends BaseMapper<User> {

    /**
     * 自定义SQL:默认也会增加多租户条件
     * 参考打印的SQL
     * @return
     */
    // @InterceptorIgnore(tenantLine = "on") // 可以使用该注解,关闭多租户插件功能
    Integer myCount();

    List<User> getUserAndAddr(@Param("username") String username);

    List<User> getAddrAndUser(@Param("name") String name);
}
UserMapper.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="com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper">

    <select id="myCount" resultType="java.lang.Integer">
        select count(1) from user
    </select>

    <select id="getUserAndAddr" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
        select u.id, u.name, a.name as addr_name
        from user u
        left join user_addr a on a.user_id=u.id
        <where>
            <if test="username!=null">
                u.name like concat(concat('%',#{username}),'%')
            </if>
        </where>
    </select>

    <select id="getAddrAndUser" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
        select a.name as addr_name, u.id, u.name
        from user_addr a
        left join user u on u.id=a.user_id
        <where>
            <if test="name!=null">
                a.name like concat(concat('%',#{name}),'%')
            </if>
        </where>
    </select>
</mapper>
MybatisPlusConfig
@Configuration
@MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
public class MybatisPlusConfig {

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    
    	// 使用的是mybatis的插件机制
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

		// 添加mybatisplus的内置插件到mybatis插件中
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
        
            @Override
            public Expression getTenantId() {
            	// 获取租户 ID 值表达式,只支持单个 ID 值
            	// (获取当前的租户)
                return new LongValue(1);
            }

			
            // 获取租户字段名, 默认字段名叫: tenant_id  
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
            	// 返回true,则忽略(不拼接租户条件sql)
                return !"user".equalsIgnoreCase(tableName);
            }
        }));
        
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }

//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return configuration -> configuration.setUseDeprecatedExecutor(false);
//    }
}

测试

TenantTest
@SpringBootTest
public class TenantTest {
    @Resource
    private UserMapper mapper;

    @Test
    public void aInsert() {
        User user = new User();
        user.setName("一一");

        // 执行的sql: INSERT INTO user (id, name, tenant_id) VALUES (?, ?, 1)
        Assertions.assertTrue(mapper.insert(user) > 0);

        // 执行的sql: SELECT id, tenant_id, name FROM user WHERE id = ? AND user.tenant_id = 1
        user = mapper.selectById(user.getId());
        Assertions.assertTrue(1 == user.getTenantId());
    }


    @Test
    public void bDelete() {
        // 执行的sql: DELETE FROM user WHERE user.tenant_id = 1 AND id = ?
        Assertions.assertTrue(mapper.deleteById(3L) > 0);
    }


    @Test
    public void cUpdate() {
        // 执行的sql: DELETE FROM user WHERE user.tenant_id = 1 AND id = ?
        Assertions.assertTrue(mapper.updateById(new User().setId(1L).setName("mp")) > 0);
    }

    @Test
    public void dSelect() {
        // 执行的sql: UPDATE user SET name = ? WHERE user.tenant_id = 1 AND id = ?
        List<User> userList = mapper.selectList(null);
        userList.forEach(u -> Assertions.assertTrue(1 == u.getTenantId()));
    }

    /**
     * 自定义SQL:默认也会增加多租户条件
     * 参考打印的SQL
     */
    @Test
    public void manualSqlTenantFilterTest() {
        // 执行的sql: SELECT count(1) FROM user WHERE user.tenant_id = 1
        System.out.println(mapper.myCount());

    }

    @Test
    public void testTenantFilter(){

        // 执行的sql: SELECT a.name AS addr_name, u.id, u.name FROM user_addr a LEFT JOIN user u ON u.id = a.user_id AND u.tenant_id = 1
        mapper.getAddrAndUser(null).forEach(System.out::println);

        // 执行的sql: SELECT a.name AS addr_name, u.id, u.name FROM user_addr a LEFT JOIN user u ON u.id = a.user_id AND u.tenant_id = 1 WHERE a.name LIKE concat(concat('%', ?), '%')
        mapper.getAddrAndUser("add").forEach(System.out::println);

        // 执行的sql: SELECT u.id, u.name, a.name AS addr_name FROM user u LEFT JOIN user_addr a ON a.user_id = u.id WHERE u.tenant_id = 1
        mapper.getUserAndAddr(null).forEach(System.out::println);

        // 执行的sql: SELECT u.id, u.name, a.name AS addr_name FROM user u LEFT JOIN user_addr a ON a.user_id = u.id WHERE u.name LIKE concat(concat('%', ?), '%') AND u.tenant_id = 1
        mapper.getUserAndAddr("J").forEach(System.out::println);
    }
}

说明

  • 多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
  • 启用多租户后所有执行的method的sql都会进行处理.
  • 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值