Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  2/2007  Pfeil  Java/J2EE/JEE
suche: 
Dieser Artikel richtet sich an Entwickler, die einen Überblick über die Implementierung von Entity Beans und die neue Java Persistence API im Kontext der EJB 3.0 Spezifikation erhalten möchten.

Glossar

Java SE/JSE
Java Standard Edition
Java EE/JEE
Java Enterprise Edition. Erweiterung von Java für Server-Anwendungen.
Persistenz
Die Fähigkeit, Objekte in nicht-flüchtigen Speichermedien wie Dateisystemen oder Datenbanken zu speichern.
POJO
Abkürzung für Plain Old Java Object. Dabei handelt es sich um ein normales Objekt in der Programmiersprache Java.
Annotations
Anmerkungen im Java Source Code, die zur Compile- oder Laufzeit des Programms ausgewertet werden können. Annotations sind seit Java 5 Bestandteil der Java Sprache. Als geistigen Vater dieser Annotations kann man das Open Source Projekt XDoclet ansehen. XDoclet erlaubte es, auch schon in früheren Java Versionen mit Annotations zu programmieren.
EJB
Enterprise Java Beans sind standardisierte Komponenten, aus denen JEE-konforme Anwendungen erstellt werden, die auf einem JEE-Application Server laufen. Für unterschiedliche Zwecke definiert der JEE-Standard verschiedene Arten von EJBs: Session Beans, Entity Beans und Message-driven Beans.
Hibernate
Dies ist ein Open Source Persistence Framework für Java. Das Framework ermöglicht es, den Zustand eines Objekts in einer relationalen Datenbank zu speichern und aus entsprechenden Datensätzen wiederum Objekte zu erzeugen. Dies bezeichnet man auch als Object Relational Mapping (OR-Mapping, kurz ORM). Es befreit den Entwickler von der Programmierung von SQL-Abfragen und hält die Applikation unabhängig vom SQL-Dialekt der verwendeten Datenbank.
EJB-QL
EJB-QL (Query Language) ist eine deklarative Abfragesprache, ähnlich der bei relationalen Datenbanken verwendeten Structured Query Language (SQL), aber maßgeschneidert für das abstrakte Persistenzschema von Entity Beans.
Data Transfer Object
Das Data Transfer Object fasst in einer verteilten Umgebung [z. B. J(2)EE] zu übertragende Daten in einem neuen Objekt zusammen, um die Anzahl der entfernten Methodenaufrufe zu reduzieren und somit das Netzwerk zu entlasten.
Diskriminator
Er wird bei der Vererbung verwendet. Die Unterscheidung in Ober- und Unterklasse erfolgt mit Hilfe eines Unterscheidungsmerkmals, dem so genannten Diskriminator. Dieser definiert den für die Strukturierung maßgeblichen Aspekt.

Weiterführende Links



Reihe EJB 3.0 (Teil III):

Persistenz für alle


Die Abbildung objektrelationaler Persistenz wurde mit EJB 3.0 von Grund auf erneuert. Mehr noch, es wurde ein einheitlicher Persistenzstandard für Java SE und Java EE definiert: die Java Persistence API (JPA). Wir werden in diesem Teil der Reihe ein wenig auf die neuen Möglichkeiten der API im Zusammenhang mit der EJB-Persistenz eingehen.

In den EJB 2.x Versionen war die Abbildung objektrelationaler Persistenz keine einfache An­gelegenheit. Obwohl schon immer zentraler Bestandteil der EJB-Spezifikation, haben sich viele Entwickler bei der Implementierung von Entity Beans schwer getan. Unnötige Komplexität, fehlende Möglichkeit der Vererbung oder der Polymorphie, nur 1:1-Zuordnung von Entity Bean zu Datenbanktabelle und eingeschränkter Leistungsumfang der integrierten Abfragemöglichkeit EJB-QL haben einem Entwickler das Leben schwer gemacht.

Java Persistence API

