JPA 101

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:

Comparing RESOURCE_LOCAL and JTA persistence contexts

With <persistence-unit transaction-type="RESOURCE_LOCAL"> you are responsible for EntityManager (PersistenceContext/Cache) creating and tracking...

With <persistence-unit transaction-type="JTA"> the container will do EntityManager (PersistenceContext/Cache) creating and tracking...

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);

    }
}