In my previous post we setup a Maven/Eclipse project for developing RESTful web applications on JBoss AS 7. A RESTful web service that is not using a database is some what of an oddity. Therefor in this blog we’ll extend the project with JPA.
Configuration: Adding a JBoss Module
I want to use Postgres as the underlying database. To install the required JDBC driver for Postgres in the JBoss AS 7 container, we need to add a module. As I mentioned in the previous post, the module structure of JBoss is reflected on the file system.
The files in the ”main” directory that constitute the module are the folloing:
- module.xml describes the module. The content follows below.
- postgresql-9.0-801.jdbc4.jar is the postgres JDBC4 driver.
- postgresql-9.0-801.jdbc4.jar.index is an index file generated by JBoss Annotation Indexer when the modules are scanned.
So in order to add a module to the JBoss container, we need to write an XML file:
[xml title="module.xml"]
<module xmlns="urn:jboss:module:1.0" name="org.postgres">
<resources>
<resource-root path="postgresql-9.0-801.jdbc4.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>
[/xml]
Now we also need to enlist the datasource in the standalone.xml: the main configuration for JBoss. You’ll find it in the ”standalone/configuration” subdirectory of the JBoss installation. The file already has a datasource and driver for h2. So we add a new node:
[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">
…
<datasources>
<datasource>
…
</datasource>
<drivers>
<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]
Now, after restarting the server, we can finish the configuration using the admin console.
The ”New Datasource” button will open up a dialog that will guide you through a few simple steps to configure the datasource.
The changes will be persisted in the standalone.xml, which will now contain a fragment like this:
[xml title="standalone.xml"]
..
<datasource jndi-name="LibraryDS" pool-name="LibraryDS_Pool" enabled="true" jta="true" use-java-context="true" use-ccm="true">
<connection-url>
jdbc:postgresql://localhost:5432/library
</connection-url>
<driver>
postgres
</driver>
<security>
<user-name>
library
</user-name>
<password>
library
</password>
</security>
</datasource>
…
[/xml]
Reading from the database
Now we need to configure our application to read from the database. To configure the database as a persistence context in JPA, we add a persistence.xml to our project.
[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="library" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/LibraryDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
</properties>
</persistence-unit>
</persistence>
[/xml]
The java code we have to write is quite trivial JPA code mixed in with some JAX-RS annotation. For completeness they are included here below (collapsed).
[java title="com/xebia/library/model/Book.java" collapse="true"]
package 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;
@Entity
@SequenceGenerator(name="BOOK_SEQ", sequenceName="BOOK_SEQ")
public class Book {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="BOOK_SEQ")
@FormParam("id")
private Long id;
@FormParam("title")
private String title;
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
}
[/java]
[java title="com/xebia/library/BookRepository.java" collapse="true"]
package 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;
@Local
@Path("books")
@Produces(APPLICATION_JSON)
public interface BookRepository {
@GET
@Path("/")
List<Book> all();
@POST
@Path("/")
Book create(@Form Book entity);
@GET
@Path("/{id}/")
Book getById(@PathParam("id") long id);
@PUT
@Path("/{id}/")
Book update(@Form Book entity);
@DELETE
@Path("/{id}/")
void remove(@PathParam("id") long id);
}
[/java]
[java title="com/xebia/library/impl/BookRepositoryBean.java" collapse="true"]
package 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;
@Stateless
public class BookRepositoryBean implements BookRepository {
@PersistenceContext
private EntityManager em;
@Override
public List<Book> all() {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Book> query = builder.createQuery(Book.class);
Root<Book> 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);
return book;
}
@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]
A few things to notice:
- The JPA entity is also used as a ”backing” object for Form posts. The @FormParam annotation is used to parameters posted to the fields of the object.
- The Repository is now also used as a local interface for the stateless bean.
After deploying the application to the server, we can test it using this simple tool.
Conclusion
With just a few classes we were able to deploy a RESTful web service that enables users to maintain a (rudimentary) book list. The WAR that we build is really tiny: It only contains a few classes. The application relies on JEE specifications that are implemented in the JBoss container. A few remarks about this:
- Developing a JEE application becomes much simpler and more fun! It feels almost like RAD web development alla Grails.
- Testing an application like this is non-trivial: Much of the functionality is provided by the container. With a ‘thicker’ maven project with Hibernate and Spring/Seam as dependencies, we could mimic (parts of) the deployed application in an in-build testing environment. How do we do this now? One possible answer is to use Arquillian. A future blog will have to cover that…
Of course the application we have now is far from finished. To start with, it has no user interface…