Spring boot 从Hibernate二级缓存获取@EntityGraph时LazyInitializationException异常

Spring boot 从Hibernate二级缓存获取@EntityGraph时LazyInitializationException异常,spring-boot,hibernate,spring-data-jpa,ehcache,hibernate-cache,Spring Boot,Hibernate,Spring Data Jpa,Ehcache,Hibernate Cache,我正在用SpringDataJPA开发SpringBoot2.3.4Web应用程序 我想将Hibernate 2级查询缓存用于带有@EntityGraph的存储库方法。但是,如果数据已经在2级缓存中,除非打开了Spring的Open Session in view,否则在生成Thymeleaf视图时会出现LazyInitializationException。当第一次从数据库中获取数据或不使用二级缓存时,即使使用spring.jpa.open in view=false,一切都正常。此外,如果启

我正在用SpringDataJPA开发SpringBoot2.3.4Web应用程序

我想将Hibernate 2级查询缓存用于带有@EntityGraph的存储库方法。但是,如果数据已经在2级缓存中,除非打开了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);
@org.springframework.data.jpa.repository.QueryHints({@javax.persistence.QueryHint(name=“org.hibernate.cacheable”,value=“true”)})
@EntityGraph(AttributePath={“地点.城市”,“阵容.艺术家”,“ticketLinks”},type=EntityGraphType.FETCH)
可选findEventPageViewGraphById(长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<>();
@实体
@org.hibernate.annotations.Cache(用法=cacheconcurrencysttrategy.NONSTRICT\u READ\u WRITE)
公开课活动{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
保护长id;
@manytone(fetch=FetchType.LAZY)
@JoinColumn(name=“场馆id”)
私人场地;;
@OneToMany(mappedBy=“event”,cascade=CascadeType.ALL,orphan=true)
@org.hibernate.annotations.Cache(用法=cacheconcurrencysttrategy.NONSTRICT\u READ\u WRITE)
@OrderBy(“orderId”)
private Set ticketLinks=new LinkedHashSet();
@OneToMany(mappedBy=“event”,cascade=CascadeType.ALL,orphan=true)
@OrderBy(“orderId”)
@org.hibernate.annotations.Cache(用法=cacheconcurrencysttrategy.NONSTRICT\u READ\u WRITE)
私有设置列表=新LinkedHashSet();

}这是一个众所周知的问题。在构造“仅代理”时,Hibernate不会检查二级缓存中的关联。您需要访问对象来初始化它们,然后触发二级缓存命中

我建议您改用DTO方法。我认为这是一个完美的用例

我创建了这个库,以便在JPA模型和自定义接口或抽象类定义的模型之间进行简单的映射,类似于类固醇上的Spring数据投影。其思想是以您喜欢的方式定义目标结构(域模型),并通过JPQL表达式将属性(getter)映射到实体模型

在Blaze持久性实体视图中,您的用例的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();
    }
}

谢谢你的回答。我通过使用静态方法Hibernate.initialize()初始化实体解决了这个问题,如下所述

@Transactional(readOnly=true)
公共可选loadEventPageViewGraph(长id){
可选eventO=eventRepository.findEventPageViewGraphById(id);
if(eventO.isPresent()){
初始化(eventO.get());
初始化(eventO.get().getVenue().getCity());
for(ArtistEvent ArtistEvent:eventO.get().getLineup()){
初始化(artistEvent.getArtist());
}
初始化(eventO.get().getTicketLinks());
返回事件;
}否则{
返回可选的.empty();
}
}

不过,我同意一般来说,最好使用DTO/预测。但是,对于DTO,获取包含关联集合(@OneToMany属性)的投影时存在问题,如下所述。特别是在我们不想选择所有实体属性的情况下。我发现Blaze持久性实体视图有一个很好的解决方案。我会检查一下。

另一个选项是在从DB获取数据的同一事务中执行Thymeleaf视图渲染
Optional<EventDto> findEventPageViewGraphById(long id);
@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();
    }
}