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

Julien Kronegg (JIRA) noreply at atlassian.com
Tue Feb 23 13:56:48 EST 2010


     [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-4910?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Julien Kronegg updated HHH-4910:
--------------------------------

    Attachment: workaround_HHH-4910.zip

I've also implemented the described workaround (workaround_HHH-4910.zip) and tested it on the same test case: it pass the test.

> 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
>         Attachments: testCase_Hibernate_3.3.2.zip, workaround_HHH-4910.zip
>
>
> 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