If there's one thing you have to understand to successfully use JPA (Java
Persistence API) it's the concept of a
Cache. Almost everything boils
down to the Cache at one point or another. Unfortunately the Cache is an
internal thing and not exposed via the JPA API classes, so it not easy to
touch or feel from a coding perspective.
Here's a quick cheat sheet of the JPA world:
- A Cache is a copy of data, copy meaning pulled from but living
outside the database.
- Flushing a Cache is the act of putting modified data back into the
database.
- A PersistenceContext is essentially a Cache. It also tends to have
it's own non-shared database connection.
- An EntityManager represents a PersistenceContext (and therefore a
Cache)
- An EntityManagerFactory creates an EntityManager (and therefore a
PersistenceContext/Cache)
Comparing
RESOURCE_LOCAL and
JTA persistence contexts
With <persistence-unit transaction-type="
RESOURCE_LOCAL">
you are
responsible for EntityManager (PersistenceContext/Cache) creating and
tracking...
- You must use the EntityManagerFactory to get an EntityManager
- The resulting EntityManager instance is a
PersistenceContext/Cache
- An EntityManagerFactory can be injected via the @PersistenceUnit
annotation only (not @PersistenceContext)
- You are not allowed to use @PersistenceContext to refer to a unit
of type RESOURCE_LOCAL
- You must use the EntityTransaction API to begin/commit around
every call to your EntityManger
- Calling entityManagerFactory.createEntityManager() twice results in
two separate EntityManager instances and therefor two separate
PersistenceContexts/Caches.
- It is almost never a good idea to have more than one instance of
an EntityManager in use (don't create a second one unless you've destroyed
the first)
With <persistence-unit transaction-type="
JTA"> the
container
will do EntityManager (PersistenceContext/Cache) creating and tracking...
- You cannot use the EntityManagerFactory to get an EntityManager
- You can only get an EntityManager supplied by the container
- An EntityManager can be injected via the @PersistenceContext
annotation only (not @PersistenceUnit)
- You are not allowed to use @PersistenceUnit to refer to a unit of
type JTA
- The EntityManager given by the container is a reference to the
PersistenceContext/Cache associated with a JTA Transaction.
- If no JTA transaction is in progress, the EntityManager cannot be
used because there is no PersistenceContext/Cache.
- Everyone with an EntityManager reference to the same unit in the
same transaction will automatically have a reference to the same
PersistenceContext/Cache
- The PersistenceContext/Cache is flushed and cleared at JTA
commit time
Cache == PersistenceContext
The concept of a database cache is an extremely important concept to be
aware of. Without a copy of the data in memory (i.e. a cache) when you
call account.getBalance() the persistence provider would have to go read
the value from the database. Calling account.getBalance() several times
would cause several trips to the database. This would obviously be a big
waste of resources. The other side of having a cache is that when you call
account.setBalance(5000) it also doesn't hit the database (usually). When
the cache is "flushed" the data in it is sent to the database via as many
SQL updates, inserts and deletes as are required. That is the basics of
java persistence of any kind all wrapped in a nutshell. If you can
understand that, you're good to go in nearly any persistence technology
java has to offer.
Complications can arise when there is more than one
PersistenceContext/Cache relating the same data in the same transaction.
In any given transaction you want exactly one PersistenceContext/Cache for
a given set of data. Using a JTA unit with an EntityManager
created by the container will always guarantee that this is the case. With
a RESOURCE_LOCAL unit and an EntityManagerFactory you should create and use
exactly one EntityManager instance in your transaction to ensure there is
only one active PersistenceContext/Cache for the given set of data active
against the current transaction.
Caches and Detaching
Detaching is the concept of a persistent object
leaving the
PersistenceContext/Cache. Leaving means that any updates made to the
object are
not reflected in the PersistenceContext/Cache. An object will
become Detached if it somehow
lives longer or is
used outside the scope
of the PersistenceContext/Cache.
For a JTA unit, the PersistenceContext/Cache will live as long as
the transaction does. When a transaction completes (commits or rollsback)
all objects that were in the PersistenceContext/Cache are Detached. You
can still use them, but they are no longer associated with a
PersistenceContext/Cache and modifications on them will
not be reflected
in a PersistenceContext/Cache and therefore not the database either.
Serializing objects that are currently in a PersistenceContext/Cache will
also cause them to Detach.
In some cases objects or collections of objects that become Detached may
not have all the data you need. This can be because of lazy loading. With
lazy loading, data isn't pulled from the database and into the
PersistenceContext/Cache until it is requested in code. In many cases the
Collections of persistent objects returned from an
javax.persistence.Query.getResultList() call are completely empty until you
iterate over them. A side effect of this is that if the Collection becomes
Detached before it's been fully read it will be permanently empty and of no
use and calling methods on the Detached Collection can cause strange errors
and exceptions to be thrown. If you wish to Detach a Collection of
persistent objects it is always a good idea to iterate over the Collection
at least once.
You
cannot call EntityManager.persist() or EntityManager.remove() on a
Detached object.
Calling EntityManager.merge() will re-attach a Detached object.
Valid RESOURCE_LOCAL Unit usage
Servlets and EJBs can use RESOURCE_LOCAL persistence units through the
EntityManagerFactory as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<!-- Tutorial "unit" -->
<persistence-unit name="Tutorial" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>myNonJtaDataSource</non-jta-data-source>
<class>org.superbiz.jpa.Account</class>
</persistence-unit>
</persistence>
And referenced as follows
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceUnit;
public class MyEjbOrServlet ... {
@PersistenceUnit(unitName="Tutorial")
private EntityManagerFactory factory;
// Proper exception handling left out for simplicity
public void ejbMethodOrServletServiceMethod() throws Exception {
EntityManager entityManager = factory.createEntityManager();
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
Account account = entityManager.find(Account.class, 12345);
account.setBalance(5000);
entityTransaction.commit();
}
...
}
Valid JTA Unit usage
EJBs can use JTA persistence units through the EntityManager as
follows:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<!-- Tutorial "unit" -->
<persistence-unit name="Tutorial" transaction-type="JTA">
<jta-data-source>myJtaDataSource</jta-data-source>
<non-jta-data-source>myNonJtaDataSource</non-jta-data-source>
<class>org.superbiz.jpa.Account</class>
</persistence-unit>
</persistence>
And referenced as follows
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class MyEjb implements MyEjbInterface {
@PersistenceContext(unitName = "Tutorial")
private EntityManager entityManager;
// Proper exception handling left out for simplicity
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void ejbMethod() throws Exception {
Account account = entityManager.find(Account.class, 12345);
account.setBalance(5000);
}
}