赞
踩
注意:方案的数据库mapper层采用的是tk.mybatis
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>4.5</version> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> <exclusions> <exclusion> <artifactId>mybatis</artifactId> <groupId>org.mybatis</groupId> </exclusion> </exclusions> </dependency>
public class MultiTenantPlugin implements Interceptor{ private static PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); private static Set<String> multiTenantTable = new HashSet<>(); @Value("${multi.tenant.table:}") private String multiTenantTables; @Value("${multi.tenant.enable:}") private boolean multiTenantEnable; @PostConstruct public void afterPropertiesSet() { if (multiTenantEnable) { initMultiTenantTable(); initTenantSqlParser(); } } /** * 初始化多租户Dao层sql相关转换器 */ private void initTenantSqlParser() { List<ISqlParser> sqlParserList = new ArrayList<>(); TenantSqlParser tenantSqlParser = new TenantSqlParser(); tenantSqlParser.setTenantHandler(new TenantHandler() { @Override public Expression getTenantId(boolean select) { if (multiTenantEnable) { return new StringValue(RequestHolder.getTenantId()); } else { String tenantId = RequestHolder.getTenantId(); return new StringValue(StringUtils.isEmpty(tenantId) ? "" : tenantId); } } @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public boolean doTableFilter(String tableName) { return !multiTenantTable.contains(tableName); } }); sqlParserList.add(tenantSqlParser); paginationInterceptor.setSqlParserList(sqlParserList); paginationInterceptor.setSqlParserFilter(metaObject -> { if (MultiTenantSqlFilterHelper.getSqlParserInfo(metaObject)) { return true; } return false; }); } /** * 初始化多租户相关表 */ private void initMultiTenantTable() { multiTenantTable = Sets.newHashSet(multiTenantTables.split(",")); } @Override public Object intercept(Invocation invocation) throws Throwable { return paginationInterceptor.intercept(invocation); } @Override public Object plugin(Object target) { return paginationInterceptor.plugin(target); } @Override public void setProperties(Properties properties) { paginationInterceptor.setProperties(properties); } }
- # 多租户表配置 多张表用英文逗号分隔
- multi.tenant.table=table1,table2
-
- # 多租户开关
- multi.tenant.enable=true
- <plugins>
- <plugin interceptor="com.fudai.interceptor.MultiTenantPlugin"/>
- </plugins>
如果Mapper接口使用的是com.baomidou.mybatisplus.core.mapper,可以直接用mybatisplus自带的注解@SqlParser。否则用以下方法自定义注解做过滤(用的是tk.mybatis.mapper.common.Mapper)。
(1)定义Mapper方法层的sql过滤注解
(Mapper层特定的方法不走多租户加上改注解)
- import java.lang.annotation.*;
-
- /**
- * @className: SqlFilter
- * @description:
- * @author: fudai
- * @date: 2022-12-14 18:04
- */
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD})
- public @interface SqlFilter {
- boolean filter() default false;
- }
(2)新增多租户sql过滤工具类
import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.wacai.loan.screw.base.annotation.SqlFilter; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.reflection.MetaObject; import org.springframework.core.annotation.AnnotationUtils; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @className: MultiTenantSqlFilterHelper * @description: 多租户sql过滤工具类 * @author: fudai * @date: 2022-12-14 18:03 */ public class MultiTenantSqlFilterHelper { public static final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement"; /** * SQL 解析缓存 * key 可能是 mappedStatement 的 ID,也可能是 class 的 name */ private static final Map<String, Boolean> SQL_FILTER_INFO_CACHE = new ConcurrentHashMap<>(); /** * 初始化缓存 Mapper方法上 SqlFilter 注解信息 * * @param mapperClassName Mapper Class Name * @param method Method */ public static void initSqlParserInfoCache(String mapperClassName, Method method) { SqlFilter sqlFilter = AnnotationUtils.findAnnotation(method,SqlFilter.class); if (sqlFilter != null) { if (SQL_FILTER_INFO_CACHE.containsKey(mapperClassName)) { // mapper 接口上有注解 Boolean value = SQL_FILTER_INFO_CACHE.get(mapperClassName); if (!value.equals(sqlFilter.filter())) { // 取反,不属于重复注解,放入缓存 String sid = mapperClassName + StringPool.DOT + method.getName(); SQL_FILTER_INFO_CACHE.putIfAbsent(sid, sqlFilter.filter()); } } else { String sid = mapperClassName + StringPool.DOT + method.getName(); SQL_FILTER_INFO_CACHE.putIfAbsent(sid, sqlFilter.filter()); } } } /** * 获取 SqlFilter 注解信息 * * @param metaObject 元数据对象 */ public static boolean getSqlParserInfo(MetaObject metaObject) { String id = getMappedStatement(metaObject).getId(); Boolean value = SQL_FILTER_INFO_CACHE.get(id); if (value != null) { return value; } String mapperName = id.substring(0, id.lastIndexOf(StringPool.DOT)); return SQL_FILTER_INFO_CACHE.getOrDefault(mapperName, false); } /** * 获取当前执行 MappedStatement * * @param metaObject 元对象 */ public static MappedStatement getMappedStatement(MetaObject metaObject) { return (MappedStatement) metaObject.getValue(DELEGATE_MAPPED_STATEMENT); } }
(3)初始化多租户Mapper方法sql过滤注解缓存服务
import com.wacai.loan.screw.base.util.MultiTenantSqlFilterHelper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Service; import tk.mybatis.mapper.common.Mapper; import java.lang.reflect.Method; import java.util.Map; /** * @className: MultiTenantSqlFilterCacheService * @description: 多租户Mapper方法sql过滤注解缓存服务 * @author: fudai * @date: 2022-12-14 16:13 */ @Service @Slf4j public class MultiTenantSqlFilterCacheService implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, Mapper> mapperMap = applicationContext.getBeansOfType(Mapper.class); mapperMap.forEach((s, mapper) -> { try { Method[] methods = mapper.getClass().getMethods(); for (Method method : methods) { if (!method.isBridge()) { MultiTenantSqlFilterHelper.initSqlParserInfoCache(applicationContext.getType(s).getName(), method); } } } catch (Exception e) { log.error("多租户Mapper方法sql过滤注解缓存异常,mapper:{},异常:", s, e); } }); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。