jpacachingeclipselinkquery-cache

What is the method to cache results using JPA when queried by a non-primary key?


I have a JPA entity EntityA mapped to a table TABLE_A with data like:

OBJ_ID | REF_ID | STATUS | ... where OBJ_ID is the primary key and REF_ID is a foreign key from another table TABLE_B which is not managed by JPA.

I need to be able to:

  1. Return all rows which relate to a given value of a non-primary key, say REF_ID:

    SELECT * FROM TABLE_A WHERE REF_ID = 'REF_ID_1'

  2. Cache results from (1), so I do not hit the database until a write has been performed.

  3. During write (UPDATE / DELETE) within a transaction, read back all rows including un-committed data as in (1):

Code:

fetchAllByRefId('REF_ID_1');
// Fires: SELECT * FROM TABLE_A WHERE REF_ID = 'REF_ID_1'
// Let's assume this returns 1 row

Transaction {
    update('REF_ID_1', 'REF_ID_2');
    // Fires: UPDATE TABLE_A SET REF_ID = 'REF_ID_2' WHERE REF_ID = 'REF_ID_1'
    fetchAllByRefId('REF_ID_1');
    // Fires: SELECT * FROM TABLE_A WHERE REF_ID = 'REF_ID_1'
    // This should return 0 rows
}

fetchAllByRefId('REF_ID_1');
// Fires: SELECT * FROM TABLE_A WHERE REF_ID = 'REF_ID_1'
// If the transaction gets committed, this should return 0 rows
// If rolled back, should return 1 row

I tried to use Eclipselink's implementation of JPA to map the entity against TABLE_A. I am using the object (L2) cache to cache by primary key, and using the query cache to cache results queried by a non-primary key field such as REF_ID or STATUS.

From the documentation of Query Results cache:

The query results cache does not pick up committed changes from the application as the object cache does. It should only be used to cache read-only objects, or should use an invalidation policy to avoid caching stale results. Committed changes to the objects in the result set will still be picked up, but changes that affect the results set (such as new or changed objects that should be added/removed from the result set) will not be picked up.

Further, if the query cache is not the correct thing to use for caching the results, what is the way to cache results fetched by a non-primary key field?

I was not able to find an equivalent to Spring Data's findAll(Example<S>) where I can specify a predicate, in the EntityManager API - everything seems to require the primary key. I have tried CriteriaQuery, but was unable to cache its results.


Solution

  • A query cache, as the doc states, is a query result cache storing the results the database returned. These results are not entities so they aren't maintained within a UnitOfWork or EntityManager context; once read in, they are unchanged until they are cleared. That isn't to say you will get stale data back; there is a separate entity cache that still gets used that you may need to configure (it is on by default). What that means is that if you execute a SELECT * FROM TABLE_A WHERE REF_ID = 'REF_ID_1' query, it will cache and return the rows that match up instead of hitting the database, but use the Entity cache to return the entity objects. So it will return all TableA instances that had a Ref_id = 'REF_ID_1' as of the time the query was cached, but if the entities exist in the entity cache, may show a different ref_id as well as any other state that might have since changed. This would also mean it might exclude new instances or include deleted ones if the query result is very stale; as of EclipseLink 2.5, EclipseLink can invalidate query cache results based on the changes involved, which will cause your query to refresh from the database on the next execution.

    Your issue though seems to be with how query caches must be used. EclipseLink only supports query caches through named queries. See the answer here for details on how to add named queries. In Spring, they are looked up using the method name first before it tries to generate a query.

    So a "TableA.fetchAllByRefId" named query would get used on a TableARepository class method:

    List<TableA> fetchAllByRefId(@Param("REF_ID") String refId);