Mit ‘Hibernate’ getaggte Artikel

Hibernate 3.3 Update und c3p0 connection pool

Donnerstag, 15. Januar 2009

Die Umstellung einer Anwendung von Hibernate Version 3.2 auf Version 3.3 ist weniger trivial als zu vermuten wäre. Ein einfaches Austauschen des hibernate3.jar führt hier nicht zum Erfolg sondern endet in einer Reihe von Fehlermeldungen.
Ab Version 3.3 benötigt Hibernate zusätzliche Bibliotheken, da z.B. das Logging auf SLF4J umgestellt wurde. Hier eine kurze Übersicht der zusätzlich nötigen JAR Dateien und welche konkret auf unserem System zum Einsatz kommen:

  • commons-collection ab Version 3.1 : commons-collection-3.2.jar
  • slf4j-api : slf4j-api-1.5.5.jar
  • slf4j Adapter für log4j: slf4j-log4j12-1.5.5.jar

Nachdem die Anwendung nun fehlerfrei startete, dachte man. alles sei in Ordnung. Doch nach einigen Stunden meldete die Überwachung Fehler beim Zugriff auf die Datenbank:

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was100413 seconds ago.The last packet sent successfully to the server was 100413 seconds ago, which is longer than the server configured value of ‘wait_timeout’. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property ‘autoReconnect=true’ to avoid this problem.

Nach einiger Suche stellte sich heraus, dass Hibernate gar nicht, wie gewünscht, für das Connection Pooling C3P0 verwendetet sondern seinen eigenen, rudimentären Connection Pool. Die hibernate.cfg.xml enthielt aber alle nötigen hibernate.c3p0 Properties weshalb die Ursache nicht offensichtlich war. Des Rätsels Lösung liegt in der ab Version 3.3 nötigen expliziten Angabe der ConnectionProviderClass. Mit Hilfe der Property hibernate.connection.provider_class lässt sich diese angeben. Folgender Eintrag muss also in der hibernate.cfg.xml hinzugefügt werden um C3P0 als Connection Pool zu verwenden:

<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</property>

Nun verwendet Hibernate auch wieder C3P0 und die Anwendung läuft seitdem ohne Probleme.

Viel Spaß und Erfolg beim Ausprobieren und Anwenden.


Christof Aenderl


Volltextsuche in persistierten Objekten mit Hibernate Search

Montag, 01. Dezember 2008

Das Hibernate Projekt bietet mit Hibernate Search die Möglichkeit, die in einer Datenbank persistierten Objekte bequem zu durchsuchen. Als Suchmaschine für die Volltextsuche kommt dabei Apache Lucene zum Einsatz. Die Einrichtung ist in nur wenigen Schritten erledigt:

  1. Zusätzlich zur vorhandenen Hibernate Installation müssen die Jar-Dateien aus dem Hibernate Search Release in das eigene Projekt eingebunden werden.
  2. Die Hibernate Konfiguration muss um 2 Properties ergänzt werden:
    <session-factory>
    ...
    <property name="hibernate.search.default.directory_provider">
    org.hibernate.search.store.FSDirectoryProvider
    </property>
    <property name="hibernate.search.default.indexBase">
    luceneIndexPfad
    </property>
    ...
    <session-factory>

    Die erste Property legt dabei den Directory-Provider fest. In diesem Fall wird der FSDirectoryProvider gewählt, der den Index für die Volltextsuche im Filesystem ablegt. Die zweite Property gibt das Basisverzeichnis für diesen Index an. Einige in Lucene bereits enthaltene Alternativen zu dem hier verwendeten Directory-Provider sind unter [1] beschrieben.

  3. Als nächstes müssen die Hibernate-Mappings der zu durchsuchenden Entitäten um einige Annotations ergänzt werden:
    1. Die Entity-Klasse muss zunächst um die Annotation
      @Indexed

      ergänzt werden. Optional kann hier noch mit

      @Analyzer(impl = MyAnalyzer.class)

      eine eigene Analyzer-Klasse angegeben werden, da der voreingestellte StandardAnalyzer nur für die Suche in englischen Texten optimiert ist und sich mit einem an die jeweilige Sprache angepaßten Analyzer bessere Ergebnisse erzielen lassen. Weitere Analyzer (u.A. für deutschsprachige Texte) findet man unter [2].

    2. Die zu durchsuchenden Felder werden mit
      @Field(index=Index.TOKENIZED)

      annotiert, wobei Index.TOKENIZED bedeutet, dass der Inhalt des Feldes beim Anlegen des Index in Tokens zerlegt wird. Dies ist nötig, damit später innerhalb des Feldes nach einzelnen Worten gesucht werden kann.

    3. Für jede Entität muss genau ein Feld mit der Annotation
      @DocumentId

      als Id für Lucene markiert werden. Dieses Feld muss ein Objekt innerhalb des gesamten Index eindeutig identifizieren (auch gegenüber Objekten andere Entity-Klassen).

