Hibernate

Inhalt

Diese Seite beschreibt einige Dinge im Zusammenhang mit der Verwendung des Hibernate Frameworks

Verwendung der Criteria API

Tutorials zum Thema Criteria API: Dabei empfiehlt sich folgende Vorgangsweise:
  • Aus dem Session Objekt ein Criteria Objekt erstellen, in dem die Klasse und der Mapping-Name der Datenbank-Tabelle angegeben wird.
  • Joins mit der Methode createCriteria realisieren. Dabei wird die Join-Eigenschaft und die zu Joinende Tabelle angegeben.
  • Where Bedingungen mit der Methode add und der Klasse Restrictions hinzufügen.
  • Eine Liste von Objekten mit der Methode list abrufen.
Datenstrukturen für die folgenden Beispiele

Tabelle Users (Zuordnung zu Java Klasse User)
Id FirstName LastName Type
1 Max Mustermann 0
2 Juergen Wanderinformatiker 1
3 Hertha Huhn 0

Tabelle Permissions (Zuordnung zu Java Klasse Permission)
Id Permission User
1 READ 1
2 READ 2
3 READ 3
4 WRITE 1
5 WRITE 2
6 ADMIN 2

Beispiel 1 - Abfrage auf Gleichheit

In diesem Beispiel werden die Berechtigungen eines Benutzers abgefragt, in dem der Nachname des Benutzers spezifiziert wird.
          
          public List<Permission> getPermissions(Session s, String userLastName) {
            
            Criteria c = session.createCriteria(Permission.class, "permission");
            c.createAlias("permission.user", "user"); // inner join by default
            c.add(Restrictions.eq("user.lastName", userLastName));
            return c.list();
          }
        

Beispiel 2 - Abfrage auf Werteliste

In diesem Beispiel werden die Berechtigungen abgefragt, die einen bestimmten Namen aufweisen.
          
          public List<Permission> getPermissions(Session s, List<String> permissionNames) {
            if (permissionNames == null) return new ArrayList<Permission>();
            if (permissionNames.size() == 0) return new ArrayList<Permission>();
            
            Criteria c = session.createCriteria(Permission.class, "permission");
            c.add(Restrictions.in("permission", permissionNames));
            return c.list();
          }
        

Beispiel 3 - Abfrage auf Ähnlichkeit

In diesem Beispiel werden die Benutzer abgefragt, deren Nachname den angegeben Namen enthält.
          
          public List<User> getUsers(Session s, String lastName) {
            if (lastName == null) return new ArrayList<User>();
            if (lastName.length() == 0) return new ArrayList<User>();
            lastName = "%" + lastName.trim() + "%";
            
            Criteria c = session.createCriteria(User.class, "user");
            c.add(Restrictions.like("lastName", lastName));
            return c.list();
          }
        

Beispiel 4 - ODER

In diesem Beispiel werden die Benutzer abgefragt, die einen bestimmten Namen entweder im Vornamen oder im Nachnamen aufweisen.
          
          public List<User> getUsers(Session s, String name) {
            if (name == null) return new ArrayList<User>();
            if (name.length() == 0) return new ArrayList<User>();
            name = "%" + name.trim() + "%";
            
            Criteria c = session.createCriteria(User.class, "user");
            c.add(Restrictions.or(Restrictions.like("lastName", name), Restrictions.like("firstName", name)));
            return c.list();
          }
        

Verwendung von SQL Abfragen

Tutorials zum Thema Hibernate SQL Abfragen: Hier empfiehlt sich folgende Vorgangsweise:
  • Die SQL Abfrage als String Konstante definieren.
  • Parameter in der Abfrage mit der Notatation :<Camelcase Variablenname> definieren.
  • Die Hibernate API verwenden, um die Abfrage auszuführen und die Ergebnisse in Java Objekten zu erhalten.
Beispiel 1

Im ersten Beispiel werden die Ids von Users zurückgegeben, die von einem bestimmten Typ sind.
          private static final String SQL_GET_USER_IDS = "select id from users where type = :userType ";
          
          public List<Long> getUserIdsOfType(Session s, Long userType) {
            SQLQuery query = s.createSQLQuery(SQL_GET_USER_IDS);
            query.setLong("userType", userType);
            query.addScalar("id", StandardBasicTypes.LONG);
            return query.list();
          }
        
In diesem Fall wird eine Liste von Long Elementen zurückgeliefert.

In solchen Fällen hatte ich auch des öfteren die Exception

java.lang.ClassCastException:
java.math.BigDecimal cannot be cast to java.lang.Long


Diese tritt auf, wenn man bei der SQLQuery vergisst addScalar zu setzen, damit man weiß, welcher Datentyp zurückgegeben werden soll.

Beispiel 2

