如何使用 Spring Data / JPQL 从子类实体 属性 select?
How to select from subclass entity property using Spring Data / JPQL?
我需要查询子类的 属性。以下是涉及的实体:
@Entity
@Data
public class Course {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "personId")
private Person person;
}
@Entity
@Data
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
@Id
private Long id;
private String name;
}
@Entity
@Data
@EqualsAndHashCode(callSuper=true)
public class Student extends Person {
@ManyToOne
@JoinColumn(name="studentTypeId")
private StudentType studentType;
}
@Entity
@Data
public class StudentType {
@Id
private Long id;
@Convert(converter = StudentTypeConverter.class)
private StudentTypeEnum name;
@RequiredArgsConstructor
@Getter
@ToString
public static enum StudentTypeEnum {
GRADUATE("Graduate");
private final String name;
}
}
我正在尝试使用 Spring 数据存储库进行查询:
public interface CourseRepository extends Repository<Course> {
List<Course> findByCoursePersonStudentTypeName(@Param("studentTypeName") String studentTypeName);
}
虽然这不起作用。它会生成错误 No property StudentTypeName found for Person!
。这是有道理的,因为 属性 仅存在于 Student
.
如何编写此方法(最好)或使用 JPQL 按学生类型查找课程?
试试这个
@Query("select c from Course c join c.person as p, Student s where p.id = s.id and s.studentType.name = ?1")
List<Course> findByCoursePersonStudentTypeName(StudentTypeEnum studentTypeEnum);
存储库中的以下查询对我有用:
public interface CourseRepository extends Repository<Course, Long> {
@Query("select c" +
" from Course c" +
" inner join Student s on c.person.id = s.id" +
" where s.studentType is not null" +
" and s.studentType.name = :studentTypeName")
List<Course> findByCoursePersonStudentTypeName(@Param("studentTypeName") StudentType.StudentTypeEnum studentTypeName);
}
下面我将插入您在 @Converter 注释中指示使用的枚举类型转换器代码:
public class StudentTypeConverter implements AttributeConverter<StudentType.StudentTypeEnum, String> {
private final Map<String, StudentType.StudentTypeEnum> groupByName = Arrays
.stream(StudentType.StudentTypeEnum.values())
.collect(Collectors.toMap(StudentType.StudentTypeEnum::getName, type -> type));
@Override
public String convertToDatabaseColumn(StudentType.StudentTypeEnum attribute) {
return attribute.getName();
}
@Override
public StudentType.StudentTypeEnum convertToEntityAttribute(String dbData) {
return groupByName.get(dbData);
}
}
我还在枚举中添加了一种类型
GRADUATE("Graduate"),
OTHER("Other type");
下面给出了方法调用及其结果:
List<Course> result = courseRepository.findByCoursePersonStudentTypeName(StudentType.StudentTypeEnum.GRADUATE);
我也粘贴了数据库中给出的数据:
student_type
人
学生
课程
这个怎么样:
@Query("select c from Course c join c.person as p where UPPER(p.studentType.name) = UPPER(?1)")
List<Course> findByCoursePersonStudentTypeName(String studentTypeName);
这使用了 Hibernate 的一个功能,称为隐式转换。
老实说,您的设计违反了非常基本的面向对象原则,对未来的维护和可扩展性造成了问题。
您正在尝试使用超级 class Person
来生成可以注册课程的不同子类型的人员。但另一方面,您想找到课程 linked 到 Person 的特定子类型,这完全打破了通用设置。这就是为什么 Java 编译器无法自动帮助您 link 对 solid 子类型的查询,并且 Java 运行时给您一个异常,因为他们不知道 Person 的任何子类型是否具有 StudentType 属性。该语言只是拒绝做可能导致错误或意外结果的事情。
正如其他一些回复所指出的,您可以通过使用 Hibernate(或其他 ORM)提供的工具来实现您想要的。但是这个解决方案显然是一种反模式,因为它将业务知识泄漏到 SQL 查询中。
它将您的设计从面向对象转变为面向SQL。
我的建议是重构你的模型,一种选择是泛化你的课程class,这在你的业务领域中似乎是多态性的:
public abstract class Course<T extends Person> {
...
@ManyToOne
@JoinColumn(name = "personId")
private T person;
}
public class CourseRegisteredByStudent extends Course<Student> {
}
public class CourseRegisteredByStaff extends Course<Staff> {
}
public interface CousreRegsiteredByStudentRepositry extends Repository<CousreRegsiteredByStudent> {
List<CousreRegsiteredByStudent> findByType(@Param("studentTypeName") String studentTypeName);
}
...
这里有一个 link 演示如何使用 Hibernate Any 映射来处理泛型实体。
https://github.com/zzantozz/testbed/tree/master/hibernate-any
(我从这个线程 借用 link)
虽然还有一些改进space,但这种设计以清晰的边界划分了关注点,避免了将域逻辑泄漏到 Repository 层。在设计领域模型时,如果您必须编写自定义 SQL 而不是持久性框架提供的默认 Repository 函数,那么您应该停下来思考模型设计是否有问题。
我需要查询子类的 属性。以下是涉及的实体:
@Entity
@Data
public class Course {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "personId")
private Person person;
}
@Entity
@Data
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
@Id
private Long id;
private String name;
}
@Entity
@Data
@EqualsAndHashCode(callSuper=true)
public class Student extends Person {
@ManyToOne
@JoinColumn(name="studentTypeId")
private StudentType studentType;
}
@Entity
@Data
public class StudentType {
@Id
private Long id;
@Convert(converter = StudentTypeConverter.class)
private StudentTypeEnum name;
@RequiredArgsConstructor
@Getter
@ToString
public static enum StudentTypeEnum {
GRADUATE("Graduate");
private final String name;
}
}
我正在尝试使用 Spring 数据存储库进行查询:
public interface CourseRepository extends Repository<Course> {
List<Course> findByCoursePersonStudentTypeName(@Param("studentTypeName") String studentTypeName);
}
虽然这不起作用。它会生成错误 No property StudentTypeName found for Person!
。这是有道理的,因为 属性 仅存在于 Student
.
如何编写此方法(最好)或使用 JPQL 按学生类型查找课程?
试试这个
@Query("select c from Course c join c.person as p, Student s where p.id = s.id and s.studentType.name = ?1")
List<Course> findByCoursePersonStudentTypeName(StudentTypeEnum studentTypeEnum);
存储库中的以下查询对我有用:
public interface CourseRepository extends Repository<Course, Long> {
@Query("select c" +
" from Course c" +
" inner join Student s on c.person.id = s.id" +
" where s.studentType is not null" +
" and s.studentType.name = :studentTypeName")
List<Course> findByCoursePersonStudentTypeName(@Param("studentTypeName") StudentType.StudentTypeEnum studentTypeName);
}
下面我将插入您在 @Converter 注释中指示使用的枚举类型转换器代码:
public class StudentTypeConverter implements AttributeConverter<StudentType.StudentTypeEnum, String> {
private final Map<String, StudentType.StudentTypeEnum> groupByName = Arrays
.stream(StudentType.StudentTypeEnum.values())
.collect(Collectors.toMap(StudentType.StudentTypeEnum::getName, type -> type));
@Override
public String convertToDatabaseColumn(StudentType.StudentTypeEnum attribute) {
return attribute.getName();
}
@Override
public StudentType.StudentTypeEnum convertToEntityAttribute(String dbData) {
return groupByName.get(dbData);
}
}
我还在枚举中添加了一种类型
GRADUATE("Graduate"),
OTHER("Other type");
下面给出了方法调用及其结果:
List<Course> result = courseRepository.findByCoursePersonStudentTypeName(StudentType.StudentTypeEnum.GRADUATE);
我也粘贴了数据库中给出的数据:
student_type
人
学生
课程
这个怎么样:
@Query("select c from Course c join c.person as p where UPPER(p.studentType.name) = UPPER(?1)")
List<Course> findByCoursePersonStudentTypeName(String studentTypeName);
这使用了 Hibernate 的一个功能,称为隐式转换。
老实说,您的设计违反了非常基本的面向对象原则,对未来的维护和可扩展性造成了问题。
您正在尝试使用超级 class Person
来生成可以注册课程的不同子类型的人员。但另一方面,您想找到课程 linked 到 Person 的特定子类型,这完全打破了通用设置。这就是为什么 Java 编译器无法自动帮助您 link 对 solid 子类型的查询,并且 Java 运行时给您一个异常,因为他们不知道 Person 的任何子类型是否具有 StudentType 属性。该语言只是拒绝做可能导致错误或意外结果的事情。
正如其他一些回复所指出的,您可以通过使用 Hibernate(或其他 ORM)提供的工具来实现您想要的。但是这个解决方案显然是一种反模式,因为它将业务知识泄漏到 SQL 查询中。
它将您的设计从面向对象转变为面向SQL。
我的建议是重构你的模型,一种选择是泛化你的课程class,这在你的业务领域中似乎是多态性的:
public abstract class Course<T extends Person> {
...
@ManyToOne
@JoinColumn(name = "personId")
private T person;
}
public class CourseRegisteredByStudent extends Course<Student> {
}
public class CourseRegisteredByStaff extends Course<Staff> {
}
public interface CousreRegsiteredByStudentRepositry extends Repository<CousreRegsiteredByStudent> {
List<CousreRegsiteredByStudent> findByType(@Param("studentTypeName") String studentTypeName);
}
...
这里有一个 link 演示如何使用 Hibernate Any 映射来处理泛型实体。
https://github.com/zzantozz/testbed/tree/master/hibernate-any (我从这个线程 借用 link)
虽然还有一些改进space,但这种设计以清晰的边界划分了关注点,避免了将域逻辑泄漏到 Repository 层。在设计领域模型时,如果您必须编写自定义 SQL 而不是持久性框架提供的默认 Repository 函数,那么您应该停下来思考模型设计是否有问题。