首页 > 其他分享 >Mybatis-Plus实现多租户数据隔离

Mybatis-Plus实现多租户数据隔离

时间:2024-12-25 22:29:50浏览次数:4  
标签:return 租户 properties Plus org Mybatis import public

上篇文章中已经介绍了实现mybatisPlus多租户的最简单的方式,但逻辑也比较单一,只有多租户的基础功能

这个多租户plus版本在原来的基础功能上做了一些功能的集成

1.通过配置来控制多租户功能的启用
2.通过配置来指定多租户字段
3.通过配置来排除需要走多租户逻辑的表
4.通过扫描数据库表来判断是否需要走多租户逻辑的表
5.通过自定义注解、切面、上下文来实现局部排除多租户逻辑

但是这个版本仍然不能做更多的适配项目的多租户逻辑,例如使用多个字段来实现多租户数据隔离

后续会再写一个可以实现多个字段控制的多租户版本

多租户配置类properties类,TenantProperties

package com.zzh.manage.tenantPlus;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@Data
@ConfigurationProperties(prefix = "mybatis-plus.tenant")
public class TenantProperties {

    /**
     * 控制是否开启多租户功能的开关
     */
    private Boolean enabled;

    /**
     * 多租户字段
     */
    private String tenantColumn;

    /**
     * 需要排除的多租户的表
     */
    private List<String> excludesTableName;
}

自定义多租户处理器TenantHandler

package com.zzh.manage.tenantPlus;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.schema.Column;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;

public class TenantHandler implements TenantLineHandler, InitializingBean {

    /**
     * 扫描出来的数据库种包含多租户字段的表
     */
    private List<String> tableNames;

    private static final String TENANT_ID = "tenant_Id";

    /**
     * 扫描数据库表字段的sql语句
     */
    private static final String SQL_PREFIX = "select distinct table_name from information_schema.columns where column_name = '%s'";

    /**
     * 配置项
     */
    private final TenantProperties properties;

    /**
     * jdbcTemplate,用jdbcTemplate是因为不会走mybatis拦截器
     */
    private final JdbcTemplate jdbcTemplate;

    public TenantHandler(TenantProperties properties, JdbcTemplate jdbcTemplate) {
        this.properties = properties;
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 获取租户 ID 值表达式,只支持单个 ID 值
     * <p>
     *
     * @return 租户 ID 值表达式
     */
    @Override
    public Expression getTenantId() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String id = request.getHeader("tenantId");
        return new StringValue(StringUtils.isNotBlank(id) ? id : "empty");
    }

    /**
     * 获取租户字段名
     * <p>
     * 默认字段名叫: tenant_id
     *
     * @return 租户字段名
     */
    @Override
    public String getTenantIdColumn() {
        return Objects.nonNull(properties) && StringUtils.isNotBlank(properties.getTenantColumn()) ? properties.getTenantColumn() : TENANT_ID;
    }

    /**
     * 根据表名判断是否忽略拼接多租户条件
     * <p>
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    @Override
    public boolean ignoreTable(String tableName) {
        if (Objects.nonNull(TenantContext.getTenantContext()) && TenantContext.getTenantContext()) {
            return false;
        }
        if (Objects.nonNull(properties)
                && CollectionUtils.isNotEmpty(properties.getExcludesTableName())
                && properties.getExcludesTableName().contains(tableName)) {
            return false;
        }
        return CollectionUtils.isNotEmpty(tableNames) && tableNames.contains(tableName);
    }

    /**
     * 忽略插入租户字段逻辑
     *
     * @param columns        插入字段
     * @param tenantIdColumn 租户 ID 字段
     * @return
     */
    @Override
    public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
        return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));
    }

    /**
     * 多租户表初始化
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        String column = Objects.nonNull(properties) && StringUtils.isNotBlank(properties.getTenantColumn()) ? properties.getTenantColumn() : TENANT_ID;
        tableNames = jdbcTemplate.queryForList(String.format(SQL_PREFIX, column), String.class);
    }
}

多租户配置类TenantConfig

package com.zzh.manage.tenantPlus;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 多租户配置类,根据配置文件来判断是否加载多租户配置类
 */
@Configuration
@EnableConfigurationProperties({TenantProperties.class})
@ConditionalOnProperty(prefix = "mybatis-plus.tenant", name = "enable", havingValue = "true")
public class TenantConfig {

    private final TenantProperties properties;

    public TenantConfig(TenantProperties properties) {
        this.properties = properties;
    }

    /**
     * 注册多租户拦截器
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public MybatisPlusInterceptor interceptor(@Autowired JdbcTemplate jdbcTemplate) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(getTenantHandler(jdbcTemplate, properties)));
        return interceptor;
    }

    /**
     * 注册多租户处理器
     *
     * @param jdbcTemplate
     * @param properties
     * @return
     */
    @Bean
    public TenantHandler getTenantHandler(JdbcTemplate jdbcTemplate, TenantProperties properties) {
        return new TenantHandler(properties, jdbcTemplate);
    }
}

自定义注解Tenant

package com.zzh.manage.tenantPlus;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解Tenant
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Tenant {

    /**
     * true:不执行多租户逻辑,false:执行多租户逻辑
     * @return
     */
    boolean isIgnore();
}

