1

I'm using spring data jpa in my spring boot application and I need to enable hibernate second level cache for caching some entities. I use Redis as cache server for my app and so I need to use Redisson with its spring boot integration. Here is my some code examples for enable second level cache:

@Entity
@Cacheable
@Cache(region = "baseInfoCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class BaseInfo {
}

application.yml:

spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_query_cache: true
          use_second_level_cache: true
          use_structured_entries: true
          region_prefix: dd_hibernate
          region:
            factory_class: org.redisson.hibernate.RedissonRegionFactory
          redisson:
            fallback: true
            config: redisson/redisson-dev.yaml

redisson-dev.yml:

singleServerConfig:
  address: "redis://localhost:6379"

My BaseInfoRepository extends jpa standard CrudRepository. After calling findAll() method for first time, I can see the cache in redis, so putting data into cache is working without problem. But, I can see hibernate select query logs next times when calling findAll() method. It means hibernate does not load data from my cache. When I override the findAll() method in my repository and put @QueryHints on it, hibernate starts to reading from cache! But I don't want to override native spring data methods. In the other words, I want to use hibernate second level cache in spring data default methods without using @QueryHints. Is that possible?

1
  • 1
    2nd level cache != query cache. It will still issue the query but it will dehydrate the results (existing entities) from the cache. Commented Jun 28, 2022 at 5:38

1 Answer 1

4

To apply second-level cache you need to define @Cacheable and @Cache annotations for the entity. After these changes methods findById, save, deleteof CrudRepository will use second-level cache automatically without additional hints.

But findAll and findAllById methods are something different. They are using JPQL query under the hood for loading records. If we want to cache results of findAll method and avoid multiple query executions we need to use the query cache. After configuring the query cache, by default no queries are cached yet. Queries need to be marked as cached explicitly.
According to hibernate documentation.

By default, individual queries are not cached even after enabling query caching. Each particular query that needs to be cached must be manually set as cacheable. This way, the query looks for existing cache results or adds the query results to the cache when being executed.

We can simplify this restriction for all our repositories and avoid defining hints for each specific repository. We can extend CrudRepository and mark all required queries as cachable one time in our new extended QueryCacheCrudRepository

import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;

import javax.annotation.Nonnull;
import javax.persistence.QueryHint;

@NoRepositoryBean
public interface QueryCacheCrudRepository<T, ID> extends CrudRepository<T, ID> {
  @QueryHints({
          @QueryHint(name = "org.hibernate.cacheable", value = "true")
  })
  Iterable<T> findAll();

  @QueryHints({
          @QueryHint(name = "org.hibernate.cacheable", value = "true")
  })
  Iterable<T> findAllById(Iterable<ID> ids);
}

Then use new QueryCacheCrudRepository instead of CrudRepository for our repositories.

public interface BaseInfoRepository extends QueryCacheCrudRepository<BaseInfo, Long{
    BaseInfo findByName(String name);
}

Example of usage:

    @Transactional
    public Iterable<BaseInfo> getAllBaseInfo() {
        baseInfoRepository.findAll(); // call DB to load all data and put it to query cache and second-level cahce
        return  baseInfoRepository.findAll(); // DB is not called. Data retrived from the cahce
    }

Summary:
Methods that use queries must be marked as cacheable via hint. It is according to documentation. This restriction can be simplified thru an extended CrudRepository. You do not need to override base methods in all entity specific repositories.

Sign up to request clarification or add additional context in comments.

1 Comment

Very nice and comprehensive answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.