一尘不染

为什么我们不应该使用Spring MVC控制器@Transactional?

spring-mvc

关于这个话题已经有一些问题了,但是为了解释为什么我们不应该制作一个Spring
MVC控制器,根本没有任何回应可以提供任何论据Transactional

所以为什么?

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

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

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

预先感谢您提出的答案!


阅读 570

收藏
2020-06-01

共1个答案

一尘不染

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

可以@Transactional构造控制器,但实际上,通常建议仅使服务层具有事务性(持久性层也不应具有事务性)。

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

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

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

数据库事务的范围实际上是一个业务概念,而不仅仅是一个技术概念:在帐户转帐中,只有在其他帐户被贷记等情况下才可以借记一个帐户,因此只有包含业务逻辑的服务层才能真正知道银行帐户转帐交易的范围。

持久层无法知道它在进行什么事务,例如method
customerDao.saveAddress。它是否应该始终在自己的独立事务中运行?没有办法知道,这取决于调用它的业务逻辑。有时它应该在单独的事务上运行,有时仅在它们saveCustomer也起作用时才保存其数据,等等。

控制器也是如此:应该saveCustomer和应该saveErrorMessages进行同一笔交易吗?您可能想要保存客户,如果失败,则尝试保存一些错误消息并向客户端返回正确的错误消息,而不是回滚包括要保存在数据库中的所有错误消息在内的所有内容。

在非事务控制器中,由于会话已关闭,因此从服务层返回的方法将返回分离的实体。这是正常的,解决方案是使用OpenSessionInView或执行渴望获取控制器知道所需结果的查询。

话虽如此,让控制员具有交易性不是犯罪,这不是最常用的做法。

2020-06-01