Why is my Hibernate Query returning stale data?

2019-03-21 19:47发布

问题:

Quick Version

Basically, I'm updating a Hibernate Table and subsequent queries are loading a stale value.

Detailed Version

Hibernate (3.3.1.GA) and EhCache (2.4.2).

Persisted Book object with a List<PageContent> of pages and I'm adding a page to the middle of this book. I'm using Databinder/Wicket, though I do not think that is related.

 public void createPageContent(Book book, int index) {
     Databinder.getHibernateSession().lock(book, LockMode.UPGRADE);
     PageContent page = new PageContent(book);
     book.addPage(page, index);
     CwmService.get().flushChanges(); // commits the transaction
 }

The applicable fields/method in Book are:

@OneToMany
@JoinColumn(name="book_id")
@IndexColumn(name="pageNum")
@Cascade({CascadeType.ALL, CascadeType.DELETE_ORPHAN})
private List<PageContent> pages = new ArrayList<PageContent>();

public synchronized void addPage(PageContent page, int index) {
    pages.add(index, page);
}

The end result is that there is a new page added to a list and the database is updated accordingly and I've confirmed this in my datastore. However, the next query for a page, say "Page #4," loads the "old" Page #4 instead of the new Page #4:

criteria.add(Restrictions.eq("book", book));
criteria.add(Restrictions.eq("pageNum", pageNum));
criteria.setCacheable(true);  

So, I grudgingly remove caching from the criteria. It queries the datastore, but still returns the wrong value. However, in both cases, if I wait about 2 minutes, everything is working as expected. I presume caching is still involved. Both PageContent and Book use this caching strategy:

@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

I confess I'm new to caching and just set up this file for the first time. Here's my ehcache.xml:

<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" statistics="false"/>

<!-- Hibernate's Cache for keeping 'lastUpdated' data on each table.  Should never expire. -->
<cache name="org.hibernate.cache.UpdateTimestampsCache" eternal="true" />

<!-- Hibernate's Query Cache - should probably be limited -->
<cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="1000" />

UPDATE: Removing the @Cache annotations on my datastore objects removes the problem. Of course, I would like to cache these objects because page modification is much less frequent than access.

So, thoughts? There are several other issues related as well, including with deleting pages. Everything updates the database as expected, but actual behavior is wonky.

Thanks in advance!

UPDATE #2: Via debugging, I can confirm that the Datastore has the correct information and when the query runs, it falls back on the Second-Level Cache - which has dirty information. I presume it's not up to me to evict from the cache every time the data changes?

回答1:

After CwmService.get().flushChanges(); // commits the transaction do an explicit commit. flush() only flushes the changes to db but does not commit it. I am not sure about flushChanges() though.



回答2:

I discovered the problem, but it introduces something else.

Basically, when modifying a Book object's List<PageContent> field, Hibernate does three things:

  1. Expires the TimeStamp cache entry for both Book and PageContent
  2. Does many queries to reset the pageNum field on each PageContent object
  3. Removes the Book object from the Second Level Cache.

This ensures that subsequent queries will search for new objects, etc. However:

  1. Hibernate fails to remove each renumbered PageContent object from the Second Level Cache

As a result, any query for the list of pages will run properly, but then will fall back on stale Second Level Cache values for the actual data.

I presume this is because Hibernate feels a pageNum change is not a change in data but a change in behind-the-scenes management. However, that is the data that I would like to read and display.

The solution is to manually refresh every page after the insertion/deletion has occurred.