使用非主键关联时JOIN FETCH导致Hibernate多查询问题
这个问题我之前也碰到过!核心原因是你用**非主键列customerid**来关联Customer和Order实体,而Hibernate的一级缓存是按主键id来索引存储实体的。当你执行JOIN FETCH查询时,虽然已经一次性加载了Customer和对应的Orders,但Hibernate在给每个Order设置customer属性时,只能通过customerid去查找对应的Customer实例——可一级缓存里根本没有按customerid索引的条目,所以它不得不额外发起一次查询来确认。
下面给你三个可行的解决方案,按实现成本从低到高排序:
方案一:修改Customer的equals/hashCode方法(推荐,无额外依赖)
你的customerid已经标记为@NaturalId且唯一,完全可以用它作为实体相等性判断的依据,而非主键id。这样Hibernate会通过自然键识别实体,不需要再额外查询。
修改后的Customer类equals和hashCode方法:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return msisdn.equals(customer.msisdn) && customerid.equals(customer.customerid); } @Override public int hashCode() { return Objects.hash(msisdn, customerid); }
为什么有效:Hibernate判断实体是否存在于持久化上下文时,会依赖equals/hashCode。用自然键做判断后,它能直接识别出JOIN FETCH加载的Customer和Order关联的Customer是同一个实例,无需再查库。
方案二:启用Hibernate自然键缓存(无需修改实体逻辑)
如果不想改动实体的equals/hashCode,可以通过启用自然键缓存,让Hibernate同时按主键和自然键缓存Customer实例。这样处理Order的关联时,就能直接从缓存中命中,避免额外查询。
步骤1:添加配置
在你的application.properties或application.yml中添加:
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.hibernate.cache.use_natural_id_cache=true
步骤2:引入缓存依赖
比如用Caffeine缓存(轻量高效),Maven依赖:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency>
Gradle依赖:
implementation 'com.github.ben-manes.caffeine:caffeine'
为什么有效:自然键缓存会把Customer实例按customerid索引存储,Hibernate处理Order的customer关联时,直接通过customerid从缓存中取到已加载的实例,不用查库。
方案三:调整关联为基于主键(需修改数据库)
如果允许修改数据库结构,这是最标准的解决方案——把Order表的关联列改为Customer的主键id,彻底避免非主键关联带来的问题。
步骤1:修改Order实体的关联映射
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id", referencedColumnName = "id") private Customer customer;
步骤2:调整数据库表
将orders表的customerid列重命名为customer_id,并建立外键关联到customers表的id列。
为什么有效:基于主键的关联是Hibernate最擅长的场景,它可以直接通过主键缓存找到对应的实体,不会产生任何额外查询。
内容的提问来源于stack exchange,提问作者Anastasios Poimenidis

