Hibernate 4.x mit Spring 3.x

Veröffentlicht in: Hibernate, Java, spring | 0

Der nachfolgende Artikel beschreibt die Verwendung des ORM – Object-Relational Mapping – Frameworks Hibernate 4 inklusive der Hibernate Integration von Spring 3, wobei letztere auch die Vorgängerversion Hibernate 3 unterstützt. Als Persistenz-Schicht wird die In-Memory Datenbank HSQLDB verwendet. Es entfällt also die Installation einer Datenbank, da für den Einsatz der HSQLDB lediglich die entsprechende Library eingebunden werden muss.

In diesem Beispiel sollen Benutzer- und Adressdaten persistiert werden, wobei einem Benutzer-Eintrag beliebig viele Adress-Informationen zugewiesen werden können. Es gibt also ein 1..n Mapping zwischen Benutzer und Adressen. Weiterhin wird ein DAO – Data Access Object – implementiert, welches die CRUD – Create/Read/Update/Delete – Datenbank Operationen für die Haupt-Entität (Benutzer) zur Verfügung stellt.

Im ersten Schritt wird ein Maven-Projekt erstellt, in dessen pom.xml die folgenden Dependencys ergänzt werden:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.7.Final</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.9</version>
</dependency>

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>

Zuerst werden die Entitäten erstellt, welche die Tabellen der relationalen Datenbank als Java-Klassen repräsentieren. Da der Benutzer Kenntnis über die zu ihm gehörenden Adressen benötigt, umgekehrt aber keine Verknüpfung notwendig ist, kennt die Adress-Entität lediglich sich selber. Ein guter Grund mit dieser zu beginnen:

@Entity
public class Address {

@Id
@GeneratedValue
private Long id;
private String street;
private String city;

// getter setter methods

}

Die Klasse Address wird mit der Annotation Entity versehen, um sie als solche zu kennzeichnen. Weiterhin erhält sie neben den benötigten Eigenschaften (Straße, Stadt, …) einen Identifier. Die entsprechende Annotation definiert diesen als Primär-Schlüssel, während GeneratedValue dafür sorgt, dass sein Wert automatisch von der Datenbank vergeben werden soll.

Nun kann auch die Klasse User mit Zugriff auf die Adress-Datensätze erzeugt werden:

@Entity
public class User {

@Id
@GeneratedValue
private Long id;
private String forname;
private String lastname;

@OneToMany
@Cascade(value=CascadeType.ALL)
private List<Address> addresses;

// getter setter methods

}

Da ein User eine beliebige Anzahl an Adressen erhalten kann, wird hier als Mapping die OneToMany-Annotation verwendet. Weiterhin wird der CascadeType ALL gewählt, der unter anderem die CascadeTypen SAVE_UPDATE und DELETE beinhaltet. Hiermit wird erreicht, dass beim Speichern des Users gleichzeitig seine Adress-Daten persistiert werden. Wäre der CascadeTyp SAVE_UPDATE nicht gewählt, müssten Adress- und Benutzerdaten separat gespeichert werden, wobei ein Benutzer nur auf Adressen verweisen darf, die bereits in der Datenbank existieren. Der CascadeType DELETE sorgt dafür, dass beim Löschen eines Benutzers auch seine Adressen aus der Datenbank entfernt werden.

Wie man an obigem Beispiel erkennt, gibt es Attribute die sich in den Entitäten wiederholen. In diesem Beispiel ist es nur der Identifier, der sowohl beim User als auch bei der Adresse vorkommt. Aber auch das Erstellungs- und letztes Änderungsdatum einer Entität wären Kandidaten, die zu Code-Wiederholung führen könnten. Doppelte Attribute können in eine gemeinsame Oberklasse ausgelagert werden, sofern die Oberklasse mit MappedSuperclass annotiert wird:

@MappedSuperclass
public abstract class AbstractEntity {

@Id
@GeneratedValue
private Long id;

// getter setter methods
}

Nachdem die Entitäten erstellt wurden, soll nun der UserDAO erzeugt werden, der sukzessive um die CRUD-Operationen erweitert wird. Hierfür benötigt er lediglich die SessionFactory. Wer bislang das HibernateTemplate verwendete, wird feststellen, dass dies unter Hibernate 4 nicht mehr unterstützt wird. Das ist nicht weiter verwunderlich, da dessen Verwendung bereits seit Hibernate 3.0.1 obsolet ist. Stattdessen kann bei gültigem Transaktions-Management über die SessionFactory auf die aktuelle Session zugegriffen und auf dieser Datenbank-Operationen ausgeführt werden. Auf die Konfiguration des Transaktions-Management wird später noch eingegangen. Aber zurück zum UserDAO, der zunächst nur aus folgendem Rumpf besteht:

public class UserDAO {

private SessionFactory sessionFactory;

// methods for crud operations
// and a setter method for the sessionFactory
}

Wenden wir uns nun der Spring Konfiguration zu, die in der Datei src/main/resources/applicationContext.xml erstellt wird. Zunächst muss die Verbindung zur Datenbank in einer Datasource konfiguriert werden. Es werden Details zum Datenbanktreiber, URL zur Datenbank, Benutzername und Passwort benötigt.

<bean id=“dataSource“ class=“org.apache.commons.dbcp.BasicDataSource“ destroy-method=“close“>
<property name=“driverClassName“ value=“org.hsqldb.jdbcDriver“/>
<property name=“url“ value=“jdbc:hsqldb:mem:myDB“/>
<property name=“username“ value=“sa“/>
<property name=“password“ value=““/>
</bean>

Bei Verwendung einer HSQLDB kommt der Treiber org.hsqldb.jdbcDriver zum Einsatz. Die URL besteht aus drei Teilen, wobei der erste für alle HSQLDBs gleich ist und jdbc:hsqldb: lauten muss. Der Prefix mem: signalisiert, dass es sich um eine In-Memory Datenbank handelt, während der letzte Teil den Namen der zu verwendeten Datenbank – myDB – bestimmt.

Will man später von einer In-Memory Datenhaltung auf eine dauerhafte Persistierung wechseln, kann die Datasource ersetzt und hier die Verbindungsdaten an die neue Datenbank angepasst werden.

Nun soll die SessionFactory definiert werden, die der UserDAO für die Datenbank-Operationen benötigt:

<bean id=“sessionFactory“ class=“org.springframework.orm.hibernate4.LocalSessionFactoryBean“>
<property name=“dataSource“ ref=“dataSource“/>
<property name=“packagesToScan“>
<list>
<value>de.mz.entity</value>
</list>
</property>
<property name=“hibernateProperties“>
<props>
<prop key=“hibernate.dialect“>org.hibernate.dialect.HSQLDialect</prop>
<prop key=“hibernate.hbm2ddl.auto“>create</prop>
<prop key=“hibernate.show_sql“>true</prop>
</props>
</property>
</bean>

Die SessionFactory braucht Zugriff auf die bereits definierte Datasource und verwaltet alle verfügbaren Entitäten. Hierzu muss entweder eine Liste aller annotierten Klassen – User und Address – über das Property annotatedClasses angegeben werden oder es wird, wie in diesem Beispiel, eine Liste von zu scannenden Packages übergeben. Anschließend können noch Hibernate-Eigenschaften konfiguriert werden, wobei die Angabe des Datenbank-Dialekts zwingend nötig ist. Bislang hat nur die Datasource Informationen über die zu konnektierende Datenbank. Aber auch Hibernate muss wissen, um welche Datenbank es sich handelt, damit die entsprechende Kommunikation gewählt werden kann. Für eine HSQLDB wird der Dialekt org.hibernate.dialect.HSQLDialect verwendet. Die weiteren Einstellungen können die Entwicklung erheblich erleichtern. So sorgt die Property hibernate.hbm2ddl.auto mit dem Wert create dafür, dass Hibernate die Datenbank-Struktur mit Tabellen, Primär- und Fremdschlüsseln automatisch anlegt. Erhält hibernate.show_sql den Wert true, wird jedes von Hibernate ausgeführte SQL-Statement geloggt und kann so nachvollzogen werden. Dies ist zwar für eine Live-Umgebung tödlich, kann aber beim Aufsetzten eines neuen Projektes durchaus hilfreich sein.

Wie bereits erwähnt, wird ein Transaktion-Management benötigt, damit die SessionFactory den UserDAO mit einer gültigen Session versorgen kann. Die Transaktionen sollen dabei über Annotationen definierbar sein. Zunächst wird der HibernateTransactionManager konfiguriert, der Zugriff auf die SessionFactory benötigt:

<bean id=“transactionManager“ class=“org.springframework.orm.hibernate4.HibernateTransactionManager“>
<property name=“sessionFactory“ ref=“sessionFactory“ />
</bean>

Um die Steuerung von Transaktionen mit der Annotation Transactional zu ermöglichen, muss dieses Verhalten aktiviert werden:

<tx:annotation-driven/>

