-->
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.  [ 15 posts ] 
Author Message
 Post subject: Cache update
PostPosted: Mon Jan 19, 2004 7:26 pm 
Newbie

Joined: Thu Oct 09, 2003 7:15 pm
Posts: 11
Location: London, UK
Hello,

I am using the query cache feature to avoid hitting the database for data that does not change frequently. Everything goes fine with retrieval and updates, since Hibernate knows when the object's state changed. However, what can I do regarding inserts? When I insert a new object the cache is not notified (so it seems) and I have to wait until the specified period for a query refresh.

This is new ground for me and there is nothing on the docs regarding this issue, so any help will be highly appreciated.

BTW, I'm using 2.1.1 w/ the default EHCache.

Below are samples of my mappings:

On the app configuration for Spring:

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<value>net/picture.hbm.xml</value>
<value>net/audit.hbm.xml</value>
<value>net/stats.hbm.xml</value>
<value>net/customer.hbm.xml</value>
<value>net/news.hbm.xml</value>
<value>net/office.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.query.substitutions">true=1 false=0</prop>
<prop key="hibernate.connection.pool_size">2</prop>
<prop key="hibernate.statement_cache.size">25</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>

The class in question is mapped as follows:

<class name="net.News" table="news">
<cache usage="read-write"/>
<id name="id" column="id" type="long">
<generator class="native"/>
</id>
<property name="author"/>
<property name="title"/>
<property name="content"/>
<property name="visible" type="boolean"/>
<property name="creationDate" column="creation_date"/>
<property name="lastModifiedDate" column="last_modified_date"/>
</class>

And finally, a snippet of the DAO code:

public List getNews() throws DataAccessException{
return ( List )getHibernateTemplate().execute( new HibernateCallback(){
public Object doInHibernate( Session session ) throws HibernateException{
List result = session.createQuery(
"from News n order by n.creationDate desc" ).setCacheable( true ).
list();
return result;
}
}
);
}

As I said, all works fine for this method and updates. It fails to update the cache on inserts because I think the cache is not notified since there was no change of state.

Cheers
jorge

_________________
The first rule of http://www.jpolls.net is you do not pay for jpolls


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jan 19, 2004 8:44 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
If true, this would be a bug - but looking at the code, I find it really, really difficult to believe....


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 20, 2004 7:48 pm 
Newbie

