These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: Potential bug for updates after readOnly queries
PostPosted: Wed Dec 15, 2010 3:03 pm 
Beginner
Beginner

Joined: Fri Oct 10, 2003 4:54 pm
Posts: 26
Location: Chicago, IL
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?


Top
 Profile  
 
 Post subject: Re: Potential bug for updates after readOnly queries
PostPosted: Wed Dec 15, 2010 4:59 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
This is documented. The Javadoc for the Query.setReadOnly() method has the following piece of text in it's see also section:

Quote:
The read-only/modifiable setting has no impact on entities/proxies returned by the query that existed in the session before the query was executed.


If you want to change the read-only flag for an existing entity you can call Session.setReadOnly(Object, boolean). You can probably fix your test code by replacing the call to session.update(updateAcct) by a call to session.setReadOnly(updateAcct, false). You may also have to move this so that it appears before you update the timestamp.


Top
 Profile  
 
 Post subject: Re: Potential bug for updates after readOnly queries
PostPosted: Wed Dec 15, 2010 6:14 pm 
Beginner
Beginner

Joined: Fri Oct 10, 2003 4:54 pm
Posts: 26
Location: Chicago, IL
Thanks for the update. I didn't even notice that javadoc because it was in the "see also" section which is usually just links to other methods, not text. And it's hard to read the underlined text there.

For the Hibernate devs, any chance you might consider moving it out of the see also and into the direct Javadoc description on the method?

It might also help to more explicitly call out the fact that this means that retrieving entities with setReadOnly(false) still could result in returning read only entities where updates to those entities will be ignored.

It might also help to have something like a "WARNING" block in the HTML documentation in the setReadOnly section stating this and possibly including a shorter version of the sample code from this post.

I'm only mentioning it because the end result is an extremely subtle side effect that can have significant consequences because it doesn't blow up anywhere, it just silently ignores the updates.

Thank you pointing out Session.setReadOnly as well. In my particular case, I may end up wrapping the non read only queries with something that explicitly forces the Session.setReadOnly status to false for everything returned by the query. Since there's no way for this particular code to know if it was already loaded in read only mode or not.

FYI, I also confirmed that you need to call Session.setReadOnly(entity, false) before actually making the updates because Hibernate will snapshot the state at the point of the setReadOnly call (assuming it wasn't already MANAGED).


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.