Sofern der Name des TransactionManagers nicht transactionManager lauten würde, müsste hier noch der entsprechende Name angegeben werden:

<tx:annotation-driven transaction-manager=“transactionManager“/>

Zu guter Letzt muss der UserDAO in der Spring-Konfiguration aufgenommen werden und Zugriff auf die SessionFactory erhalten:

<bean id=“userDAO“ class=“de.mz.dao.UserDAO“>
<property name=“sessionFactory“ ref=“sessionFactory“ />
</bean>

Nun wird der UserDAO um die erste CRUD-Operationen erweitert, wobei mit dem Speichern eines Benutzers begonnen wird:

@Transactional
public User save(User user) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(user);
return user;
}

Die save-Methode wird mit der Annotation Transactional versehen. Beim Aufruf der Methode wird nun eine Transaktion erzeugt, die erst beim Verlassen wieder geschlossen wird, womit dann die Datenbank-Änderungen wirksam werden. Träte zuvor eine Exception auf, so fände keine der Datenbank-Operationen der save-Methode statt. Weiterhin sorgt die Transaktion dafür, dass die SessionFactory eine gültige Session liefert, über die der Benutzer gespeichert werden kann.

Durch die Verwendung der saveOrUpdate-Methode kann die gleiche Methode zum Anlegen und Aktualisieren von Benutzerdaten verwendet werden. Besitzt der User keine Id, vergibt Hibernate den Primär-Schlüssel und führt das SQL-Statement Insert aus, andernfalls wird ein Update ausgeführt.

Anschließend wird zum Testen der Anwendung eine Klasse mit ausführbarer main-Methode erstellt, die den Spring-Context hochfährt und den UserDAO aus selbigem lädt:

ApplicationContext context = new ClassPathXmlApplicationContext(„applicationContext.xml“);
UserDAO userDAO = (UserDAO) context.getBean(„userDAO“);

Nun wird ein Benutzter mitsamt zugehöriger Adresse erstellt und über den UserDAO gespeichert:

List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setCity(„Cologne“);
addresses.add(address);

User user = new User();
user.setForname(„Max“);
user.setLastname(„Mustermann“);
user.setAddresses(addresses);

userDAO.save(user);

Dank der Hibernate-Einstellung show_sql lässt sich in den Log-Meldungen gut nachvollziehen, was für SQL-Statements Hibernate durchführt. Zuerst werden die Datenbank-Tabellen erstellt:

Hibernate: create table Address (id bigint generated by default as identity (start with 1), city varchar(255), street varchar(255), primary key (id))
Hibernate: create table User (id bigint generated by default as identity (start with 1), forname varchar(255), lastname varchar(255), primary key (id))
Hibernate: create table User_Address (User_id bigint not null, addresses_id bigint not null, unique (addresses_id))

Die Tabellen Address und User entsprechen unseren Java-Entitäten, während die Tabelle User_Address als Mapping-Tabelle zwischen Benutzer und Adressen fungiert. Anschließend werden der Mapping-Tabelle per constraints Fremdschlüssel auf die Haupt-Entitäten hinzugefügt:

Hibernate: alter table User_Address add constraint FK838B1F807A26EF9A foreign key (User_id) references User
Hibernate: alter table User_Address add constraint FK838B1F805ABA690C foreign key (addresses_id) references Address

Als nächstes werden nun die Benutzerdaten gespeichert. Es lässt sich gut nachvollziehen wie aufgrund des CascadeTypes SAVE_UPDATE der Benutzer und die dazugehörigen Adressen gespeichert werden. Natürlich darf auch der Eintrag in der Mapping-Tabelle nicht fehlen:

Hibernate: insert into User (id, forname, lastname) values (default, ?, ?)
Hibernate: insert into Address (id, city, street) values (default, ?, ?)
Hibernate: insert into User_Address (User_id, addresses_id) values (?, ?)

Die ersten zwei CRUD-Operationen wurden umgesetzt, als nächstes sollen Benutzer-Daten aus der Datenbank ausgelesen werden. Hierzu wird der UserDAO um die find-Methode erweitert:

@Transactional
public List<User> find() {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
List<User> users = criteria.list();
return users;
}

Hibernate bietet verschiedene Möglichkeiten, um Datenbank-Abfragen umzusetzen, so kann auf einer Session ein HQL- (Hibernate Query Language) oder SQL-Statement ausgeführt oder wie in diesem Beispiel eine Criteria erstellt werden. Criterias bieten die Möglichkeit Objekt-Orientierte-Abfragen formulieren zu können. Wie auf einer Criteria Einschränkungen definiert werden können, wird im Folgenden noch erläutert. Zunächst soll nur eine Liste aller Benutzer zurückgegeben werden.