Als nächstes muß der aktuelle Bestand erstmalig indexiert werden. Für eine vorhandene Hibernate Session mySession und eine Liste myArticles von Artikel-Objekten könnte das wie folgt geschehen:

FullTextSession fullTextSession = Search.createFullTextSession(mySession);
Transaction tx = fullTextSession.beginTransaction();
for (Article article : myArticles) {
   fullTextSession.index(article);
}
tx.commit();

Nachdem dies abgeschlossen ist, können Volltextsuchen durchgeführt werden. Neue Objekte werden zukünftig beim Persistieren automatisch in den Index aufgenommen.

Eine Suchanfrage mit vorhandener Hibernate Session mySession in einem Bestand von Artikeln, die ein mit @Field annotiertes Feld textContent haben, könnte dann wie folgt aussehen:

String searchTerm = "Hibernate";
FullTextSession fullTextSession = Search.createFullTextSession( mySession );
Transaction tx = fullTextSession.beginTransaction();
QueryParser parser = new QueryParser("textContent", new StandardAnalyzer());
org.apache.lucene.search.Query query = parser.parse( searchTerm );
org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery( query, Article.class );
List result = hibQuery.list();
tx.commit();
session.close();

Im Anschluß befinden sich in result alle Artikel, deren textContent das Wort Hibernate enthält. Neben der exakten Suche gibt es noch viele weitere Suchmöglichkeiten (z.B. mit Wildcards und unscharfe Suchen). Mehr zu diesem Thema findet man unter [3].


Malte Wulf


Gleichheit von Entitäten in Hibernate

Donnerstag, 03. April 2008

Wann sind Entitäten gleich?

Dies ist eine Frage, die einen regelmäßig in Diskussionen mit anderen Entwicklern verfallen lässt. Es gibt hier zwei Theorien, wann eine Entität gleich ist:

  • Gleichheit der primären Schlüssel der Entitäten
  • Gleichheit aller Attribute der Entitäten

Das Verständnis von relationalen Datenbanken und Entitäten lässt aber den zweiten Ansatz sinnlos erscheinen: Dort ist eindeutig geregelt, das zwei Entitäten gleich sind, wenn ihre Primärschlüssel gleich sind. Alles andere würde die Auffindbarkeit von Einträgen in der Datenbank unmöglich machen.

Unter dieser Prämisse verfolgen wir also den ersten Ansatz!

Vergleich in Java

Der Vergleich in Java ist einfach zu bewerkstelligen und erfolgt durch das Überschreiben der equals()-Methode. Wichtig hierbei ist auch daran zu denken, die hashCode()-Methode zu überschreiben. Dies wird von der Java-Dokumentation gefordert und kann ansonsten leicht zu Problemen führen (sogenannter equals()-Contract).

Wichtig bei einer sauberen Implementierung ist auch der Vergleich von nicht gespeicherten mit gespeicherten Objekten, da nicht gespeicherte Objekte noch keine ID haben.
Dies ist im Zusammenhang mit Hibernate wichtig, da hier Relationen normalerweise mit java.util.Set abgebildet werden.
Würde man lediglich einen Vergleich über den Primärschlüssel machen, wäre es nicht möglich zwei neue Entitäten einer solchen Relation hinzuzufügen bevor man speichert, da das Set kein Objekt doppelt beinhalten kann und dies über die equals()-Methode prüft.

Leider gibt es zu diesem Thema auch seitens Hibernate keinen eindeutigen Tipp, der mich zufrieden stellen konnte.

Lösung

Aus den oben geführten Überlegungen und Recherchen, ergibt sich die folgende Implementierung der Methoden hashCode() und equals().

Implementierung der hashCode-Methode:

public int hashCode() {
   if (getId() != null) {
      return getId().hashCode();
   } else {
      return System.identityHashCode(this);
   }
}

Implementierung der equals-Methode:

public boolean equals(Object obj) {
   boolean isEqual = false;
   if (obj == null) {
      isEqual = false;
   } else if (this == obj) {
      isEqual = true;
   } else if (!this.getClass().equals(obj.getClass())) {
      isEqual = false;
   } else {
     VO castObj = (VO) obj;
 
      EqualsBuilder builder = new EqualsBuilder();
      builder.append(
         this.getId() != null 
            ? this.getId() 
            : "sid_"+ System.identityHashCode(this),
         castObj.getId() != null 
            ? castObj.getId() 
            : "sid_"+ System.identityHashCode(castObj));
      isEqual = builder.isEquals();
   }
 
   return isEqual;
}

(Für den Vergleich wird hier der EqualsBuilder aus dem Projekt commons lang von Apache verwendet.)

Das Prinzip hier ist denkbar einfach: Für den Fall, dass eine Entität keinen Primärschlüssel hat, wird einfach ein “künstlicher Primärschlüssel” mit dem ursprünglichen Hash des Objektes selbst gebildet.
Ein Blick in der Java-API auf System.identityHashCode() erleichtert das Verständnis.


Thomas Bader