基于鉴别器的多租户架构下Hibernate Search全文搜索实现问询
嘿,刚好我之前在项目里处理过类似的多租户+Hibernate Search场景,给你几个靠谱的方案,从基础到优雅优化都有:
一、基础方案:手动添加租户过滤条件
这是最直接的方式,每次搜索时在查询里强制加上parkId的匹配条件,确保只返回当前租户的数据:
// 获取FullTextEntityManager FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager); // 创建QueryBuilder QueryBuilder qb = fullTextEntityManager.getSearchFactory() .buildQueryBuilder().forEntity(ProductModel.class).get(); // 构建组合查询:全文搜索 + 租户过滤 BooleanJunction<?> queryJunction = qb.bool() // 全文搜索条件(比如搜索name字段) .must(qb.keyword().onField("name").matching("你的搜索关键词").createQuery()) // 强制添加租户parkId过滤 .must(qb.keyword().onField("parkId").matching(currentTenantParkId).createQuery()); // 执行查询 FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery( queryJunction.createQuery(), ProductModel.class ); List<ProductModel> results = fullTextQuery.getResultList();
提示:currentTenantParkId要从你的系统上下文获取,比如ThreadLocal、Spring Security的登录用户信息里提取,确保每个请求对应正确的租户ID。
二、优雅方案:使用Hibernate Search的租户上下文(推荐)
如果用的是Hibernate Search 6及以上版本,官方提供了专门的多租户支持,不用每次手动写过滤条件,更简洁安全:
1. 标记租户字段
在实体的parkId字段上添加@TenantId注解,告诉Hibernate Search这是租户标识:
@Entity @Indexed public class ProductModel { @TenantId // 标记为租户ID字段 @Field private Long parkId; @Field(index = Index.YES, analyze = Analyze.YES) @Analyzer(definition = "customanalyzer") private String name; // 其他字段... }
2. 配置租户标识符提供者
实现TenantIdentifierProvider接口,用来在查询/索引时自动获取当前租户的ID:
public class ParkIdTenantProvider implements TenantIdentifierProvider { @Override public String getTenantIdentifier() { // 从系统上下文获取当前租户的parkId,转成String返回 Long currentParkId = TenantContext.getCurrentParkId(); return currentParkId != null ? currentParkId.toString() : null; } }
然后在JPA配置里指定这个提供者(比如Spring Boot的application.properties):
# Spring Boot配置示例 spring.jpa.properties.hibernate.search.tenant_identifier_provider=com.yourcompany.ParkIdTenantProvider
3. 执行搜索(自动过滤租户)
现在搜索时不用手动加parkId条件,Hibernate Search会自动帮你过滤当前租户的数据:
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager); QueryBuilder qb = fullTextEntityManager.getSearchFactory() .buildQueryBuilder().forEntity(ProductModel.class).get(); // 只需要写全文搜索条件即可,租户过滤自动生效 FullTextQuery query = fullTextEntityManager.createFullTextQuery( qb.keyword().onField("name").matching("你的搜索关键词").createQuery(), ProductModel.class ); List<ProductModel> results = query.getResultList();
三、进阶优化:按租户拆分索引
如果你的租户数据量很大,推荐按租户拆分索引(每个租户一个独立索引),这样查询性能更好,也能彻底避免跨租户数据泄露:
1. 配置动态索引名称
通过自定义SearchMapping,让每个租户的索引名称包含租户ID:
public class CustomSearchMapping implements SearchMappingConfigurer { @Override public void configure(SearchMapping mapping) { mapping.entity(ProductModel.class) // 动态生成索引名,{tenant_id}会被替换为当前租户ID .index(name = "product_{tenant_id}"); } }
2. 配置启用自定义Mapping
在JPA配置里指定这个Mapping:
spring.jpa.properties.hibernate.search.mapping.model_mapping=com.yourcompany.CustomSearchMapping
这样每个租户的数据会被索引到独立的索引中,查询时只会访问当前租户的索引,性能和安全性都更优。
四、兼容Hibernate Search 5的方案
如果还在使用Hibernate Search 5,可以用过滤器实现租户过滤:
1. 定义过滤器工厂
@Key(name = "parkIdFilter") public class ParkIdFilterFactory implements FilterFactory { @Override public Filter getFilter(FilterKey key) { Long currentParkId = TenantContext.getCurrentParkId(); // 创建parkId匹配的过滤器 return new QueryFilter(new TermQuery(new Term("parkId", currentParkId.toString()))); } }
2. 搜索时启用过滤器
FullTextQuery query = fullTextEntityManager.createFullTextQuery( qb.keyword().onField("name").matching("搜索关键词").createQuery(), ProductModel.class ); // 启用租户过滤器 query.enableFullTextFilter("parkIdFilter"); List<ProductModel> results = query.getResultList();
内容的提问来源于stack exchange,提问作者logeyg