Bei der Verarbeitung der Ergebnisliste ist zu beachten, dass Hibernate verschiedene FetchMode zum Umgang mit verknüpften Entitäten bietet. Per Default ist dies Lazy-Loading, wobei verknüpfte Entitäten nur geladen werden, sofern auf sie zugegriffen wird. In diesem Beispiel werden die Adress-Datensätze nicht geladen, da innerhalb der Transaktion kein Zugriff stattfindet. Nach Abschluss der Transaktion – also beim Verlassen der find-Methode – kann nicht mehr auf sie zugegriffen werden, ohne dass eine LazyLoadingException geworfen wird:

org.hibernate.LazyInitializationException: could not initialize proxy – no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:430)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:121)
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)

Wenn die Transaktion im verarbeitenden Service gestartet würde – nicht wie hier im UserDAO – wäre der Zugriff auf die entsprechenden Adress-Daten möglich. Hierbei wird der Vorteil des Lazy-Loadings ausgenutzt, es werden nur Daten geladen, die auch tatsächlich benötigt werden. Bei verteilten Systemen ist dies natürlich nicht unbedingt so möglich. Benötigt ein Client Adress-Informationen, die im Server geladen werden, so ist hier die Session bereits geschlossen. Daher kann für Entity-Verknüpfungen, die zwangsläufig mit der Haupt-Entität geladen werden sollen, der FetchMode Eager-Loading verwendet werden. Hier werden beim Laden des Benutzers direkt alle Adressen mitgeladen. Dies wird in der Annotation OneToMany definiert, die zusätzlich das Attribut fetch erhält. Die Property addresses der Klasse User besitzt nun folgende Annotationen:

@OneToMany(fetch=FetchType.EAGER)
@Cascade(value=CascadeType.ALL)
private List<Address> addresses;

Für große Daten-Mengen, die nicht immer benötigt werden, bietet sich dieses Verfahren natürlich nicht an. Hier kann das DTO – Data Transfair Object – Pattern zum Einsatz kommen. Hierbei wird nicht die User-Entity selber zum Client geschickt, sondern ein UserDTO, welches aber die gleichen Attribute besitzt. Bei der Anforderung der Daten werden die mitzuladenden Abhängigkeiten der Serverseite mitgeteilt, so dass die Antwort alle benötigten Daten in Form von DTOs beinhaltet. In unserem Beispiel würden beim Server die Benutzerdaten mitsamt der Adress-Datensätze angefragt werden. Sowohl die User– als auch die Adress-Daten werden in DTOs umgewandelt, wobei das UserDTO die Adressen in Form einer Liste von AddressDTOs besitzt. Da die Konvertierung innerhalb einer gültigen Session stattfindet, kommt es bei diesem Verfahren zu keiner LazyLoadingException und es werden nur die Daten geladen, die der Client auch tatsächlich benötigt.

Der Nachteil dieses Verfahrens liegt auf der Hand, bei jeder Client-Server-Kommunikation müssen DTOs in Entitäten und anschließend wieder zurück in DTOs konvertiert werden. Außerdem gibt es für jede Entität eine entsprechendes DTO, was zu dupliziertem Code führt. Der Vorteil der DTOs ist die Kapselung, da die Datenschicht vor dem Client-System versteckt wird.

Aber zurück zum Beispiel. Die find-Methode soll nicht mehr alle Benutzer liefern, sondern nur solche, deren Nachname Mustermann lautet. Hierfür wird auf der Criteria eine entsprechende Einschränkung definiert:

Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
criteria.add(Restrictions.eq(„lastname“, „Mustermann“));
List<User> users = criteria.list();

Der Criteria wird hierzu eine Restriction hinzugefügt, deren erster Parameter den Namen der Property erhält, der zweite den zu prüfenden Wert. Die Restriction besitzt verschiedene Prüfungs-Methoden. Hier wurde auf Gleichheit getestet – equals – aber auch die anderen aus der SQL-Syntax bekannten Prüf-Operationen wie like, between usw. stehen zur Verfügung. Anschließend wird die Criteria wie gehabt ausgeführt und man erhält eine passende Liste von Benutzern.

Es sind aber nicht nur Suchkriterien auf dem Benutzer möglich, es können auch Einschränkungen auf der Adresse festgelegt werden. So interessieren im Folgenden nur Benutzer, die aus Köln kommen:

Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
Criteria addressCiteria = criteria.createCriteria(„addresses“);
addressCiteria.add(Restrictions.eq(„city“, „Cologne“));
List<User> users = criteria.list();

