我有一个关于EE容器如何控制事务的问题。这是伪代码,为我的问题提供一些上下文。这不是我的编码方式,所以请继续关注这个问题,不要将主题演变成其他东西。
请考虑以下两个服务和相关的控制器。这两个服务都注入了 EntityManager,并且都有需要在事务中运行的方法。一个服务具有不需要任何事务支持的方法。
@Stateless
class UserService {
@PersistenceContext private EntityManager em;
public void saveUser(User user) {
em.merge(user);
}
public String getFullName(User user) {
return user.getFirstName() + " " + user.getLastName();
}
}
@Stateless
class LogService {
@PersistenceContext private EntityManager em;
public void logEvent(String eventText) {
Event event=new Event();
event.setText(eventText);
event.setTime(new Date());
em.persist(event);
}
}
@Named
class UserController {
User user;
@Inject UserService userService;
@Inject LogService logService;
public void updateUser(user) { // button posts to this method
String fullName=userService.getFullName(user); // 1
if(fullName.startsWith("X")) return; // 2
userService.saveUser(user); // 3
logService.logEvent("Saved user " + fullName); // 4
}
}
现在,假设有一个按钮将表单发布到userController.updateUser。
我的假设是UserController.updateUser()
将在同一事务中执行userService.saveUser(user);
和logService.logEvent("Saved user " + fullName);
。因此,如果对logService.logEvent()
的调用失败并出现 SQL 异常,则不会更新用户实体。此外,我的假设是调用userService.getFullName(user)
不会在任何事务中运行,如果我们在用户名以 X 开头时过早退出该方法,则不会创建任何事务。但显然,这些只是猜测。
有人可以解释一下Java EE容器将如何支持UserController.updateUser()
事务方法以及实际触发事务的原因吗?此外,您可以指出我的任何进一步阅读将不胜感激。我在网上看到了一些材料,但我仍然在这里错过了一些东西,也没有在工作中得到任何帮助。所以我当然不是唯一一个在这方面有分歧的人。
在您的情况下,将启动 3 个独立事务。每一种@Stateless
豆方法。这是因为会话 EJB 具有缺省情况下事务类型TransactionAttribute.REQUIRED
的跨方法。这意味着,如果事务尚未运行,则将在方法调用之前创建新事务。
要在一次事务中运行所有会话 EJB 方法,必须将它们包装在一个事务中。在您的情况下,您可以通过使用 @Transactional
@Inject
注释更改为 @EJB
,然后默认情况下使用 CMT(容器管理事务(,CDI Bean 的每个调用都将在其自己的 TX 范围内。如果不希望其中一个方法调用调用 TX,请将 @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED( 添加到该方法中。
从业务逻辑的外观来看,如果您不希望该方法在 TX 中运行,您可以通过将@Named
更改为 @Stateless
、将@Inject
更改为 @EJB
并在 getFullName(..( 上添加@TransactionalAttribute(TransactionAttributeType.NOT_SUPPORTED)
来真正简化事情。通过这些更改,您将获得所需的TX行为。
如果您希望UserController成为JSF管理的bean,那么我建议我将@Named
更改为@ManagedBean
,只需在@ManagedBean
下添加@Stateless
,而不是将@Named
更改为@Stateless
。
Java EE 中的事务必须显式控制,要么使用 JNDI 中的UserTransaction
,要么使用 EJB 上的部署描述符/注释。对于CDI组件,此处为UserController
,默认情况下不启动任何事务。(如果未指定任何内容,缺省情况下将启用 EJB 方法的 EDIT 事务。
所以首先,你的假设:
UserController.updateUser()
将在同一事务中执行userService.saveUser(user);
和logService.logEvent("Saved user " + fullName)
错了!我相信将在每次em.persist()
/em.merge()
调用时创建和提交一个新事务。
为了将调用saveUser()
和logEvent()
包装在同一事务中,您可以手动使用以下UserTransaction
:
public void updateUser(user) {
InitialContext ic = new InitialContext();
UserTransaction utx = (UserTransaction) ic.lookup("java:comp/UserTransaction");
utx.begin();
...
utx.commit(); // or rollback(), wrap them in try...finally
}
或者更友好:
@Resource UserTransaction utx;
...
public void updateUser(user) {
utx.begin();
...
utx.commit(); // or rollback(), wrap them in try...finally
}
或者更好的@Transactional
注释,无论是使用 Java EE 7 还是在 Java EE 6 中使用 DeltaSpike JPA 扩展(或任何其他类似的拦截器(。
@Transactional
public void updateUser(user) {
...
}
您可以使用javax.ejb.TransactionAttribute
注释指定 EJB 方法是事务性的。在这种情况下,仍然会有 2 笔交易。或者,您可以使用用 @TransactionAttribute
注释的方法将"业务"逻辑从 Web 层移动到 EJB 层,并在单个事务中实现运行数据库方法。
至于进一步阅读,请查看 EJB 3 规范中的"事务支持"一章。
对于仍然有这个问题的人,接受的答案(飞饺子(是错误的。
实际发生的事情是这样的。容器具有 TransactionAttributeType = REQUIRED by defeult。这意味着如果您不注释任何豆子,它们将始终是必需的。
现在这里发生的事情是这样的:
你调用方法UserController.updateUser((,当你这样做时,将创建一个事务(默认情况下,如果没有注释指示其他方式,容器会在每次执行方法时创建一个事务,并在执行结束后立即完成它(。
当你调用userService.getFullName(user(时,由于这个方法是必需的,所以会发生的情况是,当你第一次调用UserController.updateUser((时最初启动的同一个事务将在这里再次使用。然后容器回到他的第一个bean并调用另一个方法userService.saveUser(user(,再次因为事务类型是必需的,那么将使用相同的事务。当它返回并调用第三种方法logService.logEvent("保存的用户"+全名(时,使用相同的方法。
在这种情况下,如果要确保每个操作都在单独的事务中运行,以避免在其中一个操作失败时回滚所有操作,则可以在与数据库交互的每个方法中使用REQUIRES_NEW。这样,您可以确保每次运行方法时都会创建一个新事务,并且如果其中一个失败并且您希望继续使用其他事务,则不会造成任何伤害。
更多信息可以在这里找到:https://docs.oracle.com/javaee/5/tutorial/doc/bncij.html