3
\$\begingroup\$

I'm using JDK 11.

This is my code:

public class ReferenceService {

    private final transient Cache<String, Reference> cache;
    private final transient EntityManager entityManager;

    public ReferenceService(
        Cache<String,Reference> cache,
        EntityManager entityManager
    ) {
        this.cache = cache;
        this.entityManager = entityManager;
    }


    public Optional<Reference> get(String id) {
        return Optional.ofNullable(this.cache.get(id))
            .or(() -> Optional.ofNullable(this.entityManager.find(Reference.class, id)));
    }
}

I would like to know to write this code again in order to it's more elegant:

return Optional.ofNullable(this.cache.get(id))
    .or(() -> Optional.ofNullable(this.entityManager.find(Reference.class, id)));

I guess I'm using too much Optional.ofNullable each time I need to get a reference from cache or from persistence layer.

ADITIONAL CODE

private <T> Cache<String, T> getOrCreateCache(String name, Class<T> type) {
    Cache<String, T> cache = cacheManager.getCache(name, String.class, type);

    if (cache == null) {
        CompleteConfiguration<String, T> config =
            new MutableConfiguration<String, T>()
                .setTypes(String.class, type);

        cache = cacheManager.createCache(name, config);
    }

    return cache;
}
\$\endgroup\$
  • \$\begingroup\$ I've updated my answer now. I hope that answers your questions \$\endgroup\$ – Vogel612 Feb 8 at 17:28
2
\$\begingroup\$

Design Fix

The correct solution to this is to make your cache responsible for retrieving the entity on cache-miss.

Consider for a moment the goals of a cache:

  • Keep expensive to calculate / retrieve information in a manner suitable to quick access
  • Abstract away cache handling (invalidation, cache-misses, ...) from consumers
  • Give certain guarantees for the consistency of cached information

As it stands the way this code is setting up and using the cache utterly fails on the second point. Luckily javax.caching.Cache does support loading entries into the cache on misses using a CacheLoader.

For this to happen you need to create a CacheLoader that is aware of the EntityManager used to access the entities:

[..] config = new MutableConfiguration<String, T>()
  .setTypes(String.class, type)
  .setReadThrough(true) // enables silent loading
  .setCacheLoaderFactory(new FactoryBuilder.SingletonFactory(
    new CacheLoader<String, T>() {
      @Override
      public T load(String key) {
          return entityManager.find(type, key);
      }
      @Override
      public Map<String, T> loadAll(Iterable<? extends String> keys) {
          return Stream.of(keys)
            .collect(Collectors.toMap(Function.identity(), this::load));
      }
   });

You should notice at this point that you're locking yourself into caching on a single primary key (namely String). This is something that might bite you in the backside down the road, but if you only use Strings as primary keys for entities that should be alright.

n.b. that I haven't even checked whether this code compiles.

\$\endgroup\$
  • \$\begingroup\$ Could I ask you for giving some lights me about why it's a better practice a ClassLoader is aware of handling the entityManager or whatever data-access layer? I mean, my straightforward first approach was to try to get the entity from cache, and at the same method on service layer, try to get it using the entity manager. I guess, it's better to use a ClassLoader approach instead of, but I don't quite figure out how come. \$\endgroup\$ – Jordi Feb 11 at 9:53
  • \$\begingroup\$ Not ClassLoader, CacheLoader. It's better practice, because it makes sure the Cache is filled correctly. It also allows you to build on the abstraction that a Cache exposes to you. That way your code doesn't need to care about the peculiarities of the cache mechanic. \$\endgroup\$ – Vogel612 Feb 11 at 13:15
  • \$\begingroup\$ I'm struggling with an issue related with this behavior. Guess on a create(dto), shere dto has no an id field. So, when I call the cache.putIfAbsent(key??, dto), with key hould I use if it's not resolved until it's written on db. I hope I've explained so well. \$\endgroup\$ – Jordi Feb 11 at 14:33
  • \$\begingroup\$ Creating an entry in you database should not affect the cache in any way. That's not the job of the cache. The cache is a tool to avoid roundtripping to the database on repeated reads. A write to the database is usually only reflected in the cache upon a subsequent read request. For that request, you will need to have the key in the first place. So the question as I understood it shouldn't pose itself \$\endgroup\$ – Vogel612 Feb 11 at 15:06
  • \$\begingroup\$ Could take a look on this question? \$\endgroup\$ – Jordi Feb 11 at 15:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.