JPA EclipseLink - Get multiple objects by primary key maintaining order - java

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)"

Related

Update/delete queries cannot be typed

I want to implement update query in JPA. I tried this:
public void updateTransactionStatus(String uniqueId, String type, String status) throws Exception {
String hql = "update " + PaymentTransactions.class.getName()
+ " e SET e.status = :status WHERE e.unique_id = :unique_id AND e.type = :type";
TypedQuery<PaymentTransactions> query = entityManager.createQuery(hql, PaymentTransactions.class).setParameter("status", status).setParameter("unique_id", uniqueId).setParameter("type", type);
query.executeUpdate();
}
But I get Update/delete queries cannot be typed. What is the proper wya to implement this?> I tried to replace TypedQuery with Query but I get The type Query is not generic; it cannot be parameterized with arguments <PaymentTransactions>
In general if you do not need to you should avoid using batch updates with JPA unless they are triggered from within a REQUIRES_NEW marked transaction (and thats the only operation).
It seems that you need to perform an update on one unique entry so I would suggest a query followed by a modification, thats it:
// retrieve
PaymentTransactions paymentTransaction =
entityManager.createQuery("select t from " + PaymentTransactions.class.getName()
+ " where e.unique_id = :unique_id AND e.type = :type "
, PaymentTransactions.class);
.setParameter("unique_id", uniqueId).setParameter("type", type)
.getSingleResult();
// modify
paymentTransaction.setStatus(status);
Here is the place for a TypedQuery.
As long as your method is within a transactional context, thats all you need to do to update the entry.

sql to hql throwing exception

I'm using hibernate in my project and I'm trying to convert an existing sql query from DaoImplementation class to hql,
The sql query I have is
JdbcTemplate select = new JdbcTemplate(dataSource);
String sql = "SELECT * FROM (SELECT site_id,rtc,sigplan,cycle_time,health,phase_no,phase_time,active_groups,groupscolour,ip "+
"FROM status_data where rtc>='" + fromDate + "' and rtc<'" + toDate + "' and "+
"site_id=" + SiteId + " order by rtc desc limit "+recordLimit+" )as temp ORDER BY RTC ASC";
I wrote the hql version to get data from HealthLog table as
String hql = " select f from (select h from HealthLog h where rtc>='"+fromDate+"' and rtc <'"+toDate+"' "
+ "and siteId = "+siteId+" order by rtc desc limit "+limit+" ) as f order by rtc asc ";
return super.readListByHql(hql);
But the above hql throws the following exception
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: ( near line 1, column 16 [ select f from (select h from com.traff.hibernate.model.HealthLog as h where rtc>='1974-08-01 14:10:00.0' and rtc <'1974-09-01 23:46:20.6' and siteId = 20 order by rtc desc limit 50000 ) as f order by rtc asc ]
at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:54)
at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:47)
at org.hibernate.hql.internal.ast.ErrorCounter.throwQueryException(ErrorCounter.java:79)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:276)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:180)
at org.hibernate.hql.intern
I also tried the below code snippet but that giving me wrong results
Criteria criteria = createEntityCriteria();
criteria.add(Restrictions.ge("rtc", fromDate));
criteria.add(Restrictions.lt("rtc", toDate));
criteria.add(Restrictions.eq("siteId", siteId));
criteria.setMaxResults(limit);
criteria.addOrder(Order.asc("rtc"));
criteria2 = criteria;
criteria2.addOrder(Order.desc("rtc"));
return criteria2.list();
Which is the correct way to achieve the result?
First of all, as already mentioned in the comments, you cannot do a subquery within the FROM clause in HQL.
See: Hibernate Documentation
Secondly, the limit keyword is not supported by HQL.
Usually you would use query.setFirstResult(0) and query.setMaxResults(recordLimit) methods where query has the type of the Query Interface. But since you are using the limit in a subquery, there is no way.
See: How to set a limit to inner query in Hibernate?
Some options:
Use a native SQLQuery
Since you are only sorting in the outer Query. You could only execute the inner query and sort in Java.
Example for Option 2:
Session session = factory.openSession();
Query query = session
.createQuery("FROM HealthLog "
+ "WHERE rtc >= :rtcL and rtc < :rtcG and siteId = :siteId "
+ "ORDER BY rtc DESC");
query.setParameter("rtcL", fromDate);
query.setParameter("rtcG", toDate);
query.setParameter("siteId", siteId);
query.setFirstResult(0);
query.setMaxResults(recordLimit);
List<HealthLog> res = query.list();
session.close();
Collections.sort(res, new Comparator<HealthLog>() {
public int compare(HealthLog o1, HealthLog o2) {
return o1.getRtc().compareTo(o2.getRtc());
}
});
The query above returns HealthLogs with all attributes. If you want to only retrieve specific attributes, you can add a SELECT new HealthLog(siteId,rtc,sigplan,cycle_time,...) to your Query with a fitting constructor in HealthLog.
Please note that the code snippet might not be ready to use, since i do not know your model and attribute names.

