使用graphql-spring的LazyInitializationException

pue*_*elo 12 spring hibernate spring-boot graphql graphql-java

我目前正在将我的REST服务器迁移到GraphQL(至少部分).大部分工作已经完成,但我偶然发现了这个问题,我似乎无法解决这个问题:在Graphql查询中使用FetchType.LAZY的OneToMany关系.

我正在使用: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
   private List<Competition> competition;
}

@Entity
class Competition {
   private Long id;
   private String name;
}
Run Code Online (Sandbox Code Playgroud)

架构:

type Show {
    id: ID!
    name: String!
    competitions: [Competition]
}

type Competition {
    id: ID!
    name: String
}

extend type Query {
    shows : [Show]
}
Run Code Online (Sandbox Code Playgroud)

解析器:

@Component
public class ShowResolver implements GraphQLQueryResolver {
    @Autowired    
    private ShowRepository showRepository;

    public List<Show> getShows() {
        return ((List<Show>)showRepository.findAll());
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我现在使用此(简写)查询查询端点:

{
  shows {
    id
    name
    competitions {
      id
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我得到:

org.hibernate.LazyInitializationException:懒得初始化一个角色集合:Show.competitions,无法初始化代理 - 没有Session

现在我知道为什么会出现这个错误以及它意味着什么,但我真的不知道应该为此修复.我不想让我的热衷于急切地获取所有关系,因为这会否定GraphQL的一些优点.我可能需要寻找解决方案的任何想法?谢谢!

pue*_*elo 9

我解决了它,我应该更仔细地阅读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()));
    }
}
Run Code Online (Sandbox Code Playgroud)

这告诉库如何解析Show类中的复杂对象,并且仅在最初查询请求包含Competition对象时使用.新年快乐!

编辑:这里要求的是使用自定义执行策略的另一种解决方案.我正在使用graphql-spring-boot-startergraphql-java-tools:

我首先定义一个GraphQL配置,如下所示:

@Configuration
public class GraphQLConfig {
    @Bean
    public Map<String, ExecutionStrategy> executionStrategies() {
        Map<String, ExecutionStrategy> executionStrategyMap = new HashMap<>();
        executionStrategyMap.put("queryExecutionStrategy", new AsyncTransactionalExecutionStrategy());
        return executionStrategyMap;
    }
}
Run Code Online (Sandbox Code Playgroud)

在哪里AsyncTransactionalExecutionStrategy定义如下:

@Service
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {

    @Override
    @Transactional
    public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        return super.execute(executionContext, parameters);
    }
}
Run Code Online (Sandbox Code Playgroud)

这将查询的整个执行放在同一个事务中.我不知道这是否是最佳解决方案,并且它在错误处理方面也有一些缺点,但您不需要以这种方式定义类型解析器.

  • @puelo你的AsyncTransactionalExecutionStrategy已经是一个@Service,所以Spring会为其创建一个bean。因此,无需在`executionStrategies()`方法内部创建一个新的'AsyncTransactionalExecutionStrategy()`。您应该只在其中注入`AsyncTransactionalExecutionStrategy` bean。 (2认同)
  • 特定 QUERY 执行策略的另一个改进是使用 `@Transactional(readOnly = true)` (2认同)

Jax*_*t0r 6

我更喜欢的解决方案是在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();
  }

}
Run Code Online (Sandbox Code Playgroud)