Вопрос или проблема
У меня есть репозиторный метод для извлечения EmailSubscriptions
@Query("SELECT es FROM com.cargurus.emailsubscriptions.entity.EmailSubscription es "
+ "JOIN FETCH es.subscriber s "
+ "WHERE es.id > :batchStartId "
+ "AND es.subscriptionTypeId = :typeId "
+ "AND es.active = :active "
+ "ORDER BY es.id ASC")
List<EmailSubscription> findByIdGreaterThanAndSubscriptionTypeIdAndActive(
@Param("batchStartId") long batchStartId,
@Param("typeId") Integer typeId,
@Param("active") Boolean active,
Pageable pageable);
Вот как определяются EmailSubscriptions.
public class EmailSubscription {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(updatable = false, nullable = false)
private Long id;
@Column(name = "type_id")
private Integer subscriptionTypeId;
private Boolean active;
@Pattern(regexp = LOCALE_VALIDATION_REGEX, message = "invalid code")
private String locale;
private LocalDateTime lastSentTimestamp;
private Integer lastSentSequence;
private Long lastUpdatePersonId;
private LocalDateTime lastUpdateTimestamp;
private LocalDateTime creationTimestamp;
@ManyToOne(targetEntity = Subscriber.class, fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "subscriber_id", referencedColumnName = "id")
private Subscriber subscriber;
}
К сожалению, этот запрос очень дорогой для пакета из 10 тыс. записей (более 10 секунд). Поэтому я написал нативный SQL-запрос, который выполняется за меньше чем 1 секунду для извлечения 10 тыс. записей.
@Query(value = "SELECT es.* FROM subscriptions.email_subscriptions es "
+ "IGNORE INDEX (PRIMARY) "
+ "JOIN subscriptions.subscribers s ON es.subscriber_id = s.id "
+ "WHERE es.type_id = :typeId "
+ "AND es.active = :active "
+ "AND es.id > :batchStartId "
+ "ORDER BY es.id ", nativeQuery = true)
Но, к сожалению, этот запрос извлекает только EmailSubscriptions без связи с Subscriber. Как я могу извлечь Subscriber вместе с EmailSubscription, используя нативный SQL-запрос
Попробуйте это.
@Query(value = "SELECT es.id as es_id, es.type_id as es_type_id, es.active as es_active, es.locale as es_locale, " +
"es.last_sent_timestamp as es_last_sent_timestamp, es.last_sent_sequence as es_last_sent_sequence, " +
"es.last_update_person_id as es_last_update_person_id, es.last_update_timestamp as es_last_update_timestamp, " +
"es.creation_timestamp as es_creation_timestamp, " +
"s.id as s_id, s.name as s_name, s.email as s_email " + // выбрать поля подписчика
"FROM subscriptions.email_subscriptions es " +
"JOIN subscriptions.subscribers s ON es.subscriber_id = s.id " +
"WHERE es.type_id = :typeId " +
"AND es.active = :active " +
"AND es.id > :batchStartId " +
"ORDER BY es.id ",
nativeQuery = true)
List<Object[]> findByIdGreaterThanAndSubscriptionTypeIdAndActiveWithSubscriber(
@Param("batchStartId") long batchStartId,
@Param("typeId") Integer typeId,
@Param("active") Boolean active);
Поскольку вы извлекаете результат нативного запроса в List<Object[]>
, вы можете вручную сопоставить результаты с вашими сущностями.
Ответ или решение
Чтобы извлечь сущности и их связи с помощью нативного SQL-запроса в методе репозитория JPA, необходимо учитывать, что использование нативного запроса требует дополнительного маппинга результатов в ваши Java-объекты. Приведенные вами примеры кода демонстрируют, как выполнить выборку данных из базы, но также подчеркивают необходимость ручного маппинга, поскольку результатом запроса будет массив объектов.
Решение проблемы
Основная задача состоит в том, чтобы извлечь EmailSubscription
вместе с его связанной сущностью Subscriber
. Поскольку нативный запрос возвращает данные в виде List<Object[]>
, вам необходимо вручную преобразовать эти результаты в экземпляры сущностей. Давайте рассмотрим, как это можно сделать.
1. Определение DTO (Data Transfer Object)
Для упрощения маппинга можно создать DTO-класс, который будет содержать как данные EmailSubscription
, так и данные Subscriber
. Этот подход сделает ваш код более чистым и легким для поддержки.
public class EmailSubscriptionWithSubscriberDTO {
private Long esId;
private Integer esTypeId;
private Boolean esActive;
private String esLocale;
private LocalDateTime esLastSentTimestamp;
private Integer esLastSentSequence;
private Long esLastUpdatePersonId;
private LocalDateTime esLastUpdateTimestamp;
private LocalDateTime esCreationTimestamp;
private Long sId;
private String sName;
private String sEmail;
// Конструкторы, геттеры и сеттеры
}
2. Изменение метода в репозитории
Теперь измените метод в вашем репозитории, чтобы возвращать список DTO-объектов. Ниже приведен пример такого метода:
@Query(value = "SELECT es.id as es_id, es.type_id as es_type_id, es.active as es_active, " +
"es.locale as es_locale, es.last_sent_timestamp as es_last_sent_timestamp, " +
"es.last_sent_sequence as es_last_sent_sequence, " +
"es.last_update_person_id as es_last_update_person_id, " +
"es.last_update_timestamp as es_last_update_timestamp, " +
"es.creation_timestamp as es_creation_timestamp, " +
"s.id as s_id, s.name as s_name, s.email as s_email " +
"FROM subscriptions.email_subscriptions es " +
"JOIN subscriptions.subscribers s ON es.subscriber_id = s.id " +
"WHERE es.type_id = :typeId " +
"AND es.active = :active " +
"AND es.id > :batchStartId " +
"ORDER BY es.id", nativeQuery = true)
List<EmailSubscriptionWithSubscriberDTO> findByIdGreaterThanAndSubscriptionTypeIdAndActiveWithSubscriber(
@Param("batchStartId") long batchStartId,
@Param("typeId") Integer typeId,
@Param("active") Boolean active);
3. Маппинг результатов
После того, как вы получили результаты в виде List<Object[]>
, необходимо выполнить их маппинг на экземпляры созданного вами DTO. Это обычно делается в вашем сервисном слое:
public List<EmailSubscriptionWithSubscriberDTO> getEmailSubscriptions(Long batchStartId, Integer typeId, Boolean active) {
List<Object[]> results = emailSubscriptionRepository.findByIdGreaterThanAndSubscriptionTypeIdAndActiveWithSubscriber(batchStartId, typeId, active);
return results.stream()
.map(record -> new EmailSubscriptionWithSubscriberDTO(
(Long) record[0], // es_id
(Integer) record[1], // es_type_id
(Boolean) record[2], // es_active
(String) record[3], // es_locale
(LocalDateTime) record[4], // es_last_sent_timestamp
(Integer) record[5], // es_last_sent_sequence
(Long) record[6], // es_last_update_person_id
(LocalDateTime) record[7], // es_last_update_timestamp
(LocalDateTime) record[8], // es_creation_timestamp
(Long) record[9], // s_id
(String) record[10], // s_name
(String) record[11] // s_email
)).collect(Collectors.toList());
}
Заключение
Таким образом, вы сможете эффективно извлекать записи EmailSubscription
вместе с данными их связанных Subscriber
через нативный SQL-запрос в JPA. Поскольку этот подход требует ручного маппинга, с созданием DTO у вас будет более чистая и понятная структура данных. Это улучшит производительность вашего приложения без компромисса по качеству кода.
Использование DTO также позволяет легко управлять данными, обеспечивая возможность расширять функциональность в будущем, если будет необходимость добавления новых полей или отношения.