I am developing a full text search service. I use PostgreSQL's built-in FTS query 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 implementation.
In that implementation first you create a PostgreSQLFTSFunction class. For me it is:
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;
}
}
Then need to create CustomPostgreSQLDialect:(I have, for sure, added dialect configuration onto application.yml file)
public class FTSPostgresDialect extends PostgisDialect {
public FTSPostgresDialect() {
super();
registerFunction("fts", new BFTSFunction());
}
}
Lastly I created HQL in my RepositoryImp
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();
But the query created by hibernate cannot be executed. Full error will be given below, but I should say that, logged query in the console (created by Hibernate) can be executed in pgAdmin. Therefore, Query creation process is correct. Then, where can be the error is?
Full error is given:
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]
If additional info is needed, I will fastly send it. Thanks in advance.
Interestingly, as the error suggests, hibernate cannot bind the parameters onto sql. I couldn't make it happen. But in my situation, I could just concatenate as string, therefore problem is resolved by changing HQL from:
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,:searched) = true )",Long.class)
.setParameter("searched",searched);
to:
Query<Long> q = session.createQuery(
"select b.id from B b where (fts(b.name,"+searched+") = true )",Long.class);
The problem is unnecessary quotes in a plainto_tsquery call:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) ## " + "plainto_tsquery('%" +
value + "%')) ";
It should be:
fragment = "( to_tsvector(coalesce(" + field + ",' ')) ## " + "plainto_tsquery(" +
value + ")) ";
I removed those % too, because it won't work like that. value won't have an actual parameter value, it would have ? in it to create a query, that will be reused for actual executions. If you want to wrap the given value into %, you'll have to do it with SQL, not Java code.
Related
Im using Spring mvc, Hibernate and Mysql, in my DAOImplement i call sql file outsite.
This is my DAO:
public List<RenterDto> searchByNamePersonalId(String name, String personalId) {
Session session = sessionFactory.openSession();
String sqlString = GetSqlUtils.getSqlQueryString(RenterDaoImpl.class, SQL_DIR + SEARCH_BY_NAME_PERSON);
List<RenterDto> list = new ArrayList<RenterDto>(0);
try {
Query query = session.createSQLQuery(sqlString);
query.setParameter(0, name);
query.setParameter(1, personalId);
list = query.setResultTransformer(new AliasToBeanResultTransformer(RenterDto.class)).list();
} catch (HibernateException e) {
logger.error("error at RenterDaoImpl.searchByNameAddress: " + e.getMessage());
} finally {
session.close();
}
return list;
}
My SQL called (SEARCH_BY_NAME_PERSON), use to search name and personalId:
SELECT
R.ID,
R.NAME,
R.ACCOUNT_ID,
R.OTP_CODE,
R.OS_TYPE,
R.MANCHINID,
R.TYPE,
R.PERSONAL_ID,
R.PHONENUMBER,
R.EMAIL,
R.ADDRESS,
R.DISTRICT_ID,
D.NAME AS DISTRICT_NAME,
R.CITY_ID,
C.NAME AS CITY_NAME
FROM
RENTER R
INNER JOIN
DISTRICT D
ON
R.DISTRICT_ID = D.ID
INNER JOIN
CITY C
ON
R.CITY_ID = C.ID
WHERE
LOWER(R.NAME)
LIKE
('%'||'?')
OR
R.PERSONAL_ID
LIKE
'%'||'%'
When I search with keyword: 'name' or 'personalId', I received an error result following as:
SqlExceptionHelper - SQL Error: 0, SQLState: S1009
SqlExceptionHelper - Parameter index out of range (1 > number of parameters, which is 0).
RenterDaoImpl - error at RenterDaoImpl.searchByNameAddress: could not execute query
How to fix this the problem ? Thank you so much !
You have this expression:
LIKE ('%'||'?')
I am guessing that you want '?' to be a parameter. But it is a lowly string, with just a single question mark.
Instead:
LIKE CONCAT('%', ?)
In general, in MySQL, you want to use CONCAT() for string concatenation.
I use EclipseLink for JPA when I try to get data in my java application. when I Execute
public Object doInJpa(EntityManager em) throws PersistenceException {
Query q = em.createNativeQuery("SELECT t0.* FROM asset t0 WHERE t0.case_id = ?1 and t0.deleted=0 and t0.state='DEFAULT' and fully_paid=0 and NOT EXISTS ( " +
" SELECT 1 from excluding_request er where er.asset_id=t0.ID and probable=0 and deleted = 0 and rejected = 0) order by t0.id limit ?2 , ?3", Asset.class);
q.setParameter(1, caseId);
q.setParameter(2, start);
q.setParameter(3, limit);
List<Asset> result = (List<Asset>) q.getResultList();
return result;
}
});
I get no error, but almost the same query:
public Object doInJpa(EntityManager em) throws PersistenceException {
Query q = em.createNativeQuery("SELECT COUNT(t0.ID) FROM asset t0 WHERE t0.case_id = ?1 and t0.deleted=0 and t0.state='DEFAULT' and fully_paid=0 and NOT EXISTS ( " +
" SELECT 1 from excluding_request er where er.asset_id=t0.ID and probable=0 and deleted = 0 and rejected = 0)", Asset.class);
q.setParameter(1, caseId);
Integer result = ((List<Integer>)q.getSingleResult()).get(0);
return result;
}
I get error:
Exception Description: The primary key read from the row [DatabaseRecord(
=> 37931)] during the execution of the query was detected to be null. Primary keys must not contain null.
It looks like some problem with count and eclipselink. By the way 37931 is result that should be returned in my case. Both queries work fine when I execute them directly in MySQL, and error is same no matter what I put in count function (*, 1, etc...)
Can you please tell me what an I doing wrong?
Thank you.
I'm using EclipseLink as JPA implementation and I need to get multiple objects using the primary key (numeric id). But I also need to maintain the given id order.
Using native mySQL this kind of behaviour can be obtained using ORDER BY FIELD
SELECT id FROM table WHERE id IN(9,5,2,6) ORDER BY FIELD(id,9,5,2,6);
I'm now trying to replicate this query using JPA implementation. As already established from this thread, the ORDER BY FIELD is not supported, so I went to a more low-level approach using a JPA native query.
I'm try to reach this goal using a parameter query, instead of using a raw statement. The first implementation was like this
Class<?> clazz = ...;
List<Long> ids = ...;
EntityManagerFactory emf = ...;
EntityManager em = emf.createEntityManager();
String statement = "SELECT * FROM table WHERE id IN (?)";
Query createNativeQuery = em.createNativeQuery(statement, clazz);
createNativeQuery.setParameter(1, ids);
List resultList = createNativeQuery.getResultList();
As you can see the ORDER clause is not there yet, for the first step I just trying to make the parameter query work using the ids list with the IN operator. In the setParameter method I tried to provide the List object, a comma separated list (as string) but none of them works. At the end they all finish with a sql syntax error.
I also tried to play with the parenthesis, with or without, but nothing works.
Here some test I made
String statement = "SELECT * FROM " + tableName + " WHERE id IN (?)";
Query createNativeQuery = emJpa.createNativeQuery(statement, this.em.getClassObject());
createNativeQuery.setParameter(1, ids);
The query does not give any error, but no results given.
String statement = "SELECT * FROM " + tableName + " WHERE id IN (?)";
Query createNativeQuery = emJpa.createNativeQuery(statement, this.em.getClassObject());
createNativeQuery.setParameter(1, Joiner.on(",").join(ids));
Only one result is given, but 7 ids was provided to the query
From this topic I also tried using ?1 instead of ?, but no changes. Is there a way to make the nativeQuery working with a list of ids?
For the moment I'm using the full raw SQL statement
String joinedId = Joiner.on(",").join(ids);
String statement = "SELECT * FROM " + tableName + " WHERE id IN (" + joinedId + ") ORDER BY FIELD(id," + joinedId + ")";
Query createNativeQuery = emJpa.createNativeQuery(statement, this.em.getClassObject());
createNativeQuery.getResultList();
But at first I started with the parameter query for optimization and performance related of parsing each time the statement.
EDIT
With the suggestion of Chris I tried a TypedQuery using the FUNCTION operator (which is available because I'm using the latest EclipseLink). Here is the resulting code
List<Long> ids = ...;
Class<?> clazz = ...;
String statement = "SELECT e FROM " + clazz.getSimpleName() + " e WHERE e.id IN (:idList) ORDER BY FUNCTION('FIELD', e.id, :idList)";
EntityManagerFactory emf = ...;
EntityManager em = emf.createEntityManager();
TypedQuery<?> query = em.createQuery(statement, clazz);
query.setParameter("idList", ids);
List resultList = query.getResultList();
And here is the error while executing this code
Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.3.v20160428-59c81c5): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Operand should contain 1 column(s)
Error Code: 1241
Call: SELECT ... all the fields ... FROM webcontent_type WHERE (ID IN ((?,?,?,?,?,?,?))) ORDER BY FIELD(ID, (?,?,?,?,?,?,?))
bind => [14 parameters bound]
Query: ReadAllQuery(referenceClass=WebContentType sql="SELECT ... all the fields ... FROM webcontent_type WHERE (ID IN (?)) ORDER BY FIELD(ID, ?)")
EDIT 2
Tried without the parenthesis but there is still an error
SELECT e FROM FrameWorkUser e WHERE e.id IN :idList ORDER BY FUNCTION('FIELD', e.id, :idList)
I must say that with a list of one element the code works, but with another list of 10 elements there is an error
javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.3.v20160428-59c81c5): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Operand should contain 1 column(s)
Error Code: 1241
Call: SELECT .... FROM webcontent_type WHERE (ID IN (?,?,?,?,?,?,?)) ORDER BY FIELD(ID, (?,?,?,?,?,?,?))
bind => [14 parameters bound]
Query: ReadAllQuery(referenceClass=WebContentType sql="SELECT .... FROM webcontent_type WHERE (ID IN ?) ORDER BY FIELD(ID, ?)")
at org.eclipse.persistence.internal.jpa.QueryImpl.getDetailedException(QueryImpl.java:382)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:260)
at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:473)
It seems that even w/o the parenthesis, the resulting statement has them
If you are going to use a native query, you must do it exactly like you would form the SQL for your database - this means you must break the list into its component parameters as JPA providers are not expected to change the SQL for you. Most providers handle lists in JPQL though, so "select e from Entity e where e.id in (:idList)" will work in EclipseLink.
Your missing bit is that 'FIELD' is not a JPQL construct. For this, you would have to use the JPQL 2.1 FUNCTION operator. Something like:
"Select e from Entity e where e.id in :idList order by FUNCTION('FIELD', e.id, :idList)"
I'm trying to run this:
..
String modelTableName = (model == TableModelType.RING) ? "RING_PLAYERS" : "TOURNY_PLAYERS";
List<Object[]> results = dbs.createSQLQuery("SELECT group_level, COUNT(group_level) FROM :modelTableName WHERE game_id=:gameId GROUP BY group_level")
.addScalar("group_level", Hibernate.INTEGER)
.addScalar("COUNT(group_level)", Hibernate.LONG)
.setString("modelTableName", getModelTableName())
.setInteger("gameId", getGameId())
.list();
It gives an exception:
Caused by: java.sql.SQLException: ORA-00903: invalid table name
If I write "RING_PLAYERS" instead of ":tableModelName" then it WORKS!
What am I doing wrong with this parameter?
Thank you.
According to this answer, you can not inject the table name: Table name as parameter in HQL
You may have to construct it as:
List<Object[]> results = dbs.createSQLQuery("SELECT group_level, COUNT(group_level) FROM " + getModelTableName() + " WHERE game_id=:gameId GROUP BY group_level")
.addScalar("group_level", Hibernate.INTEGER)
.addScalar("COUNT(group_level)", Hibernate.LONG)
.setInteger("gameId", getGameId())
.list();
I am trying to retrieve data from my database with hibernate but it keeps throwing an exception
2012-11-11 11:35:45,943 [main] ERROR
com.storage.hibernate.DatabaseAccessRequestsImpl - there was an error
javax.persistence.PersistenceException:
org.hibernate.exception.SQLGrammarException: could not execute query
#Override
public List<Trade> requestPeriod() {
List<Trade> trades = null;
EntityManager manager = emf.createEntityManager();
Query query = manager.createQuery("from trade");
try{
trades = query.getResultList();
}
catch(PersistenceException e){
logger.error("there was an error " + e);
}
catch(SQLGrammarException e){
logger.error("there was an error " + e);
}
return trades;
}
I am guessing the syntax I am using for select all is incorrect but after looking around I can not see an alternative?
Thanks
It should be "from Trade" (uppercase T) as Trade is the name of the mapped class.
Note that in JPA QL SELECT clause is mandatory, as per: 10.2.1.1. JPQL Select Statement:
A select statement is a string which consists of the following clauses:
a SELECT clause, which determines the type of the objects or values to be selected;
a FROM clause, which provides declarations that designate the domain to which the expressions specified in the other clauses of the query apply;
[...]
In BNF syntax, a select statement is defined as:
select_statement ::= select_clause from_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause]
The bare from Trade syntax is Hibernate-specific, to be specification compliant you should always use:
select t from Trade t