我有一个 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(...)
只有在我在初始交易中调用它时才有效,这首先让我获得了我的用户。意思是,这对我没有用,因为我的用例是非常受用户驱动的。我只想在我的用户要求时得到它,而且它总是在我的应用程序的父事务之外的其他部分中。
我正在寻找以下解决方案:
- 将子解析器更改为以下内容:
@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 本身。(如果我这样想错了,请纠正我)
- 将我的
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 无论如何都急切地加载下面的集合是一样的。我也不想要这个。
设置
spring.jpa.open-in-view=false
为spring.jpa.open-in-view=true
。也不赞成这个,因为这只是一个创可贴LazyInitializationExceptions
。LazyInitializationException
通过在请求的整个生命周期中保持会话打开来让您忘记的任何其他解决方案。