我有一个带有服务的Spring Boot应用程序,该服务返回暴露给控制器的Spring Data实体。问题是我知道在数据库事务之外使用实体不是一个好主意,那么最佳做法是什么?
请考虑以下服务:
@Transactional
public MyData getMyData(Long id) {
return myDataRepository.findById(id);
}
其中MyData是数据库@Entity
,而myDataRepository
是JpaRepository
此服务方法是从控制器类中调用的,该控制器类将该对象以JSON格式发送给调用此方法的客户端。
@RequestMapping("/")
public ResponseEntity<?> getMyData(@RequestParam Long id) {
return myService.getMyData(id);
}
如果我将MyData
暴露给控制器,则它将暴露于事务之外,并且可能导致各种休眠错误。这些方案的最佳做法是什么?我应该在服务端将实体转换为POJO并返回MyDataPOJO
中的MyData
而不是MyService
吗?
答案 0 :(得分:2)
永远不要将内部模型泄漏给外部资源(在您的情况下为@RestController
)。您提到的“ POJO”通常称为DTO(数据传输对象)。 DTO可以定义为服务端的接口,并可以在控制器端实现。然后,正如您所描述的,该服务会将内部模型转换为DTO的实例,从而实现控制者与服务之间的松散耦合。
通过在服务端定义DTO接口,您可以获得额外的好处,即仅获取相应DTO接口中指定的数据,就可以优化持久性访问权限。例如,如果friends
并未明确要求User
,则无需获取@Controller
的{{1}},因此您无需执行额外的JOIN
在数据库中(如果您使用数据库)。
答案 1 :(得分:0)
在交易之外使用实体不一定会导致问题;实际上可能有有效的用例。但是,有很多变量在起作用,一旦您将它们移出视线,事情可能就会过去。请考虑以下情形:
您可能会看到,事情很快就会失控。尤其是当模型必须发展时:不久之后,您将发现自己对JSON视图,@JsonIgnore
,实体投影等感到不安。因此,这是经验法则:尽管看起来有些诱惑力,想让您的实体暴露于外部层,但这并不是一个好主意。正确设计的解决方案始终将各层之间的关注点清晰地分开:
是的,简短的答案是:持久性实体不应暴露于外部层。一种常见的技术是改为使用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#示例,但它们非常简单。