Auf der bereits erstellen UserCriteria, wird eine weitere Criteria erzeugt. Der übergebene String enthält hierbei den Namen der Property aus der User-Entität, die auf die entsprechende Tabelle verweisst – in diesem Beispiel also addresses. Über die neue Criteria können nun Suchkritieren auf der Adressen definiert werden. Die list-Methode wird allerdings weiterhin auf der UserCriteria ausgeführt, da als Ergebnis-Menge eine Liste von Benutzern und keine Adressen erwartet werden.

Soweit ein kleiner Einblick in die Welt der Criterias. Bereits einen vorangegangenen Artikel – http://martinzimmermann1979.wordpress.com/2012/03/20/verwendung-einer-having-bedingung-bei-hibernate-criterias/ – widmete ich diesem Thema, wobei hier die Having-Bedingung im Vordergrund stand.

Aber zurück zum Thema, noch ist der UserDAO nicht vollständig, da die Lösch-Funktion fehlt:

@Transactional
public void delete(User user) {
sessionFactory.getCurrentSession().delete(user);
}

Diese ist denkbar einfach zu realisieren. Es muss lediglich auf der aktuellen Session die delete-Methode aufgerufen werden, die als Parameter die entsprechende Entität erwartet. Es mag merkwürdig erscheinen, dass zunächst die ganze Entität geladen werden muss, nur um sie anschließend zu löschen. Alternativ könnte eine HQL-Query zum Löschen des Benutzers verwendet werden, aber auch hier wird der Benutzer zuerst in den Hibernate internen Cache geladen, bevor er gelöscht wird. Die einzige Möglichkeit die obige Lösch-Funktion performanter zu gestalten, wäre das Laden und Löschen des Benutzers in derselben Session.

Der Artikel ist für den Einstieg in Hibernate und für das initiale Aufsetzten eines Hibernate Projektes mit Spring Integration gedacht. Es wurde der Einsatz der CRUD-Operationen und ein einfaches Mapping zwischen zwei Enitäten erläutert. Wirklich knifflig wird es erst anschließend, wenn es an das Mapping von Entitäten geht. Welche Entitäten kennen sich untereinander, wie werden sie miteinander verknüpft, welchen CascadeType und welchen FetchMode erhält welche Verbindung. Diese Fragen müssen für jedes Projekt und jede Anwendung einzeln Entschieden werden.

Android – Kommunikation mit der Außenwelt

Veröffentlicht in: 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.

REST Services mit Spring 3

Veröffentlicht in: Java, spring | 0

Ging vor ein paar Jahren der Trend bei der Übertragung von Informationen zwischen Softwaresystemen noch in Richtung Web-Services, so geht mittlerweile die Tendenz klar in Richtung REST.

REST selber basiert auf dem HTTP-Protokoll und ist zustandslos, da alle benötigten Informationen Bestandteil des jeweiligen HTTP-Requests sind. Die einzelnen Ressourcen die ein REST-Service zur Verfügung stellt, sind über eindeutige URIs erreichbar. Auf den Ressourcen können unter anderem folgende Operationen ausgeführt werden:

  • GET – Abfrage einer Ressource
  • POST- Zur aufgerufenen Ressource wird eine Sub-Ressource erzeugt
  • PUT – Verändern einer bestehende Ressource
  • DELETE – Löschen einer Ressource

Aufgrund der Entwicklung der vergangenen Jahren und der immer weiteren Verbreitung von REST wurde JAX-RS mit der Version 1.1 in den Java EE 6 Standard aufgenommen. Aktuell wird an der Version 2.0 gearbeitet, welche in den Java EE 7 Standard einfließen soll. Die gängigsten JAX-RS Implementierungen sind:

  • Jersey – Referenzimplementierung von Oracle
  • RESTEasy von JBoss
  • Restlet

Alle diese JAX-RS Implementierungen unterstützen Spring, was auch der Grund dafür ist, dass Spring selber keine eigene JAX-RS Implementierung anbietet. Man ist daher also auf eines der oben genannten Frameworks angewiesen.

Da ich mich bereits mit Restlet beschäftigt habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/01/restlet-rest-im-osgi-kontext/), habe ich mich im folgenden Beispiel für dieses REST-Framework entschieden. Restlet kann unter http://www.restlet.org/ in der aktuellen Version 2.0.11 heruntergeladen werden.

