You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot项目中Query Cache与Specification配合失效问题咨询

解决Spring Data JPA + Hibernate二级缓存在分页查询和Specification查询中失效的问题

看来你在Spring Boot + Spring Data JPA + Hibernate + Ehcache的架构下遇到了二级缓存的特定场景失效问题,我来帮你分析原因并给出针对性解决方案:


一、HQL查询结果用Page<>包装时缓存失效

问题原因

当用Page<>包装HQL查询结果时,Spring Data JPA会自动执行两个独立查询:

  1. 查询当前页的数据列表
  2. 查询总记录数的count统计查询

即使你给数据列表的HQL设置了query.setHint("org.hibernate.cacheable", true),count查询默认不会启用缓存,这就导致整体分页查询无法完全利用二级缓存;另外分页参数(页码、每页条数)会成为缓存key的一部分,若未正确配置缓存区域,也可能导致缓存无法命中。

解决方案

  1. 手动为count查询添加缓存hint
    如果是手动构建HQL查询,需要同时给数据列表查询和count查询都开启缓存:

    // 数据列表查询
    Query listQuery = em.createQuery("SELECT c FROM City c WHERE c.province = :province", City.class);
    listQuery.setParameter("province", "Guangdong");
    listQuery.setHint("org.hibernate.cacheable", true);
    listQuery.setHint("org.hibernate.cacheRegion", "cityList");
    
    // count统计查询
    Query countQuery = em.createQuery("SELECT COUNT(c) FROM City c WHERE c.province = :province");
    countQuery.setParameter("province", "Guangdong");
    countQuery.setHint("org.hibernate.cacheable", true);
    countQuery.setHint("org.hibernate.cacheRegion", "cityCount");
    
    long total = (long) countQuery.getSingleResult();
    List<City> content = listQuery.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
    Page<City> page = new PageImpl<>(content, pageable, total);
    
  2. 使用@Query注解时配置全局缓存提示
    如果你用Spring Data的@Query注解实现分页,可以通过countQuery指定统计查询,并结合@QueryHints为两个查询同时开启缓存:

    @Query(value = "SELECT c FROM City c WHERE c.province = :province",
           countQuery = "SELECT COUNT(c) FROM City c WHERE c.province = :province")
    @QueryHints({
            @QueryHint(name = "org.hibernate.cacheable", value = "true"),
            @QueryHint(name = "org.hibernate.cacheRegion", value = "cityList")
    })
    Page<City> findByProvince(@Param("province") String province, Pageable pageable);
    

    这里的@QueryHints会同时作用于主查询和count查询,确保两者都能利用二级缓存。


二、使用Specification和Criteria查询时缓存失效

问题原因

Spring Data JPA的Specification机制默认不会为构建的CriteriaQuery添加Hibernate缓存hint,Hibernate无法识别该查询需要使用二级缓存,自然不会触发缓存读写操作。

解决方案

你需要自定义Repository实现,手动为Criteria查询添加缓存hint,或者全局扩展SimpleJpaRepository让所有Specification查询自动启用缓存:

  1. 自定义Repository实现类
    先定义自定义Repository接口:

    public interface CityCustomRepository {
        Page<City> findBySpecification(Specification<City> spec, Pageable pageable);
    }
    

    然后实现该接口,在构建查询时添加缓存hint:

    @Repository
    public class CityCustomRepositoryImpl implements CityCustomRepository {
        @PersistenceContext
        private EntityManager em;
    
        @Override
        public Page<City> findBySpecification(Specification<City> spec, Pageable pageable) {
            // 构建数据列表查询
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<City> cq = cb.createQuery(City.class);
            Root<City> root = cq.from(City.class);
            cq.where(spec.toPredicate(root, cq, cb));
            
            TypedQuery<City> listQuery = em.createQuery(cq);
            listQuery.setHint("org.hibernate.cacheable", true);
            listQuery.setHint("org.hibernate.cacheRegion", "cityList");
            listQuery.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize());
            List<City> content = listQuery.getResultList();
    
            // 构建count统计查询并添加缓存hint
            CriteriaQuery<Long> countCq = cb.createQuery(Long.class);
            Root<City> countRoot = countCq.from(City.class);
            countCq.select(cb.count(countRoot)).where(spec.toPredicate(countRoot, countCq, cb));
            
            TypedQuery<Long> countQuery = em.createQuery(countCq);
            countQuery.setHint("org.hibernate.cacheable", true);
            countQuery.setHint("org.hibernate.cacheRegion", "cityCount");
            long total = countQuery.getSingleResult();
    
            return new PageImpl<>(content, pageable, total);
        }
    }
    

    最后让你的JpaRepository继承自定义接口:

    public interface CityRepository extends JpaRepository<City, Long>, CityCustomRepository {
    }
    
  2. 全局扩展SimpleJpaRepository(可选)
    如果你希望所有Specification查询都自动启用缓存,可以扩展SimpleJpaRepository,重写相关方法统一添加缓存hint:

    public class CustomSimpleJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {
        private final EntityManager em;
    
        public CustomSimpleJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
            super(entityInformation, entityManager);
            this.em = entityManager;
        }
    
        @Override
        protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) {
            TypedQuery<S> query = super.getQuery(spec, domainClass, sort);
            query.setHint("org.hibernate.cacheable", true);
            query.setHint("org.hibernate.cacheRegion", domainClass.getSimpleName() + "List");
            return query;
        }
    
        @Override
        protected TypedQuery<Long> getCountQuery(Specification<T> spec) {
            TypedQuery<Long> query = super.getCountQuery(spec);
            query.setHint("org.hibernate.cacheable", true);
            query.setHint("org.hibernate.cacheRegion", getDomainClass().getSimpleName() + "Count");
            return query;
        }
    }
    

    然后配置Spring Data使用这个自定义的Repository基类:

    @Configuration
    @EnableJpaRepositories(repositoryBaseClass = CustomSimpleJpaRepository.class)
    public class JpaConfig {
    }
    

额外注意事项

  • 确保你的City实体类已经正确配置Hibernate二级缓存,比如添加@Cache注解:
    @Entity
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "city")
    public class City {
        // 实体字段...
    }
    
  • 确认Ehcache配置文件中已经定义了对应的缓存区域(比如cityList、cityCount、city),并设置合理的过期时间和最大容量。

内容的提问来源于stack exchange,提问作者Maxi Wu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:18:53