Verwendung der Criteria API

Tutorials zum Thema Criteria API:

Dabei empfiehlt sich folgende Vorgangsweise:

Datenstrukturen für die folgenden Beispiele

Tabelle MyUsers
(Zuordnung zu Java Klasse MyUser)

Id FirstName LastName Type
1 Max Mustermann 0
2 Juergen Wanderinformatiker 1
3 Hertha Huhn 0


Tabelle MyPermissions
(Zuordnung zu Java Klasse MyPermission)

Id Permission MyUser StartDate EndDate
1 READ 1 01.01.2000 NULL
2 READ 2 01.01.2000 NULL
3 READ 3 01.01.2000 NULL
4 WRITE 1 01.01.2000 NULL
5 WRITE 2 01.01.2000 NULL
6 ADMIN 2 01.01.2000 01.01.2030


Damit die Beispiele funktionieren, muss ein Foreign Key auf die Spalte MyUser in der Tabelle MyPermission gesetzt sein, der auf die Spalte Id in der Tabelle MyUser referenziert.

Beispiel 1 - Abfrage auf Gleichheit

In diesem Beispiel werden die Berechtigungen eines Benutzers abgefragt, in dem der Nachname des Benutzers spezifiziert wird.


        public List<MyPermission> getPermissions(Session s, String userLastName) {

          Criteria c = s.createCriteria(MyPermission.class, "permission");
          c.createAlias("permission.myUser", "user"); // inner join by default
          c.add(Restrictions.eq("user.lastName", userLastName));
          return c.list();
        }
      

In diesem Beispiel wird ein INNER JOIN gesetzt. Würde man einen LEFT JOIN benötigen müsste man folgenden Befehl verwenden:


        c.createAlias("permission.myUser", "user", CriteriaSpecification.LEFT_JOIN);
      

Beispiel 2 - Mehrere Bedingungen

In dem zweiten Beispiel wird das erste Beispiel erweitert, in dem nur aktuell gültige Permissions berücksichtigt werden. Dabei werden mit der Methode add weitere Bedingungen mit der UND-Logik hinzugefügt. Will man eine ODER-Logik einfügen, so kann das über die Restrictions.or Methode getan werden.

Konkret wird zusätzlich abgefragt, ob das Startdatum der Permission vor dem aktuellen Datum liegt und ob das Endedatum entweder leer ist oder in der Zukunft liegt.


        public List<MyPermission> getPermissions(Session s, String userLastName) {

          Criteria c = s.createCriteria(MyPermission.class, "permission");
          c.createAlias("permission.myUser", "user"); // inner join by default
          c.add(Restrictions.eq("user.lastName", userLastName));

          Calendar today = Calendar.getInstance();
          c.add(Restrictions.le("permission.startDate", today));
          c.add(Restrictions.or(Restrictions.isNull("permission.endDate"), Restrictions.ge("permission.endDate", today)));
          return c.list();
        }
      

Beispiel 3 - Abfrage auf Werteliste

In diesem Beispiel werden die Berechtigungen abgefragt, die einen bestimmten Namen aufweisen. (Hier muss kein Alias für die Tabelle MyPermission gesetzt werden, weil man lediglich auf diese Tabelle abfrägt.)


        public List<MyPermission> getPermissions(Session s, List<String> permissionNames) {
          if (permissionNames == null) return new ArrayList<MyPermission>();
          if (permissionNames.size() == 0) return new ArrayList<MyPermission>();

          Criteria c = s.createCriteria(MyPermission.class);
          c.add(Restrictions.in("permission", permissionNames));
          return c.list();
        }
      

Beispiel 4 - Abfrage auf Ähnlichkeit

In diesem Beispiel werden die Benutzer abgefragt, deren Nachname den angegeben Namen enthält.


        public List<MyUser> getUsers(Session s, String lastName) {
          if (lastName == null) return new ArrayList<MyUser>();
          if (lastName.length() == 0) return new ArrayList<MyUser>();
          lastName = "%" + lastName.trim() + "%";

          Criteria c = s.createCriteria(MyUser.class, "user");
          c.add(Restrictions.like("lastName", lastName));
          return c.list();
        }
      

Beispiel 5 - Abfrage auf Ähnlichkeit kombiniert mit ODER-Logik

