Update
- One issue to be aware of, which is not immediately obvious, is when populating a database from scripts. If the version column is left null (Eclipselink leaves it as nullable by default), then the null value causes an immediate OptimisticLockException to be thrown. This is because EclipseLink expects and checks for a zero as the initial value for a new record. If EclipseLink itself persists the record, then it writes a zero.
- One good idea to resolve this would be to enforce version columns to be non null with a zero default – although it must be said that it would be helpful if EclipsLink did this automatically in response to the @Version annotation.
- Population scripts should therefore set version to zero, unless it is set non null with a default of zero in JPA – this latter solution is cleaner and would be automatic for me because I create version columns in a mapped superclass used by all Entities. I have yet to test this out however.
Original Post
This follows on from a previous post relating to JPA 1.0/Glassfish 2.1, and contains a few notes and pointers relating to JPA 2.0 under Glassfish 3 with CDI/Weld.
Firstly, when using an EJB from a client, use CDI injection (@Inject) rather than the @EJB annotation, as CDI gives many additional benefits such as type safety.
In an EJB:-
- You are using container managed transactions, and so simply have to inject an entity manager rather than create one from an entity manager factory.
- The container managed entity manager can be injected with a simple @PersistenceContext annotation – the persistence unit name may be defaulted and is discovered automatically.
- The container is responsible for entity manager lifetime/closing. Calling e.g. em.close at the end of a bean method is illegal and will make Glassfish angry. You do not want to make Glassfish angry – you end up with many long stack traces in the log which on the surface do not appear to relate to this trivial problem!
A major issue with container managed transactions is how to use optimistic locking and detect and handle optimistic lock exceptions correctly. This is detailed in Pro JPA 2, pages 355-357 (you can preview this in Google Books). The main problem is that in a server environment, the OptimisticLockException will simply be logged by the container which then throws an EJBException. Trying to catch an OptimisticLockException in the calling client is therefore doomed to failure – it will probably never be caught!
The solution is to call flush() on the entity manger (e.g. em.flush()) just before you are ready to complete the method. This forces a write to the database, locking resources at the end of the method so that the effects on concurrency are minimised. This allows us to handle an optimistic failure while we are still in control, without the container swallowing the exception. The ChangeCollisionException might contain the object which caused the exception, but it is not guarranteed to. If there were multiple objects in the transaction, we could have invoked getEntity() on the caught exception to see whether the offending object was included.
If we do get an exception from the flush() call, we can throw a domain specific application exception which the caller can recognise, i.e. convert an OptimisticLockException to our own ChangeCollisionException, avoiding coupling the caller to the internal semantics of JPA transactions. It is also desirable to factor out the code which calls flush() into our own method, e.g. flushChanges() to avoid the need for every method which needs to flush having to catch the optimistic lock exception and then throw our domain specific one – we can centralise the exception conversion in our own single method.
We must annotate the ChangeCollisionException class with @ApplicationException, which is an EJB container annotation, to indicate to the container that the exception is not really a system level one but should be thrown back to the client as-is. Normally defining an application exception will cause the container not to roll back the transaction, but this is an EJB 3 container notion. The persistence provider that threw the OptimisticLockException (in this case Eclipselink) does not know about the special semantics of designated application exceptions and, seeing a runtime exception, will go ahead and mark the transaction for rollback. The client can now receive and handle the ChangeCollisionException and do something about it.
Here is an example of our exception class with the required annotation:-
@ApplicationException
public class ChangeCollisionException extends RunTimeException {
public ChangeCollisionException() {super(); }
}
Here are code fragment examples from a stateless session bean showing the entity manager injection, and a client JSF model bean showing the CDI injection of the stateless session bean. Note that with EJB 3.1 it is not necessary to declare local or remote interfaces for an EJB as business interfaces are not a requirement. However, I always use an interface as it allows for example a mock version of a service layer to be easily swapped in for testing. CDI supports this conveniently via alternatives, whereby a mock version of a bean can be switched in via a setting in beans.xml for testing.
@Stateless
public class SentryServiceImpl implements SentryServiceLocal {
@PersistenceContext
private EntityManager em;
…
}
@Named
@Dependent
public class ModelBeanImpl implements ModelBean, Serializable {
private static final long serialVersionUID = -366401344661990776L;
private @Inject SentryServiceLocal sentryService;
…
}
With container managed JPA and/or Glassfish your persistence.xml can also be simplified, as you can define your data source in Glassfish rather than explicitly in persistence.xml, as described here.
Note that when defining a data source directly in persistence.xml, some property names have been standardized for JPA 2.0, so have changed from e.g. eclipselink.jdbc.user to javax.persistence.jdbcuser. The following properties in persistence.xml are now standard:-
<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
<property name="javax.persistence.jdbc.user" value="sentry"/>
<property name="javax.persistence.jdbc.password" value="sentry"/>
<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
An example persistence.xml used under JPA 2.0 follows:-
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="SentryPrototype" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/SentryPool</jta-data-source>
<properties>
<property name="eclipselink.logging.level" value="INFO" />
<property name="eclipselink.target-database" value="Oracle" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
</properties>
</persistence-unit>
</persistence>