Jump to: navigation, search

EclipseLink/Development/JPA 2.0/cache api

Cache APIs

JPA 2.0 Root | Enhancement Request

Issue Summary

In JPA 2.0 the specification has added a Cache API obtainable from an EntityManagerFactory. This simple API provides developers with rudimentary access and control of a second level cache. User's can interrogate the cache through a contains(Class, Object) API call and cause cached data to be evicted from the cache through an evict(Class, Object), evict(Class) or evictAll() API call.

See The JPA 2.0 Final Release Specification section 7.10 p.306 for details.

General Solution

Should be simple to equate evict to invalidate.

Work Required

  1. Develop tests
    approx 1 day
  2. Implement API
    approx 2 days

Implementation Details

The Cache API consists of following 4 methods

  1. contains(Class cls, Object primaryKey) --- checks whether the class with the specified primary key
    is contained in IdentityMap.
  2. evict(Class cls, Object primaryKey) --- invalidates the specified object in the IdentityMap
  3. evict(Class cls) --- invalidates the specified class in the IdentityMap.
  4. evictAll() --- Invalidates all classes in the IdentityMap.

Refactor 20100322

  • See https://bugs.eclipse.org/bugs/attachment.cgi?id=165763&action=diff
  • The following details describe changes to the original SVN 2896 implementation of Dec 2008 (Thank you very much Darani Y.). These modifications based on a review by Doug C. will arrive in Trunk around April 2010.
  • The implementation was modified on the following dates as follows...
    • 20100201: Rev# 6463 - bug# 301063 : Refactor CacheImpl to implement the EclipseLink API interface JpaCache (which implements Cache from JPA 2.0 via the new functions - clear(), clear(Class), clearQueryCache(), clearQueryCache(queryName), timeToLive(Object), isValid(Object), isValid(Class, id), print(), print(Class), printLocks(), validate(), getObject(Class, id), getObject(Object), putObject(Object), removeObject(Class), removeObject(Class, id), containsObject(Object), evictObject(Object), getId(Object)
    • 20100126: Rev# 6406 - bug# 298985 : Refactor Vector usage to Id or CacheId
    • 20091124: Rev# 5875 - bug# 272895 : Check invalidation during contains()
    • 20090513: Rev# 4170 - bug# 275953 : Bootstrap performance and compatibility

Analysis

  • Fact: Only @Entity objects are cached. @MappedSuperclass objects are not cached. @Embeddable objects are not cached directly but are cached inside their containing @Entity.

Issue 1: Fix evict() to handle non-Entity classes

  • Cache.evict(Class entity) and Cache.evict(Class entity, Object key) require handling of entity Class parameters that are other than entities like MappedSuperclasses, Embeddables or non-annotated java classes.

Issue 2: Refactor to get IdentityMapAccessor state through EMF reference

  • Doug: "1. Unsure if we need to hold all of this state or just hold the EMF and access

the necessary IdentityMapAccessor through this access."

  • We should also get the serverSession from the EMF when required instead of caching it on the CacheImpl.
    • Wherever we see... ServerSession this.serversession
    • replace it with... emf.getServerSession()

Fixed/Patched in SVN Rev# NNNN

Issue 3: Refactor dependencies to use Interfaces instead of Impl subclasses

  • Doug: "2. We will need to support more then just ServerSession so we need to have

impls such as this just rely on more general interfaces for the shared session such as Session or DatabaseSession interfaces." Fixed/Patched in SVN Rev# NNNN

Issue 4: Handle no CMPPolicy case for getId()

  • Doug: "3. The createPKVector does not handle the case where the descriptor does not

have a CMPPolicy. This occurs when bootstrapping from native metadata."

  • Avoid a possible NPE on the return from descriptor.getCMPolicy()
  • createPKVector changed to createPrimaryKeyFromId to refactor out Vector usage in bug 298985

Issue 5: Handle no associated descriptor for Class parameter

  • Doug: "3. continued...Also does not properly handle the case where the cls provided is not associated with