Das Ziel von EJB 3.0 ist es, die Komplexität zu verringern und die Leistungsfähigkeit und die Möglichkeiten zu steigern. Mit der neuen Java Persistence API sind nun Vererbung und Multitable Mapping möglich. Ferner wurde die Komplexität durch die POJO-Persistenzabbildung stark vereinfacht. Viele Dinge in der neuen API werden einem aus Persistenz-Frameworks wie Hibernate oder TopLink bekannt vorkommen. Dies kommt nicht von ungefähr, da die Entwickler dieser Frameworks maßgeblich am neuen EJB 3.0 Standard mitgearbeitet haben.

Mit der Java Persistence API haben wir nun einen einheitlichen Persistenzstandard sowohl für Java SE als auch für Java EE. Das bedeutet, dass Objekte, deren Persistenzabbildung mittels dieser API erfolgt, 1. innerhalb eines Java EE Application Server und 2. außer­halb, in einer reinen Java Laufzeitumgebung, ablaufen können, ohne dass eine Änderung nötig ist.

Eine Entity Bean ist nun ein einfaches POJO, das erst mittels einer bestimmten Annotation (@Entity) zur Entity Bean wird. Das Beispiel in Abbildung 1 zeigt solch eine Entity Bean, die auf eine Tabelle KUNDE abgebildet ist. Die Tabelle KUNDE hat die Spalten NAME und ID (Primary Key).

@Entity 
@Table(name="KUNDE")
public class Kunde implements Serializable {
  private Integer id;
  private String name;
 
  @Id(generate= GeneratorType.AUTO)
  public Integer getId()  { return id; }
 
  @Column(name="NAME")
  public String getName() { return name; }
}
Abb. 1: Beispiel für eine Entity Bean.

Entity Manager

Der Entity Manager ist ein zentrales Element der Java Persistence API. Er stellt alle persistenzbezogenen Funktionalitäten zur Verfügung. Die­se Funktionalitäten waren in den vorherigen Versionen direkt in der Entity Bean enthalten. Da für diese Funktionalitäten nun der Entity Manager zuständig ist, können die Entitäten nun als leichtgewichtige POJOs implementiert werden, was die Komplexität erheblich reduziert. Über ihn können Objekte geladen, gelöscht, gespeichert, aktualisiert und gesucht werden. Abbildung 2 zeigt die Arbeitsweise des Entity Managers an einem Beispiel: einen Kunden anlegen, speichern und suchen.

@Stateless public class KundenManagerBean { 
   
  //Injection der Referenz auf den Entity Manager
  @PersistenceContext private EntityManager entityManager;
 
  public Integer anlegenKunde(String name, ...) {
    Kunde kunde = new Kunde(name,...);
    entityManager.persist(kunde);
    return kunde.getId();
  }
 
  public Kunde findeKunde(Integer id) {
    return entityManager.find(Kunde.class, id);
  }
}
Abb. 2: Arbeitsweise des Entity Managers.

Ein Client hat zudem noch die Möglichkeit, über den Entity Manager Abfragen über Entity Beans durchzuführen. Der Manager bietet folgende Möglichkeiten:


@Stateless public class KundenManagerBean { 
  ...
  public List<Kunde> findeKunden(String name) {
    Query q = entityManager.createQuery
      ("from Kunde k where k.name = :kname");
    q.setParameter("kname",name);
    List<Kunde> kunden = (List<Kunde>) q.getResultList();
    return kunden;
  }
}
Abb. 3: Beispiel für EJB-QL (Query Language).

@Entity 
@Table(name="KUNDE")
@NamedQuery(name="alleKunden",queryString="from Kunde")
public class Kunde implements Serializable { 
  ... 
}
 
@Stateless 
public class KundenManagerBean {
  ...
  public List<Kunde> alleKunden() {
    Query q = entityManager.createNamedQuery("alleKunden");
    List<Kunde> kunden = (List<Kunde>) q.getResultList();
    return kunden;
  }
  ...
} 
Abb. 4: Beispiel für benannte EJB-QL: Named Query.