多租户上下文TenantContext

package com.zzh.manage.tenantPlus;

/**
 * 多租户控制器上下文
 */
public class TenantContext {

    private static final ThreadLocal<Boolean> TENANT_CONTEXT = new ThreadLocal<>();

    /**
     * 设置上下文的值
     * @param tenant
     */
    public static void setTenantContext(Boolean tenant) {TENANT_CONTEXT.set(tenant);}

    /**
     * 获取上下文的值
     * @return
     */
    public static Boolean getTenantContext() {return TENANT_CONTEXT.get();}

    /**
     * 移除上下文的值
     */
    public static void removeTenantContext() {TENANT_CONTEXT.remove();}
}

多租户Tenant切面

package com.zzh.manage.tenantPlus;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 多租户切面
 */
@Aspect
@Component
public class TenantAspect {

    /**
     * 切面
     * 拦截类上和方法上的Tenant注解
     */
    @Pointcut("@annotation(com.zzh.manage.tenantPlus.Tenant) || @within(com.zzh.manage.tenantPlus.Tenant)")
    public void pointcut() {
    }

    /**
     * 判断是否需要为多租户上下文赋值
     * @param point
     * @return
     * @throws Throwable
     */
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        try {
            Method method = ((MethodSignature) point.getSignature()).getMethod();
            Tenant tenant = method.getAnnotation(Tenant.class);
            if (Objects.isNull(tenant)) {
                tenant = point.getClass().getAnnotation(Tenant.class);
            }
            if (Objects.nonNull(tenant)) {
                TenantContext.setTenantContext(tenant.isIgnore());
            }
            return point.proceed();
        } finally {
            TenantContext.removeTenantContext();
        }
    }
}

多租户配置application.yml

server:
  port: 8080

spring:
  application:
    name: manage

mybatis-plus:
  tenant:
    enabled: true	#多租户开关
    tenant-column: tenant_id	#多租户字段
    excludes-table-name:	#需要排除多租户的表
      - t_table_name1
      - t_table_name2
      - t_table_name3

标签:return,租户,properties,Plus,org,Mybatis,import,public
From: https://blog.csdn.net/weixin_45665996/article/details/144727609

相关文章

  • mybatis 连接 ORACLE
    mybatis连接ORACLE|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|-------------|-------------|--......
  • mybatis generatorConfiguration 生成代码
    mybatisgeneratorConfiguration生成代码|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|-------------|......
  • mybatis入门
    一、Mybatis的简介mybatis封装了jdbc的持久层框架,前身为ibatis,在配置文件中编写sql,是不完全orm映射框架。查看百度百科的介绍1、支持普通sql查询2、高级映射3、存储过程......
  • mybatis完成联表查询结果的封装。
    1.mybatis完成联表查询结果的封装。表与表之间通过外键会建立关联关系。我们也可以通过联表查询得到多张表的数据。我们java中如何通过实体类建立这种关系呢?例如:班级表1-----n学生表(外键列)。查询学生信息时要求携带班级信息。一定使用了联表查询的sql语句.select*fro......
  • mybatis动态sql标签
    根据条件--sql发生改变。需要使用mybatis的动态sql标签作用这些动态SQL标签在MyBatis中提供了灵活的查询和更新操作的能力,可以根据不同的条件动态生成SQL语句,使SQL映射文件更具可读性和可维护性。常见的mybatis动态sql标签<trim>:通过修剪SQL语句的开头和结尾来动态......
  • mybatis(2)
    1.注解模式--了解之前我们使用mybatis完成数据库表操作时,使用的是xml映射文件来完成。我们也可以使用主键模式完成对表的操作。dao接口配置文件2.mybatis的优化2.1添加sql日志文件我们刚才操作数据库表时,没有再控制台打印sql语句,添加日志文件后,即可再控制台打印sql语......
  • mybatis
    1.什么是mybatis框架?MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射实体类型、接口和JavaPOJO(PlainOldJavaObjects,普通老式......
  • mybatis中传参对sql语句的影响
    1.当有类型为Intger的参数传参时,发现传0时被认为是false,使得不满足了判断条件例如<selectid="findDataByCondition"resultMap="entity"parameterType="map"> SELECT*fromtable WHERE1=1 <iftest="state!=nullandstate!=''"&......
  • 基于 Spring Boot、MyBatis Plus、MySQL、HTML、CSS、JavaScript、Vue.js、Redis 与 S
    1.项目概述1.1项目目标为学生提供个性化课程推荐,助力高效选课。构建师生交流社区,促进课程相关交流。实现课程与用户信息的高效管理。1.2功能概述用户管理:包括注册、登录、信息修改、角色管理。课程管理:课程发布、查询、修改、删除、选课操作、评价与推荐。交流社区:课......
  • Java 实战项目:Spring Boot + MyBatis Plus + MySQL + Shiro + Thymeleaf 赋能仓库管理
    1.项目概述本仓库管理系统旨在实现对仓库中商品、供应商、客户、员工、权限、日志等信息的有效管理,提升仓库运营效率和管理水平。系统主要功能包括基础数据管理、进货管理、销售管理、库存管理、系统管理等。2.系统架构2.1技术选型后端:SpringBoot+MyBatisPlus+MySQL......