[英]LazyInitializationException with graphql-spring
我目前正在將我的 REST-Server 遷移到 GraphQL(至少部分遷移)。 大部分工作已經完成,但我偶然發現了這個我似乎無法解決的問題:graphql 查詢中的 OneToMany 關系,使用 FetchType.LAZY。
我正在使用: https : //github.com/graphql-java/graphql-spring-boot和https://github.com/graphql-java/graphql-java-tools進行集成。
下面是一個例子:
實體:
@Entity
class Show {
private Long id;
private String name;
@OneToMany(mappedBy = "show")
private List<Competition> competition;
}
@Entity
class Competition {
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
架構:
type Show {
id: ID!
name: String!
competitions: [Competition]
}
type Competition {
id: ID!
name: String
}
extend type Query {
shows : [Show]
}
解析器:
@Component
public class ShowResolver implements GraphQLQueryResolver {
@Autowired
private ShowRepository showRepository;
public List<Show> getShows() {
return ((List<Show>)showRepository.findAll());
}
}
如果我現在使用此(速記)查詢查詢端點:
{
shows {
id
name
competitions {
id
}
}
}
我得到:
org.hibernate.LazyInitializationException:無法延遲初始化角色集合:Show.competitions,無法初始化代理 - 沒有會話
現在我知道為什么會發生這個錯誤以及它意味着什么,但我真的不知道是否要為此應用修復程序。 我不想讓我的實體急切地獲取所有關系,因為這會抵消 GraphQL 的一些優勢。 我可能需要尋找解決方案的任何想法? 謝謝!
我解決了它,我想應該更仔細地閱讀 graphql-java-tools 庫的文檔。 旁邊的GraphQLQueryResolver
它解決了我也需要基本的查詢一個GraphQLResolver<T>
我的Show
類,它看起來像這樣:
@Component
public class ShowResolver implements GraphQLResolver<Show> {
@Autowired
private CompetitionRepository competitionRepository;
public List<Competition> competitions(Show show) {
return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
}
}
這告訴庫如何解析我的Show
類中的復雜對象,並且僅在初始查詢請求包含Competition
對象時使用。 新年快樂!
編輯 31.07.2019 :我從此離開了下面的解決方案。 長時間運行的事務很少是一個好主意,在這種情況下,一旦您擴展應用程序,它可能會導致問題。 我們開始實現 DataLoaders 以異步方式批量查詢。 長時間運行的事務與 DataLoaders 的異步性質相結合可能會導致死鎖: https : //github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (以上和以下為更多信息)。 我不會刪除下面的解決方案,因為對於較小的應用程序和/或不需要任何批處理查詢的應用程序來說,它可能仍然是一個很好的起點,但在這樣做時請記住此注釋。
編輯:根據這里的要求是使用自定義執行策略的另一種解決方案。 我正在使用graphql-spring-boot-starter
和graphql-java-tools
:
創建一個ExecutionStrategy
類型的 Bean 來處理事務,如下所示:
@Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {
@Override
@Transactional
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
return super.execute(executionContext, parameters);
}
}
這會將查詢的整個執行置於同一事務中。 我不知道這是否是最佳解決方案,並且它在錯誤處理方面也已經存在一些缺點,但是您不需要以這種方式定義類型解析器。
請注意,如果這是唯一存在的ExecutionStrategy
Bean,則這也將用於突變,這與 Bean 名稱可能暗示的相反。 見https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot /GraphQLWebAutoConfiguration.java#L161-L166供參考。 為避免這種情況,請定義另一個用於突變的ExecutionStrategy
:
@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
我的首選解決方案是在 Servlet 發送其響應之前打開事務。 通過這個小代碼更改,您的 LazyLoad 將正常工作:
import javax.servlet.Filter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Register the {@link OpenEntityManagerInViewFilter} so that the
* GraphQL-Servlet can handle lazy loads during execution.
*
* @return
*/
@Bean
public Filter OpenFilter() {
return new OpenEntityManagerInViewFilter();
}
}
對於任何對接受的答案感到困惑的人,您需要更改 java 實體以包含雙向關系,並確保您使用輔助方法添加Competition
否則很容易忘記正確設置關系。
@Entity
class Show {
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "show")
private List<Competition> competition;
public void addCompetition(Competition c) {
c.setShow(this);
competition.add(c);
}
}
@Entity
class Competition {
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
接受的答案背后的一般直覺是:
graphql 解析器ShowResolver
將打開一個事務以獲取節目列表,但一旦完成,它將關閉該事務。
然后,用於competitions
的嵌套 graphql 查詢將嘗試在從前一個查詢中檢索到的每個Show
實例上調用getCompetition()
,這將拋出LazyInitializationException
因為事務已關閉。
{
shows {
id
name
competitions {
id
}
}
}
接受的答案本質上是繞過通過OneToMany
關系檢索比賽列表,而是在消除問題的新事務中創建新查詢。
不確定這是否是黑客攻擊,但解析器上的@Transactional
對我不起作用,盡管這樣做的邏輯確實有意義,但我顯然不了解根本原因。
對我來說,使用AsyncTransactionalExecutionStrategy
時出現異常。 例如,惰性初始化或應用程序級異常觸發事務到僅回滾狀態。 Spring 事務機制然后在策略execute
的邊界處拋出僅回滾事務,導致HttpRequestHandlerImpl
返回 400 空響應。 有關更多詳細信息,請參閱https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250和https://github.com/graphql-java/graphql-java/issues/1652 。
對我有用的是使用Instrumentation
將整個操作包裝在一個事務中: https : //spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a
我假設每當您獲取Show對象時,您都需要Show對象的所有相關競爭。
默認情況下,實體中所有集合類型的提取類型為LAZY 。 您可以指定EAGER類型以確保 hibernate 獲取集合。
在您的Show類中,您可以將 fetchType 更改為EAGER 。
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
你只需要用@Transactional
注釋你的解析器類。 然后,從存儲庫返回的實體將能夠懶惰地獲取數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.