We encountered the following situation with Hibernate 3.5.6 during a series calls in the same Hibernate Session:
1. Early in the processing, we performed a query with setReadOnly(true)
2. Later in a different piece of code, we performed a query with setReadOnly(false)
3. We then tried to update the results of the second query, but the updates never happened to the database
The actual flow is more complicated than above (otherwise it would be trivial to have the first query simply not do a read only query). However, I have written the following test code to demonstrate the problem:
Code:
String hql = "from Acct acct where acct.acctNbr = :acctNbr and acct.corpEntCd = :corpEntCd";
Session session = sessionFactory.openSession();
// Read only query
System.out.println("READONLY QUERY");
Query readOnlyQuery = session.createQuery(hql);
readOnlyQuery.setString("acctNbr", "855500");
readOnlyQuery.setString("corpEntCd", "IL1");
readOnlyQuery.setReadOnly(true);
List<Object> readOnlyResults = readOnlyQuery.list();
Acct readOnlyAcct = (Acct)readOnlyResults.get(0);
System.out.println("readOnlyAcct timestamp: " + readOnlyAcct.getAcctLcts());
// Writable query
System.out.println("NON READONLY QUERY");
Query writableQuery = session.createQuery(hql);
writableQuery.setString("acctNbr", "855500");
writableQuery.setString("corpEntCd", "IL1");
writableQuery.setReadOnly(false);
List<Object> writableResults = writableQuery.list();
Acct writeableAcct = (Acct)readOnlyResults.get(0);
System.out.println("writeableAcct timestamp: " + writeableAcct.getAcctLcts());
// Now try to update the object
System.out.println("UPDATING TIMESTAMP");
Acct updateAcct = (Acct)writableResults.get(0);
updateAcct.setAcctLcts(new Timestamp(System.currentTimeMillis()));
session.update(updateAcct);
session.flush();
System.out.println("updateAcct timestamp: " + updateAcct.getAcctLcts());
// Retrieve the object (will just come from the session cache, but do it for comparison)
System.out.println("GETTING UPDATED OBJECT");
Acct getAcctSession1 = (Acct)session.get(Acct.class, updateAcct.getId());
System.out.println("objects identical? " + (updateAcct == getAcctSession1));
System.out.println("timestamps equal? " + (updateAcct.getAcctLcts().equals(getAcctSession1.getAcctLcts())));
System.out.println("updateAcct timestamp: " + updateAcct.getAcctLcts());
System.out.println("getAcctSession1 timestamp: " + updateAcct.getAcctLcts());
// Now close the session, open a new session and retrieve the object
System.out.println("CLOSING SESSION AND OPENING A NEW SESSION");
session.close();
session = sessionFactory.openSession();
// Retrieve the object
System.out.println("GETTING UPDATED OBJECT AGAIN");
Acct getAcctSession2 = (Acct)session.get(Acct.class, updateAcct.getId());
System.out.println("objects identical? " + (updateAcct == getAcctSession2));
System.out.println("timestamps equal? " + (updateAcct.getAcctLcts().equals(getAcctSession2.getAcctLcts())));
System.out.println("updateAcct timestamp: " + updateAcct.getAcctLcts());
System.out.println("getAcctSession2 timestamp: " + getAcctSession2.getAcctLcts());
session.close();
Running the above code, with SQL logging enabled, you never see an update and it is confirmed because the query in the second session doesn't see the update either.
The output of this code is (truncated the actual SQL):
Code:
READONLY QUERY
hibernate.SQL - select acct0_...
readOnlyAcct timestamp: 2010-12-15 11:42:42.14
NON READONLY QUERY
hibernate.SQL - select acct0_...
writeableAcct timestamp: 2010-12-15 11:42:42.14
UPDATING TIMESTAMP
updateAcct timestamp: 2010-12-15 12:51:05.937
GETTING UPDATED OBJECT
objects identical? true
timestamps equal? true
updateAcct timestamp: 2010-12-15 12:51:05.937
getAcctSession1 timestamp: 2010-12-15 12:51:05.937
CLOSING SESSION AND OPENING A NEW SESSION
GETTING UPDATED OBJECT AGAIN
hibernate.SQL - select acct0_...
objects identical? false
timestamps equal? false
updateAcct timestamp: 2010-12-15 12:51:05.937
getAcctSession2 timestamp: 2010-12-15 11:42:42.14
Now, it's unclear if this is a Hibernate bug or just a lack of documentation regarding read only indicating that once an entity is loaded read only, no matter how it is ever retrieved later in that Session, it will always be read only.
If it's a bug, debugging through the Hibernate code, it looks like the problem occurs in the Loader.loadSingleRow method where it calls getRowFromResultSet:
Code:
result = getRowFromResultSet(
resultSet,
session,
queryParameters,
getLockModes( queryParameters.getLockOptions() ),
null,
hydratedObjects,
new EntityKey[entitySpan],
returnProxies
);
Because the object is already associated with the session, it does not get added to the hydratedObjects list. Then, when initializeEntitiesAndCollections is called, there aren't any objects to trigger this logic:
Code:
if ( hydratedObjects!=null ) {
int hydratedObjectsSize = hydratedObjects.size();
if ( log.isTraceEnabled() ) {
log.trace( "total objects hydrated: " + hydratedObjectsSize );
}
for ( int i = 0; i < hydratedObjectsSize; i++ ) {
TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
}
}
And it appears that the logic to set the status of an entity to MANAGED and to perform a deep copy is in the TwoPhaseLoad class.
The end result is that the object returned from the non read only query is actually the read only object already associated with the session. Hence any updates to it are never checked to be persisted to the database.
Any thoughts from the Hibernate folks?