Multitable Mapping

Wie eingangs erwähnt, war es bisher nur möglich, eine Entity Bean auf eine Tabelle abzubilden. Dies reichte in vielen Entwicklungsprojekten nicht aus, da sich Geschäftsobjekte häufig über mehrere Tabellen einer relationalen Datenbank verteilen. Das führte dazu, dass Tabellen denormalisiert oder aber der SQL-Code vom Entwickler selbst implementiert werden musste (Stichwort BMP - Bean-managed Persistence).

In EJB 3.0 ist es nun möglich, die Daten für ein Objekt aus mehreren verschiedenen Tabellen zu erhalten. Eine Entity Bean ist nun ein logisches Geschäftsobjekt, das seine Daten aus mehreren Tabellen aus der Datenbank erhält. In der Entity Bean wird einfach über Annotations (@SecondaryTable) angegeben, aus welcher Tabelle das Objekt seine Daten bezieht. Das Beispiel in Abbildung 5 zeigt ein solches Multitable Mapping. Die in dem Beispiel verwendeten Tabellen sind:


@Entity 
@Table(name="KUNDE")
@SecondaryTable (name="ADRESSE")
public class Kunde implements Serializable {
  private Integer id;
  private String name;
  private String strasse;
  ...    
  @Column(name="NAME")
  public String getName()    { return name; }
 
  @Column(name="STRASSE", secondaryTable="ADRESSE")
  public String getStrasse() { return strasse; }
}
Abb. 5: Beispiel für Multitable Mapping.

Die Fremdschlüsselbeziehung zwischen den Tabellen wird, falls nicht explizit anders angegeben, über den Primärschlüssel (PK) der Primärtabelle und über einen Fremdschlüssel (FK) der Sekundärtabelle abgebildet. Der Name dieses Fremdschlüssels setzt sich aus dem Namen der Primärtabelle und dem Namen des Primärschlüssels der Primärtabelle zusammen. Sollte dies nicht der Fall sein, so muss die Fremdschlüsselbeziehung der Tabellen über eine spezielle Annotation in der Entity Bean erfolgen.

Die Zuordnung der Attribute zu der jeweiligen Tabellenspalte erfolgt bei den get-Methoden über die Annotation @Column. Enthält diese Annotation nicht das Element secondaryTable, so erfolgt das Mapping über die Primärtabelle, wie es im Beispiel bei der get-Methode getName() der Fall ist. Bei dem Attribut strasse erfolgt das Mapping über die Sekundärtabelle ADRESSE, da in der zugehörigen Annotation secondaryTable="ADRESSE" steht.

Beziehungsgeflechte

Zwischen den verschiedenen Objekten kann es ganz unterschiedliche Beziehungen geben. Die Art der Beziehung wird, wie erwartet, in den jeweiligen Entity Beans durch Annotations bestimmt. Es können folgende Beziehungen zwischen Objekten abgebildet werden:

Die folgenden Beispiele sollen die Möglichkeiten der Beziehungsgeflechte ein wenig veranschaulichen:

Eins-zu-Eins-Beziehungen

In dem Beispiel in Abbildung 6 soll jeder Kunde genau eine Adresse haben. Die Methode getAdresse von Kunde soll also diese Adresse zurückgeben. Der Kunde muss zwingend eine Adresse haben. Dies wird in der Annotation @OneToOne durch das Element optional=false erreicht. Die Annotation @JoinColumn wäre hier gar nicht nötig gewesen, da durch die Namensgebung der Fremdschlüssel als solcher erkannt wird. Die dazugehörigen Tabellen lauten:


@Entity 
@Table(name="ADRESSE")
public class Adresse implements Serializable {
  int id;
  String ort;
  String strasse;
  ...
}   
 
@Entity 
@Table(name="KUNDE")
public class Kunde implements Serializable {
  int id;
  String name;
  Adresse adresse;
 
