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

Spring Boot中Class实体懒加载缓存失效问题求助

Class实体缓存失效问题排查与解决

问题场景

Class实体属于读多写少的类型,需要实现全局缓存(包括通过UserClassRegistration懒加载调用、以及其他服务调用的场景)。该实体使用复合主键ClassPK,尝试在ClassRepository的查询方法上添加@Cacheable注解后,缓存未生效——两次调用API仍触发两次数据库查询。

相关代码

1. UserClassRegistration实体

public class UserClassRegistration{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "email", insertable = false, updatable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    User user;

    @JoinColumn(name = "class_id", referencedColumnName = "id", insertable = false, updatable = false)
    @JoinColumn(name = "semester", referencedColumnName = "semester", insertable = false, updatable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private Class aClass;
}

2. Class实体(复合主键)

public class Class {
    @EmbeddedId
    @Expose
    ClassPK classPK;

    String timetable;

    int maxStudent;
}

3. ClassPK主键类

public class ClassPK {
    @Column(name = "id")
    String id;

    @Column(name = "semester")
    String semester;
}

4. 尝试的Repository配置

public interface ClassRepository extends JpaRepository<Class,ClassPK> {
    @Cacheable(value = "classes")
    Class findByClassPK(ClassPK classPk);
}

日志配置

logging.level.org.hibernate.orm.jdbc.bind=TRACE
logging.level.org.springframework.cache=TRACE

解决方法

1. 给复合主键ClassPK重写equals()和hashCode()

Spring Cache依赖缓存键的equals()和hashCode()判断是否命中缓存。如果ClassPK未重写这两个方法,即使属性相同的对象也会被视为不同键,导致缓存失效。

修改ClassPK:

public class ClassPK {
    @Column(name = "id")
    String id;

    @Column(name = "semester")
    String semester;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ClassPK classPK = (ClassPK) o;
        return Objects.equals(id, classPK.id) && Objects.equals(semester, classPK.semester);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, semester);
    }

    // 补充getter/setter(JPA映射需要)
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getSemester() { return semester; }
    public void setSemester(String semester) { this.semester = semester; }
}

2. 启用Spring Cache全局支持

确保项目启动类或配置类上添加@EnableCaching注解,开启Spring Cache功能:

@SpringBootApplication
@EnableCaching
public class YourApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}

3. 配置Hibernate二级缓存覆盖懒加载场景

通过UserClassRegistration懒加载aClass时,调用的是Hibernate的实体查询,默认不走Spring Cache。需配置Hibernate二级缓存来覆盖这类场景:

步骤1:添加依赖(Maven)

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jcache</artifactId>
</dependency>

步骤2:配置application.properties

# 开启Hibernate二级缓存
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
# 指定缓存提供者
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
# 仅对标注缓存的实体启用缓存
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE
# 开启查询缓存(可选,针对JPQL/Criteria查询)
spring.jpa.properties.hibernate.cache.use_query_cache=true

步骤3:给Class实体添加Hibernate缓存注解

因为Class是读多写少,选用READ_ONLY缓存策略:

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Class {
    @EmbeddedId
    @Expose
    ClassPK classPK;

    String timetable;

    int maxStudent;
}

4. 验证缓存生效

重启应用后再次调用API,查看日志:

  • Spring Cache生效:会出现Cache hit for key...或Cache put for key...的TRACE级日志
  • Hibernate二级缓存生效:会出现HHH000359: Optimistic cache hit之类的Hibernate日志

内容的提问来源于stack exchange,提问作者Nguyen Manh Cuong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:30:06