如何使用 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);

我也粘贴了数据库中给出的数据:

  1. student_type


  2. 学生

  3. 课程

这个怎么样:

@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 函数,那么您应该停下来思考模型设计是否有问题。