Pessimistic Lock while executing paginable query - java

tl;dr How to write following query:
SELECT * FROM T_BATCH_DATA WHERE ID IN (SELECT ID FROM T_BATCH_DATA OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY) FOR UPDATE ; using JPQL?
I have a JPQL named query: name=Foo.foo, query = "FROM Foo foo WHERE foo.modified<:date"
which is supposed to retrieve paginated results and set lock to prevent concurrent reading by another POD. Below is the corresponding code:
findFooByLastInteractionUpdate(int pageIndex, int noOfRecords, LocalDateTime retentionDate)
{
return entityManager.createNamedQuery("Foo.foo", Foo.class)
.setParameter("date", date) //
.setFirstResult(pageIndex * noOfRecords) //
.setMaxResults(noOfRecords) //
.setLockMode(LockModeType.PESSIMISTIC_WRITE) //
.getResultList();
}
The unit test is extremally simple and looks like this:
// given:
initializeDb();
LocalDateTime date = ...;
// when:
List<Foo> result = cut.findFooByLastInteractionUpdate(0, 3, date);
// then:
Assert.assertEquals(2, result.size());
And initialization method looks like this:
initializeDb() {
getEntityManager().createNativeQuery("INSERT INTO FOO (ID, ..., ..., ...) VALUES (1, ..., ..., ...)").executeUpdate();
}
However, when I execute this query the resulting logs are:
javax.persistence.PessimisticLockException: Exception [EclipseLink-4002 (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException:
Syntax error in SQL statement ... FROM FOO WHERE (MODIFIED < ?) FOR UPDATE LIMIT[*] ? OFFSET ? ";
SQL statement: SELECT FOO ... FROM FOO WHERE (MODIFIED < ?) FOR UPDATE LIMIT ? OFFSET ? [42000-196]
Error Code: 42000
In H2 manual i found that 42000 code corresponds to syntax error.
Having access only to Oracle DB i tried to execute similar query by hand so:
SELECT * FROM FOO FOR UPDATE FETCH FIRST 10 ROWS ONLY OFFSET 5 ROWS;
which results in ORA-02014.
However I managed to rewrite the query in a way that executes successfully:
SELECT * FROM T_BATCH_DATA WHERE ID IN (SELECT ID FROM T_BATCH_DATA OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY) FOR UPDATE ;
How can I rewrite the JPQL query?

Related

Spring Boot, JPA / Hibernate: How to execute two raw SELECT queries at once?

When I try to execute two SELECT statements at once as follows below, the logging console returns a runtime error:
java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT * FROM ...
The Java source code that generates the statements:
#Repository
public class VehicleObjectDbAccess {
#PersistenceContext
EntityManager entityManager;
public List<Object[]> getObjectById(long objectId, long year)
{
int limit = 10;
String tableName = ("i0i"+year)+objectId;
String queryText =
"START TRANSACTION;"
+ "SELECT t.created INTO #startTime FROM ObjectTable as t WHERE t.speed > 30 LIMIT 1;"
+ "SELECT * FROM ObjectTable WHERE created <= (CASE WHEN #startTime IS NULL THEN NOW() ELSE #startTime END) ORDER BY created DESC LIMIT 10;"
+ "COMMIT;";
Query query = this.entityManager.createNativeQuery(queryText);
return query.getResultList();
}
}
Eventually the java source code above translates to
START TRANSACTION;
SELECT t.created INTO #startTime FROM ObjectTable as t WHERE t.speed > 30 LIMIT 1;
SELECT * FROM ObjectTable WHERE created <= (CASE WHEN #startTime IS NULL THEN NOW() ELSE #startTime END) ORDER BY created DESC LIMIT 10;
COMMIT;
I verified that SQL code running it on a MySQL client and it works properly.
How can I execute these two SELECT statements in a single query?
It seems you can divide your compound sql query to two separate SELECT queries:
Query query1 = this.entityManager.createNativeQuery(queryText1);
Query query2 = this.entityManager.createNativeQuery(queryText2);
After that you can get result lists from them and add result lists to one compound List:
List<Object[]> result = new ArrayList<>();
result.addAll(query1.getResultList());
result.addAll(query2.getResultList());
Just use one select statement:
SELECT *, (SELECT t.created FROM ObjectTable as t WHERE t.speed > 30 LIMIT 1) as x FROM ObjectTable WHERE created <=
(CASE WHEN x IS NULL THEN NOW()
ELSE x
END)
ORDER BY created DESC LIMIT 10;
If you don't want to do this for some reason, create a stored procedure that returns a result set and call it...

JPA EclipseLink - Get multiple objects by primary key maintaining order

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

Inner join query on Hibernate - SQL queries do not currently support iteration

I'm new to hibernate and I've this SQL query which works perfectly
SELECT count(*) as posti_disponibili from occupazione t inner join
(select id_posto_park, max(date_time) as MaxDate from occupazione
group by id_posto_park) tm on t.id_posto_park = tm.id_posto_park and
t.date_time = tm.Maxdate and t.isOccupied = 0
which gives me all the last items with isOccupied = 0
I was porting it into Hibernate, I've tried to use
result = ( (Integer) session.createSQLQuery(query).iterate().next() ).intValue()
to return posti_disponibili but i got this exception
java.lang.UnsupportedOperationException: SQL queries do not currently support iteration
How can i solve this? I cannot find the equivalent HQL query
Thank you
I would suggest you to use
Query#uniqueResult()
which will give you single result.
select count(*) .....
will always return you a single result.
Hibernate support it's own iterator-like scroll:
String sqlQuery = "select a, b, c from someTable";
ScrollableResults scroll = getSession().createSQLQuery(sqlQuery).scroll(ScrollMode.FORWARD_ONLY);
while (scroll.next()) {
Object[] row = scroll.get();
//process row columns
}
scroll.close();

'select' preparedStatement wih Timestamp returns always empty RecordSet

I try to retrieve records from ORACLE database table using JDBC thin driver.
The prepared statement I'm using:
(1)
SELECT (t1.LOGGED_TIME - ?) AS TDIFF, t1.ID, t1.STATUS, t1.LOGGED_TIME, t1.SERVER_TIME
FROM table_1 t1
WHERE (
((t1.LOGGED_TIME - ?) <= INTERVAL '10' DAY)
AND ((t1.LOGGED_TIME - ?) >= INTERVAL '-10' DAY))
ORDER BY t1.LOGGED_TIME DESC
where t1.LOGGED_TIME represents a timestamp column. Every three parameters are identical timestamps set with
java.sql.Timestamp controlTime = Timestamp.valueOf("2014-08-15 03:52:00");
lookupTime.setTimestamp(1, controlTime);
lookupTime.setTimestamp(2, controlTime);
lookupTime.setTimestamp(3, controlTime);
Executing the code works fine - no exceptions or warnings are displayed. Nevertheless the resultset returned by
rs = lookupTime.executeQuery();
is empty.
Setting the query to
(2)
SELECT (t1.LOGGED_TIME - TO_TIMESTAMP('2014-08-15 03:52', 'yyyy-mm-dd hh24:mi')) AS TDIFF, t1.ID, t1.STATUS, t1.LOGGED_TIME, t1.SERVER_TIME
FROM table_1 t1
WHERE (
((t1.LOGGED_TIME - TO_TIMESTAMP('2014-08-15 03:52', 'yyyy-mm-dd hh24:mi')) <= INTERVAL '10' DAY)
AND ((t1.LOGGED_TIME - TO_TIMESTAMP('2014-08-15 03:52', 'yyyy-mm-dd hh24:mi')) >= INTERVAL '-10' DAY))
ORDER BY t1.LOGGED_TIME DESC
returns the expected data.
When I query e.g. strings from another column of the same table with a prepared statement the result is ok.
What I'm missing here? Where is the point? Any idea?
To say it clear: the point is not to identify a kind of wrong date/time format conversion in (2). That will always lead to an oracle error message and can be fixed easily.
The question is: why stays the RecordSet returned by the preparedStatement (1) empty (= not a single record) without any error notification? If the Timestamp format is wrong in any way, why there isn't an error or a warning?
Check your TO_TIMESTAMP format:
TO_TIMESTAMP('2014-08-15 03:52',
'dd.mm.yy hh24:mi')
Aug. 14, 2015, not Aug. 15, 2014
Update
Actually, I get the following error when trying that one:
ORA-01843: not a valid month
01843. 00000 - "not a valid month"
Update2
A Java Timestamp maps to an Oracle DATE data type, not a TIMESTAMP. Don't know if that makes a difference, but you might try TO_TIMESTAMP(?).
I would however change the query to allow use of a potential index on LOGGED_TIME:
SELECT ID, STATUS, LOGGED_TIME, SERVER_TIME
FROM table_1
WHERE LOGGED_TIME BETWEEN ? AND ?
ORDER BY LOGGED_TIME DESC
Then do all the math in Java:
Timestamp controlTime = Timestamp.valueOf("2014-08-15 03:52:00");
Calendar cal = Calendar.getInstance();
cal.setTime(controlTime);
cal.add(Calendar.DAY_OF_MONTH, -10);
lookupTime.setTimestamp(1, new Timestamp(cal.getTimeInMillis()));
cal.setTime(controlTime);
cal.add(Calendar.DAY_OF_MONTH, 10);
lookupTime.setTimestamp(2, new Timestamp(cal.getTimeInMillis()));
try (ResultSet rs = lookupTime.executeQuery()) {
while (rs.next()) {
long tdiffInSeconds = (rs.getTimestamp("LOGGED_TIME").getTime() - controlTime.getTime()) / 1000;
// other code
}
}

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