2

我有一个 Spring Boot 应用程序,它使用 Hibernate 作为 ORM 和DGS 框架作为 graphql 引擎。我一直在努力寻找以正确方式初始化延迟加载集合的方法。我有以下情况:

application.properties
# The below has been set to false to get rid of the anti-pattern stuff it introduces
spring.jpa.open-in-view=false
...
@Entity
public class User {
    @Id
    @GeneratedValue
    private UUID id;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Article> articles;

    ...

}
@Entity
public class Article {
    @Id
    @GeneratedValue
    private UUID id;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private User user;

    ...
}

我的User数据提取器看起来像这样:

@DgsComponent
public class UserDataFetcher {
    @Autowired
    private UserService userService;

    @DgsQuery
    public User getUserById(@InputArgument UUID id) {
        return userService.findById(id);
    }
    ...
}

UserService看起来像这样:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findById(UUID id) {
        return userRepository.findById(id).orElseThrow(DgsEntityNotFoundException::new);
    }
    ...
}

现在,我只想在用户在 graphql 查询中请求时从数据库中初始化/加载我的文章集合。为此,我为我的文章创建了一个子解析器,它仅在用户在查询中请求文章时执行。我UserDataFetcher开始看起来像这样:

@DgsComponent
public class UserDataFetcher {
    @Autowired
    private UserService userService;

    @DgsQuery
    public User getUserById(@InputArgument UUID id) {
        return userService.findById(id);
    }

    @DgsData(parentType = "User", field = "articles")
    public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
        User user = dfe.getSource();
        Hibernate.initialize(user.getArticles());
        return user.getArticles();
    }
    ...
}

但是,上面开始抛出异常,告诉我 Hibernate 找不到上述请求的打开会话。这是有道理的,因为没有,所以我@Transactional在我的孩子解析器上放了一个,它开始看起来像这样:

@DgsComponent
public class UserDataFetcher {
    @Autowired
    private UserService userService;

    @DgsQuery
    public User getUserById(@InputArgument UUID id) {
        return userService.findById(id);
    }

    @DgsData(parentType = "User", field = "articles")
    @Transactional
    public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
        User user = dfe.getSource();
        Hibernate.initialize(user.getArticles());
        return user.getArticles();
    }
    ...
}

但是,上述方法也不起作用。我也尝试将其@Transactional移至我的服务层,但即使那样它也不起作用,并且引发了相同的异常。经过深思熟虑,我发现(也许)Hibernate.initialize(...)只有在我在初始交易中调用它时才有效,这首先让我获得了我的用户。意思是,这对我没有用,因为我的用例是非常受用户驱动的。我只想在我的用户要求时得到它,而且它总是在我的应用程序的父事务之外的其他部分中。

我正在寻找以下解决方案:

  1. 将子解析器更改为以下内容:
    @DgsData(parentType = "User", field = "articles")
    @Transactional
    public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
        User user = dfe.getSource();
        List<Article> articles = articlesRepository.getArticlesByUserId(user.getUserId);
        return articles;
    }

我不赞成上述解决方案,因为我觉得这是通过尝试自己解决关系而不是让 hibernate 自己来解决关系而未充分利用 ORM 本身。(如果我这样想错了,请纠正我)

  1. 将我的User实体更改为使用FetchMode.JOIN.
@Entity
public class User {
    @Id
    @GeneratedValue
    private UUID id;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    @Fetch(FetchMode.JOIN)
    private List<Article> articles;
    ...

}

这与告诉 hibernate 无论如何都急切地加载下面的集合是一样的。我也不想要这个。

  1. 设置spring.jpa.open-in-view=falsespring.jpa.open-in-view=true。也不赞成这个,因为这只是一个创可贴LazyInitializationExceptions

  2. LazyInitializationException通过在请求的整个生命周期中保持会话打开来让您忘记的任何其他解决方案。

4

1 回答 1

1

请注意,此答案假定可以使用 Spring Data JPA。

EntityGraphs的完整动态使用很有帮助

实体图使我们能够定义获取计划并声明必须从数据库中查询哪些关系(属性)。

根据文档

你可以做类似的事情

productRepository.findById(1L, EntityGraphUtils.fromAttributePaths(“article, “comments”));

并根据用户选择将所有必要的参数(关系)传递给EntityGraphUtils.fromAttributePaths方法。这使我们有可能只获取必要的数据。

其他资源:

  1. 示例项目
  2. Spring 博客提到了这个扩展
  3. JPA 实体图
  4. 实体图
于 2021-09-11T19:41:09.487 回答