Spring Boot JPA原生内连接查询执行两次及子实体过滤问题
Spring Boot JPA原生查询触发两次查询的原因及解决办法
问题原因
- 你的
Program实体中@OneToMany关联配置了fetch=FectchType.EAGER(注意代码里拼写错误,应为FetchType.EAGER),JPA加载Program实体时会自动触发关联子实体的查询。 - 原生查询返回的是
Program的结果,但JPA不会将原生查询中关联的Subprogram数据映射到Program的subprogram集合中,而是会单独执行一条select * from subprogram where programid=?的查询,这条查询不会带上你原生查询里的s.status='Active'条件,因此会把该Program下所有状态的Subprogram都查出来。 - 你的原生查询语法存在错误:
nativeQuery=true应该放在@Query注解的参数中,而非SQL语句内部,正确写法应为@Query(value = "SQL语句", nativeQuery = true),当前写法会导致SQL语法错误。
解决办法
方法1:改用JPQL的Fetch Join(推荐)
放弃原生查询,使用JPQL的fetch join一次性加载符合条件的父实体和子实体,同时添加子实体过滤条件:
@Query("SELECT p FROM Program p " + "JOIN FETCH p.subprogram s " + "WHERE p.status = 'Active' AND s.status = 'Active' " + "ORDER BY LOWER(p.programName), LOWER(s.subprogramName)") public List<Program> findActiveProgramAndSubprogram();
同时修改Program实体的关联配置,将fetch改为FetchType.LAZY(避免默认EAGER加载触发额外查询):
@OneToMany(mappedBy = "program", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Subprogram> subprogram = new ArrayList<>();
这种方式会执行一条内连接查询,一次性返回所有符合条件的Program和对应的Active状态Subprogram,不会触发二次查询。
方法2:原生查询+@SqlResultSetMapping
如果必须使用原生查询,需定义结果集映射,让JPA一次性映射父实体和符合条件的子实体:
- 在
Program实体上添加结果集映射配置:
@Entity @Table(name = "program") @SqlResultSetMapping( name = "ProgramWithActiveSubprogram", entities = { @EntityResult(entityClass = Program.class), @EntityResult(entityClass = Subprogram.class) }, fetch = @FetchResult( entityClass = Subprogram.class, joinColumns = @JoinColumn(name = "PROGRAMID", referencedColumnName = "PROGRAMID") ) ) public class Program { // 原有字段... }
- 修改原生查询,指定使用该结果集映射:
@Query(value = "select p.*, s.* from program p inner join subprogram s on p.programid=s.programid and s.status='Active' where p.status='Active' order by lower(p.programname),lower(s.subprogramname)", nativeQuery = true, resultSetMapping = "ProgramWithActiveSubprogram") public List<Object[]> findActiveProgramAndSubprogram();
- 手动处理返回的
Object[]数组,去重Program并将对应的Subprogram添加到集合中。
方法3:手动封装原生查询结果
直接用原生查询返回字段级结果,在业务代码中手动构建实体对象:
@Query(value = "select p.programid, p.programname, s.subprogramid, s.subprogramname from program p inner join subprogram s on p.programid=s.programid and s.status='Active' where p.status='Active' order by lower(p.programname),lower(s.subprogramname)", nativeQuery = true) public List<Object[]> findActiveProgramAndSubprogram();
遍历结果集,手动创建Program和Subprogram对象,去重Program并组装关联的子实体集合。
额外注意点
- 修正代码中
FectchType的拼写错误,改为FetchType,否则会导致编译错误。 Subprogram实体中的subprogramStatus字段对应数据库表的STATUS列,需确保@Column(name = "STATUS")配置正确,否则JPA字段映射错误会导致过滤条件失效。
内容的提问来源于stack exchange,提问作者Iswarya Elango
相关产品推荐
相关产品推荐

