Hibernate 6.2+开启二级缓存时忽略EAGER加载问题求助
使用Hibernate 6.2.13.Final搭配JCache/Ehcache实现二级缓存,依赖配置如下:
<dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.13.Final</version> </dependency> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-envers</artifactId> <version>6.2.13.Final</version> </dependency> <!-- API for 2nd Level Cache --> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-jcache</artifactId> <version>6.2.13.Final</version> </dependency> <!-- Implementation for 2nd Level Cache --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <classifier>jakarta</classifier> <version>3.10.8</version> </dependency>
实体类配置了@Cacheable、@Audited及@ManyToOne(fetch = FetchType.EAGER)(已知即时加载是反模式,但当前无法修改架构):
@Entity @Cacheable @Audited @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class FooType { // 实体代码 }
@Entity @Cacheable @Audited @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @ManyToOne(fetch = FetchType.EAGER) private FooType fooType; // 其他实体代码 }
问题现象:
- 单条查询
entityManager.find(Foo.class, id)时,FooType可正常即时加载; - 执行批量查询
final TypedQuery<Foo> fooQuery = entityManager.createQuery("FROM Foo f", Foo.class); final List<Foo> fooList = fooQuery.getResultList();时,FooType变为Hibernate代理对象,被延迟加载。
测试场景验证:
- Hibernate 6.2关闭二级缓存(配置
<property name="hibernate.cache.use_second_level_cache" value="false" />),即时加载恢复正常; - Hibernate 6.1开启二级缓存,即时加载正常;
- Hibernate 6.4.0.CR1开启二级缓存,问题复现。
不确定这是缓存的预期变更还是Bug,Hibernate 6.2迁移文档未提及相关内容,需解决开启二级缓存时实现预期的即时加载。
方法1:在JPQL查询中显式指定JOIN FETCH
修改批量查询的JPQL语句,强制加载关联实体,绕过Hibernate的缓存代理逻辑:
final TypedQuery<Foo> fooQuery = entityManager.createQuery("FROM Foo f JOIN FETCH f.fooType", Foo.class); final List<Foo> fooList = fooQuery.getResultList();
这种方式最直接,确保查询时一次性加载Foo和关联的FooType,不受二级缓存的影响。
方法2:为关联字段添加@Fetch(FetchMode.JOIN)注解
在Foo类的fooType字段上追加@Fetch(FetchMode.JOIN),强制Hibernate使用JOIN方式加载关联实体,即使开启二级缓存:
@Entity @Cacheable @Audited @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @ManyToOne(fetch = FetchType.EAGER) @Fetch(FetchMode.JOIN) private FooType fooType; // 其他实体代码 }
该注解会覆盖Hibernate默认的关联加载策略,确保EAGER关联通过JOIN查询立即加载,而非依赖缓存代理。
方法3:调整Hibernate缓存配置参数
尝试添加以下配置项,禁用缓存的最小化更新策略,可能恢复EAGER加载的原有行为:
<property name="hibernate.cache.use_minimal_puts" value="false" />
这个参数控制Hibernate是否优先从缓存获取数据而非执行查询,关闭后可能会让Hibernate在批量查询时更倾向于直接加载关联实体。
方法4:检查Envers与二级缓存的交互
由于实体使用了@Audited,可能是Envers在Hibernate 6.2+中的缓存策略变更导致的问题。可以尝试添加Envers的缓存配置:
<property name="org.hibernate.envers.cache.strategy" value="read-write" /> <property name="org.hibernate.envers.use_revision_entity_with_native_id" value="true" />
确保Envers的缓存策略与实体的二级缓存策略一致,避免冲突。
补充说明
这个现象大概率是Hibernate 6.2及以上版本的缓存优化行为变更:当关联实体存在于二级缓存中时,Hibernate会返回代理对象,认为可以在需要时从缓存中加载,从而覆盖了FetchType.EAGER的语义。这种变更未在迁移文档中明确提及,属于内部优化逻辑的调整。
内容的提问来源于stack exchange,提问作者user3612610

