Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/spring-mvc/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么我们不应该';t在事务性环境下制作Spring MVC控制器?_Java_Spring Mvc_Controller_Transactional - Fatal编程技术网

Java 为什么我们不应该';t在事务性环境下制作Spring MVC控制器?

Java 为什么我们不应该';t在事务性环境下制作Spring MVC控制器?,java,spring-mvc,controller,transactional,Java,Spring Mvc,Controller,Transactional,关于这个主题已经有一些问题了,但是没有任何回答真正提供了解释为什么我们不应该制作一个SpringMVC控制器事务性的。见: 那么,为什么呢 是否存在无法克服的技术问题 是否存在架构问题 是否存在性能/死锁/并发问题 有时需要多个单独的交易吗?如果是,用例是什么?(我喜欢这种简化的设计,即调用服务器要么完全成功,要么完全失败。这听起来是一种非常稳定的行为) 背景: 几年前,我在一个团队中工作,开发了一个用C#/NHibernate/Spring.Net实现的大型ERP软件。到服务器

关于这个主题已经有一些问题了,但是没有任何回答真正提供了解释为什么我们不应该制作一个SpringMVC控制器
事务性的
。见:

那么,为什么呢

  • 是否存在无法克服的技术问题
  • 是否存在架构问题
  • 是否存在性能/死锁/并发问题
  • 有时需要多个单独的交易吗?如果是,用例是什么?(我喜欢这种简化的设计,即调用服务器要么完全成功,要么完全失败。这听起来是一种非常稳定的行为)
背景: 几年前,我在一个团队中工作,开发了一个用C#/NHibernate/Spring.Net实现的大型ERP软件。到服务器的往返完全是这样实现的:事务在进入任何控制器逻辑之前打开,在退出控制器之后提交或回滚。事务是在框架中管理的,因此没有人需要关心它。这是一个出色的解决方案:稳定、简单,只有少数架构师需要关心事务问题,团队的其他成员只是实现了一些特性

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


提前感谢您的回答

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

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

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

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

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

数据库事务的范围实际上是一个业务概念,也是一个技术概念:在账户转账中,一个账户只能在另一个账户被贷记等情况下被借记,因此只有包含业务逻辑的服务层才能真正了解银行账户转账事务的范围

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

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

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


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

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

如果将
@Transactional
放在控制器方法上,这是强制回滚的唯一方法,它将从控制器方法中抛出事务,但无法返回正常响应对象

更新:也可以通过编程实现回滚,如中所述

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

下面的示例显示了具有
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;
    }
}

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

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

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

在一个例子中,我们有一个
BaseAction
类扩展了Struts的
Action
类,并实现了
execute(…)
方法,该方法处理Hibernate会话管理(保存到
ThreadLocal
对象)、事务开始/提交/回滚、,以及将异常映射到用户友好的错误消息。此方法将简单地回滚curren