Tuesday, 7 March 2017

RESTFul CRUD with a pinch of AJAX and Hibernate




Hello Buddies...


Let's try something bigger this time.



A commonly known task developers generally come across for sure, in their coding tenure, is working with the REST APIs.


While consuming these can be easy, thanks to various tools like POSTMAN and SOAPUI, what can be challenging is writing these APIs. We can go through so many online materials available these days that can share ins and outs of the REST architecture, but what we want is to save time and get the code working. So here we set the stage for this discussion.


What I am going to follow is an approach that will pick 4 commonly asked-for HTTP methods - POST, GET, PUT, DELETE (for Create, Read, Update and Delete respectively), with a few bonus code snippets for you - using AJAX for making calls from the JSPs and our very own Mr. Popular - Hibernate. This will make you get your hands dirty, over the journey, with database related tasks using Hibernate and MySQL workbench.


The above plan is not going to cost you anything, but it demands satisfactory code writing and mind-boggling for behind the scenes' theory.




The working code is available @


https://github.com/namitsharma99/restHibernateCRUD



You are welcome if you still wish to have a discussion. Let's start.



1. Do we have DB ready?


Make good use of MySQL workbench and have a local schema up and available with your table that you wish to use further.


Here, we have 'myEmployees' under 'namit_schema' and the table records will be impacted by the CRUD UI operations.


A snapshot of the table currently in my local -





