Spring Boot CrudRepository自定义find方法查询非唯一ID列结果重复问题
问题原因
这不是Spring Boot的bug,是JPA实现Hibernate的内置机制导致的:
- JPA规范强制要求所有实体必须具备全局唯一的主键标识,Hibernate处理任意返回实体类的查询结果时,都会优先使用主键值作为key,去当前持久化上下文的一级缓存中匹配已有实体:如果缓存中已经存在对应主键的实体,就直接返回该缓存对象,不会用新返回的数据库行数据覆盖已有实体的字段。
- 你将不唯一的
c字段标记为@Id,查询c='a'的结果时,第一行主键值为a,缓存中不存在对应实体,Hibernate就创建对应实体放入缓存;后续两行主键值同样是a,Hibernate直接复用缓存中的第一个实体对象,因此最终返回三条完全相同的记录。 - 查询
c2字段正常,是因为c2不是主键,不会作为缓存匹配的key,每一行数据库结果都会生成新的实体对象,因此不会出现重复。
无需新增主键列的解决方案
方案1:使用投影接收查询结果(最推荐)
投影不会走实体的缓存映射逻辑,直接映射数据库返回的原始结果,完全规避该问题。
首先定义投影接口:
public interface TProjection { String getC(); String getC2(); }
修改仓库方法返回值为投影类型:
public interface tr extends PagingAndSortingRepository<t, String> { List<TProjection> findAllByc(String c); }
如果业务逻辑需要实体对象,手动把投影属性复制到实体实例即可。
方案2:直接返回原始查询结果自定义映射
通过Tuple类型接收原始查询结果,自己手动映射为实体,完全绕过Hibernate的实体实例化逻辑:
public interface tr extends PagingAndSortingRepository<t, String> { @Query("select t.c as c, t.c2 as c2 from t t where t.c = :c") List<Tuple> findAllBycRaw(@Param("c") String c); }
调用时遍历Tuple列表,手动构造t实体对象即可。
方案3:使用复合主键+自定义持久化逻辑
如果一定要使用实体接收所有查询结果,可以使用复合主键,同时自行区分插入和更新逻辑,避免你提到的save方法不符合预期的问题:
- 首先定义复合主键类:
@Embeddable public class TId implements Serializable { private String c; private String c2; // 省略构造方法、equals、hashCode }
- 实体类使用复合主键:
@Entity @Table(schema = "s") public class t implements Serializable { @EmbeddedId private TId id; // 省略其他字段、getter、setter }
- 自定义
save逻辑:注入EntityManager,当保存的实体c值为b时调用merge方法执行更新,c值为a时调用persist方法执行插入,完全自主控制持久化行为。
内容的提问来源于stack exchange,提问作者KRISTIJAN TOMASINI
相关产品推荐
相关产品推荐

