使用 Hibernate 查询语言的 PostgreSQL 全文搜索无法执行查询
PostgreSQL Full Text Search with Hibernate Query Language cannot execute query
我正在开发全文搜索服务。我使用 PostgreSQL 的内置 FTS 查询 syntax. As you know, I need to use @@
characters to use it. But, this characters are not recognized by HQL, you cannot directly write it like inside the sql. One option is to use nativeQuery
, which I used to use. But, due to software's requirements, now I need to use HQL. For this purpose, I tried to implement this 实现。
在该实现中,首先创建一个 PostgreSQLFTSFunction
class。对我来说是:
public class BFTSFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}
@Override
public boolean hasParenthesesIfNoArguments() {
return false;
}
@Override
public Type getReturnType(Type type, Mapping mapping) throws QueryException {
return new BooleanType();
}
@Override
public String render(Type type, List list, SessionFactoryImplementor sessionFactoryImplementor)
throws QueryException {
if (list.size() != 2) {
throw new IllegalArgumentException("The function must be passed 3 args");
}
String field = (String) list.get(0);
String value = (String) list.get(1);
String fragment = null;
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery('%" +
value + "%')) ";
return fragment;
}
}
然后需要创建 CustomPostgreSQLDialect
:(我肯定已经在 application.yml 文件中添加了方言配置)
public class FTSPostgresDialect extends PostgisDialect {
public FTSPostgresDialect() {
super();
registerFunction("fts", new BFTSFunction());
}
}
最后我在 RepositoryImp 中创建了 HQL
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,:searched) = true )",Long.class).setParameter("searched",searched);
List<Long> listResults = q.getResultList();
但是hibernate创建的查询无法执行。下面将给出完整的错误,但我应该说,可以在 pgAdmin
中执行控制台中记录的查询(由 Hibernate 创建)。因此,Query 创建过程是正确的。那么,错误会在哪里呢?
给出完整错误:
rg.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [select b0_.id as col_0_0_ from bs b0_ where ( b0_.deleted=false) and ( to_tsvector(coalesce(b0_.name,' ')) @@ plainto_tsquery('%?%')) =true]; nested exception is org.hibernate.exception.DataException: could not execute query
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:261) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
...
Caused by: org.hibernate.exception.DataException: could not execute query
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:118) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
...
Caused by: org.postgresql.util.PSQLException: The column index is out of range: 1, number of columns: 0.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:65) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.v3.SimpleParameterList.setStringParameter(SimpleParameterList.java:128) ~[postgresql-42.2.5.jar:42.2.5]
2019-07-16 10:08:59.328 ERROR 8248 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [select b0_.id as col_0_0_ from bs b0_ where ( b0_.deleted=false) and ( to_tsvector(coalesce(b0_.name,' ')) @@ plainto_tsquery('%?%')) =true]; nested exception is org.hibernate.exception.DataException: could not execute query] with root cause
org.postgresql.util.PSQLException: The column index is out of range: 1, number of columns: 0.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:65) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.v3.SimpleParameterList.setStringParameter(SimpleParameterList.java:128) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgPreparedStatement.bindString(PgPreparedStatement.java:996) ~[postgresql-42.2.5.jar:42.2.5]
如果需要更多信息,我会尽快发送。提前致谢。
有趣的是,如错误所示,hibernate 无法将参数绑定到 sql。我做不到。但在我的情况下,我只能连接为字符串,因此通过将 HQL 更改为:
来解决问题
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,:searched) = true )",Long.class)
.setParameter("searched",searched);
至:
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,"+searched+") = true )",Long.class);
问题是 plainto_tsquery 调用中不必要的引号:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery('%" +
value + "%')) ";
应该是:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery(" +
value + ")) ";
我也删除了那些 %
,因为它不会那样工作。 value
不会有实际的参数值,它会包含 ?
来创建一个查询,该查询将被重用于实际执行。如果要将给定值包装到 %
,则必须使用 SQL,而不是 Java 代码。
我正在开发全文搜索服务。我使用 PostgreSQL 的内置 FTS 查询 syntax. As you know, I need to use @@
characters to use it. But, this characters are not recognized by HQL, you cannot directly write it like inside the sql. One option is to use nativeQuery
, which I used to use. But, due to software's requirements, now I need to use HQL. For this purpose, I tried to implement this 实现。
在该实现中,首先创建一个 PostgreSQLFTSFunction
class。对我来说是:
public class BFTSFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}
@Override
public boolean hasParenthesesIfNoArguments() {
return false;
}
@Override
public Type getReturnType(Type type, Mapping mapping) throws QueryException {
return new BooleanType();
}
@Override
public String render(Type type, List list, SessionFactoryImplementor sessionFactoryImplementor)
throws QueryException {
if (list.size() != 2) {
throw new IllegalArgumentException("The function must be passed 3 args");
}
String field = (String) list.get(0);
String value = (String) list.get(1);
String fragment = null;
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery('%" +
value + "%')) ";
return fragment;
}
}
然后需要创建 CustomPostgreSQLDialect
:(我肯定已经在 application.yml 文件中添加了方言配置)
public class FTSPostgresDialect extends PostgisDialect {
public FTSPostgresDialect() {
super();
registerFunction("fts", new BFTSFunction());
}
}
最后我在 RepositoryImp 中创建了 HQL
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,:searched) = true )",Long.class).setParameter("searched",searched);
List<Long> listResults = q.getResultList();
但是hibernate创建的查询无法执行。下面将给出完整的错误,但我应该说,可以在 pgAdmin
中执行控制台中记录的查询(由 Hibernate 创建)。因此,Query 创建过程是正确的。那么,错误会在哪里呢?
给出完整错误:
rg.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [select b0_.id as col_0_0_ from bs b0_ where ( b0_.deleted=false) and ( to_tsvector(coalesce(b0_.name,' ')) @@ plainto_tsquery('%?%')) =true]; nested exception is org.hibernate.exception.DataException: could not execute query
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:261) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
...
Caused by: org.hibernate.exception.DataException: could not execute query
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:118) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
...
Caused by: org.postgresql.util.PSQLException: The column index is out of range: 1, number of columns: 0.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:65) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.v3.SimpleParameterList.setStringParameter(SimpleParameterList.java:128) ~[postgresql-42.2.5.jar:42.2.5]
2019-07-16 10:08:59.328 ERROR 8248 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [select b0_.id as col_0_0_ from bs b0_ where ( b0_.deleted=false) and ( to_tsvector(coalesce(b0_.name,' ')) @@ plainto_tsquery('%?%')) =true]; nested exception is org.hibernate.exception.DataException: could not execute query] with root cause
org.postgresql.util.PSQLException: The column index is out of range: 1, number of columns: 0.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:65) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.v3.SimpleParameterList.setStringParameter(SimpleParameterList.java:128) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgPreparedStatement.bindString(PgPreparedStatement.java:996) ~[postgresql-42.2.5.jar:42.2.5]
如果需要更多信息,我会尽快发送。提前致谢。
有趣的是,如错误所示,hibernate 无法将参数绑定到 sql。我做不到。但在我的情况下,我只能连接为字符串,因此通过将 HQL 更改为:
来解决问题Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,:searched) = true )",Long.class)
.setParameter("searched",searched);
至:
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,"+searched+") = true )",Long.class);
问题是 plainto_tsquery 调用中不必要的引号:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery('%" +
value + "%')) ";
应该是:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) @@ " + "plainto_tsquery(" +
value + ")) ";
我也删除了那些 %
,因为它不会那样工作。 value
不会有实际的参数值,它会包含 ?
来创建一个查询,该查询将被重用于实际执行。如果要将给定值包装到 %
,则必须使用 SQL,而不是 Java 代码。