CREATE TABLE `myEmployees` (
  `id` int(11) NOT NULL,
  `firstName` varchar(255) DEFAULT NULL,
  `lastName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


SELECT `myEmployees`.`id`,
    `myEmployees`.`firstName`,
    `myEmployees`.`lastName`
FROM `namit_schema`.`myEmployees`;



Don't mind the vague entries :P

Please note - Keep the DB credentials handy.



2.  Are we ready to integrate our JAVA code with the DB?


For fun, we will be using Hibernate for this task. You are free to use other techniques like pure JDBC or JPA.


Following some commonly available tutorials, I saved time and got the hibernate connection up and running.


Please note - Since consuming requisite jars can be a headache, hence it's always better to go for a maven project with the dependencies listed in pom.xml.


POM.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>RestPathAnnotationExample</groupId>
<artifactId>RestPathAnnotationExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.6.Final</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0-b01</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>

</dependencies>

</project>








Create the backing bean




package com.myBeans;

public class Employee {
 private int id;
 private String firstName, lastName;
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
}


Set up the 2 essential XMLs here -


a. employee.hbm.xml



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
 "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 <hibernate-mapping>
  <class name="com.myBeans.Employee" table="myEmployees">
    <id name="id">
     <generator class="increment"></generator>
    </id>
    <property name="firstName"></property>
    <property name="lastName"></property>
  </class>
 </hibernate-mapping>


b. hibernate.cfg.xml



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
   <session-factory>
        <property name="hbm2ddl.auto">update</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="connection.url">jdbc:mysql://localhost:XXXX/namit_schema</property>
        <property name="connection.username">username</property>
        <property name="connection.password">password</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
   <mapping resource="employee.hbm.xml"/>
   </session-factory>
</hibernate-configuration>


3. Can we create layers of abstraction and logic segregation?


Why not. As a good practice, have atleast 3 layers (especially for enterprise app projects). Here we can have Service Classes <-> Facade Layers <-> DAO Layers.




EmployeeService.java 

This class will provide you the API declarations and will help in providing endpoints per various http methods.


package com.myCode;

import java.util.HashMap;
import java.util.Map;
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.Produces;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.myFacade.ServiceFacade;
/**
 * @author namit
 * 
 * */
@Path("/employees")
public class EmployeeService {
 /* Landing page configured = http://localhost:XXXX/RestPathAnnotationExample/ 
  * Can be changed in web.xml (welcome.jsp) */
 /**
  * 
  * This is a default message providing method
  * Can be checked using - http://localhost:XXXX/RestPathAnnotationExample/rest/employees
  * 
  * */
 @GET
 @Produces("text/html")
 public Response testMethod() {
  String output = "this is employee service test...";
  return Response.status(200).entity(output).build();
 }
 /**
  * 
  * This is a method to fetch the list of employees available
  * Can be checked using - http://localhost:XXXX/RestPathAnnotationExample/rest/employees/list
  * 
  * */
 @GET
 @Path("list")
 @Produces("application/json")
 public Response getEmployees() {
  ServiceFacade serviceFacade = new ServiceFacade();
  String output = null;
  HashMap<String, String> errorMap = new HashMap<String, String>();
  errorMap.put("Error", "No Record Found!");
  JSONObject jsonError = new JSONObject(errorMap);
  try {
   output = serviceFacade.getEmployees();
   if (null == output || output.isEmpty()) {
    output = jsonError.toJSONString();
   }
  } catch (JsonProcessingException e) {
   e.printStackTrace();
  }
  System.out.println("JSON list = " + output);
  return Response.status(200).entity(output).build();
 }
 /**
  * 
  * This is a method to persist new employee's details
  * Can be checked using - http://localhost:XXXX/RestPathAnnotationExample/rest/employees/save
  * 
  * */
 @POST
 @Path("/save")
 @Consumes("application/json")
 public Response saveEmployee(String str) {
  String[] inputArray = str.split("&");
  String fname = inputArray[0].split("=")[1];
  String lname = inputArray[1].split("=")[1];
  String result = "Record created : " + str;
  Map<String, Object> map = new HashMap<String, Object>();
  map.put("firstname", fname);
  map.put("lastname", lname);
  ServiceFacade serviceFacade = new ServiceFacade();
  serviceFacade.saveEmployees(map);
  return Response.status(201).entity(result).build();
 }
 /**
  * 
  * This is a method to update an existing employee, identified using employee id
  * Can be checked using - http://localhost:XXXX/RestPathAnnotationExample/rest/employees/update
  * 
  * */
 @PUT
 @Path("/update")
 @Consumes("application/json")
 public Response updateEmployee(String str) {
  String[] inputArray = str.split("&");
  String id = inputArray[0].split("=")[1];
  String fname = inputArray[1].split("=")[1];
  String lname = inputArray[2].split("=")[1];
  String result = "Record updated : " + str;
  Map<String, Object> map = new HashMap<String, Object>();
  map.put("id", id);
  map.put("firstname", fname);
  map.put("lastname", lname);
  ServiceFacade serviceFacade = new ServiceFacade();
  serviceFacade.updateEmployees(map);
  return Response.status(201).entity(result).build();
 }
 /**
  * 
  * This is a method to delete an employee, identified by employee id
  * Can be checked using - http://localhost:XXXX/RestPathAnnotationExample/rest/employees/delete
  * 
  * */
 @DELETE
 @Path("/delete")
 @Consumes("application/json")
 public Response deleteEmployee(String str) {
  String result = "Record deleted : " + str;
  int i = Integer.valueOf(str.split("=")[1]);
  ServiceFacade serviceFacade = new ServiceFacade();
  serviceFacade.deleteEmployee(i);
  return Response.status(200).entity(result).build();
 }
}


ServiceFacade.java

This class acts as an intermediate step while passing data from front end to the backend and vice versa. It can be advantageous if we need to alter the request/ response as per destination methods.

package com.myFacade;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.myBeans.Employee;
import com.myDB.ServiceDAO;
/**
 * @author namit
 * 
 * */
public class ServiceFacade {
 public String getEmployees() throws JsonProcessingException {
  ServiceDAO serviceDAO = new ServiceDAO();
  List<Employee> ls = serviceDAO.getEmployees();
  ObjectMapper objectMapper = new ObjectMapper();
  String jsonStr = objectMapper.writeValueAsString(ls);
  return jsonStr;
 }
 public void saveEmployees(Map<String, Object> map) {
  ServiceDAO serviceDAO = new ServiceDAO();
  serviceDAO.saveEmployees(map);
 }
 public void updateEmployees(Map<String, Object> map) {
  ServiceDAO serviceDAO = new ServiceDAO();
  serviceDAO.updateEmployees(map);
 }
 public void deleteEmployee(int i) {
  ServiceDAO serviceDAO = new ServiceDAO();
  serviceDAO.deleteEmployee(i);
 }
}


ServiceDAO.java - 
This class holds the important DB related code.

package com.myDB;
import java.util.List;
import java.util.Map;
import org.hibernate.Query;
import org.hibernate.Session;
import com.connectors.HibernateConnector;
import com.myBeans.Employee;
/**
 * @author namit
 * 
 * */
@SuppressWarnings("deprecation")
public class ServiceDAO {
 @SuppressWarnings({ "unchecked" })
 public List<Employee> getEmployees() {
  Session session = null;
  session = HibernateConnector.getInstance().getSession();
  Query<Employee> q = session.createQuery("from Employee emp");
  List<Employee> qList = q.list();
  if (qList != null && qList.isEmpty()) {
   return null;
  } else {
   System.out.println("list " + qList);
   return (List<Employee>) qList;
  }
 }

 public void saveEmployees(Map<String, Object> map) {
  Session session = HibernateConnector.getInstance().getSession();
  session.beginTransaction();
  String fname = (String) map.get("firstname");
  String lname = (String) map.get("lastname");
  Employee employee = new Employee();
  employee.setFirstName(fname);
  employee.setLastName(lname);
  session.save(employee);
  session.getTransaction().commit();
  session.close();
 }

 public void updateEmployees(Map<String, Object> map) {
  Session session = HibernateConnector.getInstance().getSession();
  session.beginTransaction();
  int id = Integer.valueOf((String) map.get("id"));
  String fname = (String) map.get("firstname");
  String lname = (String) map.get("lastname");
  Employee employee = new Employee();
  employee.setId(id);
  employee.setFirstName(fname);
  employee.setLastName(lname);
  session.update(employee);
  session.getTransaction().commit();
  session.close();
 }

 public void deleteEmployee(int i) {
  Session session = HibernateConnector.getInstance().getSession();
  session.beginTransaction();
  Object o = session.load(Employee.class, new Integer(i));
  Employee e = (Employee) o;
  session.delete(e);
  session.getTransaction().commit();
  session.close();
 }

}

Since we are using Hibernate for DB access, I wanted to have a common place for Hibernate connection, hence following class came into the picture.


HibernateConnector.java

package com.connectors;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateConnector {
    
    private static HibernateConnector hibConn;
    private Configuration cfg;
    private SessionFactory sessionFactory;
    private HibernateConnector() throws HibernateException {

        cfg = new Configuration();
        cfg.configure("hibernate.cfg.xml");
        cfg.addResource("employee.hbm.xml"); 
        sessionFactory = cfg.buildSessionFactory();
    }
    
    public static synchronized HibernateConnector getInstance() throws HibernateException {
        if (hibConn == null) {
            hibConn = new HibernateConnector();
        }
        return hibConn;
    }
 
    public Session getSession() throws HibernateException {
        Session session = sessionFactory.openSession();
        if (!session.isConnected()) {
            this.reconnect();
        }
        return session;
    }
 
    private void reconnect() throws HibernateException {
        this.sessionFactory = cfg.buildSessionFactory();
    }
    
}


Once you have your code in place, your project folder structure may look somewhat like this -



Please ignore the com.ignore package, it was just for created to see some if-else flows, I came across in an interview for a big investment firm. Have a look if that interests you, I will share more of such questions if someone needs.
Anyway, coming back to our quest, now we have the code ready, I mean from APIs perspective.

Have a look at the javadocs for service class, you will get an idea what API to hit, to check the response directly through the browser.

Well that's said, now its time to try some UI related stuff to make you proficient at both backend as well as front end.

What I have came across so far is that majority of java developers are assigned evaluation projects during onboarding times, to come up with APIs and UI to consume them.

So the below section of the discussion will help you to get a taste of such assignment.

Have a jsp as follows -

welcome.jsp

%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>

    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Welcome Page</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
        <script type="text/javascript">
            function getList() {
                $("#loaderDiv").attr("style", "display: block");
                var request = $.ajax({
                    url: "rest/employees/list",
                    type: "GET"
                });
                request.done(function(msg) {
                    console.log("Request successful!");
                    $("#loaderDiv").attr("style", "display: none");
                    $("#selectionDiv").attr("style", "display: block");
                    $("#drop").empty();
                    $("#drop").append($("<option></option>").val(0).html("<i> -- Employee Names -- <i>")) for (i in msg) {
                        $("#drop").append($("<option></option>").val(msg[i].id).html(msg[i].firstName + " " + msg[i].lastName));
                    }
                });
                request.fail(function(jqXHR, textStatus) {
                    $("#loaderDiv").attr("style", "display: none");
                    console.log("Request failed: " + textStatus);
                });
            }

            function prepareEdit() {
                $("#editDiv").attr("style", "display: block");
                console.log('The option with value ' + $("#drop").val() + ' and text ' + $("#drop option:selected").text() + ' was selected.');
                $("#editId").val($("#drop").val());
                var fullName = $("#drop option:selected").text();
                var splittedSource = fullName.replace(/\s{2,}/g, ' ').split(' ');
                $("#editFName").val(splittedSource[0]);
                $("#editLName").val(splittedSource[1]);
                $("#storageInput").val($("#editId").val() + ":" + $("#editFName").val() + ":" + $("#editLName").val());
                console.log('value stored -- ' + $("#storageInput").val());
            }

            function updateName() {
                var temp = $("#storageInput").val();
                var request = $.ajax({
                    url: "rest/employees/update",
                    type: 'PUT',
                    contentType: 'application/json; charset=utf-8',
                    data: {
                        id: $("#editId").val(),
                        fname: $("#editFName").val(),
                        lname: $("#editLName").val()
                    }
                });
                request.done(function(msg) {
                    console.log("Request successful: " + msg);
                    getList();
                    $("#editDiv").attr("style", "display: none");
                });
                request.fail(function(jqXHR, textStatus) {
                    console.log("Request failed: " + textStatus);
                });
            }

            function deleteName() {
                var request = $.ajax({
                    url: "rest/employees/delete",
                    type: 'DELETE',
                    contentType: 'application/json; charset=utf-8',
                    data: {
                        id: $("#editId").val()
                    }
                });
                request.done(function(msg) {
                    console.log("Request successful: " + msg);
                    getList();
                    $("#editDiv").attr("style", "display: none");
                });
                request.fail(function(jqXHR, textStatus) {
                    console.log("Request failed: " + textStatus);
                });
            }

            function addName() {
                $("#addName").attr("style", "display: block");
            }

            function hide() {
                $("#addName").attr("style", "display: none");
            }

            function hide2() {
                $("#editDiv").attr("style", "display: none");
                $("#selectionDiv").attr("style", "display: none");
            }

            function add() {
                var request = $.ajax({
                    url: "rest/employees/save",
                    type: 'POST',
                    contentType: 'application/json; charset=utf-8',
                    data: {
                        fname: $("#addFName").val(),
                        lname: $("#addLName").val()
                    }
                });
                request.done(function(msg) {
                    console.log("Request successful: " + msg);
                    hide();
                });
                request.fail(function(jqXHR, textStatus) {
                    console.log("Request failed: " + textStatus);
                });
            }
        </script>
    </head>

    <body>
        <div class="container" style="background-image:url(green2.jpg); background-position: center center; background-repeat: no-repeat; background-size: cover">
            <div class="row" align="center" style="color:darkgreen">
                <h3>Hello Buddies</h3>
            </div>
            <div class="row" align="center" style="color:gray">
                <h4>Let's try some RESTFul CRUDs</h4>
            </div>
            <div class="row" align="center" style="display: none" id="loaderDiv"><img alt="loading" src="loadinfo.gif">
            </div>
            <hr>
            <div class="row" align="center">
                <input type="button" class="btn-success" value="Get List" onclick="getList()">
                <input type="button" class="btn-success" value="Add Name" onclick="addName()"> </div>
            <hr>
            <div class="row" style="display: none" id="selectionDiv" align="center" class="btn-success">
                <select name="Employee List" id="drop" onchange="prepareEdit()"></select>
                <!-- <input type="button" class="btn-danger" onclick="hide2()" value="Click to Hide"><br> -->
                <a href="#" onclick="hide2()" style="color: red; font-size: 18px"> <span class="glyphicon glyphicon-remove-sign"></span> </a>
                <br> </div>
            <hr>
            <div class="row" style="display: none" id="editDiv" align="center">
                <label>EmpID : </label>
                <input id="editId" disabled="disabled">
                <br>
                <label>FNAME : </label>
                <input id="editFName">
                <br>
                <label>LNAME : </label>
                <input id="editLName">
                <br>
                <label>EDIT : </label>
                <input type="button" class="btn-success" onclick="updateName()" value="Click to Save">
                <br>
                <label>DELETE : </label>
                <input type="button" class="btn-success" onclick="deleteName()" value="Click to Delete">
                <br>
                <!-- option to use for using hidden variable as local storage -->
                <input type="text" style="display: none" id="storageInput"> </div>
            <hr>
            <div class="row" style="display: none" id="addName" align="center">
                <label>FNAME :</label>
                <input id="addFName">
                <br>
                <label>LNAME :</label>
                <input id="addLName">
                <br>
                <label>ADD : </label>
                <input type="button" class="btn-success" onclick="add()" value="Click to Add">
                <!-- <input type="button" class="btn-danger" onclick="hide()" value="Click to Hide"><br> -->
                <a href="#" onclick="hide()" style="color: red; font-size: 18px"> <span class="glyphicon glyphicon-remove-sign"></span> </a>
                <br> </div>
        </div>
    </body>

    </html>

And web.xml entries as follows -

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>RestPathAnnotationExample</display-name> <servlet> <servlet-name>jersey-serlvet</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.myCode</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jersey-serlvet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>welcome.jsp</welcome-file> </welcome-file-list> </web-app>



This will give your application a nice UI for interacting with the APIs already created. Some of the screenshots I took while going through this app are -





So buddies, go ahead and try this out. 

We will soon discuss about the UI components, why they are being used and how they are makig life easier.

Your suggestions and thoughts are always welcome, and will surely improve my knowledge as well. ;)

Happy Coding....





Featured post

JAVA based project, that can be used to hit DB using JDBC, from WSO2 ESB

Hi Buddies, Here is a small project that will enable you to hit MySQL DB using WSO2 ESB - https://github.com/namitsharma99/customM...