ORM - Caching

edit

ORM - Caching

Hibernate provides two levels of caching:

  • First-level cache (L1) — the ORM session itself. Automatic, per-request, always on. Loading the same entity twice in one request returns the same instance. See ORM - Sessions and Transactions
  • Second-level cache (L2) — shared across requests. Caches entity data and query results so repeated loads don't hit the database. Must be explicitly enabled

This page covers the L2 cache and query cache.

Enabling the L2 Cache

Add these settings to your ORM - Configuration:

this.ormSettings = {
	secondaryCacheEnabled: true,
	cacheProvider: "ehcache"
};

That's enough to enable caching. Lucee generates a default EHCache configuration automatically.

Custom EHCache Configuration

For control over cache sizes, expiry, and disk overflow, provide an ehcache.xml:

this.ormSettings = {
	secondaryCacheEnabled: true,
	cacheProvider: "ehcache",
	cacheconfig: "ehcache.xml"
};

Example ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd"
	updateCheck="false" name="myAppCache">
	<diskStore path="java.io.tmpdir"/>
	<defaultCache
		maxElementsInMemory="10000"
		eternal="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120"
		maxElementsOnDisk="10000000"
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
		<persistence strategy="localTempSwap"/>
	</defaultCache>
</ehcache>

You can add entity-specific <cache> elements for fine-grained control — the cache region name defaults to the fully qualified entity name.

Caching Entities

Mark an entity for L2 caching with the cacheuse component attribute:

component persistent="true" table="products" accessors="true"
	cacheuse="read-write" {

property name="id" fieldtype="id" ormtype="string"; property name="name" ormtype="string";
}

Cache Strategies

Strategy Description Use When
"read-only" Cached data is never modified. Fastest, safest Reference/lookup tables that never change
"nonstrict-read-write" Occasional writes, no strict consistency guarantee Data that changes rarely and where stale reads are acceptable
"read-write" Read/write with soft locks for consistency General-purpose entities with moderate write frequency
"transactional" Full transactional consistency via JTA When you need strict consistency (requires JTA transaction manager)

For most applications, "read-write" is the right choice. Use "read-only" for lookup tables (countries, statuses, categories) that you load frequently but rarely change.

Caching Collections

Cache a relationship's collection separately by adding cacheuse to the relationship property:

property name="items"
	fieldtype="one-to-many"
	cfc="Item"
	fkcolumn="categoryId"
	type="array"
	cacheuse="read-write";

This caches the list of child IDs. The child entities themselves are only cached if their entity CFC also has cacheuse set.

Query Cache

The query cache stores the results of HQL queries. Enable it by passing cacheable: true in query options:

results = ORMExecuteQuery(
	"FROM Product WHERE category = :cat",
	{ cat: "electronics" },
	false,
	{ cacheable: true }
);

The second time this query runs with the same parameters, Hibernate returns the cached result instead of hitting the database.

Note: Query caching requires secondaryCacheEnabled: true. The query cache stores entity IDs — the actual entity data comes from the L2 entity cache. For best results, enable both entity caching and query caching together.

Named Cache Regions

Organise cached queries into named regions:

results = ORMExecuteQuery(
	"FROM Product WHERE active = true",
	{},
	false,
	{ cacheable: true, cachename: "activeProducts" }
);

This lets you evict specific groups of queries without clearing everything.

Cache Eviction

When data changes outside ORM (direct SQL, another application, database triggers), the cache becomes stale. Evict manually with ORMEvictEntity(), ORMEvictCollection(), and ORMEvictQueries():

// Evict a specific entity from L2 cache
ORMEvictEntity( "Product" );              // all Product instances
ORMEvictEntity( "Product", "abc-123" );   // specific instance by PK

// Evict a collection ORMEvictCollection( "Category", "items" ); // all "items" collections on Category
// Evict query cache ORMEvictQueries(); // all cached queries ORMEvictQueries( "activeProducts" ); // specific cache region

When to Use L2 Cache

Good candidates:

  • Reference data loaded frequently but changed rarely (countries, statuses, categories, configuration)
  • Read-heavy entities where the same records are loaded across many requests
  • Query results that are expensive to compute and stable

Bad candidates:

  • Entities that change frequently — the cache overhead (invalidation, locking) outweighs the benefit
  • Entities with large data volumes — memory pressure
  • Data that must always be current (financial transactions, inventory counts)

Diagnosing Cache Behaviour

Enable cache logging in your ORM - Configuration to see hits, misses, and evictions:

this.ormSettings = {
	logCache: true
};

See ORM - Logging and Debugging for full logging setup.

What's Next?

See also