jer*_*erg 60 java controller spring-mvc transactional
关于这个主题已经有一些问题,但是没有任何响应可以提供参数来解释为什么我们不应该创建一个Spring MVC控制器Transactional.看到:
所以为什么?
背景:几年前,我在团队中使用C#/ NHibernate/Spring.Net实现了一个相当大的ERP软件.对服务器的往返实际上是这样实现的:事务在进入任何控制器逻辑之前打开,并在退出控制器后被提交或回滚.该交易在框架中进行管理,因此没有人必须关心它.这是一个出色的解决方案:稳定,简单,只有少数架构师不得不关心交易问题,团队的其他成员只是实现了功能.
从我的角度来看,这是我见过的最好的设计.当我尝试使用Spring MVC重现相同的设计时,我进入了一个带有延迟加载和事务问题的噩梦,每次都有相同的答案:不要让控制器进行事务处理,但为什么呢?
提前感谢您的答案!
Ang*_*ity 101
TLDR:这是因为只有应用程序中的服务层具有识别数据库/业务事务范围所需的逻辑.设计的控制器和持久层不能/不应该知道事务的范围.
可以制作控制器@Transactional,但实际上通常建议仅使服务层事务化(持久层也不应该是事务性的).
其原因不是技术可行性,而是分离关注点.控制器的职责是获取参数请求,然后调用一个或多个服务方法,并将结果组合在响应中,然后将响应发送回客户端.
因此,控制器具有请求执行的协调器的功能,并且域数据的变换器具有客户端可以消费的格式,例如DTO.
业务逻辑驻留在服务层上,持久层只是从数据库中来回检索/存储数据.
数据库事务的范围实际上是一个商业概念和技术概念:在帐户转移中,只有当另一个帐户被记入等时才能记入帐户,因此只有包含业务逻辑的服务层才能真正知道银行账户转账交易的范围.
持久层无法知道它所处的事务,例如采用一种方法customerDao.saveAddress.它应该始终在它自己独立的交易中运行吗?没有办法知道,这取决于调用它的业务逻辑.有时它应该在一个单独的事务上运行,有时只保存它的数据,如果它saveCustomer也有效,等等.
这同样适用于控制器:应该saveCustomer和saveErrorMessages走在相同的事务?您可能希望保存客户,如果失败,则尝试保存一些错误消息并向客户端返回正确的错误消息,而不是回滚包括您要在数据库上保存的错误消息的所有内容.
在非事务控制器中,从服务层返回的方法返回分离的实体,因为会话已关闭.这是正常的,解决方案是使用OpenSessionInView或执行急切获取控制器知道所需结果的查询.
话虽如此,控制器交易并不是犯罪行为,但这并不是最常用的做法.
Rog*_*rio 19
我已经在实践中,在中型到大型业务Web应用程序中看到了这两种情况,使用了各种Web框架(JSP/Struts 1.x,GWT,JSF 2,Java EE和Spring).
根据我的经验,最好在最高级别(即"控制器"级别)划分交易.
在一种情况下,我们有一个BaseAction类扩展Struts的Action类,具有execute(...)处理Hibernate会话管理(保存到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().
因此,根据我的观点和经验,使控制器事务处理是更好的方法.
有时,您想在引发异常时回滚事务,但是同时您想处理该异常,请在控制器中为其创建适当的响应。
如果@Transactional使用控制器方法来强制执行回滚以从控制器方法引发事务,则是唯一的方法,但是您将无法返回正常的响应对象。
更新:如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
}
}
Run Code Online (Sandbox Code Playgroud)
然后在您的控制器中,您可以将调用包装到createUsertry / 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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果@Transaction在您的控制器方法上放一个根本不可能。
| 归档时间: |
|
| 查看次数: |
31600 次 |
| 最近记录: |