[hibernate-issues] [Hibernate-JIRA] Created: (HHH-4910) L2 parent collection cache eviction when a child is added/updated/removed

Julien Kronegg (JIRA) noreply at atlassian.com
Fri Feb 12 05:55:31 EST 2010


L2 parent collection cache eviction when a child is added/updated/removed
-------------------------------------------------------------------------

                 Key: HHH-4910
                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4910
             Project: Hibernate Core
          Issue Type: Improvement
          Components: core
    Affects Versions: 3.3.1
         Environment: Hibernate 3.3.1, DB2 390
            Reporter: Julien Kronegg
            Priority: Minor


Hibernate should automatically evict the collection cache an entity is persisted/updated/removed.
Some precisions:
- I'm not wanting to update the collection cache, evicting is fine
- Doing persisted/updated/removed operation by using the collection add/remove evicts the collection
- Doing persisted/updated/removed operation on the child entity does not evict the collection

h3. Test case
I have test case with a Parent entity which holds a OneToMany Set<Child> called 'children':

{code}
...
@Entity
@Table(...)
public class Parent {
	@Id
	private long id;

	@OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
	@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Set<Child> children;

	... getters and setters
}

@Entity
@Table(...)
public class Child {
	@Id
	private long id;

	@ManyToOne()
	private Parent parent;

	... getters and setters
}
{code}

Level 2 cache is configured as follow:
- EhCache 1.6.2, default configuration, L2 cache activated
- entities are not cached
- the 'children' collection is cached
- no queries are cached

The L2 cache works as expected when reading the Parent's children collection size: 
1. new EntityManager
2. loading myParent -> makes a SQL query on the Parent table
3. calling myParent.getChildren().size() -> makes a SQL query on the Child table
4. new EntityManager
5. loading myParent -> makes a SQL query on the Parent table (since entities are not cached)
6. calling myParent.getChildren().size() -> hit the L2 cache (no SQL queries)


The test case consists in 
- persisting a new Child with a Parent
- updating a Child's Parent (i.e. changing its Parent for another Parent)
- deleting a Child with a Parent

When doing the test case by managing the Parent.children collection (e.g. myParent.getChildren().add(myNewChild)), the L2 cache works as expected:
the Parent.children collection is evicted from the L2 cache and the next myParent.getChildren().size() makes a SQL query.

But when doing the test case by managing the Child.parent, the L2 cache does not work as expected: the Parent.children collection is not evicted from L2 cache.
Example 1: persist
{code}
Parent myParent = em.find(Parent.class, 1);
System.out.println(em.find(Parent.class, 1).getChildren().size()); // by now, the Parent has 0 children
Child myChild = new Child();
myChild.setParent(myParent); 
em.persist(myChild); 
em.flush();
em = ... // get a new EntityManager
System.out.println(em.find(Parent.class, 1).getChildren().size()); // still 0 children
{code}
	--> myParent.children has still the previous size (i.e. 0 instead of 1)

Example 2: update
{code}
Child myChild = em.find(Child.class, 1);
Parent myParent = em.find(Parent.class, 1);
em.refresh(myParent); // refresh to be sure to bypass the L2 collection cache
System.out.println(em.find(Parent.class, 1).getChildren().size()); // by now, the Parent has 1 children
myChild.setParent(null);
em.flush();
em = ... // get a new EntityManager
System.out.println(em.find(Parent.class, 1).getChildren().size()); // still 1 children
{code}
	-> myParent.children has still the previous size (i.e. 1 instead of 0)

Example 3: remove
{code}
Child myChild = em.find(Child.class, 1);
Parent myParent = em.find(Parent.class, 1);
em.refresh(myParent); // refresh to be sure to bypass the L2 collection cache
System.out.println(em.find(Parent.class, 1).getChildren().size()); // by now, the Parent has 1 children
em.remove(myChild);
em.flush();
em = ... // get a new EntityManager
System.out.println(em.find(Parent.class, 1).getChildren().size()); // still 1 children and raise EntityNotFoundException
{code}
	-> myParent.children has still the previous size and a EntityNotFoundException is raised because the deleted cached element cannot be found in the database

This problem is also reported here:
- http://stackoverflow.com/questions/1505940/hibernate-ehcache-evicting-collections-from-2nd-level-cache-not-synchronized-wit/1857720
- http://stackoverflow.com/questions/1470502/hibernate-clean-collections-2nd-level-cache-while-cascade-delete-items
And is more or less related to the following JIRA issues:
- http://opensource.atlassian.com/projects/hibernate/browse/HHH-496
- http://opensource.atlassian.com/projects/hibernate/browse/HHH-1444
- http://opensource.atlassian.com/projects/hibernate/browse/HHH-1913

h3. Expected behavior
For these test cases, I expected the L2 cache behavior to be as follow:
1. when a new Child is persisted/removed, the collection which own it is evicted from the collection cache
   This means:
{code}
mySessionFactory.evictCollection("Parent.children", myChild.getParent().getId());
{code}
2. when a Child parent changes, the collection which was owning the child AND the collection which own the child are evicted from the collection cache
   This means:
{code}
mySessionFactory.evictCollection("Parent.children", myChildBeforeChange.getParent().getId());
mySessionFactory.evictCollection("Parent.children", myChild.getParent().getId());
{code}
This behavior would probably be implemented in the following classes:
 - org.hibernate.action.EntityInsertAction
 - org.hibernate.action.EntityDeleteAction
 - org.hibernate.action.EntityUpdateAction
See http://opensource.atlassian.com/projects/hibernate/browse/HHH-1913 for a patch temptative (incomplete: some code is missing)

h3. Workaround:
When working with JPA/Hibernate annotations, the above behavior can be implemented using reflection as such (pseudo-code):
1. listen to persist/update/remove entity events: @EntityListeners, @PostPersist, @PostRemove, @PreUpdate
2. in the @PostPersist/@PostRemove entity event listener:
	a. get all @ManyToOne fields/properties of the entity class (i.e. 'children' property of Child) and get the mapped class (i.e. Parent)
	b. get the collectionName (i.e. field name of Parent class with @OneToMany annotation and a type of Collection<Child>)
	c. build the collectionRole as Parent.class.getName()+"."+collectionName
	d. get the entityKey as the identifier of the field found in (a)
	e. call mySessionFactory.evictCollection(collectionRole, entityKey)

3. in the @PreUpdate entity event listener:
	a. get all @ManyToOne fields/properties of the entity class (i.e. 'children' property of Child) and get the mapped class (i.e. Parent)
	b. get the collectionName (i.e. field name of Parent class with @OneToMany annotation and a type of Collection<Child>)
	c. build the collectionRole as Parent.class.getName()+"."+collectionName
	d. get the entityKey as the identifier of the field found in (a) for the current entity
	e. get the previousEntityKey as the identifier of the field found in (a) for the previous entity
	f. if entityKey!=previousEntityKey then
		call mySessionFactory.evictCollection(collectionRole, entityKey)
		call mySessionFactory.evictCollection(collectionRole, previousEntityKey)

We tested this workaround with success. The event listener lasts for about 3 us/call (microseconds) which is okay.

Advantages:
- easy for the programmer (no need for entityManager.refresh(myParent) or mySessionFactory.evictCollections())
- clean code

Disadvantages:
- @EntityListeners annotation to be put on every entity
- requires Hibernate configuration by annotations on entities (does not work with XML configuration)


-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the hibernate-issues mailing list