Spring Data Jpa + Spring Projection с использованием @Query (native и JPQL) возвращает null для связанных сущностей
Я пытаюсь написать один и тот же запрос, используя 3 разных подхода в Spring Data Jpa с использованием интерфейса JpaRepository: 1. Стратегия именованных методов. 2. @Query с JPQL. 3. Собственный SQL @Query. Здесь вы можете увидеть, как я создал 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 обоими способами -> используя геттеры и встроенный в другой интерфейс 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 с геттерами или интерфейсом 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
}
]
[
{
"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.Final
Hibernate orm-search verion -> 5.11.5.Финал
РЕДАКТИРОВАТЬ: проблема выше решена, но... У меня есть еще один вопрос по этой теме.
Entity Visit имеет отношение @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 от Postman:
[
{
"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 ответ
Ты используешь 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);