代码之家  ›  专栏  ›  技术社区  ›  James

如何使用Spring数据/JPQL从子类实体属性中进行选择?

  •  0
  • James  · 技术社区  · 4 年前

    我需要查询子类的属性。以下是相关实体:

    @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按学生类型查找课程?

    0 回复  |  直到 4 年前
        1
  •  1
  •   Christian Beikov    4 年前

    这个怎么样:

    @Query("select c from Course c join c.person as p where UPPER(p.studentType.name) = UPPER(?1)")
    List<Course> findByCoursePersonStudentTypeName(String studentTypeName);
    

    这使用了Hibernate的一个称为隐式转换的功能。

        2
  •  1
  •   tremendous7    4 年前

    试试这个

    @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);
    
        3
  •  0
  •   Grzegorz Kawalec    4 年前

    存储库中的以下查询对我有效:

    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);
    

    result

    1. 学生类型
      student_type


    2. person

    3. 大学生
      student

    4. 课程
      course

        4
  •  0
  •   ehe888    4 年前

    老实说,您的设计遵循了非常基本的面向对象原则,并且在未来的维护和可扩展性方面存在问题。

    你在尝试使用一个超级类 Person

    正如其他一些回复所指出的那样,您可以通过使用Hibernate(或其他ORM)提供的功能来实现您想要的。但这个解决方案显然是反模式的,因为它将业务知识泄漏到SQL查询中。

    它将您的设计从面向对象转向面向SQL。

    我的建议是重构您的模型,其中一个选项是泛化您的课程类,这在您的业务领域似乎是多元的:

    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);
    }
    
    ...
    
    

    下面是演示如何使用Hibernate任意映射来处理泛型类型实体的链接。

    https://github.com/zzantozz/testbed/tree/master/hibernate-any (我借用了这个帖子的链接 https://stackoverflow.com/a/7001162/1109002 )

    尽管仍有一些改进空间,但该设计将关注点划分为清晰的边界,并避免将域逻辑泄漏到存储库层。在设计域模型时,如果您必须编写自定义SQL而不是持久性框架提供的默认存储库函数,那么您应该停下来思考模型设计是否有问题。