Spring Data Jpa + Spring Projection с использованием @Query (native и JPQL) возвращает ноль для связанных сущностей - PullRequest
1 голос
/ 18 апреля 2020

То, чего я пытаюсь добиться - это написать один и тот же запрос, используя 3 различных подхода в Spring Data Jpa с использованием интерфейса JpaRepository: 1. Стратегия именованного метода. 2. @Query с JPQL. 3. @Query native SQL. Здесь вы можете увидеть, как я создал Visit Entity со всеми связями, которые я пытаюсь выбрать.

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;

Я использую Project Lombok, чтобы не копировать все аннотации над классом. Итак, вот эксперимент. Я создал метод, который должен возвращать все посещения для определенного врача за указанный промежуток времени. Вот метод, который я написал:

List<VisitView> findByDoctorIdAndStatusAndDateFromGreaterThanEqualAndDateToLessThanEqual
            (long doctorId, VisitStatus visitStatus, LocalDateTime dateFrom, LocalDateTime dateTo);

Как вы можете видеть, я уже реализовал VisitView интерфейс с использованием Spring Projection . Вот оно:

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

И при использовании этого метода все работает просто отлично. Я могу получить doctor firstName и lastName из Doctor Entity Class двумя способами -> используя геттеры и встроенный в другой интерфейс Doctor для доступа к полям из Entity. Здесь вы можете увидеть оба 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"
    }
]
[
    {
        "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
    }
]

Теперь я хочу достичь того же результата, используя @Query с JPQL и нативный SQL. Поэтому я распечатал сгенерированный SQL из этого метода, я попытался использовать его с аннотацией @Query. Здесь вы можете увидеть это: @ Query + native 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);

@ Quuery + 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);

Оба эти запроса вернуть JSON с геттерами или интерфейс «Доктор» с нулевыми значениями в форме 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
    }
]
[
    {
        "status": "PAID",
        "visitId": 395,
        "dateTo": "2019-04-10T08:30:00",
        "doctor": null,
        "dateFrom": "2019-04-10T08:00:00",
        "doctorId": 401,
        "patientId": 394
    }
]

Я уже пробовал многие версии Hibernate, потому что я много читал о багах, которые появились в разных версиях. Попытка сгруппировать выбранные поля в алфавитном порядке, так как я нашел этот совет в другом вопросе здесь. Пытался использовать аннотацию @ Join Column , как это было предложено, но это тоже не помогло.

А теперь я схожу с ума, потому что не могу понять, почему это не работает. Может кто-нибудь, пожалуйста, помогите мне?

Версия ядра Hibernate -> 5.4.14. Окончание

Версия поиска Hibernate -> 5.11.5. Окончание

РЕДАКТИРОВАТЬ: выпуск выше решена, но .. У меня есть еще один вопрос относительно этой топи c.

Визит организации находится в @ManyToMany связи с MedicalServices. Теперь я хочу вытащить этот список, поэтому я спроектировал другой интерфейс:

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

Этот интерфейс возвращает только ОДИН объект с Списком медицинских услуг с использованием стратегии именованных методов. Вот 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"
    }
]

Но я все еще не могу понять это правильно с аннотациями 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"
    }
]

Работает так же, как в Workbench, потому что я использую MySQL.

Могу ли я что-нибудь с этим сделать, чтобы получить то же самое JSON ответ с использованием стратегии именованных методов и аннотации @Query (собственный SQL и JPQL) ??

1 Ответ

0 голосов
/ 18 апреля 2020

Вы используете d.firstName as firstName и d.lastName as lastName, это означает, что вы хотите проецировать значения в поле firstName и lastName в интерфейсе.

Используйте 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);
...