Java EE不同Bean间事务类型差异及问题咨询
Great question—this gets into the nuanced differences between CDI beans and EJBs when it comes to transaction management, which is a common point of confusion. Let’s break this down step by step.
1. Core Difference: CDI Beans vs. EJBs in Transaction Handling
Unannotated CDI Beans (like your original ClassThree): These don’t have container-managed transactions (CMT) by default. When an EJB (like ClassTwo) calls a method on a CDI bean, the CDI bean runs within the caller’s transaction context, but the container doesn’t treat exceptions from the CDI bean as trigger points for transaction rollback. The exception is just a regular Java exception—your catch block in ClassTwo can handle it, rethrow it, etc., and the transaction only gets marked for rollback if the EJB itself throws an unhandled unchecked exception (or you explicitly mark it with
setRollbackOnly()).EJB Beans (like your modified @Stateless ClassThree): EJBs use CMT by default, with the
REQUIREDtransaction attribute. This means:- When called from another EJB (ClassTwo), it joins the existing transaction context.
- If an EJB throws an unchecked exception (like
RuntimeException), the EJB container automatically marks the transaction for rollback and wraps the exception in anEjbTransactionRolledBackException(a subclass ofEjbException). Even if you catch the exception in ClassTwo, the transaction is already marked as rollback-only—so when ClassTwo’s method completes, the transaction rolls back, which breaks ClassOne’s persistence logic.
2. Why Your Transaction Attribute Tests Behaved the Way They Did
Let’s go through each attribute you tested:
- SUPPORTS/REQUIRED: Both join the existing transaction from ClassTwo. When ClassThree throws
RuntimeException, the container marks the shared transaction as rollback-only. Your catch block in ClassTwo can’t undo this—so the transaction rolls back when it propagates back to ClassOne. - NOT_SUPPORTED: This tells the container to suspend the caller’s transaction before executing ClassThree’s method. Since your
methodInClassThreeuses JpaRepository, which requires an active transaction for write operations, you getTransactionRequiredExceptionimmediately—JPA can’t work without a transaction here. - REQUIRES_NEW: This creates a brand new, independent transaction for ClassThree’s method. When the
RuntimeExceptionis thrown, only this new transaction rolls back. The original transaction from ClassTwo/ClassOne remains intact, so ClassOne’s persistence logic works just like it did with the CDI bean version.
3. Why the Default Behavior Seems Counterintuitive
You’re right that EJBs default to REQUIRED, but the key difference is that EJBs have container-managed exception handling that CDI beans don’t. With the CDI bean, the exception is just a regular Java exception—you control whether it propagates and affects the transaction. With the EJB, the container intercepts unchecked exceptions and automatically marks the transaction for rollback, regardless of whether you catch it in the caller.
4. Key Takeaways
- CDI beans rely on the caller’s transaction context but don’t trigger container-level transaction rollback from exceptions.
- EJBs with CMT automatically handle transaction rollback for unchecked exceptions, even if the caller catches the exception.
- Use
REQUIRES_NEWif you want an EJB’s exception to be isolated from the caller’s transaction (matching your original CDI bean behavior).
内容的提问来源于stack exchange,提问作者Jogin Joy

