【问题标题】:How to paging a query in JPA with nativeQuery and Spring PagingAndSortingRepository如何使用 nativeQuery 和 Spring PagingAndSortingRepository 在 JPA 中分页查询
【发布时间】:2019-10-03 20:29:50
【问题描述】:

前几天有同事提出这个问题:Order and group by with one column

已解决。一个简单干净的查询,很酷。这个解决方案似乎是值得的,只是我们需要在 JPA 中实现它。

由于 JPA 不接受 JOIN 中的子查询,因此我们必须将其作为 nativeQuery 进行,但这样做会遇到分页问题,​​因为 JPA 不会将其与本机查询结合使用。 https://docs.spring.io/spring-data/jpa/docs/1.8.0.M1/reference/html/

原生查询@Query 注解允许执行原生查询 通过将 nativeQuery 标志设置为 true。请注意,我们目前不 支持对本机查询执行分页或动态排序 因为我们必须操纵声明的实际查询,我们不能这样做 这对于原生 SQL 来说是可靠的。

我们不知道如何继续。

我们要做的:我们有电话号码、用户和日期的一系列记录,每个用户都可以拨打N次。我们需要获取所有按电话号码分组的记录,并按每组号码的最近日期排序(DESC)。

例如:

有了这些数据:

+--------------+---------------------+-------------+---------------+
| phone_number | registered          | name        | first_surname |
+--------------+---------------------+-------------+---------------+
|    222005001 | 2019-05-10 10:01:01 | Alvaro      | Garcia        |
|    222004001 | 2019-05-13 16:14:21 | David       | Garcia        |
|    111003001 | 2019-05-13 16:14:43 | Roberto     | Martin        |
|    111001000 | 2019-05-13 16:14:50 | Juan Manuel | Martin        |
|    111001000 | 2019-05-13 16:14:50 | Maria       | Alonso        |
|    111001000 | 2019-05-13 16:14:50 | Roberto     | Martin        |
|    333006001 | 2019-05-13 16:14:55 | Benito      | Lopera        |
|    123456789 | 2019-05-13 16:15:00 | NULL        | NULL          |
|    987654321 | 2019-05-13 16:15:08 | NULL        | NULL          |
|    123456789 | 2019-05-13 16:15:13 | NULL        | NULL          |
|    666999666 | 2019-05-13 16:15:18 | NULL        | NULL          |
|    454545458 | 2019-05-13 16:15:27 | NULL        | NULL          |
|    333006001 | 2019-05-13 16:23:36 | Benito      | Lopera        |
|    987654321 | 2019-05-13 16:23:46 | NULL        | NULL          |
|    666999666 | 2019-05-13 16:23:50 | NULL        | NULL          |
|    454545458 | 2019-05-13 16:23:55 | NULL        | NULL          |
|    666999666 | 2019-05-13 16:24:03 | NULL        | NULL          |
|    222004001 | 2019-05-13 16:24:10 | David       | Garcia        |
+--------------+---------------------+-------------+---------------+

像这样对它们进行排序:

+--------------+---------------------+-------------+---------------+
| phone_number | registered          | name        | first_surname |
+--------------+---------------------+-------------+---------------+
|    222004001 | 2019-05-13 16:24:10 | David       | Garcia        |
|    222004001 | 2019-05-13 16:14:21 | David       | Garcia        |
|    666999666 | 2019-05-13 16:24:03 | NULL        | NULL          |
|    666999666 | 2019-05-13 16:23:50 | NULL        | NULL          |
|    666999666 | 2019-05-13 16:15:18 | NULL        | NULL          |
|    454545458 | 2019-05-13 16:23:55 | NULL        | NULL          |
|    454545458 | 2019-05-13 16:15:27 | NULL        | NULL          |
|    987654321 | 2019-05-13 16:23:46 | NULL        | NULL          |
|    987654321 | 2019-05-13 16:15:08 | NULL        | NULL          |
|    333006001 | 2019-05-13 16:23:36 | Benito      | Lopera        |
|    333006001 | 2019-05-13 16:14:55 | Benito      | Lopera        |
|    123456789 | 2019-05-13 16:15:13 | NULL        | NULL          |
|    123456789 | 2019-05-13 16:15:00 | NULL        | NULL          |
|    111001000 | 2019-05-13 16:14:50 | Maria       | Alonso        |
|    111001000 | 2019-05-13 16:14:50 | Roberto     | Martin        |
|    111001000 | 2019-05-13 16:14:50 | Juan Manuel | Martin        |
|    111003001 | 2019-05-13 16:14:43 | Roberto     | Martin        |
|    222005001 | 2019-05-10 10:01:01 | Alvaro      | Garcia        |
+--------------+---------------------+-------------+---------------+

可以用这个查询来完成:

SELECT c.phone_number, c.registered, cl.name, cl.first_surname
FROM callers cl
    INNER JOIN callers_phones cp ON cl.caller_id = cp.caller_id
    RIGHT OUTER JOIN calls c ON c.phone_number = cp.phone_number
    JOIN (
        SELECT phone_number, MAX(registered) AS registered
        FROM calls
        GROUP BY phone_number) aux_c ON aux_c.phone_number = c.phone_number
