在事务之外使用实体不一定会导致问题;实际上,它可能有有效的用例。然而,有很多变量在起作用,一旦你让它们脱离了你的视线,事情可能会变得糟糕。考虑以下情况:
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#示例,但它们非常简单易懂。