- Part 1 - The Database
- Part 2 - Mapping the Database to JPA Entities
- Part 3 - Mapping JPA entities to XML (using JAXB)
- Part 4 - The RESTful Service
- Part 5 - The Client
Customer Service
We will use the @PersistenceContent annotation to inject an EntityManager onto our EJB session bean. We will use this EntityManager to perform all of our persistence operations. Back in part 2 we configured our persistence unit to use the Java Transaction API (JTA), so we will not need to do any of our own transaction handling.
You can see below how little JPA code is required to implement our RESTful service. Of course to make this production quality service we will need to include response objects and exception handling. In future posts I'll cover these topics in detail. For the purpose of this example the following is all the code you need to run your service.
package org.example; import java.util.List; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.persistence.Query; import javax.ws.rs.Consumes; 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 javax.ws.rs.core.MediaType; @Stateless @LocalBean @Path("/customers") public class CustomerService { @PersistenceContext(unitName="CustomerService", type=PersistenceContextType.TRANSACTION) EntityManager entityManager; @POST @Consumes(MediaType.APPLICATION_XML) public void create(Customer customer) { entityManager.persist(customer); } @GET @Produces(MediaType.APPLICATION_XML) @Path("{id}") public Customer read(@PathParam("id") long id) { return entityManager.find(Customer.class, id); } @PUT @Consumes(MediaType.APPLICATION_XML) public void update(Customer customer) { entityManager.merge(customer); } @DELETE @Path("{id}") public void delete(@PathParam("id") long id) { Customer customer = read(id); if(null != customer) { entityManager.remove(customer); } } @GET @Produces(MediaType.APPLICATION_XML) @Path("findCustomersByCity/{city}") public List<Customer> findCustomersByCity(@PathParam("city") String city) { Query query = entityManager.createNamedQuery("findCustomersByCity"); query.setParameter("city", city); return query.getResultList(); } }
WEB-INF/web.xml
In the WEB-INF/web.xml file we need to configure three things:
- Start the Jersey service for our application. We will use the Jersey implmenation of JAX-RS, it is included in the version of GlassFish we are using for this example.
- You interact with JAX-RS through URIs. We need to tell Jersey which URLs (a URL is a type of URI) relate our service. In our web context URLs starting with "rest" correspond to our RESTful service.
- We need to make our JPA persistence context we created in part 3 available for use by our EJB session bean.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <servlet> <servlet-name>Jersey Web Application</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Jersey Web Application</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <persistence-context-ref> <persistence-context-ref-name>persistence/em</persistence-context-ref-name> <persistence-unit-name>CustomerService</persistence-unit-name> </persistence-context-ref> </web-app>
- WEB-INF
- lib
- CustomerService.jar
- org
- example
- Customer.class
- Address.class
- PhoneNumber.class
- jaxb.properties
- META-INF
- MANIFEST.MF
- persistence.xml
- classes
- org
- example
- CustomerService.class
- web.xml
- META-INF
- MANIFEST.MF
Next Steps
Further Reading
If you enjoyed this post you may also be interested in:
Hi Blaise,
ReplyDeleteNice set of blog posts!
I was trying out to build something based on your example. But, I could not go far. I use the same set of configuration as described in your posts (Oracle XE, GF 3.0.1, SQL scripts). When I try to add a customer using Jersey Client, I get this exception:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: ORA-02291: integrity constraint (CUSTOMER.ADDRESS_FK) violated - parent key not found
Error Code: 2291
Call: INSERT INTO ADDRESS (ID, STREET, CITY) VALUES (?, ?, ?)
bind => [0, 1660 Blue Blvd, City]
Query: InsertObjectQuery(org.example.Address@2a82b5c3)
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324)
Any clues on what I may doing wrong?
Cheers,
Arul
Hi Arul,
ReplyDeleteGood catch. At the last minute I made the relationship between Customer and Address unidirectional and only verified the code by running a GET which worked. I have put the bidirectional relationship back. If you grab the updated source from part 3, then everything should work now.
Thanks,
Blaise
Hi Blaise,
ReplyDeleteThanks for fixing that. I am now not getting a different exception now.
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: ORA-00001: unique constraint (CUSTOMER.PHONE_NUMBER_PK) violated
Error Code: 1
Call: INSERT INTO PHONE_NUMBER (ID, NUM, TYPE, ID_CUSTOMER) VALUES (?, ?, ?, ?)
bind => [0, 123-456-7891, cell, 0]
Query: InsertObjectQuery(org.example.PhoneNumber@79adf9f1)
I am not sure why I would get a "ORA-00001: unique constraint". Here is my client code, if that is helpful.
Client client = Client.create();
WebResource r = client.resource("http://localhost:8080/customerservice/rest/customers");
Customer c = new Customer();
c.setFirstName("John");
c.setLastName("Doe");
Address a = new Address();
a.setCity("SFO");
a.setStreet("Country Manor Blvd");
PhoneNumber home = new PhoneNumber();
home.setNum("123-456-7890");
home.setType("home");
PhoneNumber cell = new PhoneNumber();
cell.setNum("123-456-7891");
cell.setType("cell");
c.setAddress(a);
Set phoneNumbers = new HashSet();
phoneNumbers.add(home);
phoneNumbers.add(cell);
c.setPhoneNumbers(phoneNumbers);
r.post(c);
Also, when I copied the latest EclipseLink jars to glassfish\modules dir, it still references the old version (2.0.1) that ships with GF 3.0.1. I had no luck with updating EL using the pkg tool.
-Arul
Hi Arul,
ReplyDeleteIn this particular example none of the IDs are automatically generated, so you will need to set them on all the objects. You are getting that error because both instances of PhoneNumber have id = 0.
-Blaise
Ok cool. I should have checked that. I believe adding the JPA annotation to id should fix my code, right? @GeneratedValue(strategy=GenerationType.SEQUENCE)
ReplyDeleteThanks,
Arul
That's correct, check out the following for more information about EclipseLink JPA and @GeneratedValue: http://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_%28ELUG%29#.40GeneratedValue
ReplyDeleteHi Dheenu,
ReplyDeleteThe purpose of @XmlInverseReference is to populate the bidirectional relationship:
- JPA Entities to XML - Bidirectional Relationships
I'm not sure I understand your use case. If you wish to provide more details I can be reached through the "Contact Me" page:
- "Contact Me" page
-Blaise
Hi Blaise,
ReplyDeleteThanks for this excellent article.
This example requires EJB container to create your CustomerService REST resource. Could you please tell me how I can create a REST resource to run in the servlet container that uses the entities you created.
Thanks,
Amit.
Hi Amit,
ReplyDeleteThe service implementation will remain mostly the same. It will differ in how you get the entity manager since you can't use injection, instead you will need to do something like the following:
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistentUnitName);
this.entityManager = emf.createEntityManager();
Also you will need to manage your own transactions, since you won't be able to leverage a session beans transactions:
entityManager.getTransaction().begin();
entityManager.persist(entity);
entityManager.getTransaction().commit();
I hope this helps.
-Blaise
Hi Blaise,
ReplyDeletelove your articles. Can you help me and answer what I have to do if my input type is MediaType.APPLICATION_JSON instead of MediaType.APPLICATION_XML.
Would this work or do I have to add something else:
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public void update(Customer customer) {
entityManager.merge(customer);
}
Thanks,
tomy
Hi Tomy,
ReplyDeleteI'm very happy to hear you are enjoying the blog. The sample code you have provided will definitely work. If you want to have your service support both JSON and XML messages then you could do the following:
@PUT
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void update(Customer customer) {
entityManager.merge(customer);
}
-Blaise
Very informative blog!
ReplyDeleteI'm currently doing something similar to this except I'm working with JBoss/Jersey. However, with this configuration I cannot get the EntityManager injection to work. Is this something that will only work properly with GF/Jersey?
Hi Sean,
ReplyDeleteThis is something that should work with JBoss/Jersey as well.
-Blaise
Great set of posts! Something I noticed was that in JBoss AS 7.1.1, in addition to commenting out the Jersey stuff in web.xml (since that's provided by RESTEasy), even though it may deploy, to activate the endpoint to accept requests, it's helpful to have the following in web.xml (mentioned in: https://issues.jboss.org/browse/AS7-1674):
ReplyDeletejavax.ws.rs.core.Application
/*
Thanks again!
Great Tutorial.. Thanks for sharing.
ReplyDeleteVery detailed and helpful tutorial. I had been looking for this for while. Glad I finally found it.:) Thanks a lot.:)
ReplyDelete