Verwendung einer HAVING-Bedingung bei Hibernate Criterias

Veröffentlicht in: Hibernate, Java | 0

Ist bei HQL – Hibernate Query Language – die Benutzung von Having-Bedingungen
erlaubt, stehen diese bei Criteria-Queries leider nicht zur Verfügung.
Hier kann man sich aber mit einer Unter-Abfrage behelfen.

Kommen wir zu einem konkreten Beispiel, welches im Bereich Benutzerberechtigung
angeordnet ist. Es werden die Datenbank-Tabellen User und UserRole benötigt.
Jedem Benutzer kann hierbei genau eine Rolle, z.B. Administrator, Gast oder Ähnliches, zugewiesen werden.

Umgekehrt steht jede definierte Rolle keinem, einem oder mehreren Benutzern zur Verfügung.
Aus Sicht des Benutzers handelt es sich also um eine ManyToOne-Verbindung.

Hier nun die User-Entity:

@Entity
public class User {
@Id
private Long id
private String name;
@ManyToOne(optional = true, fetch = FetchType.EAGER)
@JoinColumn(name = „roleId“)
private UserRole role;

// … getter and setter …
}

Die Definition des Mappings besagt, dass die Tabelle User eine Spalte roleId enthält, welche die Id einer UserRole enthalten kann. Hiermit wird dem Benutzer also die jeweilige UserRole zugewiesen.
Aus Java Sicht hat man Zugriff auf die komplette UserRole-Entity, wobei der FetchMode EAGER dafür sorgt, dass die UserRole – sofern vorhanden – beim Laden des Users mit geladen wird.

Die UserRole sieht recht unspektakulär aus:

@Entity
public class UserRole {
@Id
private Long id;
private String role;

// … getter and setter
}

Nun möchte man alle UserRoles erhalten, die mehr als einem Benutzer zugewiesen sind.
Das SQL-Statement hierzu sähe dann so aus:

SELECT roleId FROM User GROUP BY roleId Having COUNT(*) > 1;

Da Criteria-Queries keine HAVING-Bedingungen unterstützten, muss die Query umformuliert werden:

SELECT * FROM User outer WHERE 1 <
(SELECT COUNT(*) FROM User inner WHERE inner.roleId = outer.roleId);

Das SQL-Statement wird also in zwei Abfragen aufgeteilt. Der innere Teil ist dafür zuständig, die Anzahl an Benutzern mit der entsprechenden Rolle zu liefern. In der äußeren Query wird überprüft, ob die Anzahl größer als Eins ist, womit das Ergebnis der beiden Abfragen dasselbe ist.

Diese abgewandelte Abfrage kann nun auch mit Criterias abgebildet werden:

DetachedCriteria innerQuery = DetachedCriteria.forClass(User.class, „inner“);
innerQuery.setProjection(Projections.rowCount());
innerQuery.add(Restrictions.eqProperty(„role.id“, „outer.role.id“));

DetachedCriteria criteria = DetachedCriteria.forClass(User.class, „outer“);
criteria.add(Subqueries.lt(1L, innerQuery));

List<User> list = hibernateTemplate.findByCriteria(criteria);

Zunächst wird die innere Abfrage als DetachedCriteria erstellt, wobei ein alias „inner“ verwendet wird, um die innere und äußere Query unterscheiden zu können. Anschließend wird eine Projektion auf die Anzahl der Spalten definiert, da in der inneren Abfrage nur die Anzahl der Rollen entscheidend ist. Mittels einer Restrictions wird auf Gleichheit der roleId von äußerer und innerer Query geprüft. Der Alias „inner“ kann hierbei weggelassen werden, da die Restriction schließlich auf der inneren Abfrage ausgeführt wird. Um die roleId der äußeren Query zu verwenden, muss explizit das Alias „outer“ angegeben werden.

Wie im SQL-Statement auch, überprüft nun die äußere Abfrage, ob Eins kleiner ist, als die in der inneren Abfrage erhaltene Anzahl. Trifft diese Bedingung zu, so erscheint der entsprechende User-Eintrag in Ergebnis.

Auch wenn Hibernate Criterias die HAVING-Bedingung nicht direkt unterstützen, können entsprechende Queries durch die Verwendung von Unter-Abfragen so umformuliert werden, dass nicht zwangsläufig HQL eingesetzt werden muss. Auch der Übersichtlichkeit tut die objektorientierte Schreibweise keinen Abbruch.