通过父类属性查询时获取所有子类型——JPA JOINED继承策略
问题描述
现有如下JPA继承层级结构:
@Entity @Table(name = "user") @Inheritance(strategy = InheritanceType.JOINED) public class User{ private Long id; private String username; } @Entity @Table(name = "patient") @PrimaryKeyJoinColumn(name = "user_id") public class Patient extends User{ //some patient specific fields } @Entity @Table(name = "member") @PrimaryKeyJoinColumn(name = "user_id") public class Member extends User{ //some provider specific fields }
Patient表和Member表均以user_id列作为外键关联User表。根据设计,同一个用户可以同时拥有Patient和Member两种身份,现在需要查询出同时包含Patient和Member详情的用户,但执行如下查询时仅能获取到Member类型的数据:
var users = userRepository.getByUsername(username); @Query(value = "from User u where u.username = :username") List<User> getByUsername(String username);
解决方案
原因说明
使用InheritanceType.JOINED策略时,JPA默认只会返回单一子类实例(通常是查询结果中匹配的第一个子类类型),无法直接让一个User实例同时具备Patient和Member两种类型的数据,因为JPA中一个实体实例只能对应一种具体子类类型。
实现方案
要获取同一用户的Patient和Member双份详情,可通过以下几种方式实现:
- 分别查询子类后合并数据
在Repository中添加针对Patient和Member的查询方法,分别获取对应数据后,将两者属性合并到自定义的DTO中返回:
// 查询Patient数据 @Query("from Patient p where p.username = :username") Patient getPatientByUsername(String username); // 查询Member数据 @Query("from Member m where m.username = :username") Member getMemberByUsername(String username);
业务层调用时,同时获取两个实例,它们对应的User主键一致,可将两者的字段整合到同一个DTO对象中返回给前端。
- 原生SQL关联查询映射到DTO
编写原生SQL关联User、Patient、Member三张表,将所需字段一次性查询出来,再映射到包含所有必要字段的DTO类中:
@Query(value = "SELECT u.id, u.username, p.*, m.* FROM user u " + "LEFT JOIN patient p ON u.id = p.user_id " + "LEFT JOIN member m ON u.id = m.user_id " + "WHERE u.username = :username", nativeQuery = true) List<UserCombinedDTO> getUserCombinedDetails(@Param("username") String username);
需要提前定义UserCombinedDTO类,包含查询返回的所有字段,确保字段名与查询结果列名匹配,或通过@ColumnResult等注解完成字段映射。
- 调整数据模型为关联关系(可选)
如果业务允许,可将继承关系改为关联关系,在User类中添加与Patient、Member的一对一关联:
@Entity @Table(name = "user") public class User{ private Long id; private String username; @OneToOne(mappedBy = "user", fetch = FetchType.EAGER) private Patient patient; @OneToOne(mappedBy = "user", fetch = FetchType.EAGER) private Member member; } @Entity @Table(name = "patient") public class Patient{ @Id private Long id; @OneToOne @JoinColumn(name = "user_id") private User user; // 其他Patient专属字段 } @Entity @Table(name = "member") public class Member{ @Id private Long id; @OneToOne @JoinColumn(name = "user_id") private User user; // 其他Member专属字段 }
这种情况下,可通过fetch join在查询User时同时加载关联的Patient和Member数据:
@Query("from User u left join fetch u.patient left join fetch u.member where u.username = :username") User getUserWithPatientAndMember(String username);
内容的提问来源于stack exchange,提问作者Sivaranjani Duraisamy
相关产品推荐
相关产品推荐

