Start - Publikationen - Wissen - TOGAF - Impressum -

Einleitung


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:

  • JPA ist POJO basiert und arbeitet mit Mapping-Strategien auf Basis von Annotations. Code-Instrumentierung während der Entwicklung wie bei JDO ist nicht erforderlich.
  • JPA ist im Rahmen der EJB 3.0 Spec entwickelt worden, ist aber auch für den Einsatz in Java SE Umgebungen designed.
  • JPA wird mit einer attraktiven und für den produktiven Einsatz geeigneten Referenzimplementierung auf Basis von Hibernate bereitgestellt.
Nach meiner Einschätzung ist JPA damit eine viel versprechende Zukunft beschieden. Dieser Artikel zeigt die grundlegenden Konzepte bei der Arbeit mit JPA und die Unterschiede zu Hibernate.

Der Persistence Context


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.

Transaktionen


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:

  1. application managed implizit: eine einzige Transaktion beginnt und endet mit dem PC
  2. application managed explizit: im Code werden Transaktionen direkt gesteuert und eine typische Implementierung kann so aussehen:
    :
    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();
      }
    } 
    :
    
  3. application managed gebunden: im Code wird der EntityManager an eine vorhandene Transaktion gebunden:
    :
    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;
    
  4. container managed: hier übernimmt ein EJB Container die Kontrolle über die Transaktionsführung und dieses Transaktionshandling steht auch nur in Containerumgebungen zur Verfügung. Der EntityManager im Kontext der Bean wird dabei automatisch an den Transaktionskontext der Bean gebunden. Diesen Automatismus kann man umgehen, indem der EntityManager nicht per Injection sondern über eine EntityManagerFactory explizit erzeugt wird. Dann stehen dem Beanentwickler alle Möglichkeiten zur manuellen Steuerung der Transaktion zur Verfügung.

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:

  • die Entitäten sind zwischen den PC POJOs und Änderungen an ihnen werden nicht automatisch synchronisiert
  • em.merge und damit ein Reattach solcher Objektinstanzen an einen PC sind hier die Regel, nicht die Ausnahme
In mehrschichtigen Architekturen ist diese Strategie wahrscheinlich die einzig mögliche, gewöhnlich kann über Schichtgrenzen hinweg nur mit detached Instanzen gearbeitet werden.

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:

  • die Entitäten sind zwischen den PC keine POJOs, sie sind vom EntityManager verwaltete Objekte
  • das em.merge solcher Objektinstanzen ist hier die Ausnahme und muss nur erfolgen, wenn ein Rollback auf einer Transaktion ausgelöst wurde oder wenn mit mehreren EntityManager Instanzen auf dem selben Datenbestand arbeiten
Die Übergänge zwischen den beschriebenen Strategien sind dabei fließend.

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.

JPA in Java SE Umgebungen


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 detached
Der 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;
}
copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved