如何在以正确方式获取的事务之外初始化延迟加载的集合?
Posted
技术标签:
【中文标题】如何在以正确方式获取的事务之外初始化延迟加载的集合?【英文标题】:How to initialize a lazy loaded collection outside of the transaction it was fetched in the right way? 【发布时间】:2021-11-07 15:04:06 【问题描述】:我有一个 Spring Boot 应用程序,它使用 Hibernate 作为 ORM,DGS framework 作为 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
的任何其他解决方案。
【问题讨论】:
您需要打开一个新会话并重新附加分离的实体。例如通过调用update
。
这样做只会立即解决实体中的所有关系,而无需我指定要解决的关系,甚至在调用Hibernate.initialize(...)
之前。我不想要这个。我想明确指定我要解决的问题。
【参考方案1】:
请注意,此答案假定可以使用 Spring Data JPA。
有帮助的可以full dynamic usage of EntityGraphs
实体图使我们能够定义获取计划并声明哪个 必须从数据库中查询关系(属性)。
根据documentation
你可以做类似的事情
productRepository.findById(1L, EntityGraphUtils.fromAttributePaths(“article, “comments”));
并根据用户选择将所有必要的参数(关系)传递给EntityGraphUtils.fromAttributePaths
方法。
这使我们有可能只获取必要的数据。
其他资源:
-
Sample project
Spring Blog mentioned this extension
JPA EntityGraph
EntityGraph
【讨论】:
以上是关于如何在以正确方式获取的事务之外初始化延迟加载的集合?的主要内容,如果未能解决你的问题,请参考以下文章
Android - 如何在以编程方式加载或保存 JPEG 文件时获取或设置(打印)DPI(每英寸点数)?