Hibernate 4 Migration

Hibernate 4 wurde schon vor einiger Zeit veröffentlicht und beinhaltet neben Unterstützung der Mandantenfähigkeit ein grundlegendes Refactoring. Im Folgenden sollen die für eine erfolgreiche Migration notwendigen Schritte zusammengefasst werden. Insbesondere Frameworks, die Hibernate kapseln, sind in dem von Hibernate bereitgestellten
Migrationsleitfaden kaum betrachtet worden.

1. Hibernate-API-Changes

Hibernate Type Changes

Am einfachsten sind sicherlich noch die Typ-Infomationen zu migrieren. So ist statt org.hibernate.Hibernate.TEXT nun org.hibernate.type.TextType.INSTANCE zu verwenden. Das trifft analog auch auf alle anderen Datentypen zu.

Session Changes

Der direkte Zugriff auf eine JDBC-Connection über die Session ist nicht mehr möglich. Aktionen müssen nun in einer Art Command-Klasse gekapselt werden, welche das Work-Interface implementieren und direkt eine Connection reingereicht bekommen:

Session session = SessionFactoryUtils.getSession(sessionFactory, true).connection();  
Connection connection = session.connection();  
…  

wird zu

Session session = sessionFactory.getCurrentSession();  
session.doWork(new Work(){  
  public void execute(Connection connection) {  
    …  
  }  
});

User Types

Mit Hibernate 4 hat sich die UserType-Schnittstelle geändert.

// Hibernate 3  
Object nullSafeGet(ResultSet resultSet, String[] columnNames, Object owner) throws HibernateException, SQLException;  
// Hibernate 4  
Object nullSafeGet(ResultSet resultSet, String[] columnNames, SessionImplementor sessionImplementor, Object owner) throws HibernateException, SQLException;  

und

// Hibernate 3  
void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException, SQLException;  
// Hibernate 4  
void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor sessionImplementor) throws HibernateException, SQLException;  

Wie man leicht erkennt, ist der SessionImplementor hinzugekommen. Selbst geschriebene UserTypes kompilieren nun nicht mehr und ihre Signaturen müssen entsprechend erweitert werden.

2. Spring Support

Hibernate 4 Support ist ab Spring 3.1 vorhanden. In den meisten Fällen müssen nur die packages angepasst werden – statt .hibernate3. kann man einfach .hibernate4. verwenden.

SessionFactoryBean

Bisher galt, dass für Annotations-basierte Mappings AnnotationSessionFactoryBean verwendet wurde und für Xml-basierte LocalSessionFactoryBean. Mit der Spring-Hibernate4-Unterstützung gibt es nur noch eine SessionFactoryBean – die LocalSessionFactoryBean.

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  
  …  
</bean>  

wird zu

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">  
  …  
</bean>  

HibernateTemplate

Generell ist der Support für Hibernate 4 schmaler geworden, wie ein Blick auf das neue hibernate4-Package zeigt. So wird das HibernateTemplate nicht mehr unterstützt. Wesentliche Treiber für die Nutzung des HibernateTemplates sind die automatische Verwaltung der Hibernate-Sessions und die Übersetzung von (Checked) Exceptions in eine einheitliche Exception-Hierarchie.

Seitens Spring wird damit argumentiert, dass Hibernate kontextuelle Sessions untersützt und das Template somit obsolet geworden ist. Durch die Verwendung von SessionFactory.getCurrentSession() muss man sich nicht mehr um das Schließen einer Session kümmern, da dies mit dem Ende der Transaktion ohnehin geschieht (ein flush übrigens auch). Damit ist man allerdings an den Einsatz von Transaktionen gebunden.

Die Übersetzung von Exceptions erhält man über die Verwendung der @Repository-Annotation und der Deklaration eines Post-Processors.

@Repository  
public class LabelRepositoryImpl extends HibernateRepository implements LabelRepository {  
  …  
}  
…  
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>  
…  

Dadurch ist eine Übersetzung von Exceptions in die DataAccessException-Hierarchie auch ohne HibernateTemplate möglich.

Siehe dazu auch:
So should you still use Spring’s HibernateTemplate and/or JpaTemplate??,
Persistenz mit Spring und Hibernate

SessionFactoryUtils

public static Session getSession(SessionFactory sessionFactory, boolean allowCreate);  

wurde abgeschafft. Wenn an den aktuellen Thread keine Session gebunden ist, konnte man mit dem boolean-flag steuern, ob dann eine neue nicht-transaktionale Session erzeugt werden soll. Mit Hibernate 4 muss der Aufrufer entweder direkt eine neue Session erzeugen: SessionFactoryUtils.openSession(SessionFactory sessionFactory) was eine Spring-Abstraktion über die Hibernate-API darstellt und equivalent zu SessionFactory.openSession() ist – oder sich mit
SessionFactory.getCurrentSession() die aktuelle Session geben lassen.

