In meinem letzten Beitrag haben wir ein Maven/Eclipse-Projekt für die Entwicklung von RESTful-Webanwendungen auf JBoss AS 7 eingerichtet. Ein RESTful-Webdienst, der keine Datenbank verwendet, ist schon etwas seltsam. Deshalb werden wir in diesem Blog das Projekt mit JPA erweitern.
Konfiguration: Hinzufügen eines JBoss Moduls
Ich möchte Postgres als zugrunde liegende Datenbank verwenden. Um den erforderlichen JDBC-Treiber für Postgres im JBoss AS 7 Container zu installieren, müssen wir ein Modul hinzufügen. Wie ich bereits im vorherigen Beitrag erwähnt habe, spiegelt sich die Modulstruktur von JBoss im Dateisystem wider.
Die Dateien im Verzeichnis ''main'', aus denen das Modul besteht, sind die folgenden:
- module.xml beschreibt das Modul. Der Inhalt folgt unten.
- postgresql-9.0-801.jdbc4.jar ist der Postgres JDBC4-Treiber.
- postgresql-9.0-801.jdbc4.jar.index ist eine Indexdatei, die von JBoss Annotation Indexer erzeugt wird, wenn die Module gescannt werden.
Um dem JBoss-Container ein Modul hinzuzufügen, müssen wir also eine XML-Datei schreiben: [xml title="modul.xml"] <module xmlns="urn:jboss:module:1.0" name="org.postgres"> <Ressourcen> <resource-root path="postgresql-9.0-801.jdbc4.jar"/> </resources> <Abhängigkeiten> <module name="javax.api"/> <module name="javax.transaction.api"/> </dependencies> </module> [/xml] Jetzt müssen wir die Datenquelle auch in die standalone.xml eintragen: die Hauptkonfiguration für JBoss. Sie finden sie im Unterverzeichnis ''standalone/configuration'' der JBoss-Installation. Die Datei enthält bereits eine Datenquelle und einen Treiber für h2. Also fügen wir einen neuen Knoten hinzu: [xml title="standalone.xml" highlight="9,10-13"] <?xml version='1.0' encoding='UTF-8'?> <server name="brandhout.local" xmlns="urn:jboss:domain:1.0"> ... <Datenquellen> <Datenquelle> ... </datasource> <Treiber> <driver name="postgres" module="org.postgres"> <xa-datasource-class> org.postgresql.xa.PGXADataSource </xa-datasource-class> </driver> <driver name="h2" module="com.h2database.h2"> <xa-datasource-class> org.h2.jdbcx.JdbcDataSource </xa-datasource-class> </driver> </drivers> </datasources> ... </server> [/xml] Jetzt, nach dem Neustart des Servers, können wir die Konfiguration über die Verwaltungskonsole abschließen.
Über die Schaltfläche ''Neue Datenquelle'' öffnet sich ein Dialog, der Sie durch einige einfache Schritte zur Konfiguration der Datenquelle führt. Die Änderungen werden in der standalone.xml übernommen, die nun ein Fragment wie dieses enthält: [xml title="standalone.xml"] .. <datasource jndi-name="LibraryDS" pool-name="LibraryDS_Pool" enabled="true" jta="true" use-java-context="true" use-ccm="true"> <Verbindungs-Url> jdbc:postgresql://localhost:5432/bibliothek </connection-url> <Treiber> postgres </driver> <Sicherheit> <Benutzer-Name> Bibliothek </user-name> <Passwort> Bibliothek </password> </security> </datasource> ... [/xml]
Lesen aus der Datenbank
Jetzt müssen wir unsere Anwendung so konfigurieren, dass sie aus der Datenbank liest. Um die Datenbank als Persistenzkontext in JPA zu konfigurieren, fügen wir eine persistence.xml zu unserem Projekt hinzu. [xml language="src/main/resources/persistene"] <?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns="https://java.sun.com/xml/ns/persistence" xmlns_xsi="https://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="https://java.sun.com/xml/ns/persistence https://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="Bibliothek" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/LibraryDS</jta-data-source> <Eigenschaften> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> </properties> </persistence-unit> </persistence> [/xml] Der Java-Code, den wir schreiben müssen, ist recht trivialer JPA-Code, der mit einigen JAX-RS-Annotationen vermischt ist. Der Vollständigkeit halber sind sie hier unten eingefügt (zusammengeklappt). [java title="com/xebia/bibliothek/model/Book.java" collapse="true"] Paket com.xebia.library.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.ws.rs.FormParam; @Entität @SequenceGenerator(name="BOOK_SEQ", sequenceName="BOOK_SEQ") public class Book { @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="BOOK_SEQ") @FormParam("id") private Long id; @FormParam("Titel") private String title; public Long getId() { return id; } public String getTitle() { Titel zurückgeben; } } [/java] [java title="com/xebia/bibliothek/BookRepository.java" collapse="true"] Paket com.xebia.library; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import java.util.List; import javax.ejb.Local; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import org.jboss.resteasy.annotations.Form; import com.xebia.library.model.Book; @Lokal @Pfad("Bücher") @Produziert(APPLICATION_JSON) public interface BookRepository { @GET @Pfad("/") Liste<Buchen> all(); @POST @Pfad("/") Book create(@Form Book entity); @GET @Pfad("/"){id}/") Book getById(@PathParam("id") long id); @PUT @Pfad("/"){id}/") Book update(@Form Book entity); @DELETE @Pfad("/"){id}/") void remove(@PathParam("id") long id); } [/java] [java title="com/xebia/library/impl/BookRepositoryBean.java" collapse="true"] Paket com.xebia.library.impl; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import com.xebia.library.BookRepository; import com.xebia.library.model.Book; @Staatenlos public class BookRepositoryBean implements BookRepository { @PersistenzKontext private EntityManager em; @Override öffentliche Liste<Buchen> all() { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<Buchen> query = builder.createQuery(Book.class); Wurzel<Buchen> root = query.from(Book.class); query.orderBy(builder.asc(root.get("id"))); return em.createQuery(query).getResultList(); } @Override public Book create(Book book) { em.persist(book); Buch zurückgeben; } @Override public Book byId(long id) { return em.find(Book.class, id); } @Override public Book update(long id, Book book) { return em.merge(book); } @Override public void delete(long id) { em.remove(byId(id)); } } [/java] Ein paar Dinge zu beachten:
- Die JPA-Entität wird auch als ''Backing''-Objekt für Formularbeiträge verwendet. Die Annotation @FormParam wird für Parameter verwendet, die in die Felder des Objekts geschrieben werden.
- Das Repository wird jetzt auch als lokale Schnittstelle für die zustandslose Bean verwendet.
Nachdem wir die Anwendung auf dem Server installiert haben, können wir sie mit diesem einfachen Tool testen.
Fazit
Mit nur ein paar Klassen konnten wir einen RESTful-Webdienst bereitstellen, der es Benutzern ermöglicht, eine (rudimentäre) Bücherliste zu führen. Die WAR, die wir erstellt haben, ist wirklich winzig: Sie enthält nur ein paar Klassen. Die Anwendung stützt sich auf JEE-Spezifikationen, die im JBoss-Container implementiert sind. Ein paar Bemerkungen dazu:
- Die Entwicklung einer JEE-Anwendung wird viel einfacher und macht mehr Spaß! Es fühlt sich fast an wie RAD-Webentwicklung alla Grails.
- Das Testen einer solchen Anwendung ist nicht trivial: Ein Großteil der Funktionalität wird durch den Container bereitgestellt. Mit einem 'dickeren' Maven-Projekt mit Hibernate und Spring/Seam als Abhängigkeiten könnten wir (Teile) der bereitgestellten Anwendung in einer In-Build-Testumgebung nachbilden. Wie machen wir das jetzt? Eine mögliche Antwort ist die Verwendung von Arquillian. Ein zukünftiger Blog wird sich damit befassen müssen...
Natürlich ist die Anwendung, die wir jetzt haben, noch lange nicht fertig. Zunächst einmal hat sie keine Benutzeroberfläche...
Verfasst von
Maarten Winkels
Contact