a descriptor."

  • See contains(Class, Object) and contains(Object).
  • The class may still be associated with a Descriptor if it is a MappedSuperclass though.

Issue 6: Add support for @MappedSuperclass descriptors

  • Doug: "4. Should we support passing in MappedSuperclass or general types from the

entity inheritance tree? I believe we should."

  • MappedSuperclasses do not represent real descriptors - they add functionality to concrete Entities that inherit from them - therefore they should not be evicted.
  • As Gordon mentioned, we should throw an IllegalArgumentException if a Class (abstract class) representing a MappedSuperclass is passed into evict(Class) - however after further discussion between us we will evict implementing subclasses.

Fixed/Patched in SVN Rev# NNNN

Analysis 6:
  • When checking for a MappedSuperclass we cannot use session.getDescriptor(class) or session.getClassDescriptor(class) because both will return the first Entity descriptor in the hierarchy if it exists. We need to check the mappedSuperclassDescriptors Map<MetadataClass, RelationalDescriptor> Map on the Project off the Session.

Issue 7: Throw an IAE for Interfaces and Embeddable classes passed to evict()

  • Should we throw an IllegalArgumentException for interfaces or embeddables passed into evict(class) as well?

Use Cases

Test Model 1

  • The following test model is used to validate eviction of entities at different levels and types of inheritance.
  • In this case we are defining a Joined strategy on the root Entity via @Inheritance(strategy=JOINED) where the two entities have separate tables.
  • Subclassing a MappedSuperclass from a root Entity is currently supported in JPA 2.0, although this is not explicitly stated...
    • See p.53 section 2.11.2 and p.398 section 11.1.34 of the JPA 2.0 specification - "A class designated with the MappedSuperclass annotation can be mapped in the same way as an entity except the the mappings will apply only to its subclasses since no table exists for the mapped superclass itself". Since Entities can extend MappedSuperclasses or other Entities.
    • For references see p.302 listing 10-27 of the JPA 2.0 book Pro JPA2: Mastering the Java Persistence API - where a MappedSuperclass extends an Entity.
  • Class1 (non-persistable Java class)
    • +--- Entity2 (abstract Entity) - cachable
      • +--- MappedSuperclass3 (MappedSuperclass abstract or concrete)
        • +----- Entity4a (concrete Entity) - cachable
          • +--- Class5 (non-persistable Java class)
        • +----- Entity4b (concrete Entity) - cachable

Design

DI 1: How to get the ID when the CMPPolicy is null

        // Handle a null CMPPolicy from a MappedSuperclass
        Object identifier = null;
        if(!aDescriptor.hasCMPPolicy()) {
            // the following code gets the key either from the weaved _persistence_primaryKey field or using valueFromObject) if not weaved
            identifier = aDescriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, (AbstractSession)getSession());
        } else {
            // Get identifier via EMF
            identifier = getEntityManagerFactory().getIdentifier(object);
        }

DI 2: Weaving is not being disabled for the JPA test suite

  • Q)Why when weaving is turned of do I still see weaved entities in the JPA test suite?
  • A)The eclipselink.annotation-model.jar has a copy of all classes unless it is excluded - the weaver for each jar causes us to weave the class because it is in two jars
    <target name="package-annotation" depends="">
 
...
                     excludes="org/eclipse/persistence/testing/models/jpa/xml/**
 
