Search code examples
springspring-data-jpaspring-dataspring-cache

How to handle updates on cached entites with spring data jpa?


I'm using springs @Cacheable annotation on most of my spring-data repositories. This includes the findById(...) methods. As a result everytime i want to edit and save an entity i get an Exception:

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

Here is my repository:

public interface ProductRepository extends Repository<Product, Long> {

    @Cacheable(cacheNames = "products", key = "#id")
    Optional<Product> findById(Long id);

    @Caching(
        put = {
            @CachePut(cacheNames = "products", key = "#result.id"),
            @CachePut(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
        })
    <S extends Product> S save(S entity);

    @Override
    @Caching(evict = {
        @CacheEvict(cacheNames = "products", key = "#entity.id"),
        @CacheEvict(cacheNames = "productByIsbn", key = "#entity.isbn", condition = "#entity.isbn != null")
    })
    void delete(AbstractProductEntity entity);

    @Cacheable(cacheNames = "productByIsbn", condition = "#isbn != null")
    Optional<Product> findOneByIsbn(String isbn);
}

As far as i understand it, my cached entities are detached from the persistence context̃ and cant be saved aggain.

What is the best way to solve this problem?

I know i could add a second findByUncached method that is only used internaly and bypasses the cache. But to me that looks like a bad solution, as i have to do that for all methods used in code segments where entities are read from the repository and later persisted again.

If i trust that my cache is up-to-date i could reattach the cached object, but how do i do that with spring-data? There is no merge method on the normal repository interfaces.

EDIT1: Thanks to Jens Schauder for pointing out my version problem.

turns out the version property in all cached entities was not properly updated in the cache after calling save(...). While I'm not totally sure why that is, but replacing the @CachePut annotations with @CacheEvict and leave cache updates to the next read resolved this problem. I'm now looking into the way @CachePut is supposed to work.


Solution

  • The problem here is not that the entities are detached. Spring Data JPA performs a merge if you call save for a detached entity. The problem is that the entity is "stale", i.e. its version attribute no longer matches the one in the database because some other transaction updated the entity and thereby also the version.

    In general I'd advise against using a third party caching solution with JPA. JPA hast its own two caching layers. The 1st level cache is the EntityManager itself. More interesting for you is probably the 2nd level cache and possibly the query cache which is Hibernate specific afaik.