Java EE 容器如何控制事务



我有一个关于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

updateUser(...)方法来执行此操作=">您需要将

@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

最新更新