簡體   English   中英

使用 graphql-spring 的 LazyInitializationException

[英]LazyInitializationException with graphql-spring

我目前正在將我的 REST-Server 遷移到 GraphQL(至少部分遷移)。 大部分工作已經完成,但我偶然發現了這個我似乎無法解決的問題:graphql 查詢中的 OneToMany 關系,使用 FetchType.LAZY。

我正在使用: https : //github.com/graphql-java/graphql-spring-boothttps://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-startergraphql-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/250https://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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM