JPA Spring Data实体在事务外部使用

4

我有一个使用Spring Boot的应用程序,其中一个服务返回一个Spring Data实体并暴露给控制器。问题是,我知道在DB交易之外使用实体不是一个好主意,那么最佳实践是什么呢?

请考虑以下服务:

@Transactional
public MyData getMyData(Long id) {
    return myDataRepository.findById(id);
}

其中,MyData是一个数据库实体,myDataRepository是一个JpaRepository

这个服务方法被一个控制器类调用,将该对象以JSON格式发送给调用此方法的客户端。

@RequestMapping("/")
public ResponseEntity<?> getMyData(@RequestParam Long id) {
    return myService.getMyData(id);
}

如果我将MyData暴露给控制器,那么它将在事务之外暴露,可能会引起各种Hibernate错误。对于这些情况,最佳实践是什么?我应该在服务内部将实体转换为POJO,并在MyService中返回MyDataPOJO而不是MyData吗?

是的,你应该创建一个响应的POJO对象,并将结果从数据库传输到你的响应对象中。 - Artanis
2个回答

4
在事务之外使用实体不一定会导致问题;实际上,它可能有有效的用例。然而,有很多变量在起作用,一旦你让它们脱离了你的视线,事情可能会变得糟糕。考虑以下情况:
1. 你的实体没有与其他实体的关系或这些关系非常浅显易懂,并且被急切地获取。你从仓库中检索该实体,将其从持久性单元中分离(隐式或显式),并将其传递给控制器。控制器不尝试修改实体;它只将其序列化为JSON——完全安全。
2. 与上述情况相同,但是控制器在将实体序列化为JSON之前修改了实体——同样安全(只是不要期望这些更改反映在数据库中)。
3. 与上述情况相同,但是你忘记了从PU中分离该实体——如果控制器更改该实体,则您可能会看到它反映在数据库中或者得到事务关闭异常;两者都很可能是意外后果。
4. 与上述情况相同,但是实体的某些关系是惰性的。同样,您可能会得到任何异常,具体取决于是否正在访问这些惰性属性。
5. 还有很多意向和非意向的设计选择组合......
正如您所看到的,事情可能会很快失控。特别是当您的模型必须发展时:不久之后,您将发现自己在摆弄JSON视图、@JsonIgnore、实体投影等等。因此,经验法则是:尽管似乎诱人去缩短一些路程并将实体暴露给外部层,但这很少是一个好主意。正确设计的解决方案始终在层之间具有明确的关注点分离:
1. 持久层从不公开比业务逻辑所需更多的方法或实体。同一张表可以并且应该映射到几个不同的实体中,具体取决于它们参与的用例。
2. 业务逻辑层(顺便说一下,这是您的API,而不是REST服务!请参见下文)从不泄漏来自持久层的任何细节。它的方法清楚地定义了问题域中的用例。
3. 表示层仅将业务逻辑提供的API转换为适合客户端的一种形式,永远不会实现其他用例。请记住,REST控制器、SOAP服务等在逻辑上都是表示层的一部分,而不是业务逻辑。
所以,是的,简短的答案是:持久实体不应该暴露给外部层。一种常见的技术是使用DTO代替;此外,DTO对象在您需要更改实体但保留API或反之亦然的情况下提供了额外的抽象层。如果在某个时刻,您的DTO与实体非常相似,则有Java bean映射框架(如Dozer、Orika、MapStruct、JMapper、ModelMapper等)可帮助消除样板代码。

尝试搜索“六边形架构”。这是一种设计清晰分离层的非常有趣的概念。以下是关于此主题的一篇文章https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/,其中使用了C#示例,但它们非常简单易懂。


2
您不应将内部模型泄漏给外部资源(在您的情况下-@RestController)。您提到的“POJO”通常称为DTO(数据传输对象)。 DTO可以在服务端定义为接口,并在控制器端实现。然后,服务将内部模型转换为DTO实例,从而实现控制器和服务之间的松散耦合,就像您所描述的那样。
通过在服务端定义DTO接口,您还可以通过仅获取相应DTO接口中指定的数据来优化持久性访问。例如,如果@Controller没有明确请求Userfriends,则无需获取它们,因此您无需在数据库中执行额外的JOIN操作(前提是您使用数据库)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接