JAX-RS Restful Web Services with JNDI Datasource for MySQL in Tomcat
JAX-RS Restful Web Services with JNDI Datasource and Connection Pooling for MySQL in Tomcat
In this tutorial we will discuss how to set up JAX-RS RESTful web services and configure a JNDI Datasource with Connection Pooling in Tomcat and connect to MYSQL Database. JNDI (Java Naming and Directory Interface) provides and interface to multiple naming and directory services.
What is JNDI?
The Java Naming and Directory Interface ( JNDI) is an API that allows access into naming and directory services in Java programs. JNDI works by associating names with objects and provide a mechanism of finding objects based on their names. JNDI also focuses on directory services which is another extension of naming services. Using JNDI you can access services like LDAP (Lightweight Directory Access Protocol) and NDS (Novell Directory Services).
What is Connection Pooling?
A Connection Pool is a cache of database connections that is actively maintained by the system so that connections may be reused whenever new requests can be used. By creating a connection pool we alleviate the need to create new connections to the database which are time consuming and costly database operations. By using connection pooling we effectively reduce connection creation time which improves system performance. A connection pool will maintain a cache of multiple open connections and reuse them as often as possible before opening up any new connections. The size and growth of the cache is maintained by the properties defined on the context.xml file.
How to Configure MySQL DataSource in Tomcat using JNDI Lookup
Using this tutorial, we will discuss the step by step process of setting up and configuring a MySQL Data Source for Tomcat using a JNDI lookup.
Getting Started
In order to run this tutorial yourself, you will need the following:
- Java JDK 1.6 or greater
- Favorite IDE Spring Tool Suite (STS), Eclipse IDE or NetBeans (I happen to be using STS because it comes with a Tomcat server built-in)
- Tomcat 7 or greater or other popular container (Weblogic, Websphere, Glassfish, JBoss, VMWare vFabric, etc). For this tutorial I am using VMware vFabric tc Server Developer Edition which is essentially an enhanced Tomcat instance integrated with Spring STS
- Jersey JAX-RS
- MySQL 5.5 or greater
- MySQL Connector/J jar
- Jackson Mapper for Object to JSON and vice-versa serialization/deserialization
- log4J (for logging purposes)
Required Libraries
Copy all of the following jars to WebContent->WEB-INF->lib folder.
asm-3.1.jar jackson-core-asl-1.9.13.jar jackson-core-lgpl-1.9.13.jar jackson-jaxrs-1.9.13.jar jackson-mapper-asl-1.9.13.jar jackson-mapper-lgpl-1.9.13.jar jackson-xc-1.9.13.jar jersey-client-1.18.jar jersey-core-1.18.jar jersey-json-1.18.jar jersey-server-1.18.jar jersey-servlet-1.18.jar jsr311-api-1.1.1.jar log4j-1.2.17.jar mysql-connector-java-5.1.22.jar persistence-api-1.0.2.jar
Complete Project Overview
I have added the project overview to give you a full view of the structure and show you all files contained in this sample project.
RESTful Web Service End Points
# | URI | Method | Description |
---|---|---|---|
1 | /rest/tomcat/status | GET | Displays the current status of the API being used. Non-restricted REST end-point |
2 | /rest/tomcat/showallcustomers | GET | Shows all customers in the mySQL database and returns the data as JSON objects |
3 | /rest/tomcat/getcustomer?id={id} | GET | Searches for an customer by its ID returning the customer as a JSON object. |
4 | /rest/tomcat/addcustomer | POST | Adds a customer into our mySQL datastore return the customer as a JSON object. |
5 | /rest/tomcat/updatecustomer | PUT | Update a customer in our mySQL datastore return the customer as a JSON object. |
6 | /rest/tomcat/deletecustomer?id={id} | DELETE | Deletes a customer by its ID in the mySQL database returning a status of the operation as a JSON string |
The Customer Model (Customer.java)
This will be used to as the object which we store and retrieve in order to test out our application. I added it because I wanted my web service to store and retrieve some Java object.
package com.avaldes.model; import org.codehaus.jackson.annotate.JsonProperty; public class Customer { private int customerId; private String firstName; private String lastName; private String address; private String city; private String state; private String zipCode; private int isActive; @JsonProperty(value = "customer-id") public int getCustomerId() { return customerId; } public void setCustomerId(int customerId) { this.customerId = customerId; } @JsonProperty(value = "first-name") public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @JsonProperty(value = "last-name") public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @JsonProperty(value = "address") public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @JsonProperty(value = "city") public String getCity() { return city; } public void setCity(String city) { this.city = city; } @JsonProperty(value = "state") public String getState() { return state; } public void setState(String state) { this.state = state; } @JsonProperty(value = "zip-code") public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } @JsonProperty(value = "is-active") public int getIsActive() { return isActive; } public void setIsActive(int isActive) { this.isActive = isActive; } @Override public String toString() { return "Customer [customerId=" + customerId + ", firstName=" + firstName + ", lastName=" + lastName + ", address=" + address + ", city=" + city + ", state=" + state + ", zipCode=" + zipCode + ", isActive=" + isActive + "]"; } }
StatusMessage Class
This class is used to return JSON messages back to clients containing status, message properties.
package com.avaldes.model; import org.codehaus.jackson.annotate.JsonProperty; public class StatusMessage { private Integer status; private String message; public StatusMessage() { } @JsonProperty(value = "status_code") public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } @JsonProperty(value = "message") public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Customer DAO (Data Access Object)
This interface is used to define all of the methods that will be needed when interacting with our MySQL database. With this interface you will see that all of the basic CRUD (Create, Update, Delete and Get) operations are available.
package com.avaldes.dao; import javax.ws.rs.core.Response; import com.avaldes.model.Customer; public interface CustomerDAO { public Response getCustomer(int id); public Response createCustomer(Customer customer); public Response updateCustomer(Customer customer); public Response deleteCustomer(int id); public Response getAllCustomers(); }
Customer DAO Impl Class
This is the actual concrete class that performs the implementation of the methods defined in the interface CustomerDAO.class.
package com.avaldes.dao.impl; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.log4j.Logger; import com.avaldes.dao.CustomerDAO; import com.avaldes.model.Customer; import com.avaldes.model.StatusMessage; import com.avaldes.util.Database; public class CustomerDAOImpl implements CustomerDAO { private DataSource datasource = Database.getDataSource(); private Logger logger = Logger.getLogger(CustomerDAOImpl.class); @Override public Response getCustomer(int id) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Customer customer = null; String sql = "select customer_id, first_name, last_name, address, " + "city, state, zip_code, is_active from Customers " + "where customer_id = ?"; try { conn = datasource.getConnection(); ps = conn.prepareStatement(sql); ps.setInt(1, id); rs = ps.executeQuery(); if (rs.next()) { customer = new Customer(); customer.setCustomerId(rs.getInt("customer_id")); customer.setFirstName(rs.getString("first_name")); customer.setLastName(rs.getString("last_name")); customer.setAddress(rs.getString("address")); customer.setCity(rs.getString("city")); customer.setState(rs.getString("state")); customer.setZipCode(rs.getString("zip_code")); customer.setIsActive(rs.getInt("is_active")); } else { logger.error( String.format("Customer with ID of %d is not found.", id)); StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.NOT_FOUND.getStatusCode()); statusMessage.setMessage( String.format("Customer with ID of %d is not found.", id)); return Response.status(404).entity(statusMessage).build(); } } catch (SQLException e) { logger.error("Error: " + e.getMessage()); e.printStackTrace(); } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { logger.error("Error closing resultset: " + e.getMessage()); e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { logger.error("Error closing PreparedStatement: " + e.getMessage()); e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { logger.error("Error closing connection: " + e.getMessage()); e.printStackTrace(); } } } return Response.status(200).entity(customer).build(); } @Override public Response createCustomer(Customer customer) { Connection conn = null; PreparedStatement ps = null; Statement stmt = null; ResultSet rs = null; StatusMessage statusMessage = null; int autoID = -1; String sql = "insert into customers (first_name, last_name, " + "address, city, state, zip_code, is_active) " + "values (?,?,?,?,?,?,?)"; try { conn = datasource.getConnection(); ps = conn.prepareStatement(sql); ps.setString(1, customer.getFirstName()); ps.setString(2, customer.getLastName()); ps.setString(3, customer.getAddress()); ps.setString(4, customer.getCity()); ps.setString(5, customer.getState()); ps.setString(6, customer.getZipCode()); ps.setInt(7, customer.getIsActive()); int rows = ps.executeUpdate(); if (rows == 0) { logger.error("Unable to create customer..."); statusMessage = new StatusMessage(); statusMessage.setStatus(Status.NOT_FOUND.getStatusCode()); statusMessage.setMessage("Unable to create customer..."); return Response.status(404).entity(statusMessage).build(); } stmt = conn.createStatement(); rs = stmt.executeQuery("select LAST_INSERT_ID()"); if (rs.next()) { autoID = rs.getInt(1); customer.setCustomerId(autoID); } } catch (SQLException e) { e.printStackTrace(); } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { logger.error("Error closing resultset: " + e.getMessage()); e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { logger.error("Error closing PreparedStatement: " + e.getMessage()); e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { logger.error("Error closing connection: " + e.getMessage()); e.printStackTrace(); } } } return Response.status(200).entity(customer).build(); } @Override public Response updateCustomer(Customer customer) { Connection conn = null; PreparedStatement ps = null; String sql = "update customers set first_name=?, last_name=?, " + "address=?, city=?, state=?, zip_code=?, is_active=? " + "where customer_id = ?"; try { conn = datasource.getConnection(); ps = conn.prepareStatement(sql); ps.setString(1, customer.getFirstName()); ps.setString(2, customer.getLastName()); ps.setString(3, customer.getAddress()); ps.setString(4, customer.getCity()); ps.setString(5, customer.getState()); ps.setString(6, customer.getZipCode()); ps.setInt(7, customer.getIsActive()); ps.setInt(8, customer.getCustomerId()); int rows = ps.executeUpdate(); if (rows == 0) { logger.error("Unable to update customer..."); StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.NOT_FOUND.getStatusCode()); statusMessage.setMessage("Unable to update customer..."); return Response.status(404).entity(statusMessage).build(); } } catch (SQLException e) { e.printStackTrace(); } finally { if (ps != null) { try { ps.close(); } catch (SQLException e) { logger.error("Error closing PreparedStatement: " + e.getMessage()); e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { logger.error("Error closing connection: " + e.getMessage()); e.printStackTrace(); } } } return Response.status(200).entity(customer).build(); } @Override public Response deleteCustomer(int id) { Connection conn = null; PreparedStatement ps = null; StatusMessage statusMessage = null; String sql = "delete from customers where customer_id = ?"; try { conn = datasource.getConnection(); ps = conn.prepareStatement(sql); ps.setInt(1, id); int rows = ps.executeUpdate(); if (rows == 0) { logger.error( String.format("Unable to DELETE customer with ID of %d...", id)); statusMessage = new StatusMessage(); statusMessage.setStatus(Status.NOT_FOUND.getStatusCode()); statusMessage.setMessage( String.format("Unable to DELETE customer with ID of %d...", id)); return Response.status(404).entity(statusMessage).build(); } } catch (SQLException e) { logger.error("Error: " + e.getMessage()); e.printStackTrace(); } finally { if (ps != null) { try { ps.close(); } catch (SQLException e) { logger.error( "Error closing PreparedStatement: " + e.getMessage()); e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { logger.error("Error closing connection: " + e.getMessage()); e.printStackTrace(); } } } statusMessage = new StatusMessage(); statusMessage.setStatus(Status.OK.getStatusCode()); statusMessage.setMessage( String.format("Successfully deleted customer with ID of %d...", id)); return Response.status(200).entity(statusMessage).build(); } @Override public Response getAllCustomers() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; List<Customer> allCustomers = new ArrayList<Customer>(); String sql = "select customer_id, first_name, last_name, address, " + "city, state, zip_code, is_active from Customers"; try { conn = datasource.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()) { Customer cust = new Customer(); cust.setCustomerId(rs.getInt("customer_id")); cust.setFirstName(rs.getString("first_name")); cust.setLastName(rs.getString("last_name")); cust.setAddress(rs.getString("address")); cust.setCity(rs.getString("city")); cust.setState(rs.getString("state")); cust.setZipCode(rs.getString("zip_code")); cust.setIsActive(rs.getInt("is_active")); allCustomers.add(cust); } if (allCustomers.isEmpty()) { logger.error("No Customers Exists..."); StatusMessage statusMessage = new StatusMessage(); statusMessage.setStatus(Status.NOT_FOUND.getStatusCode()); statusMessage.setMessage("No Customers Exists..."); return Response.status(404).entity(statusMessage).build(); } } catch (SQLException e) { e.printStackTrace(); } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { logger.error("Error closing resultset: " + e.getMessage()); e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { logger.error( "Error closing PreparedStatement: " + e.getMessage()); e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { logger.error("Error closing connection: " + e.getMessage()); e.printStackTrace(); } } } return Response.status(200).entity(allCustomers).build(); } }
Complete Program (RestfulTomcatJNDIExample.java)
package com.avaldes.service; import java.io.IOException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.log4j.Logger; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.map.JsonMappingException; import com.avaldes.dao.CustomerDAO; import com.avaldes.dao.impl.CustomerDAOImpl; import com.avaldes.model.Customer; @Path("tomcat") public class RestfulTomcatJNDIExample { private Logger logger = Logger.getLogger(RestfulTomcatJNDIExample.class); @Path("status") @GET @Produces(MediaType.TEXT_HTML) public String getStatus() { logger.info("Inside getStatus()..."); return "TomcatJNDIExample Status is OK..."; } @GET @Path("getcustomer") @Produces(MediaType.APPLICATION_JSON) public Response getCustomer( @DefaultValue("0") @QueryParam("id") int id) { CustomerDAO daoImpl = new CustomerDAOImpl(); logger.info("Inside getCustomer..."); Response resp = daoImpl.getCustomer(id); return resp; } @POST @Path("addcustomer") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createCustomer(Customer customer) { CustomerDAO daoImpl = new CustomerDAOImpl(); logger.info("Inside createCustomer..."); Response resp = daoImpl.createCustomer(customer); return resp; } @PUT @Path("updatecustomer") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response updateCustomer(Customer customer) { CustomerDAO daoImpl = new CustomerDAOImpl(); logger.info("Inside createCustomer..."); Response resp = daoImpl.updateCustomer(customer); return resp; } @DELETE @Path("deletecustomer") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response deleteCustomer( @DefaultValue("0") @QueryParam("id") int id) { CustomerDAO daoImpl = new CustomerDAOImpl(); logger.info("Inside deleteCustomer..."); Response resp = daoImpl.deleteCustomer(id); return resp; } @GET @Path("showallcustomers") @Produces(MediaType.APPLICATION_JSON) public Response showAllCustomers() throws JsonGenerationException, JsonMappingException, IOException { CustomerDAO daoImpl = new CustomerDAOImpl(); logger.info("Inside showAllCustomers..."); Response resp = daoImpl.getAllCustomers(); return resp; } }
LOG4J Configuration File (log4j.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC " -//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p: %c - %m%n" /> </layout> </appender> <!-- Application Loggers --> <logger name="com.avaldes"> <level value="info" /> </logger> <!-- Root Logger --> <root> <priority value="warn" /> <appender-ref ref="console" /> </root> </log4j:configuration>
Web Deployment Descriptor (web.xml)
This is a pretty straight forward deployment descriptor file – only thing you need to add is the resource reference. A resource reference allows you to define your JNDI requirements so that you can set up your data source in Tomcat. Please ensure you add it to the web.xml file as shown below.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Tomcat JNDI Example</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>Jersey REST Service</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <init-param> <param-name> com.sun.jersey.config.property.packages </param-name> <param-value>com.avaldes</param-value> </init-param> <init-param> <param-name> com.sun.jersey.api.json.POJOMappingFeature </param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Jersey REST Service</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <resource-ref> <description>MySQL Datasource</description> <res-ref-name>jdbc/tutorialdb</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
Configure Context for Tomcat (Context.xml)
This optional file contains a <Context> tag (Context Fragment) for your Tomcat web application. This separate file was introduced with Tomcat 5 so that a less obtrusive mechanism than a full server restart would be available.
Context elements may be defined in multiple places in Tomcat:
- In the $CATALINA_HOME/conf/context.xml file: the Context element information will be loaded by all webapps.
- In the $CATALINA_HOME/conf/[enginename]/[hostname]/context.xml.default file: the Context element information will be loaded by all webapps of that host.
- In META-INF/context.xml inside your applicationand packaged in the WAR file.
<Context> <Resource name="jdbc/tutorialdb" auth="Container" type="javax.sql.DataSource" maxActive="25" maxIdle="10" maxWait="10000" removeAbandoned="true" removeAbandonedTimeout="300" defaultAutoCommit="true" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" username="webuser" password="EasyAs123!" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/tutorialdb" /> </Context>
Tomcat Resource Factory Settings
- name – Resource name matching the resource reference name (<res-ref-name>) we used in the web deployment descriptor (web.xml).
- auth – Specify whether the web Application code signs on to the corresponding resource manager programmatically or via the Container. The two values available are Container or Application.
- driverClassName – Name of the fully qualified Java class name of the JDBC driver.
- username – Database username used for authentication purposes.
- password – Database password used for authentication purposes.
- url – Connection URL to be passed to the JDBC driver.
- type – The fully qualified Java class name expected by the web application when it performs a lookup for this resource.
- maxActive – The maximum number of connections that can be allocated from this pool at the same time.
- maxIdle – The maximum number of connections that can sit idle in this pool at the same time.
- maxWait – The maximum number of milliseconds that the pool will wait (when there are no available connections) for a connection to be returned before throwing an exception.
- maxTotal – The maximum number of connections that can be allocated from this pool at the same time (Tomcat 8).
- removeAbandoned – Flag to determine whether to remove abandoned connections from the pool (true or false).
- removeAbandonedTimeout – The number of seconds after which a borrowed connection is assumed to be abandoned.
- defaultAutoCommit – Default auto-commit state of the connections created by this pool (true or false).
Creating the Customer Table in MySQL
CREATE TABLE `tutorialdb`.`customers` ( `customer_id` INT NOT NULL AUTO_INCREMENT, `first_name` VARCHAR(45) NULL, `last_name` VARCHAR(45) NULL, `address` VARCHAR(45) NULL, `city` VARCHAR(30) NULL, `state` VARCHAR(20) NULL, `zip_code` VARCHAR(10) NULL, `is_active` TINYINT(1) NULL, PRIMARY KEY (`customer_id`) );
Inserting sample data into Customer Table in MySQL
I have included a sample insert statement using SQL to ease you database work and makeit easier for you to get started sooner.
INSERT INTO `tutorialdb`.`customers` (`first_name`, `last_name`, `address`, `city`, `state`, `zip_code`, `is_active`) VALUES ('Amaury', 'Valdes', '100 Main Street', 'Wayne', 'New Jersey', '07470', 1); INSERT INTO `tutorialdb`.`customers` (`first_name`, `last_name`, `address`, `city`, `state`, `zip_code`, `is_active`) VALUES ('Jessica', 'Silva', '1285 South Street', 'Clifton', 'New Jersey', '07013', 1);
Testing out the Web Services
To test out the application I used Postman which is a Google Chrome Application. Using this tool I validated each of the REST API calls. Please review the screen shots below:
Testing out using POSTMAN Chrome Extension
Download the Complete Source Code
That’s It!
I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!
Leave a Reply