Die Java Persistence API (JPA) definiert eine Persistenzmodell für objektrelationales Mapping. In Java EE Umgebungen füllt JPA eine schon seit langer Zeit identifizierte Lücke zwischen den container-lastigen EntityBeans einerseits und komplett vom Container gelösten Persistenztechnologien andererseits. Viele Eigenschaften der JPA besitzen enormes Potential, ich liste hier die wichtigen:
Der Persistence Context (PC) ist keine API sondern ein Konzept bei der Arbeit mit JPA. Ein PC ist die Abstraktion für einen Bereich, in dem Objektinstanzen und deren persistente Repräsentierung konsistent gehalten werden. Aus einer etwas technischeren Sichtweise ist der PC eine Session begrenzter Lebensdauer, in der im Rahmen einer oder mehrerer Transaktionen Änderungen an verwalteten Objektinstanzen vorgenommen werden. Der Lifecycle dieser Objektinstanzen bezieht sich auf den entsprechenden PC.
Zur Laufzeit werden ein oder mehrerer nacheinander existierende PC von einer EntityManager-Instanz repräsentiert, ein EntityManager, seine Transaktionen und seine von ihm verwalteten Entitäten sind single-threaded zu benutzen. Ein sehr grundlegendes Konzept bei der Arbeit mit einem EntityManager ist die Zusicherung, dass Objektinstanzen die die gleiche Persistenzeinheit repräsentieren, Instanz-identisch sind, der == Operator kann benutzt werden. Weiterhin dürfen Entitäten der selben Objekt-Identität nicht gleichzeitig in mehreren PC bearbeitet werden: das Verhalten des Persistenz-Frameworks ist dann nicht definiert. Das ist übrigens eine direkte Folgerung aus der Anforderung, dass EntityManager single-threaded zu benutzen sind.
Die meisten Manipulationen an den Entitäten eines PC müssen von einer JPA-Transaktionen eingehüllt werden. Das betrifft praktisch alle "schreibenden" EntityManager-Methoden (persist, flush, remove, merge, lock und refresh). Es gibt mehrere Möglichkeiten, JPA-Transaktionen zu erhalten:
: EntityTransaction tx = null; try { tx = em.getTransaction(); tx.begin(); // // Manipulation der Entitäten // tx.commit(); } catch(Exception ex) { // Code für Exceptionhandling } finally { if(tx != null && tx.isActive()) { tx.rollback(); } } :
: em.joinTransaction(); // koppelt den EM an den Transaktionskontext der Umgebung // // Manipulation der Entities // em.flush(); // (nach schreibenden Zugriffen) alle Änderungen werden mit der DB synchronisiert // und beim Commit der TX in die DB geschrieben em.clear(); // (bei Bedarf) alle Entities sind nun detached return;
Der Zuschnitt des PC und der Persistence Context Type
Eine Konversation ist eine fachliche Klammer um alle Nutzeraktivitäten zur Abarbeitung eines Anwendungsfalles. Im Allgemeinen überdauert eine Konversation mehrere Clientaufrufe zur Abarbeitung eines Use Cases und muss Ausnahmen verkraften. Es ist vielleicht DIE zentrale Designentscheidung bei der Arbeit mit JPA, wie der Zuschnitt des PC im Verhältnis zu den Conversationen der Anwendung gestaltet wird.
Das Ende des PC wird erreicht, indem entityManager.clear() gerufen wird, alle Entitäten sind dann vom EM gelöst. entityManager.close() bewirkt das gleiche, jedoch ist dann die EntityManager Instanz nicht mehr nutzbar. In einem Szenario, dass man als detached object state strategie bezeichnet kommen zur Abarbeitung einer Konversation mehrere kurzlebige PC nacheinander zum Einsatz. Im extremen Fall endet der PC nach jeder Transaktion. Es gilt:
Das dazu entgegengesetzte Szenario kann man als extended persistence context strategie bezeichnen. Hier erstreckt sich der PC über die gesamte Konversation oder vielleicht über die gesamte Anwesenheit des Clients. Zwischen den Transaktionen des PC bleiben die Entitäten unter Verwaltung des EntityManager. Im extremen Fall dauert ein PC eine gesamte Clientsitzung. Es gilt:
Wird ein EntityManager vom Container bereitgestellt übernimmt dieser die Steuerung des PC. Standardmäßig wird dann dem EntityManager der Transaktionstyp TRANSACTION zugewiesen, hier wird der PC einfach an die Transaktionssteuerung gebunden und das Szenario entspricht dem der kurzen PC. Man kann container managed EntityManager aber auch mit dem Transaktionstyp EXTENDED anfordern, der PC wird hier an den Lebenszyklus der Bean gebunden und endet erst mit deren Zerstörung. In gewisser Weise repräsentieren damit TRANSACTION und EXTENDED die beiden beschriebenen Extreme beim Zuschnitt des PC. Muss in Containerumgebungen ein Szenario zwischen diesen beiden Extremen umgesetzt werden, kann man sich eine EntityManager-Instanz selber beschaffen und steuern.
In Java SE Umgebungen besorgt man sich einen EntityManager über eine EntityManagerFactory. Die so gewonnenen Instanzen werden als resource local bezeichnet.
// einmal pro PersistenceUnit: EntityManagerFactory emf = javax.persistence.Persistence.createEntityManagerFactory("Order"); // einmal pro PersistenceContext: EntityManager em = emf.createEntityManager(); : : ein oder mehrere TX : em.close(); // Ende des PC, alle Instanzen sind detachedDer Transaktionstyp ist hier standardmäßig EXTENDED und erst ein em.close() beendet den PC. Er kann aber auch explizit als TRANSACTION vorgegeben werden, dann sind nach jeder Transaktion alle Entitäten detached. Das Transaktionshandling in Java SE Umgebungen muss wie oben beschrieben ausprogrammiert werden.
JPA in Java EE Umgebungen - Container Managed
Im einfachsten Falle überlässt man die Bereitstellung der EntityManager-Instanz dem Container (also per dependency injection oder JNDI-lookup). Die so gewonnenen EntityManager werden als container managed bezeichnet. Der Transaktionstyp ist standardmäßig TRANSACTION, Transaktionen werden an die transaction demarcation der Beanmethoden gebunden und der PC beginnt und endet mit jeder für die Beanmethode deklarierten Transaktion.
@Stateless @PersistenceContext(name="OrderEM") public class MyBeanImpl implements MyBeanImpl { // // dependency injection bindet den EM an den Lifecycle der Bean // und sein TX an die Beanmethode @PersistenceContext EntityManager em;public void doSomething() { Product prod = (Product) em.find..; : manipuliere prod em.persist(prod); } }
Im Kontext einer Stateful SessionBean kann der Transaktionstyp explizit als EXTENDED gegeben sein. Die Entitäten die den conversational state der Bean ausmachen bleiben die ganze Zeit aus der Sicht der EM-Instanz gemanaged. Der EntityManager ist an den Lifecycle der Bean gekoppelt und endet erst, wenn das @Remove der Bean gerufen wird. Gewöhnlich bleibt der Conversational State der Bean über ihre gesamte Lebensdauer attached.
@Stateful @Transaction(REQUIRES_NEW) public class SomeBeanImpl implements SomeBean { // @PersistenceContext(type=PersistenceContextType.EXTENDED) EntityManager em; // private Product prod; // public void initProduct() { prod = (Product) em.find... } // public void setSomething() { prod.setSomething... } }
JPA in Java EE Umgebungen - Application managed
Auch in Containerumgebungen darf mit application managed EntityManager gearbeitet werden. Die verschiedenen EntityManager-Instanzen repräsentieren aber isolierte PC und nehmen nicht automatisch am Transaktionshandling des Containers teil! Injiziert (oder per JNDI lookup geholt) wird dann eine EntityMangerFactory und nicht der EntityManager selbst. Das createEntityManager am Anfang und em.close() am Ende jeder Methode sind typische Details einer solchen Implementierung:
: @PersistenceUnit EntityManagerFactory emf; // public void doSomething() { EntityManager em = emf.createEntityManager(); Product prod = (Product) em.find.. prod.setSomething.. em.close(); }Aus Laufzeitsicht sind so kurzlebige EM-Instanzen nicht immer optimal, die Erzeugung und Beseitigung des EntityManager koppelt man besser an den Lebenszyklus der Bean. Für Stateless Beans nutzt man @PostConstruct und @PreDestroy:
@PersistenceUnit private EntityManagerFactory emf; // private EntityManager em; // @PostConstruct public void init() { em = emf.createEntityManager(); } // @PreDestroy public void destroy() { em.close(); }Für Stateful Beans @PostConstruct und @Remove:
@PersistenceUnit private EntityManagerFactory emf; // private EntityManager em; // @PostConstruct public void init() { em = emf.createEntityManager(); } // @Remove public void destroy() { em.close(); }Da dieser EntityManager nicht vom Container übrwacht ist, muss sein Transaktionshandling explizit gegeben sein. Insbesondere werden in finder-Methoden typischerweise em.clear() Aufrufe zum detachen von Entities vorzusehen sein. Sollen Entitäten geändert werden, muss explizit eine Transaktion gegeben sein. Hier bietet sich an, sich an die Transaktion der Umgebung zu binden:
public Product doSomthing() { em.joinTransaction(); // binde EM an TX der Umgebung Product prod = (Product) em.find... prod.doSomthing... em.persist(bo); em.flush(); // flush wenn die TX committed em.clear(); // detach alle Entitäten return prod; }