Natürlich gibt es auch die Möglichkeit Restlet als Maven Dependency zu laden. Hier kann man zusätzlich die Restlet-Spring-Extension verwenden, die Spring in der Version 3.0.1 verwendet. Will man eine neuere Spring Version einbinden – aktuell ist Spring 3.1.0 verfügbar – so muss man dies explizit in der pom.xml definieren.

Hier die Maven Dependencys für Restlet:

<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.spring</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-oxm-tiger</artifactId>
<version>1.5.9</version>
</dependency>

Die Library spring-oxm-tiger beinhaltet den Jaxb2Marshaller, der zum marshalling bzw. unmarshalling von Java Objekten in XML und umgekehrt benötigt wird.

Für die Ergebnis-Darstellung wird Spring MVC verwendet. Unter Spring 3.x wurden weitere Views, wie die MarshallingView zur Darstellung von XML-Inhalten und die MappingJacksonJsonView für JSON-Inhalte, hinzugefügt.

Im folgenden Beispiel soll ein REST-Service umgesetzt werden, der die bekannten GET, POST, PUT und DELETE Operationen auf der Ressource MyBean zur Verfügung stellt. Im ersten Schritt findet der Datentransfer ausschließlich mittels XML statt, im zweiten Schritt soll zusätzlich JSON unterstützt werden. D.h. der Service kann XML und JSON verarbeiten und abhängig vom unterstützten Datenformat des Clients findet die Antwort des Servers in XML oder JSON statt.

Starten wir mit der Spring-Konfiguration. Zunächst muss die MVC Java Konfiguration aktiviert werden:

<mvc:annotation-driven />

Weiterhin wird das Component-Scanning auf Package-Ebene aktiviert. Hiermit erspart man sich, jede Bean einzeln in der Konfigurationsdatei anzugeben:

<context:component-scan base-package=“de.mz.rest“ />

Nun wird das RequestMapping auf Type- und Methoden-Ebene aktiviert:

<bean class=“org.spring…DefaultAnnotationHandlerMapping“/>
<bean class=“org.spring…AnnotationMethodHandlerAdapter“ />

Anschließend wird der Jaxb2Marshaller definiert, der fürs marshalling bzw. unmarshalling von Objekten in XML und umgekehrt zuständig ist. Unter classesToBeBound sind alle Objekte aufgelistet, die der Marshaller in irgendeiner Form umwandeln soll:

<bean id=“jaxbMarshaller“ class=“org.springframework.oxm.jaxb.Jaxb2Marshaller“>
<property name=“classesToBeBound“>
<list>
<value>de.mz.rest.data.MyBean</value>
</list>
</property>
</bean>

Für die Darstellung der vom Jaxb2Marshaller aufbereiteten XML-Inhalte ist die MarshallingView zuständig:

<bean id=“viewName“ class=“org.springframework.web.servlet.view.xml.MarshallingView“>
<constructor-arg ref=“jaxbMarshaller“ />
</bean>

Zu guter Letzt wird noch ein ViewResolver benötigt, der die Requests auf die entsprechenden Controller weiterleitet. Hier wird der BeanNameViewResolver verwendet. Über das RequestMapping im Controller selber, wird  das Mapping später aber noch mal explizit gesetzt:

<bean  class=“org.spring…BeanNameViewResolver“ id=“viewResolver“ />

Nun wird die Klasse de.mz.rest.data.MyBean erstellt, bei der es sich um die Bean handelt, die vom Jaxb2Marshaller in XML umgewandelt werden soll. Hierfür werden JAXB (Java Architecture for XML Binding) Annotationen verwendet:

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

Die Annotation @XmlRootElement definiert MyBean als ein Top Level Element. @XmlType hingegen kann dazu verwendet werden, um für die Attribute von MyBean eine bestimmte Reihenfolge zu definieren. Eigentlich ist diese Annotation laut JAXB-Spezifikation nicht zwingend nötig, allerdings benötigt der Jaxb2Marshaller diese Information. Andernfalls erachtet sich der Jaxb2Marshaller für die Klasse MyBean als nicht zuständig und beim Versuch diese Klasse in XML zu transformieren wird folgende, irreführende Fehlermeldung geworfen:

javax.servlet.ServletException: Unable to locate object to be marshalled in model

Wenden wir uns nun der REST-Ressource zu, die die gewohnte Spring MVC Annotation @Controller verwendet:

