Last year we had successfully migrated our legacy web applications to EJB 3 on JBoss. EJB 3 has simplified Java Enterprise development greatly. However, the learning curve is still high. Among other things that we have learned, you really need to understand how the EntityManager works in different settings.
EJB 3 let you to declare EntityManager dependency using annotation @PersistenceContext. Then the container will inject the EntityManager to your class. This is all good but we have to be mindful, there are two types of EntityManager i.e: Extended and TransactionScoped. By default when we annotated the EntityManager dependency, what we will get is TransactionScoped EntityManager. It means all the EntityManager
operations, including persisting new beans, removing beans and updating beans, are cached in memory. They are synchronized to the database in a single batch when the current thread completed, or before the next database query is issued, whichever comes first.
But for some users, it might be desirable to manually control and optimize exactly when the database is synchronized inside a thread. For instance, you might want to send in the changes earlier to reduce memory use and avoid large cache replication in clusters. Inside a transaction, you can always flush the current in-memory changes in the EntityManager to the database by calling the EntityManager.flush() method.
The second type is Extended PersistenceContext EntityManager which allow us to cache all database updates from multiple method calls (i.e., multiple threads), and only commit the changes in a batch when the application finishes a logical session (e.g., at the checkout time of a shopping cart). To get this type of EntityManager, we could annotate our class with @PersistenceContext(type=PersistenceContextType.EXTENDED). The Extended EntityManager will commit the changes only when it has been removed.
We also need to tell the JTA transaction manager NOT to commit any updates to the database at the end of each thread. We do this by giving each database related method the NOT_SUPPORTED transactional attribute. The EntityManager should only update the database when the stateful session is destroyed. We do this by annotating the method with @Remove.
The write behind strategy of the EntityManager doesn't allow us to throw a checked exception to the client. There is a code inside org.jboss.ejb3.tx.Ejb3TxPolicy class.
public void handleExceptionInOurTx(Invocation invocation, Throwable t, Transaction tx) throws Throwable {
if (t.getClass().isAnnotationPresent(ApplicationException.class)) {
ApplicationException ae = (ApplicationException) t.getClass().getAnnotation(ApplicationException.class);
if (ae.rollback()) setRollbackOnly(tx);
throw t;
}
if (!(t instanceof RuntimeException || t instanceof RemoteException)) {
throw t;
}
setRollbackOnly(tx);
if (t instanceof RuntimeException && !(t instanceof EJBException)) {
throw new EJBException((Exception) t);
}
throw t;
}
As we can see to throw a checked exception we need to:-
(1) - manually Flush the EntityManager at the point the transaction is committed and any exceptions will be gratefully received
(2) - When throwing a checked exception inside your session bean ensure that you annotate it with @ApplicationException. This allows JBoss to recognise that this is an application checked exception and not an unchecked runtime exception. Jboss will then deal with the exception accordingly. For more details read this forum thread.