Tutorials zum Thema Criteria API:
Dabei empfiehlt sich folgende Vorgangsweise:
Session
Objekt ein Criteria
Objekt erstellen, in dem die Klasse und im Falle eines Joins ein
Alias für die Datenbank-Tabelle angegeben wird.
createCriteria
realisieren. Dabei wird die Join-Eigenschaft und die zu Joinende Tabelle angegeben.
add
und der Klasse Restrictions
hinzufügen.
list
bzw. ein eindeutiges Resultat mit uniqueResult
abrufen.
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.
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);
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(); }
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(); }
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(); }
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(); }
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; }
Tutorials zum Thema Hibernate SQL Abfragen:
Hier empfiehlt sich folgende Vorgangsweise:
:<Camelcase Variablenname>
definieren.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.
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; }
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(); }
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(); } } }
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); }
public void insertNewUser(Session s, String firstName, String lastName) throws Exception { MyUser user = new MyUser(); user.setFirstName(fistName); user.setLastName(lastName); s.save(user); }
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)); } }
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)); } }
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:
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
.)
update
der Klasse Session
verwenden, um dessen Eigenschaften in die aktuelle Session zu laden.
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.