@Controller
@RequestMapping(„/myPath“)
public class MyResource extends ServerResource {

@RequestMapping(value=“/{id}“, method=RequestMethod.GET)
public ModelAndView get(@PathVariable String id)  {
return new ModelAndView(„viewName“, „object“, new MyBean());
}
}

Wie man in obigem Beispiel sieht, kommt die @RequestMapping Annotation zweimal zum Einsatz. Zum einen wird über die annotierte Klasse MyResource der Pfad definiert, unter dem die komplette REST-Ressource erreichbar ist, zum anderen werden die Methoden-Details definiert, wie Übergabe-Parameter und die jeweiligen Operationen (GET, POST, …) für die die Methode zuständig ist. Um innerhalb der Methode Zugriff auf die id aus dem Pfad zu erlangen, wird diese einfach als Übergabe-Parameter angegeben und mit der Annotation @PathVariable versehen. Nun muss dem ModelAndView-Element noch der ViewName der MarshallingView und die gewünschte MyBean übergeben werden. Die obige REST-Ressource ist über einen GET-Request auf die URI http://<server>:<port>/<webapp>/myPath/<id> aufrufbar.

Die letzten Einstellungen die noch fehlen, sind der ContextLoaderListener und das DispatcherServlet in der web.xml Datei:

<listener>
<listener-class>org.spring…ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>servletName</servlet-name>
<servlet-class>org.spring…DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Bislang liefert die REST-Ressource nur ein einziges MyBean-Element zurück, welches über die id spezifiziert wird. Will man die vollständige Liste an MyBeans erhalten, wird ein Wrapper für eine Liste von MyBeans benötigt. Auch dieser Wrapper, die MyBeanList, ist wie gehabt mit dem JAXB @XmlRootElement zu versehen:

@XmlRootElement
@XmlType
public class MyBeanList {

private List<MyBean> myBeans;

public List<MyBean> getMyBeans() {
return myBeans;
}
public void setMyBeans(List<MyBean> myBean) {
this.myBean = myBean;
}
}

Anschließend kann die MyBean-Ressource um folgende Methode ergänzt werden:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView getMyBeans() {
List<MyBean> myBeans = new ArrayList<MyBean>();
// add MyBeans to list
MyBeanList beanList = new MyBeanList();
beanList.setMyBeans(myBeans);
return new ModelAndView(„viewName“, „object“, beanList);
}

Es ist zu beachten, dass auch die MyBeanList in der Spring Konfiguration des Jaxb2Marshaller unter classesToBeBound hinzugefügt werden muss.

Diese Ressource ist mittels eines GET-Requests unter der URI http://<server>:<port>/<webapp>/myPath erreichbar.

Die GET-Requests aus den vorangegangenen Beispielen sind recht simpel zu testen, da diese Ressourcen über einen Web-Browser aufrufbar sind. Die vom Jaxb2Marshaller serialisierten Daten werden im Browser als XML dargestellt. Zusätzlich sollen aber noch die Operationen POST, PUT und DELETE umgesetzt werden. Der POST und PUT Request beinhaltet Daten, die von der REST-Ressource deserialisiert werden müssen, um das Objekt MyBean zu erhalten. Hier nun die Umsetzung der POST-Schnittstelle:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView addBean(@RequestBody MyBean bean) {
// add bean
return new ModelAndView(„viewName“, „object“, bean);
}

Die Methode des RequestMapping wird als POST-Operation definiert. Die Daten selber werden als Übergabeparameter in die Methoden-Signatur aufgenommen und müssen lediglich mit der Annotation @RequestBody versehen werden. Den Rest übernimmt der Jaxb2Marshaller.

Die Ressource ist mit einem POST-Request unter der URI http://<server>:<port>/webapp/myPath erreichbar.

Zum Testen der Ressource kann das Kommandozeilen-Tool curl http://curl.haxx.se/ verwendet werden. Hier der Befehl inklusive Parameter, mit denen sich die Ressource Testen lässt:

curl -v -H „Accept: application/xml“ -H „Content-type: application/xml“ -X POST -d ‚<myBean><id>1</id><message>some text</message></myBean>‘  http://<server>:<port>/<webapp>/myPath

Hier die Erläuterung zu den einzelnen Parametern:

  • H <header>: Hiermit wird dem Request eine Header-Information hinzugefügt.
    1. Die Antwort des Servers soll XML sein, da dies vom Client akzeptiert wird.
    2. Die vom Client gesendeten Daten bestehen aus XML.
  • X <command>: Der Request wird ausgewählt, z.B. GET, POST, PUT oder DELETE
  • d <data>: Hier wird der eigentliche Inhalt des Requests definiert.
  • v: Steht für verbose. Wird verwendet um mehr Informationen zu erhalten, kann ansonsten aber auch weggelassen werden.