  @OneToOne(optional=false)
  @JoinColumn(name="ADRESSE_ID")
  public Adresse getAdresse() { return adresse; }
  ...
}
Abb. 6: Beispiel für eine Eins-zu-Eins-Beziehung.

Eins-zu-Viele-Beziehungen

Hier gibt es zu jedem Kunden mehrere Adressen. Dies wird durch die Annotation @OneToMany bei der Methode getAdressen() erreicht (siehe Abbildung 7). Weitere Angaben sind in diesem Fall nicht nötig, da die Beziehung der Tabellen in diesem Fall durch die Namensgebung gegeben ist. Die dazugehörigen Tabellen lauten:


@Entity 
@Table(name="ADRESSE")
public class Adresse implements Serializable {
  int id;
  String strasse;  ...
}
 
@Entity 
@Table(name="KUNDE")
public class Kunde implements Serializable {
  int id;
  String name;
  Collection<Adresse> adressen;
 
  @OneToMany
  public Collection<Adresse> getAdressen() { return adressen;}
  ...
}
Abb. 7: Beispiel für eine Eins-zu-Viele-Beziehung.

Viele-zu-Eins-Beziehungen

In dem Beispiel in Abbildung 8 möchten wir zu einer gegebenen Adresse den dazugehörigen Kunden ermitteln. Dies erreichen wir durch die Annotation @ManyToOne bei der Methode getKunde() in der Klasse Adresse. Da wir nicht mit einer Join-Tabelle arbeiten, müssen wir durch das Element mappedBy="kunde" in der Annotation @OneToMany in der Klasse Kunde die Beziehung festlegen. Die in diesem Beispiel verwendeten Tabellen lauten:


@Entity 
@Table(name="ADRESSE")
public class Adresse implements Serializable { ... 
  Kunde kunde;

  @ManyToOne
  public Kunde getKunde() { return kunde; }
}
 
@Entity 
@Table(name="KUNDE")
public class Kunde implements Serializable { ...
  Collection<Adresse> adressen;
 
  @OneToMany(mappedBy="kunde")
  public Collection<Adresse> getAdressen() { return adressen; }
}
Abb. 8: Beispiel für eine Viele-zu-Eins-Beziehung.

Viele-zu-Viele-Beziehungen

In unserem letzten Beispiel zu Beziehungsgeflechten kann jeder Kunde mehrere Adressen haben, aber es können auch zu einer Adresse mehrere Kunden existieren (siehe Abbildung 9). Erreicht wird dies durch eine Join-Tabelle und die Annotation @ManyToMany, die wir sowohl bei der Methode getKunden() in der Klasse Adresse, als auch bei getAdressen() in der Klasse Kunde verwenden. Die entsprechenden Tabellen sind die gleichen, wie in unserem Beispiel für Eins-zu-Viele-Beziehungen (siehe Abbildung 7).

@Entity 
@Table(name="ADRESSE")
public class Adresse implements Serializable { ... 
  Collection<Kunde> kunden;
 
  @ManyToMany
  public Collection<Kunde> getKunden() { return kunden; }
}
 
@Entity 
@Table(name="KUNDE")
public class Kunde implements Serializable { ...
  Collection<Adresse> adressen;
 
  @ManyToMany(mappedBy="kunde")
  public Collection<Adresse> getAdressen() { return adressen; } 
}
Abb. 9: Beispiel für eine Viele-zu-Viele-Beziehung.

Vererbung

Mit EJB 3.0 und der darin definierten Java Persistence API ist nun auch Vererbung und Polymorphie für persistente Objekte wie z. B. Entity Beans möglich. EJB 3.0 stellt für die Umsetzung der Vererbung bzw. die Abbildung der Vererbungshierarchie eines Objektmodells auf eine relationale Datenbank verschiedene Strategien zu Verfügung:

Single Table per Class Hierarchy (SINGLE_TABLE)

