0

我正在使用 Spring Data JPA 开发 Spring Boot 2.3.4 Web 应用程序。

我想将 Hibernate 2 级查询缓存用于带有 @EntityGraph 的存储库方法。但是,如果数据已经在二级缓存中,除非我打开了 Spring 的 Open Session In View,否则在生成 Thymeleaf 视图时会出现 LazyInitializationException。第一次从数据库或没有二级缓存的情况下获取数据时,即使 spring.jpa.open-in-view=false 也一切正常。此外,如果我启用 spring.jpa.open-in-view 从缓存中获取数据而不选择数据库时也不例外。

使用 Hibernate 二级缓存时,如何让 Hibernate 一次获取 @EntityGraph 中指定的所有关联?

这是我的存储库方法:

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

}

4

2 回答 2

0

谢谢克里斯蒂安的回答。我通过使用静态方法 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/投影。但是,对于 DTO,在获取包含相关集合(@OneToMany 属性)的投影时会出现问题,如https://vladmihalcea.com/one-to-many-dto-projection-hibernate/所述。特别是在我们不想选择所有实体属性的情况下。我发现 Blaze-Persistence Entity Views 有一个很好的解决方案https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/#subset-basic-collection-mapping。我会检查一下。

于 2020-11-19T09:53:02.697 回答
0

这是一个已知问题。Hibernate 在构建“只是代理”时不会检查 2 级缓存的关联。您需要访问对象以对其进行初始化,这将触发二级缓存命中。

我建议您改用 DTO 方法。我认为这是Blaze-Persistence Entity Views的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

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

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

EventDto a = entityViewManager.find(entityManager, EventDto.class, id);

Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Optional<EventDto> findEventPageViewGraphById(long id);
于 2020-11-16T16:13:33.387 回答