Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  3/2007  Pfeil  Java/J2EE/JEE
suche: 
Dieser Artikel richtet sich an Java-Entwickler, die einen Einblick in Hibernate Annotations gewinnen möchten. Voraussetzung sind grundlegende Erfahrungen mit Hibernate. Oder Sie lesen vorher den ORDIX News Artikel „Hibernate: Hält Java Winterschlaf" [1].

Glossar

POJO
Plain Old Java Object. Dabei handelt es sich um ein normales Objekt in der Programmiersprache Java.
O-/R-Mapping
Object-Relational-Mapping. O-/R-Mapping bezeichnet die Abbildung von objektorientierten Daten auf relationale Daten und umgekehrt.


Reihe Hibernate (Teil V)

Hibernate Annotations: Schneller Erfolg als Kommentator


Die Erweiterung „Hibernate Annotations" stellt seit der Einführung von Annotations in Java einen eleganten Weg dar, das Persistenz-Framework zu nutzen. Basierend auf einem Beispielprojekt zeigt dieser Artikel die Umstellung eines „klassischen" Hibernate O-/R-Mappings mit hbm-Dateien auf Hibernate Annotations.

  • antlr.jar
  • cglib.jar
  • asm.jar
  • asm-attrs.jars
  • commons-collections.jar
  • commons-logging.jar
  • dom4j.jar
  • ejb3-persistence.jar
  • hibernate-annotations.jar
  • hibernate-commons-annotations.jar
  • hibernate3.jar
  • jta.jar
  • log4j.jar
Abb. 1: Benötigte Libraries.

Die Vorberichterstattung