...
                               org/eclipse/persistence/testing/models/jpa/metamodel/**

DI 3: Eviction of a MappedSuperclass also evicts root Entity Superclass

  • Bug # 312503 directly affects the behavior or change in behavior of invalidateClass()
  • This issue has 2 parts.
    • 1) Do we support an inheritance heirarchy that involves Entity parents of MappedSuperclasses?
      • A) Yes, we support an inheritance tree like the following - see Test Model 1 above
        • Entity <-- MappedSuperclass <-- Entity
    • 2) How much do we invalidate when invalidating a MappedSuperclass that is not itself in the cache - but exists indirectly via implementing subclasses and because of 1) entities extended above.
      • Currently we evict everything down from the top root Entity in the inheritance hierarchy (from E1 in Test model 1)
Decision 3:
  • This was discussed with James and then Gordon and peter
    • Eviction with false recurse flag does not evict implementing subclasses
  • It was decided thatdecided that we will implement the change in bug# 312503 and change evict(Class) behavior to evict only the subtree from Class down - and not the entire rooted tree.
Before 3
IdentityMapAccessor.java:834
public void invalidateClass(Class myClass, boolean recurse) {
  if (recurse || ((obj != null) && obj.getClass().equals(myClass))) {
    key.setInvalidationState(CacheKey.CACHE_KEY_INVALID);
  }
Renaming the recurse flag
  • The recurse flag in IdentityMapAccessor.invalidateClass(Class, boolean) will be renamed to recurseAndInvalidateToParentRoot.
Regression Analysis: References and Implementors
  • The following functions use the non-default function call invalidateClass(Class, boolean) that is being modified here to support eviction of a subtree
RepeatableWriteUnitOfWork.mergeChangesIntoParent():289
After 3
IdentityMapAccessor.java:834
public void invalidateClass(Class myClass, boolean recurse) {
  // 312503: When recurse is false we also invalidate all assignable implementing subclasses of [obj]
  if (recurseAndInvalidateToParentRoot || ((obj != null) && (null != myClass) && myClass.isInstance(obj))) {
    key.setInvalidationState(CacheKey.CACHE_KEY_INVALID);
  }

Implementation

Testing

getId() when CMPPolicy is null with weaving on

Thread [Thread-3] (Suspended)	
	ObjectBuilder.extractPrimaryKeyFromObject(Object, AbstractSession, boolean) line: 1917	
	ObjectBuilder.extractPrimaryKeyFromObject(Object, AbstractSession) line: 1905	
	CacheImpl.getId(Object) line: 400	
	CacheImplJUnitTest.testGetId_fromNativeMappedSuperclass_handles_null_cmp3policy_weaving_on() line: 535

getId() when CMPPolicy is null with weaving off

Thread [Thread-3] (Suspended)	
	ObjectBuilder.extractPrimaryKeyFromObject(Object, AbstractSession, boolean) line: 1952	
	ObjectBuilder.extractPrimaryKeyFromObject(Object, AbstractSession) line: 1905	
	CacheImpl.getId(Object) line: 400	
	CacheImplJUnitTest.testGetId_fromNativeMappedSuperclass_handles_null_cmp3policy_and_null_pk_with_weaving_off() line: 618

Release Notes

312503: invalidateClass recurse=false functionality modified

  • In bug# 312503 the non-default function call in IdentityMapAccessor.invalidateClass(Class aClass, boolean recurse) has been modified for the recurse=false case.
    • Old functionality up to 2.0: only aClass itself is invalidated
    • New functionality as of 2.1: aClass and all implementing subclasses are invalidated
  • The recurse=true case - which is the default used in invalidateClass(Class aClass) remains unaffected.

References

Meetings

20100512: 312503: Change IdentityMapAccessor.invalidateClass(Class, false) behaviour to also invalidate subtree

  • After a discussion with James on the current behavior of IdentityMapAccessor.invalidateClass(Class, false)
  • Good talk and decision with Gordon, Peter, Tom, Michael O'Brien in the meeting today
  • see bug# 312503 http://bugs.eclipse.org/312503
    • 1) - we will change the functionality of invalidateClass(Class, boolean recurse) for the case of recurse=false
      • This change will now invalidate the subtree rooted at Class down
      • Where previously only the single Classs was invalidated
    • 2) - we will change the name of the recurse flag to something like invalidateHierachy
      • no functionality change will occur when the recurse flag is true - where the entire tree to the root entity is invalidated (possibly up)
    • 3) We will publish a description of the change in bug# 312503 in the 2.1 release notes for users of the recurse=false method