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

时间:2018-12-24 21:16:51

标签: java spring spring-data-jpa

我有一个带有服务的Spring Boot应用程序,该服务返回暴露给控制器的Spring Data实体。问题是我知道在数据库事务之外使用实体不是一个好主意,那么最佳做法是什么?

请考虑以下服务:

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

其中MyData是数据库@Entity,而myDataRepositoryJpaRepository

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

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

如果我将MyData暴露给控制器,则它将暴露于事务之外,并且可能导致各种休眠错误。这些方案的最佳做法是什么?我应该在服务端将实体转换为POJO并返回MyDataPOJO中的MyData而不是MyService吗?

2 个答案:

答案 0 :(得分:2)

永远不要将内部模型泄漏给外部资源(在您的情况下为@RestController)。您提到的“ POJO”通常称为DTO(数据传输对象)。 DTO可以定义为服务端的接口,并可以在控制器端实现。然后,正如您所描述的,该服务会将内部模型转换为DTO的实例,从而实现控制者与服务之间的松散耦合。

通过在服务端定义DTO接口,您可以获得额外的好处,即仅获取相应DTO接口中指定的数据,就可以优化持久性访问权限。例如,如果friends并未明确要求User,则无需获取@Controller的{​​{1}},因此您无需执行额外的JOIN在数据库中(如果您使用数据库)。

答案 1 :(得分:0)

在交易之外使用实体不一定会导致问题;实际上可能有有效的用例。但是,有很多变量在起作用,一旦您将它们移出视线,事情可能就会过去。请考虑以下情形:

  1. 您的实体与其他实体没有任何关系,或者这些关系很浅并且很热衷。您从存储库中检索该实体,将其与持久性单元分离(隐式或显式),然后传递给控制器​​。控制器不尝试修改实体;只会将其序列化为JSON-完全安全。
  2. 与上述相同,但控制器在将实体序列化为JSON之前对其进行了修改-再次完全安全(只是不要期望这些更改会反映在数据库中)
  3. 与上述相同,但是您忘记了将实体与PU分离-如果控制器更改了实体,则您可能会看到它反映在DB中或导致事务关闭异常;两者极有可能是意想不到的后果。
  4. 与上面相同,但是实体的某些关系是惰性的。同样,根据是否访问这些惰性属性,您可能会也可能不会获得任何例外。
  5. 故意设计和非故意设计选择的组合更多...

您可能会看到,事情很快就会失控。尤其是当模型必须发展时:不久之后,您将发现自己对JSON视图,@JsonIgnore,实体投影等感到不安。因此,这是经验法则:尽管看起来有些诱惑力,想让您的实体暴露于外部层,但这并不是一个好主意。正确设计的解决方案始终将各层之间的关注点清晰地分开:

  • 持久层永远不会公开超过业务逻辑要求的方法或实体。而且,可以根据一个表或多个表所涉及的用例将它们映射到几个不同的实体中。
  • 业务逻辑层(顺便说一句,这是您的API,而不是REST服务!请参见下文)永远不会泄漏持久层的任何详细信息。它的方法从问题领域清楚地定义了用例。
  • 表示层仅将业务逻辑提供的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#示例,但它们非常简单。