Hibernate updation not working

I am using following method to update data in database.
String hql = "UPDATE EmployeeSalary set salary = :sl,"
+ "monthYr=:dt "
+ "WHERE id =:id and client.id=:cid";
for (EmployeeSalary e : eList) {
Query query = session.createQuery(hql);
query.setParameter("sl", e.getSalary());
query.setParameter("dt", e.getMonthYr());
query.setParameter("id", e.getId());
query.setParameter("cid", e.getClient().getId());
int result = query.executeUpdate();
System.out.println("result is " + result);
if (eAttList.size() % 20 == 0) {
session.flush();
session.clear();
}
}
Is there any problem with code.
On execution it is showing
result is 0
How to resolve above problem.
The documentation about update queries says:
No "Forms of join syntax", either implicit or explicit, can be specified in a bulk HQL query. Sub-queries can be used in the where-clause, where the subqueries themselves may contain joins.
Your query seems to violate this rule: client.id=:cid is an implicit join to the client entity.
Note that you're making your life difficult. You could simply get the entity by ID from the session (using Session.get()), and update it. Update queries are useful to update many rows at once.

Hibernate Criteria with joins not generating correct SQL

I'm trying to execute a Criteria query but when I execute the code, the SQL only brings up company_synonym in the from clause, not the other two.
The mappings are:
Company has 1-to-Many CompanySynonyms
Company has 1-to-Many CompanyProductRoles
I want the query to return CompanySynonym objects, so I tried this:
Criteria criteria = session.createCriteria(CompanySynonym.class, "cs")
.createAlias("cs." + CompanySynonym.COMPANY, "cmp")
.createAlias("cmp." + Company.COMPANY_PRODUCT_ROLE, "cpr")
.add(Restrictions.eq("cs." + CompanySynonym.TYPE, "1234"))
.add(Restrictions.eq("cpr." + CompanyProductRole.PRODUCT_ID, Integer.valueOf(productId)))
.add(Restrictions.isNotNull("cpr." + CompanySynonym.Company_ID))
.add(Restrictions.eq("cpr." + CompanyProductRole.ROLE_CODE, "AB"));
And it generates this SQL:
select this_.company_syn_id_i as company1_0_0_, this_.company_id_i as company5_0_0_, this_.cmp_syn_end_d as cmp2_0_0_, this_.cmp_syn_type_cd_c as cmp3_0_0_, this_.cmp_syn_c as cmp4_0_0_
from product.dbo.company_synonym this_
where this_.cmp_syn_type_cd_c=?
and cpr1_.product_id_i=?
and cpr1_.company_id_i is not null
and cpr1_.role_cd_c=c
What am I missing or doing wrong here?

Getting org.hibernate.hql.ast.QuerySyntaxException though it works fine sql server?

i have below code snippet. It throws the exception at line 3 but query works fine managemnt studio(sql server 2005)
String query = "select * from user where userId=" + profileId
+ " and spaceName='" + spaceName + "'";
Session session = HibernateUtil.getSession();
List<PersonDetailsData> personDetailsData = new ArrayList<PersonDetailsData>(
session.createQuery(query).list()); //line 3
Here is the exception
org.hibernate.hql.ast.QuerySyntaxException: unexpected token: * near
line 1, column 8 [select * from user where userId=216 and
spaceName='DIG']
I am not able to figure out what's the problem with query when it is running fine in management sudio?
It's native query, not hql.
If you have mapped table field to class fields you need
session.createSQLQuery(query, PersonDetailsData.class).list();
or create hql type query -
select p from PersonDetailData p where p.userId = :userId and p.spaceName =:spaceName
and use parameters in query, not direct values.
As you are using sql query so you have to create a sql query such as
sess.createSQLQuery("SELECT * FROM CATS").list();
see the source source

Categories

Resources