Verwendung einer HAVING-Bedingung bei Hibernate Criterias

Eingetragen bei: 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.

Android – Kommunikation mit der Außenwelt

Eingetragen bei: Android | 0

Nachdem ich in einem vorangegangenen Artikel die Verwendung einer SQLite Datenbank in einer Android-Anwendung beschrieben habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/17/verwendung-einer-sqlite-datenbank-in-einer-android-app/), möchte ich mich in diesem Artikel der Kommunikation mit einem REST-Service widmen. Hierfür kann die im Android-SDK integrierte Apache HttpClient Library verwendet werden. Als REST-Schnittstelle soll das Beispiel aus meinem letzten Artikel (siehe http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3/) verwendet werden. Hier wurde exemplarisch gezeigt wie man auf der REST-Ressource MyBean die Operationen GET, POST, PUT und DELETE ausführt. Neben XML- wurden auch JSON-Inhalte ausgetauscht. Letzteres Datenformat wird im folgenden Beispiel benötigt. Hier nun die Java-Repräsentation der MyBean-Ressource:

public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Im ersten Schritt soll die Android-Anwendung per GET-Request alle MyBean-Einträge erfragen und den erhaltenen JSON-Inhalt in MyBean-Objekte transformieren. Anschließend wird die Funktionalität sukzessive um die REST-Operationen POST, PUT und DELETE erweitert, wobei der POST- und PUT-Request entsprechenden JSON-Inhalt mitliefern muss.

Bevor es an die REST-Kommunikation geht, muss die Android-Anwendung die Erlaubnis zum Zugriff aufs Internet und damit folgenden Eintrag im Android-Manifest erhalten:

<uses-permission android:name=“android.permission.INTERNET“ />

Anschließend wird eine Hilfsklasse erstellt, welche die Verbindung zum REST-Service aufbaut, den erhaltenden InputStream in einen String umwandelt und für alle Arten von REST-Operationen genutzt werden kann:

public String connect(HttpRequestBase httpRequest) {
HttpClient httpclient = new DefaultHttpClient();
httpRequest.addHeader(„Accept“, „application/json“);
try {
HttpResponse response = httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
String result = convertStreamToString(inputStream);
inputStream.close();
return result;
}
} catch (Exception e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
return „“;
}

Die übergebene HttpRequestBase abstrahiert die jeweilige Http-Operation, die bereits die URI der Rest-Ressource und gegebenenfalls weitere Inhalte enthält. Die Kommunikation sieht bei allen Operationen gleich aus. Zunächst wird ein HttpClient benötigt – für dieses Beispiel reicht der Standard-HttpClient aus. Da der Http-Response vom Server JSON enthalten soll, wird die Header-Information Accept: application/json hinzugefügt. Sofern der Server außer dem Status – response.getStatusLine() – mit weiteren Daten antwortet, befinden sich diese in der HttpEntity in Form eines InputStreams. Da auf mobilen Endgeräten nur begrenzt Speicher vorhanden ist, können nicht beliebig Libraries verwendet werden. So kommt man nicht Drumherum, den InputStream selbstständig in einen String zu transformieren:

private String convertStreamToString(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder(50);
String line = null;
try {
while ((line = reader.readLine()) != null) {
stringBuilder.append(line + „n“);
}
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
} finally {
try {
inputStream.close();
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}
return stringBuilder.toString();
}

Der InputStreamReader dient dem InputStream als wrapper, der wiederrum von einem BufferedReader gewrappt wird. Letzterer kann zeilenweise ausgelesen und der Inhalt einem StringBuilder hinzugefügt werden. Sofern die Antwort des Servers voraussichtlich mehr als 16 Zeichen beträgt, empfiehlt es sich den StringBuilder mit einem höheren Wert zu initialisieren. Andernfalls muss der interne Buffer des StringBuilders sukzessive erhöht werden.

Nun soll die GET-Operation erstellt und die oben gezeigte connect-Methode aufgerufen werden:

HttpRequestBase httpGet = new HttpGet(REST_RESSOURCE_URI);
String result = connect(httpGet);

Anschließend enthält das result einen JSON-String folgender Art:

{„object“:{„myview“:[{„id“:“1″,“message“:“Some text“},{„id“:“2″,“message“:“Some more text“}]}}

Dieser String soll  nun geparst und der JSON-Inhalt in entsprechende MyBean-Objekte transformiert werden. Hierfür wird der komplette String zunächst einem JSONTokener übergeben. In unserem Fall liefert dessen nextValue-Methode ein JSONObject zurück, grundsätzlich wären aber auch ein JSONArray, String, NULL oder weitere Java-Typen möglich. Das so erhaltene JSONObject enthält wiederrum ein JSONObject mit dem Schlüsselwert „object“. Erst jetzt kommt man an das JSONArray „myview“, welches die gesammelten MyBean-Einträge enthält. Jeder dieser Einträge ist in einem JSONObject hinterlegt, welches die MyBean-Properties id und message enthält. Diese Werte können nun einem neu zu erzeugenden MyBean-Objekt übergeben werden:

.JSONTokener token = new JSONTokener(jsonContent);
while (token.more()) {
Object object = token.nextValue();
if (object instanceof JSONObject) {
JSONObject jsonRoot = (JSONObject) object;
try {
JSONObject jsonObject = jsonRoot.getJSONObject(„object“);
if (jsonObject != null) {
JSONArray jsonBeans = jsonObject.getJSONArray(„myview“);
if (jsonBeans != null) {
for (int i = 0; i < jsonBeans.length(); i++) {
JSONObject jsonBean = jsonBeans.getJSONObject(i);
int id = jsonBean.getInt(„id“);
String subject = jsonBean.getString(„message“);
MyBean myBean = new MyBean(id, message);
// adding myBean to a myBeans list
}
}
}
} catch (JSONException e) {
// do nothing, wrong JSON content
}
}

Soll nun eine POST- oder PUT-Operation getätigt werden, muss der umgekehrte Weg gegangen werden. Es wird nicht empfangener JSON-Inhalt deserialisiert, sondern ein MyBean-Objekt in JSON-Inhalt umgewandelt und an den Server gesendet. Hierfür wird eine Implementierung des HttpEntity-Interfaces benötigt:

public class MyHttpEntity implements HttpEntity {

private JSONObject jsonObject;

public MyHttpEntity(MyBean myBean) {
try {
jsonObject = new JSONObject();
jsonObject.put(„id“, myBean.getId());
jsonObject.put(„message“, myBean.getMessage());
} catch (JSONException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}

@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new ByteArrayInputStream(jsonObject.toString().getBytes());
}

@Override
public long getContentLength() {
return jsonObject.toString().length();
}

@Override
public Header getContentType() {
return new BasicHeader(„Content-Type“, „application/json“);
}

@Override
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(jsonObject.toString().getBytes());
}

// … further methods, which are not required
//    for this example …
}

Im Konstruktor wird das zu transformierende MyBean-Objekt übergeben und dessen Eigenschaften in einem zu erstellenden JSONObject hinterlegt. Anschließend sind folgende Methoden zu erstellen:

  • getContent – Liefert den JSON-Inhalt als InputStream
  • getContentLength – Enthält die Länge des JSON-Inhaltes
  • getContentType – Fügt die Header Information „Content-Type: application/json“ hinzu. Hierfür wird ein BasicHeader verwendet.
  • writeTo – Der JSON-Inhalt wird in einen OutputStream gespeichert.

Das Interface HttpEntity enthält noch weitere zu implementierende Methoden, deren Funktionalität spielt für dieses Beispiel aber keine Rolle. Nachdem der Serialisierung einer MyBean nichts mehr im Wege steht, kann jetzt die eigentliche POST-Operation ausgeführt werden:

HttpPost post = new HttpPost(REST_RESSOURCE_URI);
post.setEntity(new MyHttpEntity(myBean));
String result = connect(post);

Äquivalent hierzu ist der PUT-Request, nur dass dieser auf einer bestehenden MyBean getätigt wird. Daher wird in der URI noch eine id zur Identifikation der MyBean übergeben:

HttpPut put = new HttpPut(REST_RESSOURCE_URI + „/myBean/“ + myBean.getId());
put.setEntity(new MyHttpEntity(myBean));
connect(put);

Einfach gestaltet sich die DELETE-Operation, da der Request außer der zu löschenden Ressource keine weiteren Informationen benötigt. Die id der zum Löschen auserwählten MyBean wird im Pfad übergeben:

HttpDelete delete = new HttpDelete(REST_RESSOURCE_URL + „/myBean/“ + id);
connect(delete);

Ist die Hilfsklasse für die Kommunikation erst einmal geschrieben, sind die einzelnen REST-Operationen schnell umgesetzt. Lediglich bei der Serialisierung bzw. Deserialisierung von und nach JSON handelt es sich um lästige Fleißarbeit. Zumal hier jeder Fehler dank des recht langsamen Android-Emulators zusätzlich Zeit kostet.