如何仅针对指定Repository禁用Hibernate缓存(SpringBoot+JPA环境)
我之前碰到过一模一样的问题——Hibernate的Session(一级)缓存会把同一主键的实体实例缓存起来,哪怕你查询时加了其他参数,只要主键相同,就会直接返回缓存里的对象,不会重新去数据库查询。下面给你几个针对性的解决方案,不用全局关闭缓存,支持在Repository或Service层配置:
1. 修正实体主键设计(最推荐,根本解决)
你的问题本质可能是SomeView的主键映射不合理:同一id搭配不同languageId明明是不同的视图记录,但你只用Long id作为唯一主键,导致Hibernate把它们当成同一个实体,缓存自然会复用实例。
解决方法是把id和languageId设为复合主键:
- 首先创建复合主键类:
@Embeddable public class SomeViewId implements Serializable { private Long id; private Long languageId; // 必须实现无参构造、equals和hashCode public SomeViewId() {} public SomeViewId(Long id, Long languageId) { this.id = id; this.languageId = languageId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SomeViewId that = (SomeViewId) o; return Objects.equals(id, that.id) && Objects.equals(languageId, that.languageId); } @Override public int hashCode() { return Objects.hash(id, languageId); } // 生成getter和setter } - 修改
SomeView实体类:@Entity public class SomeView { @EmbeddedId private SomeViewId id; // 其他业务字段... // 生成getter和setter } - 更新Repository接口:
public interface ViewRepository extends JpaRepository<SomeView, SomeViewId>, JpaSpecificationExecutor<SomeView> { // 方式1:按复合主键属性命名查询 Optional<SomeView> findByIdIdAndIdLanguageId(Long id, Long lid); // 方式2:用JPQL更直观 @Query("SELECT v FROM SomeView v WHERE v.id.id = :id AND v.id.languageId = :lid") Optional<SomeView> findByViewId(@Param("id") Long id, @Param("lid") Long lid); }
这样修改后,不同的id+languageId组合会被Hibernate识别为完全不同的实体,缓存里会存储各自的实例,自然不会出现结果重复的问题。
2. 在Repository层禁用缓存(针对性强)
如果不能修改主键设计,你可以给Repository的目标方法添加Hibernate查询提示,强制绕过缓存直接查询数据库:
public interface ViewRepository extends JpaRepository<SomeView, Long>, JpaSpecificationExecutor<SomeView> { @QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "false"), @QueryHint(name = org.hibernate.annotations.QueryHints.CACHE_MODE, value = "IGNORE") }) Optional<SomeView> findByIdAndLanguageId(Long id, Long lid); }
CACHEABLE=false:禁用二级缓存CACHE_MODE=IGNORE:绕过一级缓存,强制从数据库获取最新数据
如果要给整个Repository的所有方法都禁用缓存,可以自定义Repository基类,把查询提示配置在基类里,让ViewRepository继承这个基类。
3. 在Service层手动规避缓存
如果不想修改Repository,也可以在Service层通过EntityManager手动控制缓存:
方式1:清空当前Session缓存
@Service public class SomeViewService { @Autowired private ViewRepository repo; @Autowired private EntityManager entityManager; SomeView getSomeView(Long id, Long lid1, Long lid2){ SomeView someView1 = repo.findByIdAndLanguageId(id, lid1).orElse(null); // 清空当前Session的所有缓存,下一次查询会重新从数据库拉取 entityManager.clear(); SomeView someView2 = repo.findByIdAndLanguageId(id, lid2).orElse(null); return someView2; } }
注意:clear()会清空Session里所有缓存的实体,可能影响同一方法内的其他查询,谨慎使用。
方式2:带查询提示的JPQL查询
直接用EntityManager写查询并添加缓存提示,绕过缓存:
SomeView someView2 = entityManager.createQuery( "SELECT v FROM SomeView v WHERE v.id = :id AND v.languageId = :lid", SomeView.class) .setParameter("id", id) .setParameter("lid", lid2) .setHint(org.hibernate.annotations.QueryHints.CACHEABLE, false) .setHint(org.hibernate.annotations.QueryHints.CACHE_MODE, "IGNORE") .getSingleResult();
4. 实体级缓存禁用(不推荐,影响范围大)
如果SomeView是只读视图,且所有对它的查询都不需要缓存,可以直接在实体类上禁用缓存:
@Entity @Cacheable(false) // 禁用Spring缓存 @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONE) // 禁用Hibernate二级缓存 public class SomeView { // ... 字段 }
但这种方式会影响所有对SomeView的查询,不适合只需要禁用某一个Repository缓存的场景。
内容的提问来源于stack exchange,提问作者UnnameDSoS

