使用@Query(本机和JPQL)的Spring Data Jpa + Spring Projections为相关实体返回null

Ant*_*fan 2 java spring hibernate spring-data-jpa spring-projections

我想要实现的是使用 JpaRepository 接口在 Spring Data Jpa 中使用 3 种不同的方法编写相同的查询:

  1. 命名方法策略。
  2. @Query 与 JPQL。
  3. @Query 原生 SQL。

在这里,您可以看到我如何使用我尝试选择的所有关系创建访问实体。

public class Visit {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    long visitId;
    LocalDateTime dateFrom;
    LocalDateTime dateTo;
    @Enumerated(EnumType.STRING)
    VisitStatus status;
    @ManyToOne(fetch = FetchType.EAGER)
    @JsonManagedReference
    Doctor doctor;
    @ManyToOne
    @JsonManagedReference
    Patient patient;
    @ManyToMany
    @JsonManagedReference
    List<Disease> diseases;
    @ManyToMany
    @JsonManagedReference
    List<MedicalService> medicalServices;
    String mainSymptoms;
    String treatment;
    String allergy;
    String addiction;
    String comment;
Run Code Online (Sandbox Code Playgroud)

我正在使用 Project Lombok 我不会复制类上方的所有注释。所以这里是实验。我创建的方法应该在给定的时间间隔内返回特定医生的所有访问。

这是我写的方法:

List<VisitView> findByDoctorIdAndStatusAndDateFromGreaterThanEqualAndDateToLessThanEqual
            (long doctorId, VisitStatus visitStatus, LocalDateTime dateFrom, LocalDateTime dateTo);
Run Code Online (Sandbox Code Playgroud)

如您所见,我已经使用Spring Projections实现了VisitView接口。

这里是:

public interface VisitView {
    long getDoctorId();
//    Doctor getDoctor();
//    interface Doctor {
//        String getFirstName();
//        String getLastName();
//    }
    String getDoctorFirstName();
    String getDoctorLastName();
    Long getPatientId();
    long getVisitId();
    LocalDateTime getDateFrom();
    LocalDateTime getDateTo();
    VisitStatus getStatus();
}
Run Code Online (Sandbox Code Playgroud)

使用这种方法一切正常。我可以通过两种方式从 Doctor Entity Class 中获取 Doctor firstName 和 lastName -> 使用 getter 并内置另一个 Doctor 接口来访问实体中的字段。在这里,您可以使用投影接口看到两个 JSON:

[
    {
        "status": "PAID",
        "visitId": 395,
        "dateTo": "2019-04-10T08:30:00",
        "dateFrom": "2019-04-10T08:00:00",
        "doctorId": 401,
        "patientId": 394,
        "doctorFirstName": "Aleksander",
        "doctorLastName": "Zió?ko"
    }
]
Run Code Online (Sandbox Code Playgroud)
[
    {
        "status": "PAID",
        "visitId": 395,
        "dateTo": "2019-04-10T08:30:00",
        "doctor": {
            "firstName": "Aleksander",
            "lastName": "Zió?ko"
        },
        "dateFrom": "2019-04-10T08:00:00",
        "doctorId": 401,
        "patientId": 394
    }
]
Run Code Online (Sandbox Code Playgroud)

现在我想使用带有 JPQL 和本机 SQL 的 @Query 获得相同的结果。所以我从这个方法中打印出生成的 SQL,我尝试将它与 @Query 注释一起使用。在这里你可以看到它:

@Query + 原生 SQL:

@Query(value = "SELECT d.id as doctorId, d.firstName as firstName, d.lastName as lastName, p.id as patientId, v.id as visitId, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status \n" +
            "FROM visit v \n" +
            "LEFT OUTER JOIN doctor d on v.doctor_id=d.id \n" +
            "LEFT OUTER JOIN users ud on d.id=ud.id \n" +
            "LEFT OUTER JOIN patient p on v.patient_id=p.id \n" +
            "LEFT OUTER JOIN users up on p.id=up.id \n" +
            "where d.id= :doctorId and v.status= :status and v.dateFrom>= :dateFrom and v.dateTo<= :dateTo ", nativeQuery = true)
    List<VisitView> searchForDoctorsVisitByStatusAndTimeIntervalNativeQuery(
            @Param("doctorId") long doctorId, @Param("status") String status, @Param("dateFrom") LocalDateTime dateFrom, @Param("dateTo") LocalDateTime dateTo);

Run Code Online (Sandbox Code Playgroud)

@查询 + JPQL:

@Query("SELECT d.id as doctorId, d.firstName as firstName, d.lastName as lastName, p.id as patientId, v.visitId as visitId, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status \n" +
            "FROM Visit v \n" +
            "LEFT OUTER JOIN Doctor d ON v.doctor.id=d.id \n" +
            "LEFT OUTER JOIN Patient p ON v.patient.id=p.id \n" +
            "WHERE d.id= :doctorId AND v.status= :status AND v.dateFrom>= :dateFrom AND v.dateTo<= :dateTo")
    List<VisitView> searchForDoctorsVisitByStatusAndTimeIntervalJqplQuery(
            @Param("doctorId") long doctorId, @Param("status") VisitStatus status, @Param("dateFrom") LocalDateTime dateFrom, @Param("dateTo") LocalDateTime dateTo);
Run Code Online (Sandbox Code Playgroud)

这两个查询都返回带有 getter 的 JSON 或带有空值的 Doctor 接口来自 VisitView

[
    {
        "status": "PAID",
        "visitId": 395,
        "dateTo": "2019-04-10T08:30:00",
        "dateFrom": "2019-04-10T08:00:00",
        "doctorId": 401,
        "patientId": 394,
        "doctorFirstName": null,
        "doctorLastName": null
    }
]
Run Code Online (Sandbox Code Playgroud)
[
    {
        "status": "PAID",
        "visitId": 395,
        "dateTo": "2019-04-10T08:30:00",
        "doctor": null,
        "dateFrom": "2019-04-10T08:00:00",
        "doctorId": 401,
        "patientId": 394
    }
]
Run Code Online (Sandbox Code Playgroud)

我已经尝试过许多版本的 Hibernate,因为我阅读了很多关于在不同版本中出现的错误的信息。尝试按字母顺序对所选字段进行分组,因为我在此处的另一个问题中找到了此提示。尝试使用@Join Column注释,因为建议这样做,但它也没有帮助。

现在我快疯了,因为我不知道为什么它不起作用。有人能帮帮我吗?

Hibernate 核心版本 -> 5.4.14.Final

Hibernate orm-search 版本 -> 5.11.5.Final

编辑:上面的问题已经解决,但是.. 我还有一个关于这个话题的问题。

实体访问与 MedicalServices 存在 @ManyToMany 关系。现在我想拉这个列表,所以我投影了另一个界面:

public interface VisitInfoWithPatientAndMedServices {
    LocalDateTime getDateFrom();
    LocalDateTime getDateTo();
    VisitStatus getStatus();
//  long getMedicalServicesId();
//  String getMedicalServicesService();
//  float getMedicalServicesPrice();
    List<MedicalService> getMedicalServices();
    interface MedicalService {
        String getId();
        String getService();
        float getPrice();
    }
}
Run Code Online (Sandbox Code Playgroud)

此接口使用命名方法策略仅返回一个带有 MedicalServices 列表的对象。这是来自 Postman 的 JSON:

[
    {
        "status": "PAID",
        "medicalServices": [
            {
                "id": "3",
                "service": "Something",
                "price": 250.0
            },
            {
                "id": "4",
                "service": "USG",
                "price": 400.0
            }
        ],
        "dateTo": "2019-04-10T08:30:00",
        "dateFrom": "2019-04-10T08:00:00"
    }
]
Run Code Online (Sandbox Code Playgroud)

但是我仍然无法正确使用 Native SQL 和 @Query 注释。我知道我可以使用这个问题的解决方案来解决它,你可以看到它在上面的 VisitInfoWithPatientAndMedServices 接口和它的工作中被注释掉,但它返回的不是 1 个带有医疗服务列表的访问对象,而是 2 个相同的对象,每个对象都有一个医疗服务。它看起来像这样:

    {
        "dateTo": "2019-04-10T08:30:00",
        "dateFrom": "2019-04-10T08:00:00",
        "medicalServicesId": 3,
        "medicalServicesPrice": 250.0,
        "medicalServicesService": "Something",
        "status": "PAID"
    },
    {
        "dateTo": "2019-04-10T08:30:00",
        "dateFrom": "2019-04-10T08:00:00",
        "medicalServicesId": 4,
        "medicalServicesPrice": 400.0,
        "medicalServicesService": "USG",
        "status": "PAID"
    }
]
Run Code Online (Sandbox Code Playgroud)

它就像在工作台中一样工作,因为我使用的是 MySQL。

我可以做些什么来使用命名方法策略和@Query 注释(本机 SQL 和 JPQL)获得相同的 JSON 响应吗?

Ekl*_*vya 5

您正在使用d.firstName as firstNamed.lastName as lastName这意味着您要投影界面中的值firstNamelastName字段。

d.firstName as doctorFirstName, d.lastName as doctorLastName在@Query 中使用以获取值。

 @Query("SELECT d.id as doctorId, d.firstName as doctorFirstName, d.lastName as doctorLastName, p.id as patientId, v.visitId as visitId, v.dateFrom as dateFrom, v.dateTo as dateTo, v.status as status \n" +
            "FROM Visit v \n" +
            "LEFT OUTER JOIN Doctor d ON v.doctor.id=d.id \n" +
            "LEFT OUTER JOIN Patient p ON v.patient.id=p.id \n" +
            "WHERE d.id= :doctorId AND v.status= :status AND v.dateFrom>= :dateFrom AND v.dateTo<= :dateTo")
    List<VisitView> searchForDoctorsVisitByStatusAndTimeIntervalJqplQuery(
            @Param("doctorId") long doctorId, @Param("status") VisitStatus status, @Param("dateFrom") LocalDateTime dateFrom, @Param("dateTo") LocalDateTime dateTo);
Run Code Online (Sandbox Code Playgroud)