4

I have a question about how transactions are controlled by an EE container. This is pseudo code to give some context to my question. This is not how I code, so please stay on the question and do not evolve the topic into other stuff.

Consider the following two services and related controller. Both services have injected EntityManager and both have methods that need to run within a transaction. One service has a method that does not need any transaction support.

@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
    }
}

Now, imagine there's a button that posts a form to userController.updateUser.

My assumption is that the UserController.updateUser() will execute userService.saveUser(user); and logService.logEvent("Saved user " + fullName); within the same transaction. So if the call to logService.logEvent() fails with an SQL Exception, the user entity will not be updated. Also, my assumption is that call to userService.getFullName(user) is not run within any transaction and if we prematurely exit the method when user's name starts with an X, then no transaction is created. But clearly, these are just guesses.

Can someone explain what the Java EE container will do to support UserController.updateUser() method with transaction and what actually triggers the transaction? Also, any further reading you can point me to, would be much appreciated. I've seen some material online but still I'm missing something here and didn't get any help asking around at work, either. So I'm certainly not the only one who's got a gap on this.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
jacekn
  • 1,521
  • 5
  • 29
  • 50

4 Answers4

4

In your case 3 independent transactions will be started. Each by one of your @Stateless beans methods. It's because session EJBs have transacional methods with transaction type TransactionAttribute.REQUIRED by default. This means that if a transaction is not already running the new one will be created before method invocation.

To run all of your session EJBs methods in one transaction you must wrap them in one transaction. In your case you can do this by annotating updateUser(...) method with @Transactional

Flying Dumpling
  • 1,294
  • 1
  • 11
  • 13
  • Dumpling, so you're telling me that container does not provide transaction support for a `@Named` by default like it does for a `@Stateless`. You're also telling me that `userService.saveUser(user);` will begin and commit its own transaction and that `logService.logEvent()` will not have access to it and consequently has to start its own transaction. Please confirm. – jacekn Dec 06 '13 at 18:03
  • Exactly, container does not provide transaction support for a `@Named` by default. Only session EJBs have this support. And yes, you are right every method from `@Stateless` wil start its own transaction because that's how session EJBs work by default :) – Flying Dumpling Dec 06 '13 at 19:10
  • 1
    @jacekn There are two 'containers' here. One is the EJB container which will manage a bean annotated with `@Stateless` among other annotations. The other container is CDI which will manage (among other beans) ones annotated with `@Named`. CDI beans by default are *not* transactional, EJB beans by default are. At this point the EJB container provides more functionality in the transactional management space then CDI does. – NBW Dec 07 '13 at 17:31
  • Hi @flyingDumpling, so `@Transactional` exists also in JEE and there is not only a Spring annotation? – CodeSlave Dec 03 '19 at 08:21
0

Transactions in Java EE must be explicitly controlled, either using the UserTransaction from JNDI or using deployment descriptor/annotations on EJBs. For a CDI component, here the UserController, no transaction is started by default. (EDIT Transactions for EJB methods are enabled by default, if nothing is specified.)

So to start, your assumption:

the UserController.updateUser() will execute userService.saveUser(user); and logService.logEvent("Saved user " + fullName); within the same transaction

is wrong! I believe that a new transaction will be created and committed on each em.persist()/em.merge() call.

In order to wrap the calls saveUser() and logEvent() in the same transaction you can manually use the 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
}

Or the friendlier:

@Resource UserTransaction utx;
...
public void updateUser(user) {
    utx.begin();
    ...
    utx.commit(); // or rollback(), wrap them in try...finally
}

Or even better the @Transactional annotation, either with Java EE 7 or in Java EE 6 with the DeltaSpike JPA extension (or any other similar interceptor).

@Transactional
public void updateUser(user) {
    ...
}

You could specify that the EJB methods are transactional using the javax.ejb.TransactionAttribute annotation. There would still be 2 transactions in this case. Alternatively you could move the "business" logic from the web-tier to the EJB-tier in a method annotated with @TransactionAttribute and achieve running the DB methods in a single transaction.