Im nächsten Fall wird abgefragt, ob es Benutzer mit einem bestimmten Nachnamen gibt.

          private static final String SQL_USERS_EXIST = "select count(id) from users where lastName = :lastName ";
          
          public boolean usersExist(Session s, String lastName) {
            SQLQuery query = s.createSQLQuery(SQL_USERS_EXIST);
            query.setString("lastName", lastName);
            query.addScalar("id", StandardBasicTypes.INTEGER);
            return ((Integer) query.uniqueResult()).intValue() > 0;
          }
        

Mischen von Criteria API und SQL Abfragen

In folgendem Beispiel wird die Anwendung der Criteria API und der Verwendung von SQL Abfragen gemischt, um die User Objekte eines bestimmten Typs zurückzugeben.
          private static final String SQL_GET_USER_IDS = "select id from users where type = :userType ";
          
          public List<User> getUserIdsOfType(Session s, Long userType) {
            SQLQuery query = s.createSQLQuery(SQL_GET_USER_IDS);
            query.setLong("userType", userType);
            query.addScalar("id", StandardBasicTypes.LONG);
            List<Long> validIds query.list();
            
            if (validAddressIds.size() > 0) {
              Criteria criteria = s.createCriteria(User.class);
              criteria.add(Restrictions.in("id", validIds));
              return criteria.list();
              
            }
            else {
              return new ArrayList<User>();
            }
          }
        
Diese Mischung ist vor allem dann hilfreich, wenn zum Auffinden der Ids komplexe SQL Abfragen nötig sind. In diesem einfachen Fall wäre man mit der Criteria API natürlich auch schnell ans Ziel gekommen.
          
          public List<User> getUserIdsOfType(Session s, Long userType) {
            
            Criteria criteria = s.createCriteria(User.class);
            criteria.add(Restrictions.eq("type", userType));
            return criteria.list();
            
          
          }
        

LazyInitializationException - could not initialize proxy - No Session

Ich bezeichne diesen Fehler meist als "No session Error". Ausgeschrieben heißt er aber org.hibernate.LazyInitializationException: could not initialize proxy - no Session.

Zu diesem Thema habe ich folgende Links recht hilfreich gefunden: Im folgenden habe ich das wichtigste der Artikel für mich zusammengefasst.

Der Fehler tritt auf, weil Hibernate meist auf "Lazy Initialization" eingestellt ist. Das bedeutet, dass Referenzen zu anderen Objekten nicht vollständig also "lazy" geladen werden. Beispielsweise, wenn man eine Klasse User hat, die eine Liste von Permission Objekte enthält, so werden beim Laden eines User Objektes nicht alle Eigenschaften der Permission Objekte mitgeladen sondern nur deren Id. Wird dann die Session geschlossen und man versucht danach auf ein Attribut eines Permission Objekts zuzugreifen, so wird der Fehler geworfen. (Hibernate müsste nämlich auf die Datenbank zugreifen, um das Permmission Objekt zu initialisieren, allerdings wurde die Session und damit die Datenbank-Verbindung schon geschlossen.)

Jetzt könnte man natürlich die "Lazy initialization" ausschalten, was aber bei vielen Referenzen bzw. langen Listen von Objekten erhebliche Performance Nachteile und auch eine hohe Speicher-Belegung bringen kann. Das heißt man würde ein eigentliches Feature von Hibernate deaktivieren. Viel besser ist es, den Sourcecode so zu gestalten, dass dieser Fehler nicht mehr auftritt.

Für meine Aufgabenstellungen hat dies bisher folgendes bedeutet:
  • Bevor der Speicher-Prozess abgeschlossen ist, sollten alle Objekte, die danach angesprochen werden, korrekt in Hibernate geladen sein: Dafür war es beispielsweise wichtig ein Objekt mit der get Methode zu laden, damit alle Attribute in Hibernate vorhanden waren. Ansonsten kann es sein, dass mit load nur ein Proxy erzeugt wird, was optimal ist, wenn nur die Id des Objekts benötigt wird. (Weiter unten beschreibe ich den Unterschied zwischen load und get.)
  • Wenn das Objekt an einer Stelle nach dem Schließen einer Session benötigt wird, so kann man die Methode update der Klasse Session verwenden, um dessen Eigenschaften in die aktuelle Session zu laden.
Unterschied zwischen den Methoden load und get

Der Hauptunterschied ist, dass die Methode get einen Datenbank-Zugriff durchführt, wenn ein Object nicht im Cache der Session ist. Dabei wird ein vollständig initialisiertes Objekt in die Session geladen. Damit stellt man sicher, dass man auch auf alle Eigenschaften des Objekts zugreifen kann, auch wenn die Datenbank-Verbindung bereits geschlossen wurde.

Die Methode load hingegen, retourniert nur einen Proxy, der nur die Id des Objekts enthält. Diese Methode ist daher effizienter und nicht so speicher-intensiv. Sie kann verwendet werden, um Beziehungen zwischen Objekten zu erstellen.