簡體   English   中英

Java EE 容器如何控制事務?

[英]How does Java EE container control transactions?

我有一個關於 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 bean 方法之一構成。 這是因為默認情況下會話 EJB 具有事務類型為TransactionAttribute.REQUIRED方法。 這意味着如果事務尚未運行,則將在方法調用之前創建新事務。

要在一個事務中運行所有會話 EJB 方法,您必須將它們包裝在一個事務中。 在您的情況下,您可以通過使用@Transactional注釋updateUser(...)方法來做到這一點

Java EE 中的事務必須顯式控制,要么使用來自 JNDI 的UserTransaction ,要么使用 EJB 上的部署描述符/注釋。 對於 CDI 組件,這里是UserController ,默認情況下不啟動任何事務。 (如果未指定任何內容,則默認情況下啟用 EJB 方法的EDIT Transactions。)

所以首先,你的假設:

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 層,並實現在單個事務中運行 DB 方法。

至於進一步閱讀,請查看 EJB 3 規范中的“事務支持”一章。

您需要將@Inject注釋更改為@EJB ,然后在默認情況下,使用 CMT(容器管理事務),CDI bean 的每個調用都將在其自己的 TX 范圍內。 如果您不希望其中一個方法調用調用 TX,則將 @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 添加到該方法中。

從您的業務邏輯的外觀來看,您可以通過將@Named更改為@Stateless ,將@Inject更改為@EJB並在 getFullName(..) 上添加@TransactionalAttribute(TransactionAttributeType.NOT_SUPPORTED)來真正簡化事情,如果您不想要該方法在 TX 中運行。 通過這些更改,您將獲得所需的 TX 行為。

如果你想UserController中是一個JSF托管bean,然后與其他MODS沿着我認為我會改變@Named@ManagedBean和簡單地添加@Stateless@ManagedBean而不是改變@Named@Stateless

對於仍然有此問題的任何人,公認的答案(飛餃子)是錯誤的。

實際發生的事情是這樣的。 默認情況下,容器具有 TransactionAttributeType = REQUIRED。 這意味着如果您不注釋任何 bean,它們將始終是必需的。

現在這里發生的是:

您調用方法 UserController.updateUser() 並且當您這樣做時,將創建一個事務(默認情況下,如果沒有注釋表示否則,容器會在每次執行方法時創建一個事務,並在執行結束后立即完成)。

當您調用 userService.getFullName(user) 時,由於此方法是必需的,將發生的情況是,最初調用 UserController.updateUser() 時最初啟動的同一事務將在此處再次使用。 然后容器返回到他的第一個 bean 並調用另一個方法 userService.saveUser(user),因為事務類型是 REQUIRED,所以將使用相同的事務。 當它返回並調用第三個方法時,logService.logEvent("Saved user " + fullName) 使用相同的方法。

在這種情況下,如果您想確保每個操作都在單獨的事務中運行以避免在其中一個失敗時回滾所有操作,您可以在與數據庫交互的每個方法中使用 REQUIRES_NEW。 這樣,您可以確保每次運行一個方法時,都會創建一個新事務,如果其中一個失敗並且您希望繼續其他事務,也不會造成任何傷害。

更多信息可以在這里找到: https : //docs.oracle.com/javaee/5/tutorial/doc/bncij.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM