当前位置: 首页 > 知识库问答 >
问题:

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

艾星河
2023-03-14

已经有一些关于这个主题的问题,但是没有任何回应真正提供论据来解释为什么我们不应该制作一个Spring html" target="_blank">MVC控制器Transactional。见:

>

对于web MVC,Spring应用程序是否应在控制器或服务上运行@Transactional?

制作Spring 3 MVC控制器的方法Transactional

springmvc控制器

那么,为什么呢?

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

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

在我看来,这是我见过的最好的设计。当我试图用SpringMVC复制相同的设计时,我陷入了一场噩梦,出现了延迟加载和事务问题,每次都得到了相同的答案:不要使控制器具有事务性,但为什么?

提前感谢您的回答!

共有3个答案

闽阳州
2023-03-14

有时,当抛出异常时,您希望回滚事务,但同时您希望处理异常,在控制器中创建适当的响应。

更新:回滚也可以通过编程实现,如Rodério的回答中所述。

更好的解决方案是使服务方法具有事务性,然后处理控制器方法中可能出现的异常。

下面的示例显示了具有 createUser方法的用户服务,该方法负责创建用户并向用户发送电子邮件。如果发送邮件失败,我们希望回滚用户创建:

 
  @Service
public class UserService {

    @Transactional
    public User createUser(Dto userDetails) {

        // 1. create user and persist to DB

        // 2. submit a confirmation mail
        //    -> might cause exception if mail server has an error

        // return the user
    }
}

 

然后在控制器中,您可以将对 createUser的调用包装在try/catch中,并创建对用户的正确响应:

 
  @Controller
public class UserController {

    @RequestMapping
    public UserResultDto createUser (UserDto userDto) {

        UserResultDto result = new UserResultDto();

        try {

            User user = userService.createUser(userDto);

            // built result from user

        } catch (Exception e) {
            // transaction has already been rolled back.

            result.message = "User could not be created " + 
                             "because mail server caused error";
        }

        return result;
    }
}

 

如果您在控制器方法上放置了一个 @事务,这是不可能的。

寿亦
2023-03-14

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

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

在一种情况下,我们有一个扩展Struts'`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````/commit/rollback,以及将异常映射到用户友好的错误消息。如果任何异常被传播到这个级别,或者如果它被标记为只进行回滚,这个方法将简单地回滚当前事务;否则,它将提交事务。这在每种情况下都有效,通常整个HTTP请求/响应周期只有一个数据库事务。需要多个事务的罕见情况将在特定于用例的代码中处理。

在GWT-RPC的例子中,一个类似的解决方案是由一个基本GWT Servlet实现实现的。

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

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

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

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

魏安宁
2023-03-14

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

控制器可以设置为事务性的,但实际上,通常建议只设置服务层事务性(持久层也不应该是事务性的)。

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

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

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

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

持久层无法知道它在哪个事务中,例如方法customerDao。保存地址。它是否应该始终在自己的单独事务中运行?没有办法知道,这取决于调用它的业务逻辑。有时它应该在单独的事务上运行,有时仅在saveCustomer也工作时保存它的数据,等等。

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

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

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

 类似资料:
  • 我的老师让我这样做,但在评论区我被告知我不应该这样做。 为什么?

  • 我想了解一些关于Spring的东西。我正在尝试配置它,但是有些东西我不明白,因为Spring似乎不认识我的控制器。 这是我的web.xml 这就是servlet 控制器:

  • 问题内容: Java中的原始类型是什么?为什么我经常听到不应该在新代码中使用它们的信息? 如果我们不能使用原始类型,那有什么选择呢?有什么更好的选择? 问题答案: 什么是原始类型? Java语言规范对原始类型的定义如下: JLS 4.8原始类型 原始类型定义为以下之一: 通过采用通用类型声明的名称而没有随附的类型参数列表而形成的引用类型。 数组类型,其元素类型为原始类型。 未从的超类或超接口继承s

  • 从外观上看-似乎创建了一个对象的克隆。如果是这样,那么对于实现可克隆接口(只有不可变对象是新的,因为可变对象有引用复制)的关注,哪一个是最好的,为什么? 我昨天实现了克隆,然后意识到我必须为非字符串/首字母元素提供自己的修改。然后我被告知我现在正在使用的。这两个实现似乎都提供了类似的功能。 谢谢

  • 问题内容: 我有2个线程T1和T2,两者都有不同的工作,因此通常我们更喜欢通过线程Joins完成此任务。 但是我们无需使用join()就可以做到这一点。我们可以在T1线程中添加T2线程的代码。这有什么区别? 问题答案: 主要区别在于,当我们将T2线程与T1连接在一起时,T2执行该任务的时间也可以由T1占用,这意味着它们将并行执行不同的任务。但是,当您在T1中包含T2线程代码时,不会发生这种情况。线

  • 问题内容: 我在ORM上还很新。我刚刚开始阅读有关使用Hibernate的Java Persistence API的书籍和文档。 我只是想知道,关闭EntityManagerFactory与jdbc数据库连接关闭类似吗? 我们是否应该在每次持久/更新/删除后关闭它?如果我们不关闭它,数据库连接会保持打开状态吗? 问题答案: 我只是想知道,关闭与jdbc数据库连接关闭类似吗? 这并非完全正确,但关闭