将 postgresql join 和 group by query 转换为 JPA 条件 API

Convert postgresql join and group by query to JPA criteria API

我无法将以下 postgresql 查询(使用连接和分组依据)转换为 JPA 标准 API Spring Boot、JPA、Hibernate 应用程序:

select u.id, u.full_name, count(*) project_applications_count from users u
join project_applications pa on pa.created_by = u.id
group by u.id, u.full_name
having count(*) >= 1 and count(*) <= 5

表格如下所示:

create table project_applications (
    id serial primary key,
    ...
    city_id integer not null references cities (id),
    created_by integer not null references users (id)
);

create table users (
    id serial primary key,
    ...
    full_name varchar(100) not null
);

实体看起来像这样:

@Entity
@Table(name = "project_applications")
public class ProjectApplication {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "created_by")
    private User createdBy;

    ...
}

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "full_name")
    private String fullName;

    ...
}

我尝试在线搜索解决方案,但我发现的每个示例都使用了连接或分组依据,但不是两者都使用。

您可能会研究 projections 以实现您想要的。

例如考虑以下投影和存储库:

@Data
@AllArgsConstructor
public class ProjectApplicationSummary {

    private Long id;
    private String fullName;
    private Long count;

}

并且:

@Repository
public interface ProjectApplicationRepository extends JpaRepository<ProjectApplication, Long> {

    @Query(
        """
        SELECT new com.example.springdemo.entities.ProjectApplicationSummary(u.id, u.fullName, count(pa))
        FROM User u, ProjectApplication pa
        GROUP BY u.id, u.fullName
        """
    )
    List<ProjectApplicationSummary> getSummaries();

}

您很可能需要稍微调整一下查询(这需要用 JPQL 进行试验),但除此之外,基本思想就在那里。

我不确定我的解决方案,但它应该是相似的。我从 那里得到了一个想法。也许它可以帮助您解决问题。

    public static Specification<User> getUsers() {
            return Specification.where((root, query, criteriaBuilder) -> {
                CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);

                Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
                Root<ProjectApplication> subRoot = subQuery.from(ProjectApplication.class);

                subQuery
                    .select(criteriaBuilder.count(subRoot))
                    .where(criteriaBuilder.equal(root.get("id"), subRoot.get("createdBy").get("id")));

                query
                    .multiselect(criteriaBuilder.construct(root.get("id"), root.get("fullName")))
                    .groupBy(root.get("id"), root.get("fullName"))
                    .having(criteriaBuilder.and(
                            criteriaBuilder.greaterThanOrEqualTo(subQuery.getSelection(), 1L),
                            criteriaBuilder.lessThanOrEqualTo(subQuery.getSelection(), 5L)));

                return query.getRestriction();
        });
    }

将@akortex 的想法与投影结合使用,我认为这样的事情应该可行:

public class UserSummary {
    private Long id;
    private String fullName;
    private Long count;

    public UserSummary() {
    }

    public UserSummary(Long id, String fullName, Long count) {
        this.id = id;
        this.fullName = fullName;
        this.count = count;
    }

    ... (getters and setters)

}

public List<UserSummary> getSummaries(Integer minProjectAppsCount, Integer maxProjectAppsCount) {
    CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
    CriteriaQuery<UserSummary> query = cb.createQuery(UserSummary.class);  

    Root<ProjectApplication> projectApp = query.from(ProjectApplication.class);
    Join<ProjectApplication, User> userJoin = projectApp.join("createdBy", JoinType.INNER);
    query.multiselect(userJoin.get("id"), userJoin.get("fullName"), cb.count(projectApp))
        .groupBy(userJoin.get("id"), userJoin.get("fullName"));

    List<Predicate> predicates = new ArrayList<>();
    if (minProjectAppsCount != null ) {
        Predicate p = cb.ge(cb.count(projectApp), minProjectAppsCount);
        predicates.add(p);
    }

    if (maxProjectAppsCount != null ) {
        Predicate p = cb.le(cb.count(projectApp), maxProjectAppsCount);
        predicates.add(p);
    }

    
    query.having(predicates.toArray(new Predicate[0]));

    return _entityManager.createQuery(query).getResultList();
}