JPA按链表大小查找所有订单

时间:2018-06-07 20:55:59

标签: java spring spring-data-jpa jpql

我遇到了一个问题,我不知道是否有可能使用JPA。

我正在尝试使用JPA进行查询并进行以下操作:

获取课程实体字段(id,name)的所有课程,同时使用非持久字段(@Transient),其中将填写与此课程相关的所有学生的计数

这样的事情:

List<Course> courses = courseRepository.findAll();

但是得到(为了示例目的表示为json)

[{1, soccer}, {2, art}, {3, singing}]

我需要这样的东西

[{1, soccer, 2}, {2, art, 0}, {3, singing, 1}]

正如您所看到的值2,0 a 1是所有相关行的学生的计数

Student table
| id | name | description | course |
|  1 | thg1 | a thing     | 1      |
|  2 | thg2 | another one | 1      |
|  3 | thg3 | one more    | 3      |   

Course Table
| id | name | 
|  1 | soccer |     
|  2 | art |     
|  3 | singing |     

因此,限制是一名学生只能参加一门课程。

使用JPA我想选择所有的课程,但由于我使用的是分页,我无法在春季部分(我的意思是作为服务)这样做,我试图直接用JPA做,有吗我能做到这一点吗?也许有规格?想法?

由于

5 个答案:

答案 0 :(得分:5)

您可以使用Hibernate中的@Formula注释:

@Formula("select count(*) FROM student s WHERE s.course = id")
private int totalStudents;
  

有时,您希望数据库为您做一些计算   与JVM相比,您可能还会创建某种虚拟列。   您可以使用SQL片段(也称为公式)而不是映射属性   进入专栏。这种属性是只读的(它的值是   由公式片段计算得出。)

@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
     

SQL片段可以根据需要复杂化甚至包含   子查询。

hibernate reference

备选方案,您可以使用双向关系来计算学生数:

@OneToMany(mappedBy="course")
private List<Student> students;

public int getTotalStudents() {
    return students.size();
}

或使用transient字段:

@OneToMany(mappedBy="course")
private List<Student> students;

@Transient
private int studentCount;

@PostLoad
public void setStudentCount() {
    studentCount = students.size();
}

为了避免Cepr0提到的N + 1问题,您可以将获取模式设置为加入:

@OneToMany(mappedBy="course")
@Fetch(FetchMode.JOIN)
private List<Student> students;

答案 1 :(得分:3)

您可以使用projections来实现您的需求。

假设您使用以下实体:

@Entity
@Data
@AllArgsConstructor
public class Course implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @Transient
    private Long total;

    public Course() {
    }

    public Course(String name) {
        this.name = name;
    }
}
@Entity
@Data
@AllArgsConstructor
public class Student implements Serializable {
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @ManyToOne(optional = false)
    private Course course;

    public Student() {
    }

    public Student(String name, Course course) {
        this.name = name;
        this.course = course;
    }
}

1)然后您可以创建interface based projection

public interface CourseWithCountProjection {
    Integer getId();
    String getName();
    Long getTotal();
}

以及课程资料库中的以下查询方法:

public interface CourseRepo extends JpaRepository<Course, Integer> {
    @Query(value = "" +
            "select " +
            "  c.id as id, " +
            "  c.name as name, " +
            "  count(s) as total " +
            "from " +
            "  Course c " +
            "  left join Student s on s.course.id = c.id " +
            "group by " +
            "  c " +
            "order by " +
            "  count(s) desc" +
            "", countQuery = "select count(c) from Course c")
    Page<CourseWithCountProjection> getProjectionWithCount(Pageable pageable);    
}

在这种情况下,您不需要total中的短暂Course字段,您可以将其删除

请注意,您必须在countQuery注释中添加额外的@Query参数,因为主查询具有分组。

还要注意查询中的别名(c.id as id等) - 当您使用投影时,它们是必需的。

2)另一种方法是在JPQL查询中使用Course构造函数,如@KarolDowbecki已经显示的那样。您可以使用几乎相同的查询:

public interface CourseRepo extends JpaRepository<Course, Integer> {
    @Query(value = "" +
        "select " +
        "  new Course(c.id, c.name, count(s)) " +
        "from " +
        "  Course c " +
        "  left join Student s on s.course.id = c.id " +
        "group by " +
        "  c " +
        "order by " +
        "  count(s) desc" +
        "", countQuery = "select count(c) from Course c")
    Page<Course> getCoursesWithCount(Pageable pageable);
}

<强>已更新

第一个选项更为可取,因为它将模型Course)和视图CourseWithCountProjection)相互划分。

更新2

要获得动态排序,您可以从查询中排除order by,并在查询方法的Pageable参数中提供排序,例如:

@Query(value = "" +
        "select " +
        "  c.id as id, " +
        "  c.name as name, " +
        "  count(s) as total " +
        "from " +
        "  Course c " +
        "  left join Student s on s.course.id = c.id " +
        "group by " +
        "  c " +
        "", countQuery = "select count(c) from Course c")
Page<CourseWithCountProjection> getProjectionWithCount(Pageable pageable);

Page<CourseWithCountProjection> result = parentRepo.getProjectionWithCount(PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "total")));

工作示例在这里:sb-jpa-orderby-related

答案 2 :(得分:2)

您可以使用将创建投影的自定义GROUP BY查询:

@Query("SELECT new full.path.to.Course(c.id, c.name, count(s)) " +
       "FROM Course c " +
       "LEFT JOIN Student s " +
       "GROUP BY c.id")
List<Course> findCourseWithStudentCount();

假设您在Course类中有相应的构造函数,这将返回投影对象。它们不会作为授权进行管理,但是如果您不打算稍后修改它们,它们就会这样做。

答案 3 :(得分:1)

您可以在数据库中提供新视图,该视图会收集您需要的所有信息(例如,使用分组声明)。然后视图应该包含这样的数据,完全按照以后需要的形式:

CourseWithStudents view
| id | course  | students
|  1 | soccer  | 2
|  2 | art     | 0
|  3 | singing | 1  

创建一个新类(可能扩展当前的Course类)可以映射到这个视图:

@Entity
@Table(name="CourseWithStudents")
class CourseWithStudents extends Course {
    // Insert Mapping
}

然后,您可以从新视图中选择所有信息:

List<CourseWithStudents> courses = courseStudentRepository.findAll();

但是由于您无法更新视图,因此只有在您只需要读取访问权限时,此解决方案才有效。如果要在选择后更改课程/学生对象并在数据库中更新,则此方法不起作用。

答案 4 :(得分:0)

将字段标记为@Transient告诉JPA该字段未被保留。因此,JPA不会将数据映射到该字段。

您是否只是希望该字段无法更新?也许你可以删除@Transient注释并将字段上的'updateable'属性设置为false?

How to exclude an entity field when doing an update with JPA