如何在Hibernate 6与QueryDSL中使用MATCH...AGAINST全文搜索?
我正在开发基于Spring Boot 3.2 + Hibernate 6.6.11 + QueryDSL 5的项目,使用MySQL数据库,尝试通过MATCH...AGAINST(搭配ngram解析器)实现全文搜索。
已在MySQL中创建全文索引:
ALTER TABLE stores ADD FULLTEXT INDEX ft_store_name (name) WITH PARSER ngram;
在仓库层尝试用QueryDSL编写如下代码:
import static com.querydsl.core.types.dsl.Expressions.stringTemplate; BooleanBuilder builder = new BooleanBuilder(); if (name != null && !name.isEmpty()) { builder.and( stringTemplate( "MATCH({0}) AGAINST ({1} IN NATURAL LANGUAGE MODE)", store.name, name ).isNotNull() ); }
但遇到错误:
org.hibernate.query.SyntaxException: At 3:24 and token 'AGAINST', mismatched input 'AGAINST', expecting one of the following tokens...
根本原因:MATCH...AGAINST并非合法的JPQL语法,Hibernate解析时失败。
希望继续使用QueryDSL,尽可能避免使用纯原生SQL,请问如何在Hibernate 6中搭配QueryDSL使用MATCH...AGAINST?
方法1:使用QueryDSL原生SQL模板(安全绑定参数)
直接使用Expressions.template替代stringTemplate,绕过JPQL语法校验,同时确保参数安全绑定:
import static com.querydsl.core.types.dsl.Expressions.template; BooleanBuilder builder = new BooleanBuilder(); if (name != null && !name.isEmpty()) { builder.and( template(Boolean.class, "MATCH({0}) AGAINST (?1 IN NATURAL LANGUAGE MODE)", store.name, name ) ); }
这里通过指定返回类型为Boolean,让QueryDSL直接生成原生SQL片段,参数?1会被自动绑定,避免SQL注入问题。
方法2:注册Hibernate自定义函数
通过自定义方言注册MATCH...AGAINST为JPQL支持的函数,让QueryDSL可以无缝调用:
- 配置自定义方言(
application.properties):
spring.jpa.properties.hibernate.dialect=com.example.CustomMySQLDialect
- 实现自定义方言类:
public class CustomMySQLDialect extends MySQLDialect { public CustomMySQLDialect() { super(); registerFunction( "match_against", new SQLFunctionTemplate( BooleanType.INSTANCE, "MATCH(?1) AGAINST (?2 IN NATURAL LANGUAGE MODE)" ) ); } }
- QueryDSL中调用自定义函数:
import static com.querydsl.core.types.dsl.Expressions.function; BooleanBuilder builder = new BooleanBuilder(); if (name != null && !name.isEmpty()) { builder.and( function(Boolean.class, "match_against", store.name, name) ); }
这种方式符合JPQL规范,Hibernate会正确解析函数逻辑,同时保留QueryDSL的类型安全特性。
方法3:使用Hibernate 6自带全文搜索API(跨库替代方案)
如果不想依赖MySQL特定语法,可使用Hibernate 6原生全文搜索支持:
- 实体类字段添加
@FullText注解:
@Entity @Table(name = "stores") public class Store { @Id private Long id; @FullText(analyzer = "ngram") private String name; // 其他字段与方法 }
- QueryDSL中构建全文搜索条件:
import com.querydsl.core.types.dsl.Expressions; BooleanBuilder builder = new BooleanBuilder(); if (name != null && !name.isEmpty()) { builder.and( Expressions.predicate( "fulltext", store.name, Expressions.constant(name) ) ); }
该方案不绑定数据库,需提前配置Hibernate全文搜索后端(如Lucene)。
内容的提问来源于stack exchange,提问作者holyshmoly

