EJB 3.0 Annotations mit Hibernate Lazy Loading

Wissenswertes
2 Kommentare

Mit Hilfe der EJB 3.0 Annotations aus dem Package javax.persistence ist es sehr einfach möglich, Java-Klassen auf Datenbanktabellen abzubilden, ohne dafür XML schreiben zu müssen. Diese Annotations können entweder direkt auf die Felder, oder auf die dazugehörigen getter gesetzt werden. Im folgenden Beispiel verwenden wir die direkt die Felder:

import javax.persistence.*
 
@Entity
public class Person {
 
  @Id @GeneratedValue
  private long id;
 
  private String name;
 
  public String getId() {
    return this.id;
  }
 
  public String getName() {
    return this.name;
  }
}

Das Setzen der Annotations direkt auf die Felder hat jedoch eine unangenehme – wenn auch nicht offensichtliche – Nebenwirkung, und zwar in Verbindung mit Hibernate Proxies, die durch Lazy Loading erzeugt werden.

Sehen wir uns folgendes Beispiel mit einer 1:1-Relationship an (die Konstruktoren werden der Übersichtlichkeit halber weggelassen):

@Entity public class Person {   @Id @GeneratedValue private long id;   private String name;   @OneToOne(fetch = FetchType.LAZY) private Address address;   public String getId() { return this.id; }   public String getName() { return this.name; } }   @Entity public class Address { @Id @GeneratedValue private long id;   private String town;   public long getId() { return this.id; }   public String getTown() { return this.town; } }

Wenn wir nun ein Person-Objekt aus einer Hibernate-Session laden, dann wird vorerst aufgrund des FetchType.LAZY die Adresse nicht mitgeladen; Hibernate erzeugt dafür einen Proxy:

Person person = (Person) session.load(Person.class, Long.valueOf(1)); Address address = person.getAddress(); // Hibernate gibt hier einen Proxy zurück

Nun greifen Hibernate Proxies auf die Datenbank zu, sobald auf eines der Felder zugegriffen wird:

String town = address.getTown(); // impliziter DB-Zugriff durch Hibernate

Dies gilt für alle Felder, ausgenommen dem identifier property (also jenem, auf dem die @Id Annotation sitzt, da dieser Wert ja schon beim SELECT auf die Person-Tabelle mitgeladen wurde:

long addressId = address.getId();

Und hier steckt die Falle. Obwohl wir nur auf die ID zugreifen, initialisiert Hibernate trotzdem den Proxy und holt sich den gesamten Datensatz. Der Grund dafür ist, dass Hibernate von der getId() Methode nichts weiss, wenn die @Id-Annotation auf dem Feld sitzt. Hibernate nimmt dadurch an, dass getId() ein normaler Feldzugriff ist und lädt den gesamten Datensatz (obwohl dieser möglicherweise gar nicht gebraucht wird). Wird auf dieses Feld außerhalb der Hibernate-Session zugegriffen, gibt es noch dazu eine LazyInitializationException.

Um diesem Problem zu entgegnen, muss die @Id-Annotation auf den getter und nicht auf das Feld gesetzt werden. Das wiederum zwingt uns dazu, auch alle anderen Annotations auf die getter zu setzen, da Hibernate aufgrund der Position der @Id-Annotation bestimmt, ob die Annotations auf den gettern oder Feldern gesetzt sind. Beides gleichzeitig geht (leider) nicht. Und für den Fall, dass wir die Annotations auf die getter setzen, verlangt Hibernate auch setter (diese können allerdings private oder protected sein).

Unser Code sieht dadurch folgendermaßen aus:

@Entity public class Person {   private long id; private String name; private Address address;   @Id @GeneratedValue public String getId() { return this.id; }   public String getName() { return this.name; }   @OneToOne(fetch = FetchType.LAZY) public Address getAddress() { return this.address; } }   @Entity public class Address { private long id;   private String town;   @Id @GeneratedValue public long getId() { return this.id; }   public String getTown() { return this.town; } }

Nun funktioniert das Lazy-Loading auch wie erwartet:

Person person = (Person) session.load(Person.class, Long.valueOf(1)); // DB-Select Address address = person.getAddress(); long addressId = address.getId(); // kein DB-Select String town = address.getTown(); // Proxy wird initialisiert, DB-Select

Auf http://blog.xebia.com/2009/06/13/jpa-implementation-patterns-field-access-vs-property-access/ wird ein Workaround beschrieben mittels dem man auf die Id zugreifen kann ohne den Proxy zu initialisieren.

 

Vorheriger Beitrag
Akzeptanztests und exploratives Testen
Nächster Beitrag
Continuous Integration mit FlexUnit und TeamCity

Related Posts

2 Kommentare. Hinterlasse eine Antwort

This is a known bug in Hibernate 3.3.1 that is hopefully addressed in one of the next releases. See http://opensource.atlassian.com/projects/hibernate/browse/HHH-3718

There are other pitfalls one might stumble upon when using Hibernate proxies, further information can be found at http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/

At http://www.fnogol.de/media/ejb3.0-anno-cheat-1.2.pdf there is a handy cheat sheet for EJB annotations i found helpful.

Antworten

EJB 3.0 Annotations with Hibernate Lazy Loading — Catalysts for Agile Software Developers…

By means of the EJB 3.0 annotations from the package java.persistence it’s very easy to map java classes to database tables without writing any XML. These annotations can be directly put onto the fields or on the corresponding getter methods. In the …

Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Bitte füllen Sie dieses Feld aus
Bitte füllen Sie dieses Feld aus
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren

Menü