Archiv für die Kategorie ‘Hibernate’

Vererbung in Hibernate

Freitag, 17. September 2010

Beim Design von Datenbanken stellen wir häufig fest, das Gruppen von Attributen in mehreren Entitäten vorkommen. Aus objektorientierter Sicht würden man hier auf eine Vererbung hinarbeiten. Leider sind solche Konstrukte in der Datenbank nicht direkt möglich. Hibernate bietet uns hier die Möglichkeit der Vererbung.

Als Beispiel nehmen wir an, das zu unseren Kunden Firmen und Personen gehören und alle über Bankdaten verfügen:

Beispiel: Komponenten im DB-Design

In Java würde diese Vererbung wie folgt aussehen:

    @Entity
    @Table(name = "account_holders")
    @Inheritance(strategy = InheritanceType.JOINED)
    public abstract class AccountHolder {
        private String bankName;
        private String bankCode;
        private String accountNumber;
        ...
    }
 
    @Entity
    @Table(name = "persons")
    public class Person extends AccountHolder {
        private String firstName;
        private String lastName;
        ...
    }
 
    @Entity
    @Table(name = "companies")
    public class Company extends AccountHolder {
        private String name;
        ...
    }

Neben der Vermeidung von doppelten Datenstrukturen in der Datenbank, zeigt sich hier ein weiterer interessanter Aspekt: Das Laden abstrakter Klassen!

Wenn wir im oben genannten Beispiel eine Liste aller Kunden (Personen und Firmen) mitsamt ihren Bankdaten laden wollen, reicht ein einziger Aufruf:

    List<AccountHolder> clients = new AccountHolderDAO().getList();

Hiermit kann man viel Freude haben, wenn man alle Aspekte im Blick hat: Da Hibernate bei Aufruf noch nicht weiß, welche Daten für eine Instanz erforderlich sind, wird ein Join über die Tabelle der abstrakten Klasse selbst und der Tabellen aller Kindklassen erstellt. Dies kann bei einer tiefen oder weitläufigen Vererbungshierarchie durch große Joins die Performance spürbar drücken. Von einer Verwendung für häufig angehängte Daten würde ich daher Abstand nehmen.


Thomas Bader


Hibernate Proxies und Polymorphismus

Freitag, 07. Mai 2010

Beim LazyLoading mit Hibernate werden die eigentlichen Entitäten durch Proxy Objekte ersetzt. Diese Proxy Objekte werden erst dann initialisiert, wenn auf sie zugegriffen wird. Hibernate erstellt eine Unterklasse der Entität und überschreibt alle Methoden der Elternklasse. Wird eine dieser Methoden aufgerufen, so delegiert der Proxy die Anfrage an die Datenbank und füllt die Entität aus der Datenbank. Diese Objekte lassen sich mit instanceof auf ihren Typ prüfen und sie können in ihren Ursprungstyp gecastet werden, da es sich ja bei dem Proxy um eine Kindklasse der eigentlichen Entität handelt.

Wenn jetzt aber Objekthierarchien und Polymorphismus mit ins Spiel kommen, dann wird es schwieriger.

Ein Beispiel:

public class Vehicle{
...
}
 
public class Car extends Vehicle{
   public void reFuel(){};
}
 
public class Bicycle extends Vehicle{
...
}
 
public class Garage {
   Set<Vehicle> vehicle = new HashSet<Vehicle>();
   public Set<Vehicle> getVehicle(){
       return vehicle;
   }
}

Alle Autos sollen nun aufgetankt werden.
Eine einfache Möglichkeit wäre:

for(Vehicle v : garage.getVehicle()){
   if(v instanceof Car){
       Car car = (Car)v;
       car.reFuel();
   }
}

Wenn jetzt aber Hibernate mit LazyLoading eingesetzt wird, so werden wir leider bei nächster Gelegenheit mit dem Auto ohne Benzin liegen bleiben. Hibernate ersetzt die einzelnen „Vehicle“ durch Proxies, also durch Kindklassen der Klasse “Vehicle”. Da der Proxy dann aber kein „Car“ ist, schlägt das instanceof fehl und das Auto wird nicht aufgetankt. Selbst wenn wir uns sicher sind, dass nur Autos in der Garage stehen (also auf das instanceof verzichten), schlägt spätestens der Cast nach „Car“ mit einer ClassCastException fehl.

