Overview of User Managed Caches

What are user managed caches and what do they offer?

A user managed cache gives you a simple way to configure a cache directly, without the complexity of setting up or using a CacheManager. The choice whether to use a UserManagedCache rather than a CacheManager usually depends on whether you need all of the built-in functionality of a CacheManager. In cases where your cache requirements are relatively straightforward, and you do not require the full range of features of a CacheManager, consider using a UserManagedCache instead.

Typical scenarios for using a UserManagedCache are: method local caches, thread local caches or any other place where the lifecycle of the cache is shorter than the application lifecycle.

API Extensions

User Managed Cache

If you use a UserManagedCache, you need to configure all required services by hand.

The UserManagedCache class extends the Cache class by offering additional methods:

  • init() - initializes the cache

  • close() - releases the cache resources

  • getStatus() - returns a status

The init and close methods deal with the lifecycle of the cache and need to be called explicitly, whereas these methods are hidden when the cache is inside a CacheManager.

The interface definition is shown in this code:

package org.ehcache;

import java.io.Closeable;

/**
 * Represents a {@link Cache} that is not managed by a {@link org.ehcache.CacheManager CacheManager}.
 * <p>
 * These caches must be {@link #close() closed} in order to release all their resources.
 *
 * @param <K> the key type for the cache
 * @param <V> the value type for the cache
 */
public interface UserManagedCache<K, V> extends Cache<K, V>, Closeable {

  /**
   * Transitions this {@code UserManagedCache} to {@link org.ehcache.Status#AVAILABLE AVAILABLE}.
   * <p>
   * If an error occurs before the {@code UserManagedCache} is {@code AVAILABLE}, it will revert to
   * {@link org.ehcache.Status#UNINITIALIZED UNINITIALIZED} and attempt to properly release all resources.
   *
   * @throws IllegalStateException if the {@code UserManagedCache} is not {@code UNINITIALIZED}
   * @throws StateTransitionException if the {@code UserManagedCache} could not be made {@code AVAILABLE}
   */
  void init() throws StateTransitionException;

  /**
   * Transitions this {@code UserManagedCache} to {@link Status#UNINITIALIZED UNINITIALIZED}.
   * <p>
   * This will release all resources held by this cache.
   * <p>
   * Failure to release a resource will not prevent other resources from being released.
   *
   * @throws StateTransitionException if the {@code UserManagedCache} could not reach {@code UNINITIALIZED} cleanly
   * @throws IllegalStateException if the {@code UserManagedCache} is not {@code AVAILABLE}
   */
  @Override
  void close() throws StateTransitionException;

  /**
   * Returns the current {@link org.ehcache.Status Status} of this {@code UserManagedCache}.
   *
   * @return the current {@code Status}
   */
  Status getStatus();

}

User Managed Persistent Cache

A user managed persistent cache holds cached data in a persistent store such as disk, so that the stored data can outlive the JVM in which your caching application runs.

If you want to create a user managed persistent cache, there is an additional interface PersistentUserManagedCache that extends UserManagedCache and adds the destroy method.

The destroy method deletes all data structures, including data stored persistently on disk, for a PersistentUserManagedCache.

The destroy method deals with the lifecycle of the cache and needs to be called explicitly.

The interface definition is shown in this code:

package org.ehcache;

/**
 * A {@link UserManagedCache} that holds data that can outlive the JVM.
 *
 * @param <K> the key type for the cache
 * @param <V> the value type for the cache
 */
public interface PersistentUserManagedCache<K, V> extends UserManagedCache<K, V> {

  /**
   * Destroys all persistent data structures for this {@code PersistentUserManagedCache}.
   *
   * @throws java.lang.IllegalStateException if state {@link org.ehcache.Status#MAINTENANCE MAINTENANCE} couldn't be reached
   * @throws CachePersistenceException if the persistent data cannot be destroyed
   */
  void destroy() throws CachePersistenceException;
}

Code examples for User Managed Caches

Example of a basic cache lifecycle

Here is a simple example showing a basic lifecycle of a user managed cache:

UserManagedCache<Long, String> userManagedCache =
    UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
        .build(false); (1)
userManagedCache.init(); (2)

userManagedCache.put(1L, "da one!"); (3)

userManagedCache.close(); (4)
1 Create a UserManagedCache instance. You can either pass true to have the builder init() it for you, or you can pass false and it is up to you to init() it prior to using it.
2 Since false was passed in, you have to init() the UserManagedCache prior to using it.
3 You can use the cache exactly as a managed cache.
4 In the same vein, a UserManagedCache requires you to close it explicitly using UserManagedCache.close(). If you are also using managed caches simultaneously, the CacheManager.close() operation would not impact the user managed cache(s).

From this basic example, explore the API of UserManagedCacheBuilder in code or through Javadoc to discover all the directly available features.

The following features apply in the exact same way to user managed caches:

Simply use the methods from UserManagedCacheBuilder which are equivalent to the ones from CacheConfigurationBuilder.

Below we will describe a more advanced setup where you need to maintain a service instance in order to have a working user managed cache.

Example with disk persistence and lifecycle

If you want to use disk persistent cache, you will need to create and lifecycle the persistence service:

LocalPersistenceService persistenceService = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(new File(getStoragePath(), "myUserData"))); (1)

PersistentUserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .with(new UserManagedPersistenceContext<>("cache-name", persistenceService)) (2)
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(10L, EntryUnit.ENTRIES)
        .disk(10L, MemoryUnit.MB, true)) (3)
    .build(true);

// Work with the cache
cache.put(42L, "The Answer!");
assertThat(cache.get(42L), is("The Answer!"));

cache.close(); (4)
cache.destroy(); (5)

persistenceService.stop(); (6)
1 Create the persistence service to be used by the cache for storing data on disk.
2 Pass the persistence service to the builder as well as a name for the cache. Note that this will make the builder produce a more specific type: PersistentUserManagedCache.
3 As usual, indicate here if the data should outlive the cache.
4 Closing the cache will not delete the data it saved on disk, since the cache is marked as persistent.
5 To delete the data on disk after closing the cache, you need to invoke the destroy method explicitly.
6 You need to stop the persistence service once you have finished using the cache.

Example with cache event listeners

Cache event listeners require executor services in order to work. You will have to provide either a CacheEventDispatcher implementation or make use of the default one by providing two executor services: one for ordered events and one for unordered ones.

The ordered events executor must be single threaded to guarantee ordering.

For more information on cache event listeners, see the section Cache Event Listeners.

UserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .withEventExecutors(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(5)) (1)
    .withEventListeners(CacheEventListenerConfigurationBuilder
        .newEventListenerConfiguration(ListenerObject.class, EventType.CREATED, EventType.UPDATED)
        .asynchronous()
        .unordered()) (2)
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(3, EntryUnit.ENTRIES))
    .build(true);

cache.put(1L, "Put it");
cache.put(1L, "Update it");

cache.close();
1 Provide the ExecutorService for ordered and unordered event delivery.
2 Provide a listener configuration using CacheEventListenerConfigurationBuilder.