What are user managed caches and what do they offer?

User managed caches is a new concept introduced in Ehcache 3. It offers the ability to create caches that are not managed by a CacheManager. Hence the name of user managed caches.

The objective of this feature is to satisfy cache use cases where the added complexity of a cache manager is of no added value. Ideas are: method local caches, thread local caches or any other place where the lifecycle of the cache is shorter than the application lifecycle.

Limitations

As there is no longer a cache manager offering up services, the main limitation of user managed caches is that the user has to configure all required services by hand. Of course, if you find yourself requiring plenty of services, maybe the cache manager is a better option!

API extensions

While a UserManagedCache extends Cache, it offers additional methods:

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.
 * </P>
 *
 * @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.
   * </P>
   *
   * @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>
   * <P>
   *   Failure to release a resource will not prevent other resources from being released.
   * </P>
   *
   * @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();

}

As can be seen, these methods deal with the lifecycle of the cache and need to be called explicitly.

There is also the following interface which comes into play when a user managed persistent cache is created:

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;
}

Getting started with user managed caches

Starting example with lifecycle

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, again you can either have the builder init() it for you, passing true or
2 pass false and it is up to you to UserManagedCache.init() them, prior to using them.
3 You can use the cache exactly as a managed cache
4 In the same vein, a UserManagedCache requires you to UserManagedCache.close() it explicitly. If you would also use managed caches simultaneously, the CacheManager.close() operation would not impact the user managed cache(s).

From this basic example, explore the API of UserManagedCacheBuilder to find 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 some more advanced setup where there is need to maintain a service instance in order to have working user managed cache.

Example with disk persistent 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<Long, String>("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 next to an id 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 when marked as persistent.
5 To delete the data, after closing the cache, destroy has to be explicitly invoked.
6 It is also your responsibility to stop the persistence service once you are done with the cache.

Example with cache event listeners

Cache event listeners require executor services 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 un-ordered ones.

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

For more information on cache event listeners, see the section dedicated to them.

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 ExecutorService for ordered and unordered events delivery.
2 Provide listener configuration using CacheEventListenerConfigurationBuilder.