为什么我们不应该制作一个 Spring MVC 控制器@Transactional?

新手上路,请多包涵

已经有一些关于该主题的问题,但根本没有任何回应真正提供论据来解释为什么我们不应该制作 Spring MVC 控制器 Transactional 。看:

所以为什么?

  • 是否存在 无法克服 的技术问题?
  • 是否存在架构问题?
  • 是否存在性能/死锁/并发问题?
  • 有时需要多个单独的交易吗?如果是,用例是什么? (我喜欢简化的设计,对服务器的调用要么完全成功要么完全失败。这听起来是一种非常稳定的行为)

背景:几年前,我在一个团队中工作,开发了一个用 C#/NHibernate/Spring.Net 实现的相当大的 ERP 软件。到服务器的往返完全是这样实现的:事务在进入任何控制器逻辑之前打开,并在退出控制器后提交或回滚。事务在框架中进行管理,因此没有人需要关心它。 这是一个绝妙的解决方案:稳定、简单,只有少数架构师需要关心事务问题,团队的其他成员只是实现功能。

从我的角度来看,这是我见过的最好的设计。当我试图用 Spring MVC 重现相同的设计时,我陷入了延迟加载和事务问题的噩梦中,每次都是相同的答案:不要让控制器成为事务性的,但为什么呢?

预先感谢您提供有根据的答案!

原文由 jeromerg 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 433
2 个回答

TLDR :这是因为只有应用程序中的服务层具有识别数据库/业务事务范围所需的逻辑。设计上的控制器和持久层不能/不应该知道事务的范围。

可以制作控制器 @Transactional ,但实际上,只使服务层事务性(持久层也不应该是事务性的)确实是一个常见的建议。

这样做的原因不是技术可行性,而是关注点分离。控制器的职责是获取参数请求,然后调用一个或多个服务方法并将结果组合成一个响应,然后发送回客户端。

因此,控制器具有请求执行协调器的功能,以及将域数据转换为客户端可以使用的格式(例如 DTO)的功能。

业务逻辑驻留在服务层,持久层只是从数据库中来回检索/存储数据。

数据库事务的范围实际上是一个业务概念,也是一个技术概念:在账户转账中,一个账户只有在另一个账户被贷记时才能被借记等等,所以只有包含业务逻辑的服务层才能真正知道银行账户转账交易的范围。

持久层无法知道它在什么事务中,例如一个方法 customerDao.saveAddress 。它应该始终在自己的单独事务中运行吗?没有办法知道,这取决于调用它的业务逻辑。有时它应该在单独的事务上运行,有时只在 saveCustomer 也有效时才保存它的数据,等等。

这同样适用于控制器: saveCustomersaveErrorMessages 应该在同一个事务中吗?您可能想要保存客户,如果失败,则尝试保存一些错误消息并向客户端返回正确的错误消息,而不是回滚所有内容,包括您想要保存在数据库中的错误消息。

在非事务控制器中,从服务层返回的方法返回分离的实体,因为会话已关闭。这是正常的,解决方案是要么使用 OpenSessionInView 要么进行急切获取控制器知道它需要的结果的查询。

话虽如此,使控制器具有事务性并不是犯罪,只是不是最常用的做法。

原文由 Angular University 发布,翻译遵循 CC BY-SA 3.0 许可协议

我在实践中看到过这两种情况,在大中型业务 Web 应用程序中,使用各种 Web 框架(JSP/Struts 1.x、GWT、JSF 2,以及 Java EE 和 Spring)。

根据我的经验,最好在最高级别(即“控制器”级别)划分事务。

在一个案例中,我们有一个 BaseAction 类扩展了 Struts 的 Action 类,具有 ber-nate 管理的实现( execute(...) 会话管理方法 ThreadLocal 对象),事务开始/提交/回滚,以及异常到用户友好错误消息的映射。如果任何异常传播到此级别,或者如果它被标记为仅回滚,则此方法将简单地回滚当前事务;否则,它将提交事务。这适用于所有情况,通常整个 HTTP 请求/响应周期只有一个数据库事务。需要多个事务的极少数情况将在用例特定代码中处理。

对于 GWT-RPC,类似的解决方案由基本 GWT Servlet 实现实现。

对于 JSF 2,到目前为止我只使用了服务级别划分(使用自动具有“必需”事务传播的 EJB 会话 bean)。与在 JSF 支持 bean 级别划分事务相比,这里有一些缺点。基本上,问题在于在许多情况下 JSF 控制器需要进行多次服务调用,每次调用都访问应用程序数据库。对于服务级事务,这意味着多个单独的事务(全部提交,除非发生异常),这会增加数据库服务器的负担。不过,这不仅仅是性能劣势。对单个请求/响应进行多个事务也可能导致细微的错误(我不记得细节了,只是这样的问题确实发生了)。

这个问题的其他答案谈到“识别数据库/业务交易范围所需的逻辑”。这个论点对我来说没有意义,因为通常 根本没有 与事务划分相关的逻辑。控制器类和服务类都不需要真正“知道”事务。在绝大多数情况下,在 Web 应用程序中,每个业务操作都发生在 HTTP 请求/响应对中,事务的范围是从收到请求到响应完成为止正在执行的所有单个操作。

有时,业务服务或控制器可能需要以特定方式处理异常,然后可能将当前事务标记为仅回滚。在 Java EE (JTA) 中,这是通过调用 UserTransaction#setRollbackOnly() 来完成的。 UserTransaction 对象可以注入 @Resource 字段,或以编程方式从某些 ThreadLocal 获得。在 Spring 中, @Transactional 注释允许为某些异常类型指定回滚,或者代码可以获得线程本地 TransactionStatus 并调用 setRollbackOnly()

因此,以我的观点和经验,使控制器具有事务性是更好的方法。

原文由 Rogério 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题