简体   繁体   English

要在事务之外使用的 JPA Spring Data 实体

[英]JPA Spring Data entity to be used outside of transaction

I have a Spring Boot application with a service that returns a Spring Data entity that is exposed to a controller.我有一个带有服务的 Spring Boot 应用程序,该服务返回一个暴露给控制器的 Spring Data 实体。 The problem is that I know it's not a good idea to use entities outside of DB transactions, so what would be the best practices?问题是我知道在数据库事务之外使用实体不是一个好主意,那么最佳实践是什么?

Consider the following service:考虑以下服务:

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

where MyData is a database @Entity and myDataRepository is a JpaRepository其中 MyData 是一个数据库@EntitymyDataRepository是一个JpaRepository

This service method is called from a controller class, that sends this object in JSON format to a client that calls this method.该服务方法是从控制器类调用的,控制器类以 JSON 格式将此对象发送到调用此方法的客户端。

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

If I expose MyData to a controller, then it will be exposed outside of a transaction and might cause all kind of hibernate errors.如果我将MyData公开给控制器,那么它将在事务之外公开,并可能导致各种休眠错误。 What are the best practices for these scenarios?这些场景的最佳实践是什么? Should I convert entity to POJO in side the service and return MyDataPOJO instead of MyData in MyService ?我应该在服务MyDataPOJO实体转换为 POJO 并在MyService返回MyDataPOJO而不是MyData吗?

Using entities outside of transactions does not necessarily lead to problems;在事务之外使用实体并不一定会导致问题; it may actually have valid use cases.它实际上可能有有效的用例。 However, there's quite a few variables at play and once you let them out of your sight things may and will go south.然而,有很多变数在起作用,一旦你让它们离开你的视线,事情可能会而且将会向南。 Consider the following scenarios:考虑以下场景:

  1. Your entity doesn't have any relationships to other entities or those relationships are pretty shallow and eagerly fetched.您的实体与其他实体没有任何关系,或者这些关系非常浅薄且急切地获取。 You retrieve that entity from repository, detach it from persistence unit (implicitly or explicitly) and pass to controller.您从存储库中检索该实体,将其与持久性单元(隐式或显式)分离并传递给控制器​​。 Controller does not attempt to modify the entity;控制器不会尝试修改实体; it only serializes it into JSON - totally safe.它只将其序列化为 JSON - 完全安全。
  2. Same as above but controller modifies the entity before serializing it into JSON - again, totally safe (just don't expect those changes to be reflected in DB)与上面相同,但控制器在将实体序列化为 JSON 之前修改实体 - 同样,完全安全(只是不要指望这些更改会反映在 DB 中)
  3. Same as above, but you've forgotten to detach the entity from PU - ouch, if controller changes the entity you may either see it reflected in DB or get transaction closed exception;与上面相同,但您忘记将实体与 PU 分离 - 哎哟,如果控制器更改了实体,您可能会看到它反映在 DB 中或获取事务关闭异常; both most likely being unintended consequences.两者都极有可能是意想不到的后果。
  4. Same as above, but some of entity's relationships are lazy.同上,但实体的一些关系是惰性的。 Again, you may or may not get any exceptions depending on whether these lazy properties are being accessed or not.同样,根据是否正在访问这些惰性属性,您可能会也可能不会得到任何异常。
  5. And there are so many more combinations of intentional and unintentional design choices...还有更多有意和无意的设计选择组合......

As you may see, things can get out of control very quickly.如您所见,事情很快就会失控。 Especially so when your model has to evolve: before long you're going to find yourself fiddling with JSON views, @JsonIgnore , entity projections and so on.特别是当您的模型必须发展时:不久您会发现自己摆弄 JSON 视图、 @JsonIgnore 、实体投影等。 Thus the rule of thumb: although it may seem tempting to cut some corners and expose your entities to external layers, it's rarely a good idea.因此,经验法则是:虽然看起来很想偷工减料并将您的实体暴露给外部层,但这很少是一个好主意。 Properly designed solution always has a clear separation of concerns between layers:正确设计的解决方案始终在层之间明确分离关注点:

  • Persistence layer never exposes more methods or entities than required by business logic.持久层永远不会暴露比业务逻辑所需的更多的方法或实体。 More over, the same table(s) can and should be mapped into several different entities depending on the use cases they participate in.此外,相同的表可以并且应该根据它们参与的用例映射到几个不同的实体。
  • Business logic layer (btw this is your API, not the REST services! see below) never leaks any details from persistence layer.业务逻辑层(顺便说一句,这是您的 API,而不是 REST 服务!见下文)永远不会从持久层泄漏任何细节。 Its methods clearly define use cases from the problem domain.它的方法清楚地定义了问题域中的用例。
  • Presentation layer only translates API provided by business logic into one or another form suitable for client and never implements additional use cases.表示层仅将业务逻辑提供的 API 转换为适合客户端的一种或另一种形式,从不实现额外的用例。 Keep in mind that REST controllers, SOAP services etc logically are all part of presentation layer, not business logic.请记住,REST 控制器、SOAP 服务等在逻辑上都是表示层的一部分,而不是业务逻辑。

So yeah, the short answer is: persistence entities should not be exposed to external layers.所以是的,简短的回答是:持久性实体不应该暴露给外部层。 One common technique is to use DTOs instead;一种常见的技术是改用 DTO; besides, DTO objects provide additional abstraction layer in case you need to change your entities but leave API intact or vice versa.此外,DTO 对象提供额外的抽象层,以防您需要更改实体但保持 API 不变,反之亦然。 If at some point your DTOs happen to closely resemble your entities, there are Java bean mapping frameworks like Dozer, Orika, MapStruct, JMapper, ModelMapper etc that help to eliminate the boilerplate code.如果在某些时候您的 DTO 碰巧与您的实体非常相似,则可以使用 Java bean 映射框架(如 Dozer、Orika、MapStruct、JMapper、ModelMapper 等)来帮助消除样板代码。

Try googling "hexagonal architecture".尝试谷歌搜索“六边形架构”。 This is a very interesting concept for designing cleanly separated layers.这是设计干净分离层的一个非常有趣的概念。 Here's one of the articles on this subject https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/ ;这是关于这个主题的一篇文章https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/ it uses C# examples but they're pretty simple.它使用 C# 示例,但它们非常简单。

You should never leak the internal model to outside resources (in your case - the @RestController ).您永远不应该将内部模型泄漏给外部资源(在您的情况下 - @RestController )。 The "POJO" you mentioned is typically called a DTO (Data Transfer Object).您提到的“POJO”通常称为DTO (数据传输对象)。 The DTO can be defined as an interface on the Service-side and implemented on the Controller-side. DTO可以定义为Service端的接口,在Controller端实现。 The Service would then - as you described - transform the internal model into an instance of the DTO, achieving looser coupling between the Controler and the Service.然后服务将 - 正如您所描述的 - 将内部模型转换为 DTO 的实例,实现控制器和服务之间的松散耦合。

By defining the DTO-interface on the service-side, you have the additional benefit that you can optimize your persistence-acces by only fetching the data specified in the corresponding DTO-interface.通过在服务端定义 DTO 接口,您可以获得额外的好处,即您可以通过仅获取在相应 DTO 接口中指定的数据来优化持久性访问。 There is, for example, no need to fetch the friends of a User if the @Controller does not specifically requests them, thus you do not need to perform the additional JOIN in the database (provided you use a database).例如,如果@Controller没有特别请求他们,则不需要获取Userfriends ,因此您不需要在数据库中执行额外的JOIN (前提是您使用数据库)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM