SpringBoot+Mybatis实现多租户代码

其实就是往表里加个字段

mybatis拦截器配置

package com.example.tenantmybatisdemo.config;

import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author hrui
 * @date 2024/7/24 6:32
 */
@Configuration
public class MyBatisConfig {


    //添加自定义拦截器
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                //MultiTenantInterceptor和MultiTenantInterceptor1,MultiTenantInterceptor2虽然可用,但修改强度过大
                //使用MultiTenantInterceptor3 通过StatementHandler修改最好
                //那么执行流程是这样的  1.构建 SqlSessionFactory(一次生成)  2.获取 SqlSession(每次链接)
                //3.获取 MappedStatement  4.生成 BoundSql   5.创建 StatementHandler  最后一步是StatementHandler 准备执行sql
                configuration.addInterceptor(new MultiTenantInterceptor3());
            }
        };
    }
}

package com.example.tenantmybatisdemo.config;

import com.example.tenantmybatisdemo.pojo.LoginUser;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.sql.Connection;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author hrui
 * @date 2024/7/24 6:33
 */
//prepare方法是在执行SQL语句之前调用的,用于准备和优化SQL语句。因此,这个拦截器将适用于所有类型的SQL操作,包括增、删、改、查等。
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MultiTenantInterceptor3 implements Interceptor {
    //排除的表名
    private static final Set<String> IGNORE_TABLES = Stream.of("t_user").collect(Collectors.toSet());

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取当前登录用户的租户ID  这里只是演示  这个值随意  只要代表用户 redis随便放一个
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        //如果用户已经认证
        if (authentication != null && authentication.isAuthenticated()) {
            LoginUser user = (LoginUser) authentication.getPrincipal();
            Integer tenantId = user.getUserId();

            //获取StatementHandler实例
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            String originalSql = boundSql.getSql();

            //在 SQL 中添加多租户条件
            String newSql = addTenantCondition(originalSql, tenantId);

            //使用反射设置新的 SQL
            MetaObject metaObject = SystemMetaObject.forObject(boundSql);
            metaObject.setValue("sql", newSql);
        }

        //执行目标方法
        return invocation.proceed();
    }

    /**
     * 添加多租户条件到SQL语句
     * @param originalSql 原始SQL语句
     * @param tenantId 租户ID
     * @return 修改后的SQL语句
     */
    private String addTenantCondition(String originalSql, Integer tenantId) {
        //检查是否为 INSERT 语句
        if (originalSql.trim().toLowerCase().startsWith("insert")) {
            // 添加 tenant_id 列和值
            return addTenantIdToInsert(originalSql, tenantId);
        }

        //对于其他 SQL 语句,添加 tenant_id 条件
        String modifiedSql;
        if (originalSql.toLowerCase().contains("where")) {
            modifiedSql = originalSql + " AND tenant_id = " + tenantId;
        } else {
            modifiedSql = originalSql + " WHERE tenant_id = " + tenantId;
        }
        return modifiedSql;
    }

    /**
     * 为 INSERT 语句添加 tenant_id 列和值
     * @param originalSql 原始SQL语句
     * @param tenantId 租户ID
     * @return 修改后的SQL语句
     */
    private String addTenantIdToInsert(String originalSql, Integer tenantId) {
        //找到 VALUES 关键字的位置
        int valuesIndex = originalSql.toLowerCase().indexOf("values");
        if (valuesIndex == -1) {
            throw new IllegalArgumentException("无效的 INSERT SQL 语句: " + originalSql);
        }

        //在列部分添加 tenant_id
        String columnsPart = originalSql.substring(0, valuesIndex).trim();
        int lastParenthesisIndex = columnsPart.lastIndexOf(")");
        if (lastParenthesisIndex == -1) {
            throw new IllegalArgumentException("无效的 INSERT SQL 语句: " + originalSql);
        }
        columnsPart = columnsPart.substring(0, lastParenthesisIndex) + ", tenant_id" + columnsPart.substring(lastParenthesisIndex);

        //在值部分添加 tenant_id
        String valuesPart = originalSql.substring(valuesIndex).trim();
        int lastValuesParenthesisIndex = valuesPart.lastIndexOf(")");
        if (lastValuesParenthesisIndex == -1) {
            throw new IllegalArgumentException("无效的 INSERT SQL 语句: " + originalSql);
        }
        valuesPart = valuesPart.substring(0, lastValuesParenthesisIndex) + ", " + tenantId + valuesPart.substring(lastValuesParenthesisIndex);

        return columnsPart + " " + valuesPart;
    }


}

spring.application.name=tenant-mybatis-demo

server.port=8888

spring.datasource.url=jdbc:mysql://localhost:3306/tenantdemo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root


#mybatis.mapper-locations=classpath:mappers/*.xml   这样配置会报错 可能是用了@Select注解
mybatis.type-aliases-package=com.example.tenantmybatisdemo

#自动转驼峰
mybatis.configuration.map-underscore-to-camel-case=true

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hrui0706

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值