Exposing JPA Entities through RESTful Services
This page
describes how to expose JPA entities through a standards-based
(JAX-RS/JAXB/JPA) RESTful service. REST is an acronym for Representational
State Transfer, which is a design idiom that embraces a stateless client-server
architecture in which Web services are viewed as resources which can be
identified by their URLs.
Implementing a JPA-Based JAX-RS Service
To
implement a JAX-RS service, extend JPASingleKeyResource or JPACompositeKeyResourcedepending upon the key type. The following example
extends JPASingleKeyResource:
import
javax.ejb.LocalBean;
import
javax.ejb.Stateless;
import
javax.persistence.EntityManager;
import
javax.persistence.PersistenceContext;
import
javax.persistence.PersistenceContextType;
import
javax.ws.rs.Path;
import
org.eclipse.persistence.jpa.rest.JPASingleKeyResource;
@Stateless
@LocalBean
@Path("/customers")
public
class CustomerService extends
JPASingleKeyResource<Customer, Long> {
@PersistenceContext(unitName="CustomerService",
type=PersistenceContextType.TRANSACTION)
EntityManager entityManager;
public
CustomerService() {
super(Customer.class);
}
@Override
protected EntityManager entityManager()
{
return
entityManager;
}
}
URIs for JPA Entities with Composite Keys
To locate a resource with composite
keys, the URI includes the property names from the JPA key class as matrix
parameters. The advantage of using matrix parameters is that they may be
cached. The same representation is also used if composite keys are represented
using an embedded key class.
@Entity
@IdClass(CustomerID.class)
public class Customer implements Serializable {
@Id
private long id;
@Id
private String country;
}
public class CustomerID {
private String country;
private long id;
public CustomerID() {
super();
}
public CustomerID(String country, long id) {
this.country = country;
this.id = id;
}
public String getCountry() {
return country;
}
public long getId() {
return id;
}
}
Using Create, Read, Update, and Delete (CRUD) Operations on
RESTful Services
The CRUD operations are described in
the following sections:
The examples in these sections use the
Jersey client APIs. Jersey is the open source JAX-RS (JSR 311) Reference
Implementation for building RESTful Web services.
POST - Create Operation
The following example shows a post
operation called on a service, using the Jersery client APIs. The XML message
is converted to the appropriate object type using JAXB.
Client c = Client.create();
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers");
ClientResponse response = resource.type("application/xml").post(ClientResponse.class, "<customer>...</customer>");
System.out.println(response);
This call will be received by the
following:
@POST
@Consumes({"application/xml", "application/json"})
public Response create(@Context UriInfo uriInfo, EntityType entity) {
entityManager().persist(entity);
UriBuilder uriBuilder = pkUriBuilder(uriInfo.getAbsolutePathBuilder(), entity);
return Response.created(uriBuilder.build()).build();
}
Examples of successful responses to
this call are:
§ 200 OK
§ The URI (as described earlier) for the created entity is
returned.
Examples of error responses are:
§ ? (The
spec only has this question mark.)
GET - Read Operation
Get is a read-only operation. It is
used to query resources. The following is an example of how to invoke a GET
call using the Jersey client APIs:
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers;id=1;country=CA");
ClientResponse response = resource.accept(mimeType).get(ClientResponse.class);
Single key cases that use path
parameters and composite key cases that use matrix parameters are handled
differently, as shown below:
Single Key - Path
Parameter
@GET
@Path("{id}")
@Produces({"application/xml", "application/json"})
public EntityType read(@PathParam("id") KeyType id) {
return entityManager().find(entityClass, id);
}
Composite Key -
Matrix Parameters
@GET
@Produces({"application/xml", "application/json"})
public EntityType read(@Context UriInfo info) {
return entityManager().find(entityClass, getPrimaryKey(info));
}
Examples of successful responses to
this call are:
§ 200 OK - If a result is returned
§ 204 No Content - If no results are returned
Examples of error responses are:
§ ? (The
spec only has this question mark.)
PUT - Update Operation
The put operation updates the
underlying resource. When using put the client knows the identity of the
resource being updated. The following is an example of how to invoke a PUT call
using the Jersey client APIs:
Client c = Client.create();
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers/1");
ClientResponse response = resource.type("application/xml").put(ClientResponse.class, "<customer>...</customer>");
System.out.println(response);
This call will be received by
@PUT
@Consumes({"application/xml", "application/json"})
public void update(EntityType entity) {
entityManager().merge(entity);
}
An example of a successful response is:
§ 200 OK
An example of an error response is:
§ 409 Conflict - Locking related exception
DELETE - Delete Operation
The delete operation is used to remove
resources. It is not an error to remove a non-existent resource. Below is an
example using the Jersey client APIs:
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers/1");
ClientResponse response = resource.delete(ClientResponse.class);
Single key cases that use path
parameters and composite key cases that use matrix parameters are handled
differently, as shown below:
Single Key - Path
Parameter
@DELETE
@Path("{id}")
public void delete(@PathParam("id") KeyType id) {
super.delete(id);
}
Composite Key -
Matrix Parameters
@DELETE
public void delete(@Context UriInfo info) {
super.delete(getPrimaryKey(info));
}
An example of a successful response is:
§ 200 OK
An example of an error response is:
§ ? (The
spec only has this question mark.)
You can convert matrix parameters to an
ID class, as shown below: (From
spec: "(Note the code below is currently using query parameters and needs
to be updated):")
private KeyType getPrimaryKey(UriInfo info) {
try {
KeyType pk = (KeyType) PrivilegedAccessHelper.newInstanceFromClass(keyClass);
for(Entry<String, List<String>> entry : info.getQueryParameters().entrySet()) {
Field pkField = PrivilegedAccessHelper.getField(keyClass, entry.getKey(), true);
Object pkValue = ConversionManager.getDefaultManager().convertObject(entry.getValue().get(0), pkField.getType());
PrivilegedAccessHelper.setValueInField(pkField, pk, pkValue);
}
return pk;
} catch(Exception e) {
throw new RuntimeException(e);
}
}
Obtain the key class using the the JPA
metamodel facility:
keyClass = (Class<KeyType>) entityManager().getMetamodel().entity(entityClass).getIdType().getJavaType();
Configuration
Files
No extra configuration files are
required for RESTful services. However, you are responsible for providing the
required JAX-RS, JPA, and JAXB configuration files:
§ JAX-RS - You must create the JAX-RS deployment artifacts
appropriate to your deployment platform.
§ JPA - You must create the necessary artifacts for JPA.
§ JAXB - You must create the necessary artifacts for JAXB:
§ jaxb.properties file to specify JAXB implementation
§ eclipselink-oxm.xml as an alternate metadata representation
Post a Comment
Post a Comment