WHERE c.answered = FALSE
    AND (null is null or null is null or c.registered between null and null)
    AND (null is null or c.phone_number = null)
    AND (null is null or cl.caller_id = null)
ORDER BY aux_c.registered DESC, c.registered DESC

这些是表格:

CREATE TABLE callers
(
    caller_id int NOT NULL UNIQUE AUTO_INCREMENT,
    name varchar(50) NOT NULL,
    first_surname varchar(50) NOT NULL,
    CONSTRAINT callers_pkey PRIMARY KEY (caller_id)
);

CREATE TABLE callers_phones
(
    phone_id int NOT NULL UNIQUE AUTO_INCREMENT,
    caller_id int NOT NULL,
    phone_number int NOT NULL,
    CONSTRAINT callers_phones_pkey PRIMARY KEY (phone_id)
);

ALTER TABLE callers_phones
    ADD CONSTRAINT callers_phones_fkey_callers FOREIGN KEY (caller_id)
    REFERENCES callers (caller_id);

CREATE TABLE calls
(   
    call_id int NOT NULL UNIQUE AUTO_INCREMENT,
    phone_number int NOT NULL,
    answered boolean NOT NULL DEFAULT false,
    registered datetime NOT NULL,
    CONSTRAINT calls_pkey PRIMARY KEY (call_id)
);

问题是我们必须在 JPA 中使用分页来实现它,但是子查询在 JOIN 子句中不起作用,并且分页不适用于 nativeQuery。

这就是我们所做的:

@实体:

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.Id;
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;

@SqlResultSetMapping (name = "MissedCallResult",
        entities = {
                     @EntityResult (entityClass = MissedCallEntity.class,
                             fields = {
                                        @FieldResult (name = "callId", column = "id"),
                                        @FieldResult (name = "phoneNumber", column = "pH"),
                                        @FieldResult (name = "registered", column = "reg"),
                                        @FieldResult (name = "callerName", column = "cN"),
                                        @FieldResult (name = "callerFirstSurname", column = "cFS")
                             })
        })
@NamedNativeQuery (name = "findMissedCalls",
        query = "select c.call_id as id, c.phone_number as pH, c.registered as reg, cl.name as cN, cl.first_surname as cFS "
                + "from callers cl "
                + "    inner join callers_phones cp on cl.caller_id = cp.caller_id "
                + "    right outer join calls c on c.phone_number = cp.phone_number "
                + "    join (select c2.phone_number, MAX(c2.registered) as registered "
                + "        from calls c2 "
                + "        group by c2.phone_number) aux_c on aux_c.phone_number = c.phone_number "
                + "where c.answered = false "
                + "    and (:startDate is null or :endDate is null or c.registered between :startDate and :endDate) "
                + "    and (:callerId is null or cl.caller_id = :callerId) "
                + "    and (:phoneNumber is null or c.phone_number = :phoneNumber) "
                + "order by aux_c.registered desc, c.registered desc",
        resultSetMapping = "MissedCallResult")
@Entity
public class MissedCallEntity
{
    @Id
    private Integer callId;
    private Integer phoneNumber;
    private Date registered;
    private String callerName;
    private String callerFirstSurname;
    private String callerSecondSurname;

...

}

@Repository:

import java.util.Date;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import es.panel.domain.MissedCallEntity;

@RepositoryRestResource (path = "missedCalls", collectionResourceRel = "missedCalls")
public interface MissedCallRepository extends PagingAndSortingRepository<MissedCallEntity, Integer>
{
    @Query (nativeQuery = true, name = "findMissedCalls")
    Page<MissedCallEntity> findMissedCalls(@Param ("startDate") Date startDate,
                                           @Param ("endDate") Date endDate,
                                           @Param ("callerId") Integer callerId,
                                           @Param ("phoneNumber") Integer phoneNumber,
                                           Pageable page);
}

在@Service 中:

public Page<MissedCallEntity> getMissedCalls(Date startDate,
                                                 Date endDate,
                                                 Integer callerId,
                                                 Integer phoneNumber,
                                                 int actualPage,
                                                 int limit)
    {
        Page<MissedCallEntity> calls = mcRepository.findMissedCalls(
                startDate, endDate, callerId, phoneNumber, PageRequest.of(1, 5));

        return calls;
    }

提前致谢!

【问题讨论】:

    标签: mysql spring-data-jpa


    【解决方案1】:

    一个非常简单的解决方案是根据您对计算值(即计数和最大值等)的查询创建一个数据库视图。

    您可以使用 JPA @SecondaryTable 注释将其映射到相关实体,该注释可让您将实体映射到多个表(或视图)。

    在这里,您可以使用标准 JPA/spring 数据功能进行排序和过滤,就像处理任何其他字段一样,您几乎可以删除您编写的所有代码。

    我会进一步详细说明,但是您要实现的目标不是很清楚:您询问的是您尝试的解决方案,而不是问题本身。 MissedCall 也不是实体。您系统中的实体是用户、呼叫、电话等。

    【讨论】:

      猜你喜欢
      • 2017-09-13
      • 2020-04-05
      • 1970-01-01
      • 2012-07-25
      • 2020-01-29
      • 2014-02-15
      • 2020-11-13
      • 2014-08-10
      • 1970-01-01
      相关资源
      最近更新 更多