Grundlage dieses Artikels ist das Beispielprojekt in dem Artikel “Hält Java Winterschlaf?” [1] In diesem Einführungsartikel der Hibernate-Reihe [2] wird das Hibernate Mapping in hbm.xml Dateien definiert. Seit Java 1.5 und Hibernate ab Version 3 kann man in den einzelnen Klassen durch die Verwendung von Annotations (zu Deutsch: „Kommentare") vollständig auf Mapping-Dateien verzichten.

Voraussetzungen

Anhand der beiden Klassen Person und Project betrachten wir die Vorgehensweise bei der Umstellung von Mapping und Tabellenabhängigkeiten auf Hibernate Annotations. Beide Klassen haben jeweils Id- und Namensattribute. Es handelt sich dabei um Plain Old Java Objects (POJOs), also um „ganz normale" Java-Objekte.

Damit Hibernate die Attribute ansprechen kann, müssen alle persistenten Attribute über eine Getter- und Setter-Methode verfügen. Des Weiteren benötigt jede Klasse einen Standard-Konstruktor. Außerdem gibt es eine ManyToOne-Beziehung von dem Attribut projectowner in der Klasse Project auf die Klasse Person.

Zunächst stellen wir die alte Mappingdatei Project.hbm.xml aus Abbildung 2 um. Dafür sind die Ergänzungen in Abbildung 3 in der Klasse Project nötig.

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="de.ordix.jug.Project" table="project">
<id name="projectid" column="id" type="java.lang.Integer">
      <generator class="sequence">
         <param name="sequence">project_seq</param>
      </generator>
   </id>
   <property name="projectname" column="name" />
   <many-to-one name="projectowner" class="de.ordix.jug.Person"
         column="owner" />
   </class>
</hibernate-mapping>
Abb. 2: Die ursprüngliche Mappingdatei "Project.hbm.xml".

@Entity
@SequenceGenerator(name="project_id", sequenceName="project_seq", initialValue=1, allocationSize=1)
@Table(name="PROJECT")
public class Project {
   Integer projectid;
   String projectname;
   Person projectowner;
   public Project() {
   }
   @Id
   @GeneratedValue(strategy= GenerationType.SEQUENCE,
    generator = "project_id")
   public Integer getProjectid() {
     return projectid;
   }
   public void setProjectid(Integer projectid) {
     this.projectid = projectid;
   }
   @Column(name="name")
   public String getProjectname() {
     return projectname;
   }
   public void setProjectname(String projectname) {
     this.projectname = projectname;
   }
...
Abb. 3: Teil 1 des Quellcodes zur Klasse Project mit Annotations.

@Entity definiert die Klasse Project zunächst als Entität. Jeder Datensatz benötigt einen Primärschlüssel. Die Generierung ist in der alten Mapping-Datei per <generator>-Tag beschrieben. In diesem Fall verwenden wir eine Sequenz, die mit der Annotation @SequenceGenerator initialisiert und später beim Persistieren des Objektes benutzt wird.

Unter dem vergebenen Namen project_id können wir den Generator später dort aufrufen. Der sequenceName definiert die Bezeichnung der entsprechenden Sequenz in der Datenbank. Wir übergeben den Startwert und das Erhöhungsintervall der Sequenz mit initialValue=1 und allocationSize=1. Im nächsten Schritt bilden wir die Klasse mit @Table auf die Tabelle PROJECT ab.

Keine Persistenz ohne Identifizierung

Standardmäßig bildet Hibernate die Namen der Datenbankspalten anhand der Attributnamen ab. Um die Spalte des Attributs projectname auf unsere bestehende Spalte name abzubilden, verwenden wir die Annotation @Column. Alle Annotations, die sich auf einzelne Attribute beziehen, stehen vor den jeweiligen Getter-Methoden.

Gemäß Hibernate-Konvention müssen die Getter- und Setter-Methoden für die Attribute immer dem Schema get+<Attributname(beginnend mit Großbuchstaben)>

entsprechen, z. B. getProjectname() für projectname.

Hibernate übernimmt bei der Persistierung von Objekten die Generierung von Primärschlüsseln der jeweiligen Datensätze. @Id kennzeichnet die PK-Spalte. Um den dafür vorher definierten Generator einzusetzen, wird die Annotation @GeneratedValue eingesetzt. Als Strategie fällt die Wahl auf GenerationType.SEQUENCE und, wie vorher angegeben, wird mit project_id auf den Sequenz-Generator zugegriffen.

Im nächsten Schritt wird die Klasse Person angepasst (siehe Abbildung 4). Dabei ist die Vorgehensweise äquivalent zu der Klasse Project.

@Entity
@SequenceGenerator(name="personen_id", sequenceName="person_seq", initialValue=1, allocationSize=1)
@Table(name="PERSON")
public class Person {

  Integer personid;
  String firstname;
  String lastname;
  String phone;

  public Person() {
  }
  public String getFirstname() {
    return firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }
  public String getLastname() {
    return lastname;
  }
  @Column(name="last_name_of_person")
  public void setLastname(String lastname) {
    this.lastname = lastname;
  }
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personen_id")
  public Integer getPersonid() {
    return personid;
  }

  public void setPersonid(Integer personid) {
    this.personid = personid;
  }
  public String getPhone() {
    return phone;
  }
  public void setPhone(String phone) {
    this.phone = phone;
  }
}
Abb. 4: Quellcode zur Klasse Person mit Annotations.

Beziehungen machen das Leben interessant ...

Um die Many-To-One-Beziehung aus dem Altprojekt zu kopieren, wird die Klasse Project um die Annotation @ManyToOne ergänzt und die Spalte owner als @JoinColumn ausgewählt. Wie Abbildung 5 zeigt, sind die beiden Annotations vor dem Getter des Attributes projectowner platziert und verweisen somit auf die vom Getter zurückgegebene Klasse Person.

...
@ManyToOne
@JoinColumn(name="owner")
public Person getProjectowner() {
  return projectowner;
     }
public void setProjectowner(Person projectowner) {
  this.projectowner = projectowner;
     }
}
Abb. 5: Teil 2 des Quellcodes zur Klasse Project mit Annotations.

In den Papierkorb

Alle Vorbereitungen für die Umstellung sind nun abgeschlossen. Wie in Abbildung 6 zu sehen, wird in der hibernate.cfg.xml das Mapping auf die hbm-Dateien durch ein Klassen-Mapping ersetzt.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
   [...]
   <property name="hbm2ddl.auto">create</property>
   <!--
   <mapping resource="de/ordix/jug/Project.hbm.xml"/>
   <mapping resource="de/ordix/jug/Person.hbm.xml"/>
   -->
   <mapping class="de.ordix.jug.Person"/>
   <mapping class="de.ordix.jug.Project"/>
  </session-factory>
</hibernate-configuration>
Abb. 6: hibernate.cfg.xml mit Annotations.

Der Aufruf des Hauptprogramms meldet allerdings noch eine Exception, die darauf hinweist, dass eine AnnotationConfiguration-Instanz benötigt wird, um die Klassen abzubilden. Abbildung 7 zeigt, dass lediglich eine kleine Änderung an der Initialisierung der SessionFactory nötig ist.

private static final SessionFactory sessionFactory =
  new AnnotationConfiguration().configure("/de/ordix/jug/hibernate.cfg.xml").buildSessionFactory();
//Alte SessionFactory
//private static final SessionFactory sessionFactory = new
Configuration().configure("de/ordix/jug/hibernate.cfg.xml").buildSessionFactory();
Abb. 7: Änderung SessionFactory im Quellcode des Hauptprogramms in der main(String[])-Methode.

Nach einem erneuten Aufruf des Hauptprogramms zeigt die Protokollierung der beiden Select- und Insert-Statements auf der Konsole den erfolgreichen Umbau mit Annotations.

Eine kurze Abfrage der Datenbank bestätigt, dass alle Daten gespeichert sind und die Fremdschlüsselbeziehung zwischen den Entitäten Project und Person richtig umgesetzt ist.

Warum ist eine Umstellung auf Annotations attraktiv?

Bei vielen Anwendungen, in denen Daten von Objekten in einem relationalen Datenbanksystem gespeichert werden, sind die Vorteile eines Persistenz-Frameworks nicht von der Hand zu weisen. Der Programmierer kann sich auf die Geschäftslogik konzentrieren, ohne seine Klassen mit SQL-Statements und Connections aufzublähen.

Der Verzicht auf die hbm-Mapping-Dateien für die einzelnen Klassen macht Mappings übersichtlicher und bündelt alle relevanten Mapping-Informationen über den jeweiligen Gettern. Bei Hibernate Annotations muss nur noch eine Datei angepasst werden, wohingegen im alten Projekt immer die beiden Dateien .hbm.xml und .java aufeinander abgestimmt sein müssen.

Für die Generierung von hbm-Dateien kommt oft XDoclet zum Einsatz. Mit den Annotations ab Java 1.5 ist die Zukunft von XDoclet sehr ungewiss. Für den Hibernate-Einsatz gilt XDoc-let wegen mangelnder Unterstützung neuerer Hibernate-Funktionen mittlerweile als veraltet. Hibernate Annotations hingegen sind fester Bestandteil von Hibernate, werden weiterentwickelt und entsprechen immer dem Funktionsumfang aktueller Hibernate-Versionen.

Der Verzicht auf Werkzeuge zur Generierung von hbm-Dateien, wie zum Beispiel XDoclet bietet also mehrere Vorteile. Es macht Softwareprojekte unabhängig von der Weiterentwicklung externer Tools und vermeidet durch Nutzung von Java-Sprachelementen (Annotations) einen Bruch in der Technologie, den die hbm-Mapping-Dateien in diesem Fall darstellen.

Ein Mischbetrieb von Klassen mit Annotations und den alten hbm.xml-Dateien ermöglicht eine problemlose, schrittweise Migration. An der Geschäftslogik und der Speicherung von Objekten sind keine Änderungen im Quelltext notwendig.

In zahlreichen Großprojekten konnte die ORDIX AG Know-how auf dem Gebiet Hibernate aufbauen. Auch Ihnen stehen wir gerne mit unserer Projekterfahrung zur Seite. Sprechen Sie uns an!

Julian Gärtner (info@ordix.de).