Spring Data JPA:1-N实体结构下非关联表的LEFT JOIN实现问询
嘿,针对你这种Spring Data JPA里非关联表的LEFT JOIN需求,结合你的实体结构,我给你整理了几个实用的解决办法:
方法一:用JPQL手动指定关联条件
因为你的ExtA、ExtB和BaseEntity之间只有通过嵌入ID的字段关联,没有直接的映射关系,所以可以在JPQL里手动写JOIN的关联条件。
比如你要查询某个BaseEntity对应的ExtA和ExtB数据,就可以在Repository接口里定义这样的方法:
@Query("SELECT b, a, bExt FROM BaseEntity b " + "LEFT JOIN ExtA a ON b.id = a.id.id " + "LEFT JOIN ExtB bExt ON b.id = bExt.id.id " + "WHERE b.id = :baseId") List<Object[]> findBaseWithExts(@Param("baseId") Long baseId);
这里的a.id.id是因为ExtA的嵌入IDRelId里有个id字段(对应BaseEntity的id),所以要通过嵌入ID对象去访问这个字段。
如果觉得返回Object[]不够优雅,还可以自定义一个DTO类来封装结果,前提是DTO要有对应的构造函数:
// 先定义DTO类 public class BaseWithExtsDTO { private BaseEntity baseEntity; private ExtA extA; private ExtB extB; public BaseWithExtsDTO(BaseEntity baseEntity, ExtA extA, ExtB extB) { this.baseEntity = baseEntity; this.extA = extA; this.extB = extB; } // getter和setter可选 } // 然后在Repository里写JPQL @Query("SELECT new com.yourpackage.BaseWithExtsDTO(b, a, bExt) FROM BaseEntity b " + "LEFT JOIN ExtA a ON b.id = a.id.id " + "LEFT JOIN ExtB bExt ON b.id = bExt.id.id " + "WHERE b.id = :baseId") List<BaseWithExtsDTO> findBaseWithExtsDTO(@Param("baseId") Long baseId);
方法二:使用原生SQL查询
如果JPQL满足不了复杂的数据库特定语法需求,或者你更习惯写原生SQL,也可以用这种方式:
@Query(value = "SELECT b.*, a.*, be.* FROM base_entity b " + "LEFT JOIN ext_a a ON b.id = a.ID " + "LEFT JOIN ext_b be ON b.id = be.ID " + "WHERE b.id = :baseId", nativeQuery = true) List<Object[]> findBaseWithExtsNative(@Param("baseId") Long baseId);
注意这里的表名和字段名要和数据库里的实际名称完全对应,比如ext_a是ExtA对应的表,a.ID对应RelId里的ID字段(因为你用了@Column(name = "ID"))。
方法三:用CriteriaQuery动态构建查询
如果需要根据不同的参数动态拼接JOIN条件或者过滤条件,可以用JPA的CriteriaQuery来手动构建查询,比如在Service层注入EntityManager:
@Service public class BaseEntityService { @Autowired private EntityManager entityManager; public List<Object[]> findBaseWithExtsDynamic(Long baseId) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Object[]> query = cb.createQuery(Object[].class); // 从三个实体类获取查询根 Root<BaseEntity> baseRoot = query.from(BaseEntity.class); Root<ExtA> extARoot = query.from(ExtA.class); Root<ExtB> extBRoot = query.from(ExtB.class); // 指定要查询的字段 query.multiselect(baseRoot, extARoot, extBRoot); // 设置LEFT JOIN的关联条件,以及过滤条件 query.where(cb.and( cb.equal(baseRoot.get("id"), extARoot.get("id").get("id")), cb.equal(baseRoot.get("id"), extBRoot.get("id").get("id")), cb.equal(baseRoot.get("id"), baseId) )); // 避免重复数据,可根据需求添加 query.distinct(true); return entityManager.createQuery(query).getResultList(); } }
几个注意点
- 因为是
LEFT JOIN,所以即使某个BaseEntity没有对应的ExtA或ExtB数据,这条记录依然会被返回,对应的ExtA/ExtB对象会是null。 - 如果查询结果出现重复数据,可以在JPQL或原生SQL里加上
DISTINCT关键字,或者用分组来处理。 - 访问嵌入ID的字段时,要注意层级,比如
a.id.buId可以访问RelId里的buId字段,如果需要根据buId过滤也可以用这个条件。
内容的提问来源于stack exchange,提问作者dash1e
相关产品推荐
相关产品推荐