In diesem Beispiel werden die Benutzer abgefragt, die einen bestimmten Namen entweder im Vornamen oder im Nachnamen aufweisen.


        public List<MyUser> getUsers(Session s, String name) {
          if (name == null) return new ArrayList<MyUser>();
          if (name.length() == 0) return new ArrayList<MyUser>();
          name = "%" + name.trim() + "%";

          Criteria c = s.createCriteria(MyUser.class, "user");
          c.add(Restrictions.or(Restrictions.like("lastName", name), Restrictions.like("firstName", name)));
          return c.list();
        }
      

Beispiel 6 - Ein eindeutiges Objekt abfragen

Hier wird nach einem eindeutigen MyUser Objekt anhand des Vor- und Nachnamens abgefragt. Es wird eine Exception geworfen, wenn es mehrere MyUser mit diesen Eigenschaften gibt bzw. wenn es keinen MyUser gibt, auf den dieser Vor- und Nachname zutrifft.


        public MyUser getUniqueUser(Session s, String firstName, String lastName) throws Exception {
          Criteria c = s.createCriteria(MyUser.class);
          c.add(Restrictions.eq("firstName", firstName));
          c.add(Restrictions.eq("lastName", lastName));
          MyUser uniqueUser = null;
          try {
            uniqueUser = (MyUser) c.uniqueResult();
          }
          catch (Exception e) {
            throw new Exception("More than one user found with this first name and last name!");
          }

          if (uniqueUser == null) throw new Exception("No user found with this first name and last name!");

          return uniqueUser;

        }
      

Verwendung von SQL Abfragen

Tutorials zum Thema Hibernate SQL Abfragen:

Hier empfiehlt sich folgende Vorgangsweise:

Beispiel 1 - Abfrage einer Liste von Werten

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 MyUsers 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 - Abfrage eines Werts

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 MyUsers 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 MyUser Objekte eines bestimmten Typs zurückzugeben.

        private static final String SQL_GET_USER_IDS = "select id from MyUsers where type = :userType ";

        public List<MyUser> 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(MyUser.class);
            criteria.add(Restrictions.in("id", validIds));
            return criteria.list();

          }
          else {
            return new ArrayList<MyUser>();
          }
        }
      

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<MyUser> getUserIdsOfType(Session s, Long userType) {

          Criteria criteria = s.createCriteria(MyUser.class);
          criteria.add(Restrictions.eq("type", userType));
          return criteria.list();


        }
      

Transaktionen

Das folgende Code Snippet zeigt eine Möglichkeit, wie man Transaktionen verweden kann.


          public void importData(Session s) {

            Transaction tx = null;

             try {
               tx = s.beginTransaction();
               // perform import routines
               tx.commit();
             } catch (Exception e) {
               if (tx != null) {
                 tx.rollback();
               }
             }

          }
        

Laden von Objekten

Ist die Id eines bestimmten Objekts bekannt, kann man es einfach laden.


        public MyUser getUser(Session s, long userId) throws Exception {
            return (MyUser) s.load(MyUser.class, userId);
        }
      

Erstellen eines neuen Objekts


        public void insertNewUser(Session s, String firstName, String lastName) throws Exception {
            MyUser user = new MyUser();
            user.setFirstName(fistName);
            user.setLastName(lastName);
            s.save(user);
        }
      

Löschen von Objekten


        public void removeUsers(Session s, List<Long> ids) throws Exception {

          Criteria c = s.createCriteria(MyUser.class);
          c.add(Restrictions.in("id", ids));
          List<MyUser> users = c.list();

          int size = users.size();
          for (int i = 0; i < size; i++) {
            s.delete(users.get(i));
          }

        }
      

Update von Objekten

In diesem Beispiel werden bestehende MyUser Objekte, die vom Programm verändert wurden, bzw. neue Objekte in der Datenbank gespeichert. Es wird dabei festgestellt, ob das Objekt schon in der Datenbank vorliegt, dann wird ein Update ausgeführt. Ansonsten wird das Objekt angelegt.


        public void saveUsers(Session s, List<MyUser> users) throws Exception {

          int size = users.size();
          for (int i = 0; i < size; i++) {
            s.saveOrUpdate(users.get(i));
          }

        }
      

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 MyUser hat, die eine Liste von MyPermission Objekte enthält, so werden beim Laden eines MyUser Objektes nicht alle Eigenschaften der MyPermission Objekte mitgeladen sondern nur deren Id. Wird dann die Session geschlossen und man versucht danach auf ein Attribut eines MyPermission 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:

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.