Bei dieser Strategie werden alle Klassen einer Vererbungshierarchie auf eine einzige Tabelle in der Datenbank abgebildet. Alle Informationen dieser Klassen werden in denormalisierter Form in der Datenbank gespeichert. Zur Unterscheidung der Klassen wird in der Tabelle ein Kennzeichen, der so genannte "Discriminator", eingeführt (siehe Abbildung 10). Gibt man bei der Programmierung nicht an, welche Strategie man verwenden möchte, so ist dies die Standardstrategie.

@Entity 
@Table(name="GESCHAEFTSPARTNER")
@Inheritance(strategy=SINGLE_TABLE)
@DiscriminatorColumn(name="GP_TYPE",
@discriminatorType=STRING,length=20)
public class Geschaeftspartner implements Serializable {
  int id;
  String name1;
  String name2; ...
}
 
@Entity 
@DiscriminatorValue("KUNDE")
public class Kunde extends Geschaeftspartner {
  String attributkunde; ...
}
 
@Entity 
@DiscriminatorValue("LIEFERANT")
public class Lieferant extends Geschaeftspartner {
  String attributlieferant; ...
}
Abb. 10: Beispiel für Vererbung mittels Single Table per Class Hierarchy.

Single Table per Concrete Entity Class (TABLE_PER_CLASS)

Hier wird jede Klasse einer Vererbungshierarchie auf eine separate Tabelle abgebildet. Jede Tabelle enthält alle Daten einer Klasse - auch die geerbten Attribute. Festgelegt wird diese Strategie durch die Annotation @Inheritance(strategy=TABLE_PER_CLASS).

Joined Subclass Strategy (JOINED)

Diese Strategie ähnelt der vorhergehenden, nur enthalten die Tabellen der Subklassen nur die spezifischen Attribute der jeweiligen Subklasse, also ohne die geerbten Attribute. Diese werden in der Tabelle der Basisklasse gespeichert. Jede Tabelle der Subklasse enthält den Fremdschlüssel der Tabelle der Basisklasse. Diese Strategie wird durch die Verwendung der Annotation @Inheritance(strategy=JOINED) bestimmt.

Als letztes Beispiel in unserem Artikel soll die Vererbung mittels der Single Table per Class Hierarchy Strategie veranschaulicht werden (siehe Abbildung 10). In diesem Beispiel gibt es eine Basisklasse Geschaeftspartner, von der die Subklassen Kunde und Lieferant erben. Um diese Klassen zu unterscheiden, gibt es in der Datenbanktabelle als Discriminator ein Kennzeichen (GP_TYPE). Die dazugehörige Tabelle lautet:

Detachment

In den meisten Fällen ist es nun nicht mehr notwendig, Data Transfer Objects (DTO) zu verwenden. Die unter der Überwachung eines Entity Managers stehenden POJOs bzw. Objekte können als so genannte Detached Objects einfach serialisiert zum Client transferiert werden. Das Objekt bzw. das mit @Entity annotierte POJO muss einfach nur das Interface java.io.Serializable implementieren.

Fazit

Dieser Artikel gibt einen ersten Überblick über die neue Java Persistence API. Auch wenn nicht alle Eigenschaften und Details behandelt werden konnten, wird deutlich, dass sich durch die neue JPA nun mit erheblich niedrigerem Aufwand eine Anbindung an relationale Datenbanken realisieren lässt. Mit JPA wurde ein einheitlicher Standard geschaffen, sowohl für Java SE als auch für Java EE. Da nun Entity Beans konsequent als POJOs umgesetzt werden, ist ein großer Teil der Komplexität der alten Versionen beseitigt worden. Dies ist ein großer Schritt nach vorne und hilft dabei, die Verwendung von JEE im Allgemeinen und Entity Beans im Besonderen in Projekten verstärkt einzusetzen. Dies hat sich in unseren Projekten bereits in der Praxis bestätigt.

Oliver Kaluza (info@ordix.de).