#spring-boot #hibernate #spring-data-jpa #ehcache #hibernate-cache
#весенняя загрузка #переход в спящий режим #spring-data-jpa #ehcache #спящий режим-кэш
Вопрос:
Я разрабатываю веб-приложение Spring Boot 2.3.4 с JPA Spring Data.
Я хочу использовать кэш запросов 2-го уровня гибернации для метода репозитория с @EntityGraph. Однако я получаю исключение LazyInitializationException при создании представления Thymeleaf в случае, если данные уже находятся в кэше 2-го уровня, если у меня не включен открытый сеанс Spring в представлении. При первом извлечении данных из базы данных или без кэша 2-го уровня все в порядке, даже с spring.jpa.open-in-view=false . Более того, если я включу spring.jpa.open-in-view, при извлечении данных из кэша без какого-либо выбора в базе данных исключения не будет.
Как я могу заставить Hibernate извлекать сразу все ассоциации, указанные в @EntityGraph, при использовании кэша 2-го уровня гибернации?
Вот мой метод репозитория:
@org.springframework.data.jpa.repository.QueryHints({@javax.persistence.QueryHint(name = "org.hibernate.cacheable", value = "true")})
@EntityGraph(attributePaths = { "venue.city", "lineup.artist", "ticketLinks" }, type = EntityGraphType.FETCH)
Optional<Event> findEventPageViewGraphById(long id);
и часть сущности:
@Entity
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "venue_id")
private Venue venue;
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@OrderBy("orderId")
private Set<TicketLink> ticketLinks = new LinkedHashSet<>();
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("orderId")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<ArtistEvent> lineup = new LinkedHashSet<>();
}
Ответ №1:
Это известная проблема. Hibernate не проверяет кэш 2-го уровня на наличие ассоциаций при создании «просто прокси». Вам необходимо получить доступ к объектам, чтобы инициализировать их, что затем вызовет попадание в кэш 2-го уровня.
Я бы рекомендовал вам вместо этого использовать подход DTO. Я думаю, что это идеальный вариант использования для представлений объектов с сохранением Блейза.
Я создал библиотеку, позволяющую легко сопоставлять модели JPA с моделями, определяемыми пользовательским интерфейсом или абстрактным классом, что-то вроде весенних прогнозов данных на стероидах. Идея заключается в том, что вы определяете свою целевую структуру (модель домена) так, как вам нравится, и сопоставляете атрибуты (геттеры) через выражения JPQL с моделью сущности.
Модель DTO для вашего варианта использования может выглядеть следующим образом с помощью представлений объектов Blaze-Persistence:
@EntityView(Event.class)
public interface EventDto {
@IdMapping
Long getId();
VenueDto getVenue();
@MappingIndex("orderId")
List<TicketLinkDto> getTicketLinks();
@MappingIndex("orderId")
List<ArtistEventDto> getLineup();
@EntityView(Venue.class)
interface VenueDto {
@IdMapping
Long getId();
CityDto getCity();
}
@EntityView(City.class)
interface CityDto {
@IdMapping
Long getId();
String getName();
}
@EntityView(TicketLink.class)
interface TicketLinkDto {
@IdMapping
Long getId();
String getName();
}
@EntityView(ArtistEvent.class)
interface ArtistEventDto {
@IdMapping
Long getId();
ArtistDto getArtist();
}
@EntityView(Artist.class)
interface ArtistDto {
@IdMapping
Long getId();
String getName();
}
}
Запрос — это вопрос применения представления сущности к запросу, простейшим из которых является просто запрос по идентификатору.
EventDto a = entityViewManager.find(entityManager, EventDto.class, id);
Интеграция данных Spring позволяет использовать его почти как проекции данных Spring: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Optional<EventDto> findEventPageViewGraphById(long id);
Ответ №2:
Спасибо, Кристиан, за твой ответ. Я решил проблему, инициализировав объекты с помощью статического метода Hibernate.initialize(), как описано здесь https://vladmihalcea.com/initialize-lazy-proxies-collections-jpa-hibernate /
@Transactional(readOnly = true)
public Optional<Event> loadEventPageViewGraph(long id) {
Optional<Event> eventO = eventRepository.findEventPageViewGraphById(id);
if(eventO.isPresent()) {
Hibernate.initialize(eventO.get());
Hibernate.initialize(eventO.get().getVenue().getCity());
for (ArtistEvent artistEvent: eventO.get().getLineup()) {
Hibernate.initialize(artistEvent.getArtist());
}
Hibernate.initialize(eventO.get().getTicketLinks());
return eventO;
} else {
return Optional.empty();
}
}
Хотя я согласен, что в целом лучше использовать DTO / projections . Однако при использовании DTO возникает проблема с извлечением проекций, которые включают связанные коллекции (свойства @OneToMany), как описано здесь https://vladmihalcea.com/one-to-many-dto-projection-hibernate /. В частности, в случае, когда мы не хотим выбирать все свойства объекта. Я обнаружил, что представления сущностей с сохранением блеска имеют хорошее решение для этого https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/#subset-basic-collection-mapping. Я проверю это.
Комментарии:
1. Другой вариант — выполнить рендеринг представления Thymeleaf в той же транзакции, в которой вы извлекаете данные из БД