Joined: Thu Oct 09, 2003 7:15 pm
Posts: 11
Location: London, UK
Well, I added some debug tweak code onto the insert method ( Spring's getHibernateTemplate().insert() )

if (o instanceof News){
getHbernateTemplate().update(o);
}

And it works for the time being -updating forces a cache refresh. I will obviously make inserts slower but my main interest was in having retrievals of the same list cheap by not hitting the db unnecessarely.

Cheers
jorge


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 23, 2004 5:19 pm 
Newbie

Joined: Thu Oct 09, 2003 7:15 pm
Posts: 11
Location: London, UK
Gavin,

Are you positive this query cache behavior is not a bug? If so, could you guide me towards where should I look at to fix it?

Cheers
jorge

_________________
The first rule of http://www.jpolls.net is you do not pay for jpolls


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 28, 2004 3:32 pm 
Regular
Regular

Joined: Tue Dec 02, 2003 6:25 pm
Posts: 61
Location: Dallas, TX
I am having exactly the same problem.

I have a cached query that selects all elements, and the entities are also cached and that works great.

However when I insert, the cache does not update to include my new object. But if I update one of my objects (it doesn't matter whether it's the new one), the query will then include the newly created object.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 28, 2004 3:39 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
Guys, of necessity, I ignore any "bugs" that don't come with code to reproduce them (in a JIRA issue). I simply don't have enough hours in the day to try and reproduce every "bug" that gets reported in the forum - especially since they are mostly not really bugs....


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 28, 2004 4:48 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
I've tested this against current CVS - can't find any problem....


Top
 Profile  
 
 Post subject: Code...
PostPosted: Wed Jan 28, 2004 5:56 pm 
Regular
Regular

Joined: Tue Dec 02, 2003 6:25 pm
Posts: 61
Location: Dallas, TX
Ok, I've come up with a pretty simple test case for this.

The class is called AssetStatus and it is a plain java bean with no surprises, so I won't post the code for it.

I am using Spring to load my SessionFactory. Everything works perfectly, including the caching, except for this error, so I am not including my spring config file or my hibernate.cfg.xml file, but if you like I will also post them.

I am using EHCache 0.7. Here is ehcache.xml. It is exactly what is contained in the default (failsafe) xml file that comes with EHCache.

Code:
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
</ehcache>


Here is AssetStatus.hbm.xml

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>   
   <class name="com.orixcm.prism.business.AssetStatus" table="process_status">
      <cache usage="read-write"/>
      <id name="id" type="long" unsaved-value="null">
         <column name="process_status_id" not-null="true"/>
         <generator class="identity"/>
      </id>
      <property name="name" column="process_name"></property>
   </class>
</hibernate-mapping>


Here is a test case

Code:
package com.orixcm.prism.business;
import net.sf.hibernate.Session;
import com.orixcm.prism.Prism;
import com.orixcm.prism.data.HibernateHelper;
import net.sf.hibernate.Query;
import junit.framework.TestCase;
import java.util.List;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.HibernateException;

public class TestCache extends TestCase {

   public TestCache(String s){super(s);}

   // My session factory, loaded from a Spring application context
    SessionFactory sf = (SessionFactory) Prism.getAppContext().getBean("prismSessionFactory");

    /**
     * loads all instances of a class, optionally caching them
     **/
    public List loadAll(Session session, Class clazz, boolean cached) throws HibernateException {
      Query query = session.createQuery("from " + clazz.getName());
      if (cached){
         query.setCacheable(true);
      }
      return query.list();
    }

    /**
     * Simply calls loadAll for the AssetStatus class
     **/
    public List loadAllStatuses() throws HibernateException {
        Session session = sf.openSession();
        List list = loadAll(session, AssetStatus.class,true);
        session.close();
        return list;
    }

    /**
     * test caching after insert
     **/
    public void testIt() throws Exception {
        Session session = null;
        session = sf.openSession();
        // flag whether the test case passes or not;
      boolean pass = false;

      // Initialize the cache - this should execute SQL
      System.out.println("Init cache");
        List queryResults = loadAllStatuses();
        System.out.println("list size=" + queryResults.size() + " contents: " + queryResults);
      int initialSize = queryResults.size();

      // Rerun the query - this should NOT execute SQL
      System.out.println("Test cache...");
      queryResults = loadAllStatuses();
        System.out.println("list size=" + queryResults.size() + " contents: " + queryResults);

      //Create an Object
      System.out.println("create object");
      AssetStatus status = new AssetStatus();
        status.setName("new status");
        session.save(status);

      /*
           Run the query again - this SHOULD have the new element in it
            but it does not.
        */
      System.out.println("Test cache...");
      queryResults = loadAllStatuses();
        System.out.println("list size=" + queryResults.size() + " contents: " + queryResults);
      int sizeAfterInsert = queryResults.size();
      // after an insert, the collection should be one element larger
        int expectedSize = initialSize + 1;

        if (sizeAfterInsert != expectedSize)
            System.out.println("***ERROR!  Size after insert should be: " + expectedSize);

        // update the first element in the list, whatever it is.
        session = sf.openSession();
        AssetStatus firstItem = (AssetStatus) queryResults.get(0);
        firstItem.setName(firstItem.getName() + " MODIFIED");
        session.update(firstItem);
        session.flush();
        session.close();

        // Run the query again.  This time it will have our new element in it.
      queryResults = loadAllStatuses();
        System.out.println("list size=" + queryResults.size() + " contents: " + queryResults);
      int sizeAfterUpdate = queryResults.size();
        if (sizeAfterUpdate==initialSize + 1)
            System.out.println("Workaround worked.");

        this.assertEquals(
            "Size after insert should be one greater than initial size",
            new Integer(expectedSize),
            new Integer(sizeAfterInsert)
            );
    }
}



Here are the results:

Code:
DEBUG com.orixcm.prism.testing.TestRunner (TestRunner.java:88) - using testClass: com.orixcm.prism.business.TestCache
DEBUG com.orixcm.prism.testing.TestRunner (TestRunner.java:107) - constructing TestCase for class: com.orixcm.prism.business.TestCache, running test: testIt
WARN net.sf.ehcache.hibernate.Plugin (Plugin.java:95) - Could not find configuration for com.orixcm.prism.business.AssetStatus. Configuring using the defaultCache settings.
WARN net.sf.ehcache.hibernate.Plugin (Plugin.java:95) - Could not find configuration for net.sf.hibernate.cache.UpdateTimestampsCache. Configuring using the defaultCache settings.
WARN net.sf.ehcache.hibernate.Plugin (Plugin.java:95) - Could not find configuration for net.sf.hibernate.cache.QueryCache. Configuring using the defaultCache settings.
.Init cache
Hibernate: select assetstatu0_.process_status_id as process_1_, assetstatu0_.process_name as process_2_ from process_status assetstatu0_
list size=2 contents: [A status, Another status]
Test cache...
list size=2 contents: [A status, Another status]
create object
DEBUG net.sf.hibernate.persister.EntityPersister (EntityPersister.java:491) - Inserting entity: com.orixcm.prism.business.AssetStatus (native id)
Hibernate: insert into process_status (process_name) values (?)
DEBUG net.sf.hibernate.persister.EntityPersister (EntityPersister.java:389) - Dehydrating entity: [com.orixcm.prism.business.AssetStatus#<null>]
Hibernate: select @@identity
DEBUG net.sf.hibernate.persister.EntityPersister (EntityPersister.java:528) - Natively generated identity: 80
Test cache...
list size=2 contents: [A status, Another status]
***ERROR!  Size after insert should be: 3
DEBUG net.sf.hibernate.persister.EntityPersister (EntityPersister.java:631) - Updating entity: [com.orixcm.prism.business.AssetStatus#48]
Hibernate: update process_status set process_name=? where process_status_id=?
DEBUG net.sf.hibernate.persister.EntityPersister (EntityPersister.java:389) - Dehydrating entity: [com.orixcm.prism.business.AssetStatus#48]
Hibernate: select assetstatu0_.process_status_id as process_1_, assetstatu0_.process_name as process_2_ from process_status assetstatu0_
list size=3 contents: [A status MODIFIED, Another status, new status]
Workaround worked.
F
Time: 1.343
There was 1 failure:
1) testIt(com.orixcm.prism.business.TestCache)junit.framework.AssertionFailedError: Size after insert should be one greater than initial size expected:<3> but was:<2>
   at com.orixcm.prism.business.TestCache.testIt(TestCache.java:94)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
   at com.orixcm.prism.testing.TestRunner.main(TestRunner.java:110)

FAILURES!!!
Tests run: 1,  Failures: 1,  Errors: 0



Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 28, 2004 6:44 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
This is, of course, absolutely correct behavior since you did not flush the session that was saving the new object and commit the transaction.


Top
 Profile  
 
 Post subject: good point
PostPosted: Wed Jan 28, 2004 6:51 pm 
Regular
Regular

Joined: Tue Dec 02, 2003 6:25 pm
Posts: 61
Location: Dallas, TX
Good point. The original code I was using does this correctly, however.

I just modified the part of the test case where the object is created to flush, commit, and close the session, and the test case produces exactly the same result.

Here is what that section looks like now:

Code:
   //Create an Object
   System.out.println("create object");
   AssetStatus status = new AssetStatus();
   status.setName("new status");
   Transaction tx = session.beginTransaction();
   session.save(status);
   session.flush();
   tx.commit();
   session.close()


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 29, 2004 1:39 pm 
Regular
Regular

Joined: Tue Dec 02, 2003 6:25 pm
Posts: 61
Location: Dallas, TX
Should I submit this to JIRA? Do you think it is a bug?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 29, 2004 3:11 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
I incredibly much doubt that there is any kind of bug here. As I said, I tested this and it works perfectly. I think you need to spend some time understanding how Hibernate transactions work and the notion of transaction isolation.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 30, 2004 8:16 pm 
Regular
Regular

Joined: Tue Dec 02, 2003 6:25 pm
Posts: 61
Location: Dallas, TX
Ok, after much debugging, I think I've isolated the problem, and I do believe that it was a bug, but it may be fixed now for all I know. I couldn't tell from just browsing CVS. I assume that since your test case ran successfully, it is fixed. However, if you haven't already, you might want to also run the test case using an identity for the primary key. Here's what I did...

I've been working with Hibernate 2.1.1 final.

I reworked my junit test so I could run it with the standard hibernate tests via the Ant build file supplied with hibernate. I refactored the test to use the org.hibernate.test.Simple class instead of my own class.

The test ran successfully.

BUT, the provided mapping for Simple used an assigned id, and I was using an identity id.

I changed the mapping to use an identity generator (using the 'count' property to hold the identifier), and it failed to update the cache after the insert. (I did have to use an older version of hssql db because of the problem mentioned in this email, but I don't think that the different hsqldb version led to my test case failing).

Then after sifting through the source code, I found this on line 875 of SessionImpl stating that the method does not add newly inserted object to the second-level cache:

Code:
                  if (useIdentityColumn) {
                           id = persister.insert(values, object, this);
                           //TODO: does not add newly inserted object to the second-level cache
                           key = new Key(id, persister);
                           checkUniqueness(key, object);
                           persister.setIdentifier(object, id);
                   }


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 30, 2004 8:30 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
Ah. You are right I am wrong. It is a problem affecting identity id generation. I will fix this bug. Thanks for pursuing this.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Feb 01, 2004 12:50 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
I have fixed this in CVS.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 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.