Wenn man lieber mit einem Grafischen Tool arbeiten möchte, kann man statt curl das Firefox Plugin REST Client verwenden.

Erweitern wir nun die REST-Ressource um eine PUT-Methode, damit eine bestehende MyBean verändert werden kann. Hierbei wird zum einen die id benötigt, die im Pfad angegeben wird und als @PathVariable in der Methoden-Signatur annotiert ist. Zum anderen wird – wie beim POST-Request – die mit @RequestBody annotierte MyBean übergeben, die die aktualisierten Daten beinhaltet:

@RequestMapping(value = „/{id}“, method=RequestMethod.PUT)
public ModelAndView updateMyBean(@PathVariable String id, @RequestBody MyBean bean) {
// update bean
return new ModelAndView(„viewName“, „object“, bean);
}

Aufrufbar ist diese Ressource unter einem PUT-Request auf http://<server>:<port>/webapp/myPath/<id>.

Für die DELETE-Operation wird nur die id der zu löschenden MyBean im Pfad übergeben. Die REST-Ressource gibt dann eine Liste der verbleibenden MyBeans zurück, die wie gehabt in dem Wrapper MyBeanList enthalten sind.

@RequestMapping(value = „/{id}“, method=RequestMethod.DELETE)
public ModelAndView removeMyBean(@PathVariable String id) {
// delete mybean and return list of remaining beans
return new ModelAndView(„todos“, „object“, remainingBeans);
}

Die Ressource wird mittels eines DELETE-Requests auf http://<server>:<port>/webapp/myPath/<id> aufgerufen.

Im letzten Teil des Artikels soll der REST-Service erweitert werden, dass er nicht nur XML sondern auch JSON unterstützt. Hierfür bietet Spring den ContentNegotiatingViewResolver an, der für die korrekte Darstellung das View Konzept verwendet. Abhängig vom gewünschten Inhalt - diese gibt der Client im Accept-Header des Requests mit an (z.B.: Accept: application/json) - wird die entsprechende View angesprochen.

Für die JSON Unterstützung wird das Jackson-Framework http://xircles.codehaus.org/projects/jackson benötigt, welches zur Zeit in der Version 1.9.5 zu downloaden ist.

Das Jackson Framework kann natürlich auch als Maven Dependency verwendet werden:

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.5</version>
</dependency>

Nun muss der ContentNegotiatingViewResolver konfiguriert werden:

<bean class=“org.spring…ContentNegotiatingViewResolver“>
<property name=“mediaTypes“>
<map>
<entry key=“json“ value=“application/json“/>
<entry key=“xml“ value=“application/xml“/>
</map>
</property>
<property name=“viewResolvers“>
<list>
<ref bean=“viewResolver“ />
</list>
</property>
<property name=“defaultViews“>
<list>
<ref bean=“viewName“/>
<bean class=“org.spring…MappingJacksonJsonView“>
<property name=“prefixJson“ value=“true“/>
</bean>
</list>
</property>
</bean>

Zunächst werden die beiden MediaTypes definiert, die unterstützt werden sollen, also XML und JSON. Als ViewResolver kann der bereits definierte BeanNameViewResolver verwendet werden. Ansonsten müssen jetzt noch die Views definiert werden. Da weiterhin XML unterstützt werden soll, erhält die Property defaultViews die bereits definierte MarshallingView übergeben. Außerdem wird hier noch die MappingJacksonJsonView definiert.

Die JSON-Unterstützung der REST-Ressource kann ebenfalls mit curl überprüft werden, indem die Header-Informationen und die Daten angepasst werden:

curl -v -H „Accept: application/json“ -H „Content-type: application/json“ -X POST -d ‚{„id“:“1″,“message“:“Some Text“}‘  http://<server>:<port>/<webapp>/myPath

Insgesamt lässt sich sagen, dass man mit Spring 3.x recht komfortable REST-Services erstellen kann. Die dafür benötigte Konfigurations-Arbeit hält sich in Grenzen und mit der Kombination aus Spring MVC- und JAXB-Annotations kommt man schnell zum Ziel. Lediglich die Dokumentation ist mangelhaft und man muss sich die Informationen recht mühsam zusammen suchen. Auch das die vom Jaxb2Marshaller zu transformierenden Klassen zwangsläufig die @XmlType Annotation benötigen, ist nur schwer nachzuvollziehen.