# jpa-tenant-plugin **Repository Path**: czykeith/jpa-tenant-plugin ## Basic Information - **Project Name**: jpa-tenant-plugin - **Description**: 本插件基于springboot实现jpa基于数据库字段实现租户数据隔离功能(当前自动创建租户字段仅支持MySql数据库) - **Primary Language**: Java - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 7 - **Forks**: 1 - **Created**: 2023-10-25 - **Last Updated**: 2025-06-19 ## Categories & Tags **Categories**: database-dev **Tags**: None ## README # jpa-tenant-plugin 单字段租户版本 **本插件基于springboot实现jpa基于数据库字段实现租户数据隔离功能(当前自动创建租户字段仅支持MySql数据库)** **注意事项:本插件基于spring aop实现,使用@JpaTenant时,需要注意注解失效的场景** **============本插件的使用方法============** * 在pom文件中加入插件依赖,示例如下: ``` com.keith jpa-tenant-plugin 1.0 ``` * 在YML文件中添加JPA拦截与插件相关配置,示例如下: ``` spring: jpa: tenant: enable: true # 是否开启租户解析,租户插件总开关,默认为false defaultTenant: true #没有JpaTenant注解情况下默认是否做租户处理,默认为true tenantColumn: tenant_id #租户数据库字段名称,默认为tenant_id tenantColumnType: Long #租户字段数据类型,仅支持Long、String两种类型,默认为Long ignoreTables: sys_tenant #忽略租户处理的表名,多个以逗号分隔,也可以以注解方法忽略 log: false #是否开启调试日志 开启后将打印处理器与处理后的sql语句,默认为false penetration: false #是否开启租户穿透,注解优先级更高 filterTenantIdValues: #配置需要替换处理的租户ID,例如:配置为0,则到检测到sql语句中租户字段的值为0时,则会用当前上下文中的租户ID替换 properties: hibernate: ejb: interceptor: com.keith.jpa.tenant.TenantJpaEmptyInterceptor #拦截jpa对象,兼容保存操作jdbc预处理无法自动处理租户的问题,如果jpa保存操作需要自动处理租户请配置该项 session_factory: statement_inspector: com.keith.jpa.tenant.TenantJpaInspector #配置jpa租户处理拦截器 ``` * 租户处理上下文处理,通过TenantContextHolder类进行处理,该类针对异步执行时无法获取ThreadLocal的租户信息进行了处理(通常情况下在拦截器或者过滤器中统一处理),使用方法如下: ``` TenantContextHolder.setTenantId(user.getTenantId()); #在需要用到租户信息的入口进行当前线程用户租户信息设置,此设置作用于当前线程以及当前线程创建的异步线程以及线程池 TenantContextHolder.removeTenantId(); #使用完成后清理当前线程的租户信息 ``` * 线程池中租户处理上下文处理,由于普通线程池在使用TenantContextHolder上下文时会造成线程复用导致的数据异常,因此本插件提供了租户线程池工具类:TenantThreadPoolUtils;在线程池中需要使用到TenantContextHolder租户信息的时候必须使用 该工具类获取线程池;如果有其他自定义线程需求,请参考TransmittableThreadLocal的使用进行线程池创建。 ``` ExecutorService executorService = TenantThreadPoolUtils.getThreadPool(4, 8, 10000, "execute-pool"); ``` * @JpaTenant注解的使用方法,注解的优先级高于配置文件中defaultTenant的配置,但是无法覆盖ignoreTables设置;可以在类或者方法上使用该注解(方法注解优先级高于类上注解);支持嵌套注解;建议在dao层进行注解,以最小粒度进行控制,使用示例如下: ``` #@JpaTenant注解中属性作用: #tenant: 是否忽略租户处理,true,添加注解后tenant的设置将覆盖配置中defaultTenant字段,但是无法覆盖ignoreTables设置 #tenentId: 当前操作的租户ID,默认为空,添加该属性注解后,租户操作将以该值作为优先值,如果该属性为空,将以TenantContextHolder中的租户ID为准 #1、忽略当前操作的租户处理 @JpaTenant(tenant = false) public interface JobCfjDao extends CcbRepository #2、当前操作需要做租户处理,且设置租户ID @JpaTenant(tenentId = "租户Id") public interface ProgDefDao extends CcbRepository #3、当前操作需要做租户处理,租户ID从线程上下文中获取 @JpaTenant public interface ProgDefDao extends CcbRepository ``` * 各参数优先级说明 配置spring.jpa.tenant.ignoreTables>JpaTenant.tenant>配置spring.jpa.tenant.defaultTenant JpaTenant.tenantId>TenantContextHolder.setTenantId(); * 租户穿透的实现,实现TenantPenetration接口,返回穿透查询逻辑 ``` @Component public class Test implements TenantPenetration{ @Override public List getTenantIds(String tenantId) { //实现租户穿透的查询逻辑,返回穿透后的租户ID集合 } } ``` **============使用插件异常情况及解决办法============** * 项目启动异常: The query you provided is not a valid SQL Query! 报错示例如下: ``` Caused by: java.lang.IllegalArgumentException: The query you provided is not a valid SQL Query! at org.springframework.data.jpa.repository.query.JSqlParserQueryEnhancer.detectParsedType(JSqlParserQueryEnhancer.java:102) at org.springframework.data.jpa.repository.query.JSqlParserQueryEnhancer.(JSqlParserQueryEnhancer.java:75) at org.springframework.data.jpa.repository.query.QueryEnhancerFactory.forQuery(QueryEnhancerFactory.java:45) at org.springframework.data.jpa.repository.query.StringQuery.(StringQuery.java:86) at org.springframework.data.jpa.repository.query.DeclaredQuery.of(DeclaredQuery.java:40) at org.springframework.data.jpa.repository.query.JpaQueryMethod.assertParameterNamesInAnnotatedQuery(JpaQueryMethod.java:158) ``` * 异常原因:由于jpa版本升级后,spring-boot项目启动时,如果项目中引用了jsqlparser的jar包后,会使用CCJSqlParser对SQL进行分析处理,然而注解形式的原生SQL语法并非最终SQL,故使用jsqlparser进行解析时会抛出异常。 * 解决办法: ``` 1、改写项目中JPA注解的native sql中使用冒号+占位名称的形式的SQL代码(如 where name=:name),改用问号占位的方式(如 where name=?1)。 2、在启动main方法中加入jpa启动的jsqlparser分析处理屏蔽代码,示例如下: public static void main(String[] args) { //加入特殊处理代码,在启动类之前调用 TenantUtils.disableStartJpaParser(); SpringApplication.run(Demo.class) } ```