Welche Session die aktuelle ist, kann man über die Property hibernate.current_session_context_class steuern. Siehe dazu auch: Current Session in Hibernate

Verwendet man Spring, wird per default der SpringSessionContext verwendet, so dass der SessionContext von Spring verwaltet wird und man sich um diese Hibernate-Property nicht zu kümmern braucht.

3. Joda-Time

Da sich mit Hibernate 4 die UserType-Schnittstelle geändert hat, sind auch neue UserTypes für Joda-Time erforderlich:

<dependency>  
  <groupId>joda-time</groupId>  
  <artifactId>joda-time</artifactId>  
  <version>2.0</version>  
</dependency>  
<dependency>  
  <groupId>org.jadira.usertype</groupId>  
  <artifactId>usertype.core</artifactId>  
  <version>3.0.0.CR1</version>  
</dependency>  

Leider sind die UserTypes von Jodatime erst als Release Candidate verfügbar.

4. Probleme

Logging

Nachdem alle Unit-, Integrations- und Systemtests mit Hibernate laufen, fiel bei der nächsten Anpassung an der Persistenz auf, dass keine Hibernate-Log-Ausgaben mehr ausgegeben wurden.

Zuvor verwendete Hibernate slf4j nun jboss-logging, welches als Logging-Fassade fungiert. Darunter wird standardmäßig log4j benutzt. Dadurch konnte festgestellt werden, dass unsere log4j-slf4j-Bridge noch nie richtig funktioniert hat, da wir log4j noch über transitive Abhängigkeiten im Klassenpfad liegen hatten. Nachdem log4j per Exclusion rausgeworfen wurde, funktionierte es auch mit dem JBoss-Logger. Siehe dazu auch jboss-logging und slf4j.

Transaktionen

Wie bereits beschrieben, kann man über die SessionFactoryUtils nunmehr keine Session ohne Transaktion erzeugen.
Das folgende Code-Beispiel macht nun keinen Sinn mehr:

 /**  
 * Dient zur Isolierung der Tests. Jede Testmethode kann sich  
 * darauf verlassen eine saubere Persistenz-Umgebung vorzufinden.  
 */  
 @BeforeTransaction  
 public void clearHibernateSessionBeforeTransaction() {  
   getSession().clear();  
 }  

Denn wenn getSession() die aktuelle Session aus dem SessionContext verwendet, ist vor dem Öffnen der Transaktion noch keine Session vorhanden und der folgende Fehler wird erscheinen:

 org.hibernate.HibernateException: No Session found for current thread  
 at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)  
 at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1039)  

Ein „Säubern“ des Persistenz-Kontextes ist nun nicht mehr notwendig.

Dependency-Management

Die Umstellung auf JBoss-Logging beeinträchtigt leider auch das Dependency-Management. Das zeigt der folgende Auszug des Maven-Dependency-Trees.

 [INFO] +- org.hibernate:hibernate-core:jar:4.1.2.Final:compile  
 [INFO] | +- antlr:antlr:jar:2.7.7:compile  
 [INFO] | +- org.jboss.logging:jboss-logging:jar:3.1.0.GA:compile  
 [INFO] | +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:jar:1.0.0.Final:compile  
 [INFO] | +- dom4j:dom4j:jar:1.6.1:compile  
 [INFO] | +- org.hibernate.javax.persistence:hibernate-jpa-2.0-api:jar:1.0.1.Final:compile  
 [INFO] | +- org.javassist:javassist:jar:3.15.0-GA:compile  
 [INFO] | – org.hibernate.common:hibernate-commons-annotations:jar:4.0.1.Final:compile  
 [INFO] | – (org.jboss.logging:jboss-logging:jar:3.1.0.CR2:compile – omitted for conflict with 3.1.0.GA)  

Denn jboss-logging kommt auch noch in Version 3.1.0.CR2 transitiv über die Hibernate-Annotations in den Klassenpfad. Da wir das Maven-Enforcer-Plugin einsetzen, welches mehrfache Versionen eines Jars verbietet, musste das Dependency-Management angepasst werden und eine Jar-Version ausgeschlossen werden.

5. Zusammenfassung

Zusammenfassend muss man leider sagen, dass die Migration erheblich mehr erfordert, als nur die Versionsnummer von Hibernate auszutauschen. Neben einigen leicht nachzuziehenden Änderungen an der Hibernate-API, hat sich an der Spring-Unterstützung einiges geändert. Unter Umständen muss hier doch einiges mehr am Code geändert werden, da das HibernateTemplate für Hibernate 4 nicht mehr existiert und sich auch die Erzeugung von Sessions geändert hat.