As for further reading, check out the "Support for Transactions" chapter in the EJB 3 spec.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • Your comment that you must 'activate' EJB transactions using an XML DD is wrong. By default a DD is *optional* and EJB transaction support is active in a container managed environment and a new TX is started when an EJB method is called. There are also rules for TX propagation should that method call other methods on other EJB or on itself (via SessionContext wrapper or self injected instance). The use of the `@TransactionAttribute` annotation is optional in CMT, `@TransactionAttribute(TransactionAttribute.REQUIRED)` is assumed by default. – NBW Dec 05 '13 at 14:17
  • In my last comment, `@TransactionAttribute(TransactionAttribute.REQUIRED)` should have been `@TransactionAttribute(TransactionAttributeType.REQUIRED)` – NBW Dec 05 '13 at 14:30
  • Your comment that a new TX is created with each call to .persist() or .merge is **WRONG**. You can have as many persists or merges as you want in a TX. In CMT by default the TX boundary is the EJB method, and it can have as many persists or merges as you want. Also the TX _can be_ propagated to other EJB methods and by default it is the same TX, however, this TX propagation can be controlled via the use of @TransactionAttribute annotations on the called EJB classes/methods. – NBW Dec 05 '13 at 14:34
  • (1) I never said DD is mandatory (2) You are right, transaction `REQUIRED` is default, corrected (3) I never said you cannot have as many `persist()`s etc in a transaction; I said that *in this case* and the way it is used, there will be a transaction per operation. This was based on my erroneous assumption that no transaction is started in the EJB method by default. Still, in the OP's code `persist()` and `merge()` will be called *in their own transactions*, so the rest of the argument is true. – Nikos Paraskevopoulos Dec 05 '13 at 15:08
  • In your original response which my comment was directed at you said, "...if nothing is specified in the XML DD for the EJBs, no transaction is started for them either." I guess I misinterpreted what you were trying to express there as to me that's what it sounded like. – NBW Dec 05 '13 at 22:19
0

You need to change your @Inject annotations to @EJB, then by default with CMT (container managed transactions) each call by the CDI bean will be in its own TX scope. If you do not want one of the method calls to invoke a TX then add @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) to the method.

From the looks of your business logic you could really simplify things by changing @Named to @Stateless, change @Inject to @EJB and add @TransactionalAttribute(TransactionAttributeType.NOT_SUPPORTED) on the getFullName(..) if you don't want that method to run in a TX. With those changes you will get the TX behavior you are looking for.

If you want UserController to be a JSF managed bean then along with the other mods I suggested I would change @Named to @ManagedBean and simply add @Stateless under @ManagedBean instead of changing @Named to @Stateless.

NBW
  • 1,467
  • 2
  • 18
  • 27
  • NBW, thanks for your contribution but I must say I'm not following this post. Everything I read recently suggests dropping the use of both `@EJB` and `@ManagedBean` in favour of `@Inject` and `@Named`, respectively. By telling me I should use them, you're sending me to a place where I am even more confused. I'll do some further reading on differences between `@EJB` and `@Inject` but I believe that recommendations are published for a reason. – jacekn Dec 06 '13 at 17:27
  • @jacekn As with most solutions in this space there isn't a right or wrong answer but there are several nuanced ones. If you are coding to EE6 then you would definately use `@Managed` and `@EJB` in EE7 you can use `@Inject` and `@Named` or you can do it the EE6 way. EE7 moves away from the use of `@Managed` because it was seen as extraneous, much the same way the EE6 requirement of an empty beans.xml to enable the CDI container was dropped in EE7. – NBW Dec 07 '13 at 17:36
  • @jacekn Subjectively, I prefer using `@EJB` for my TX management logic, however, because of the short comings of the the EJB API back in the pre EE5 days they got a bad name. The recent push to add TX mgmnt into CDI is to some extent an effort to market EE as a stack which does not require the 'evil' EJB word. As of EE7 CDI TX support is not as feature rich and powerful as the EJB container and IMHO the EJB API is just as easy if not easier to work with. David Levins (TomEE, OpenEJB, etc.) has a good comparison here http://stackoverflow.com/questions/13487987/where-to-use-ejb-3-1-and-cdi – NBW Dec 07 '13 at 17:46
0

For anynone still having this problem, the accepted answer ( Flying Dumpling) is wrong.

What actually happens is this. The container has the TransactionAttributeType = REQUIRED by defeult. Which means if you do not annotate any of your beans, they will always be REQUIRED.

Now what happens here is this:

You call method UserController.updateUser() and when you do, a transaction will be created (by default if there are no annotations indicating otherwise, the container creates a transaction everytime a method is executed, and finishes it as soon as the execution is over).

When you invoke userService.getFullName(user), since this method is REQUIRED, what will happen is that the same transaction started initially when you first called UserController.updateUser(), will be used again here. Then the container comes back to he first bean and invokes another method, userService.saveUser(user), again since the transaction type is REQUIRED, then the same transaction will be used. And when it comes back and invokes the third method, logService.logEvent("Saved user " + fullName), uses the same one.

In this case, if you want to make sure every operation is runned in a separate transaction to avoid rolling back everything if one of them fails, you can use REQUIRES_NEW in every method that interacts with the DB. That way you ensure that everytime you run a method, a new transaction is created and there is no harm if one of them fails and you wish to go ahead with the others.

More information can be found here: https://docs.oracle.com/javaee/5/tutorial/doc/bncij.html