Um sowohl auf das „instanceof“ wie auch auf den Cast verzeichten zu können, können wir das Visitor Pattern einsetzen.

public interface VehicleVisitor{
   void visit(Car car);
   void visit(Bicycle bicycle);
}

Für die Bequemlichkeit einen Adapter:

public VehilceVisitorAdapter implements VehicleVisitor{
   void visit(Car car){ /* leer */};
   void visit(Bicycle bicycle){/* leer */};
}

Unsere Fahrzeuge müssen den Visitor entgegennehmen:

public class Vehicle{
   public void accept(VehilceVisitor visitor){
       // leer
   }
...
}
 
public class Car extends Vehicle{
   public void reFuel(){};
   public void accept(VehilceVisitor visitor){
       visitor.visit(this);
   }
}

Jetzt kann die ursprüngliche Schleife angepasst werden:

VehicleVisitor fuelVisitor = new VehicleVisitorAdapter(){
     public void visit(Car car){
        car.reFuel();
     }
};
 
for(Vehicle v : garage.getVehicle()){
   v.accept(fuelVisitor);
}

Jetzt sind dann auch wirklich alle Autos aufgetankt und wir brauchen keine Angst mehr vor Hibernate Proxies haben. Nebenbei haben wir auf das „hässliche“ instanceof verzichtet und brauchen auch nicht mehr die Objekte zu casten.


Felix Breske


Hibernate, HashCode und Sets

Montag, 29. März 2010

Verwendet man Hibernate in seinem Projekt als O/R-Mapper, zählt es zu einer der ersten Aufaben eine adäquate Implementierung von equals und hashCode für die Entitäten bereitzustellen. Eine verbreitete Möglichkeit für die HashCode-Methode ist dabei die folgende:

    @Override
    public int hashCode() {
        return getId();
    }

Dabei liefert getId() die durch Hibernate generierte Id der Entität, sprich den PrimaryKey des Datenbank-Eintrages. Soweit verständlich. Allerdings bricht diese Implementation mit der ersten Anforderung des Hash-Code Contracts (Javadoc Auszug):

   /**
     * Returns a hash code value for the object. This method is 
     * supported for the benefit of hashtables such as those provided by 
     * <code>java.util.Hashtable</code>. 
     * <p>
     * The general contract of <code>hashCode</code> is: 
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during 
     *     an execution of a Java application, the <tt>hashCode</tt> method 
     *     must consistently return the same integer, provided no information 
     *     used in <tt>equals</tt> comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application. 
     * <li>If two objects are equal according to the <tt>equals(Object)</tt>
     *     method, then calling the <code>hashCode</code> method on each of 
     *     the two objects must produce the same integer result. 
     * <li>It is <em>not</em> required that if two objects are unequal 
     *     according to the {@link java.lang.Object#equals(java.lang.Object)} 
     *     method, then calling the <tt>hashCode</tt> method on each of the 
     *     two objects must produce distinct integer results.  However, the 
     *     programmer should be aware that producing distinct integer results 
     *     for unequal objects may improve the performance of hashtables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by 
     * class <tt>Object</tt> does return distinct integers for distinct 
     * objects. (This is typically implemented by converting the internal 
     * address of the object into an integer, but this implementation 
     * technique is not required by the 
     * Java<font size="-2"><sup>TM</sup></font> programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.util.Hashtable
     */

Da Hibernate die Id erst erzeugt, wenn die Entität gespeichert wird, verändert sich der Hash-Code während des Lebenszykluses des Objektes. In vielen Fällen stellt das kein Problem da. Arbeitet man allerdings mit HashSets als Collection für eine Assoziation und verwendet gleichzeitig Speicher-Kaskaden, führt diese Implementierung zu Schwierigkeiten. Beispiel:

@Entity
public class FunkyVO extents BaseVO {
 
    @OneToMany(mappedBy = "funky", fetch = FetchType.LAZY)
    @Cascade({ org.hibernate.annotations.CascadeType.ALL})
    private final Set<GrooveVO> grooves= new HashSet<GrooveVO>();
 
}

Und dazu folgender Pseudocode:

FunkyVO funkyVO = new FunkyVO();
GrooveVO grooveVO = new GrooveVO();
funkyVO.getGrooves().add(grooveVO);
PersistService.persist(funkyVO);
funkyVO.getGrooves().contains(grooveVO); // => false
funkyVO.getGrooves().remove(grooveVO); // => false
funkyVO.getGrooves().add(grooveVO); // => false

Da die Id erst durch das Persistieren erzeugt wird, ändert sich der Hash-Code nachdem das Objekt zur Liste hinzugefügt wurde. Die Konsequenz ist, dass das Objekt nun nicht mehr wiedergefunden wird (contains), nicht mehr entfernt werden kann (remove) und bei einem erneuten Hinzufügen auch nicht als Duplikat erkannt wird (add).

Und wie sieht die Lösung aus?

Eine Möglichkeit wäre, im ganzen Projekt darauf zu achten, dass die Entitäten erst nach dem Speichern den Listen hinzugefügt werden…

Hibernate verweist dagegen bei diesem Problem darauf, dass die generierte Id nicht in equals oder hashCode verwendet werden soll. Als Lösung wird dort ein expliziter BusinessKey vorgeschlagen, der nichts mit dem PrimaryKey zu tun hat und somit auch beim Erzeugen der Entität angelegt werden könnte.

Möchte man weiterhin den PrimaryKey benutzen, kann man alternativ auch die Id selbst generieren. Allerdings muss man Hibernate dann beibringen, anhand eines anderen Attributes zu entscheiden, ob die Entität neu ist und ein INSERT ausgeführt werden muss oder ob lediglich ein UPDATE nötig ist. Ein solches Attribut könnte das Version Attribut – welches beim OptimisticLocking verwendet wird – oder etwa ein CreatedAt Feld sein, dass beim Insert durch die Datenbank gefüllt wird.


Christian Schätzlein


Erfahrungen mit Hibernate-Criteria und pgAdmin(postgres)

Montag, 15. Februar 2010

Wärend der Arbeit an einer Web Applikation, die Hibernate und eine PostgresDB verwendet, sind mir in letzter Zeit folgende Kleinigkeiten aufgefallen:

Hinzufügen von “Nicht NULL” Spalten im pgAdmin III

Möchte man mit dem pgAdmin III Tool nachträglich eine Spalte zu einer Tabelle hinzufügen, so öffnet man den entsprechenden Dialog z.B. über das Menü “Bearbeiten -> Neues Objekt -> neue Spalte”. Wir versuchen nun eine Spalte “darfAngeln” vom Typ “boolean” anzulegen. Diese soll keine “null”-Werte enthalten und als Standardwert “false” benutzen. Das ganze funktioniert solange, wie noch keine Einträge in der Tabelle existieren. Enthält die Tabelle allerdings Einträge, erhalten wir folgende Fehlermeldung:

Ein Fehler ist aufgetreten:
FEHLER: Spalte >darfAngeln< enthält NULL-Werte

Die Fehlermeldung läßt sich umgehen, indem man das Anlegen der Spalte per simplem SQL erledigt. Ins Query Fenster des pgAdmin wechseln und folgenden Code ausführen:

ALTER TABLE adm_raum_art
ADD COLUMN darfAngeln boolean NOT NULL DEFAULT false;

Punkt-Notation bei Criteria

Leider mußte ich durch eine Exception feststellen, dass die Verwendung der Punkt-Notation (implizite Erzeugung von Joins) zum Zugriff auf verknüpfte Objekte nicht möglich ist (im Gegensatz zu HQL). Um also Restrictions auf verschiedenen, assozierten Objekten “oder” verknüpfen zu können, bedarf es “createAlias” für die Erzeugung von expliziten Joins. Kleines Beispiel:

final Criteria criteria = getSession().createCriteria(Schule.class);
criteria.createAlias("schulAnsprechpartner", "schulAP", Criteria.LEFT_JOIN)
.createAlias("schulAP.person", "pers", Criteria.LEFT_JOIN)
.createAlias("pers.benutzer", "benutzer", Criteria.LEFT_JOIN)
.add(Restrictions.or(Restrictions.eq("darfAngeln", true),
Restrictions.eq("benutzer.id", 0815)));

Im Beispiel wurde nun doch die Punkt-Notation verwendet, allerdings immer nur mit der “Rekursionstiefe 1″.
Vielleicht kommt irgendwann auch für Criteria die Möglichkeit eine beliebige Tiefe zu verwenden.


Sven Seiler