Friday, January 7, 2011

Spring 3: Dynamic MVC using jqGrid and MongoDB

In this tutorial we will create a simple Spring MVC 3 application that uses a document-oriented database: MongoDB, for its persistence layer and a JQuery plugin: jqGrid, for its presentation layer. We will explore and discover the dynamic relationship of both technlogies, along with Spring MVC 3. Our application is a simple CRUD service for managing a list of Users. We will provide facilities for adding, deleting, editing, and viewing of all registered users.

Note: An updated version of this tutorial is now accessible at Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide

What is jqGrid
jqGrid is an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web. Since the grid is a client-side solution loading data dynamically through Ajax callbacks, it can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.

jqGrid uses a jQuery Java Script Library and is written as plugin for that package.

Source: http://www.trirand.com/jqgridwiki/doku.php
Here's a screenshot of how our jqGrid-powered application would look like:


In a nutshell jqGrid is a table for manipulating data. It's an AJAX application that's built on top of JQuery. It communicates via JSON.

What is MongoDB?
MongoDB (from "humongous") is a scalable, high-performance, open source, document-oriented database. Written in C++, MongoDB features:
  • Document-oriented storage
  • Full Index Support
  • Replication & High Availability
  • Scale horizontally without compromising functionality.
  • Rich, document-based queries.
  • Atomic modifiers for contention-free performance.
  • Flexible aggregation and data processing.
  • Store files of any size without complicating your stack.
  • Enterprise class support, training, and consulting available.

Source: http://www.mongodb.org/
Here's our database schema:
{  
   id:'',
   firstName:'',
   lastName:'',
   money:''
}
In a nutshell MongoDB is a database that uses JSON instead of SQL There's no static schema to create. All schemas are dynamic, meaning you create them on-the-fly. You can try a real-time online shell for MongoDB at http://try.mongodb.org/. Visit the official MongoDB site for a through discussion.

In order to complete this tutorial, you will be required to install a copy of MongoDB. If you don't have a MongoDB yet, grabe one now by visiting this link http://www.mongodb.org/display/DOCS/Quickstart. The installation is really easy.

Let's begin by defining our MongoDBFactory.

MongoDBFactory

MongoDBFactory is simply a factory for retrieving a single instance of your database via getDB() and a single instance of your collection via getCollection(). This is a custom class we created to simplify the retrieval of these items. If you prefer to retrieve them manually instead of using this MongoDBFactory, you're free to do so. Here's an example on how you may retrieve them manually:

Remember our database and collections will be created on-the-fly. We will not create a domain object here because the fields of our jqGrid exactly matches our dynamic schema in MongoDB. This is intentional. What happens if we change the fields in jqGrid? Then we can simply create a new schema in MongoDB. What if we change the schema in MongoDB, then we can simply change the fields in jqGrid. This is what makes them dynamic.

Let's start.

First, we declare our service interface.

IUserService

Here's the service implementation:

UserService

This service defines our basic CRUD system. We have the following methods:
getAll() - for retrieving all persons
edit() - for editing
delete() - for deleting
add() - for adding
get() - for retrieving single person

The database is initialized once in the UserService' constructor through the init() method:


Notice we're creating a dynamic JSON schema here with the following format:
{  
   id:'',
   firstName:'',
   lastName:''
}

We have declared our service. Let's now declare a controller.

MediatorController

This controller declares a single mapping:
/main/users
This loads a JSP page containing our jqGrid.


Here's the JSP page:

users.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<c:url value="spring-jqgrid-mongo" var="baseUrl"/>

<head>
 <link rel="stylesheet" type="text/css" media="screen" href="/${baseUrl}/resources/css/jquery/ui-lightness/jquery-ui-1.8.6.custom.css" />
 <link rel="stylesheet" type="text/css" media="screen" href="/${baseUrl}/resources/css/jqgrid/ui.jqgrid.css" />

 <script type="text/javascript" src="/${baseUrl}/resources/js/jquery/jquery-1.4.4.min.js"></script>
 <script type="text/javascript">
     var jq = jQuery.noConflict();
 </script>
 <script type="text/javascript" src="/${baseUrl}/resources/js/jquery/jquery-ui-1.8.6.custom.min.js"></script> 
 <script type="text/javascript" src="/${baseUrl}/resources/js/jqgrid/grid.locale-en.js" ></script>
 <script type="text/javascript" src="/${baseUrl}/resources/js/jqgrid/jquery.jqGrid.min.js"></script>
 
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>Spring MVC 3: jqGrid and MongoDB Integration Tutorial</title>
 
</head>

<body >

<script type="text/javascript">
 jq(function() {
  jq("#grid").jqGrid({
      url:'/${baseUrl}/krams/crud',
   datatype: 'json',
   mtype: 'GET',
      colNames:['Id', 'First Name', 'Last Name'],
      colModel:[
       {name:'id',index:'id', width:55,editable:false,editoptions:{readonly:true,size:10},hidden:true},
       {name:'firstName',index:'lastName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}},
       {name:'lastName',index:'firstName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}}
      ],
      postData: { 
   },
   rowNum:20,
      rowList:[20,40,60],
      height: 200,
      autowidth: true,
   rownumbers: true,
      pager: '#pager',
      sortname: 'id',
      viewrecords: true,
      sortorder: "asc",
      caption:"Users",
      emptyrecords: "Empty records",
      loadonce: false,
      loadComplete: function() {
   },
      jsonReader : {
          root: "rows",
          page: "page",
          total: "total",
          records: "records",
          repeatitems: false,
          cell: "cell",
          id: "id"
      }
  });
  jq("#grid").jqGrid('navGrid','#pager',
    {edit:false,add:false,del:false,search:true},
    { },
          { },
          { }, 
    { 
        sopt:['eq', 'ne', 'lt', 'gt', 'cn', 'bw', 'ew'],
           closeOnEscape: true, 
            multipleSearch: true, 
             closeAfterSearch: true }
  );


  
  jq("#grid").navButtonAdd('#pager',
    {  caption:"Add", 
     buttonicon:"ui-icon-plus", 
     onClickButton: addRow,
     position: "last", 
     title:"", 
     cursor: "pointer"
    } 
  );
  
  jq("#grid").navButtonAdd('#pager',
    {  caption:"Edit", 
     buttonicon:"ui-icon-pencil", 
     onClickButton: editRow,
     position: "last", 
     title:"", 
     cursor: "pointer"
    } 
  );
  
  jq("#grid").navButtonAdd('#pager',
   {  caption:"Delete", 
    buttonicon:"ui-icon-trash", 
    onClickButton: deleteRow,
    position: "last", 
    title:"", 
    cursor: "pointer"
   } 
  );

  jq("#btnFilter").click(function(){
   jq("#grid").jqGrid('searchGrid',
     {multipleSearch: false, 
      sopt:['eq']}
   );
  });

  // Toolbar Search
  jq("#grid").jqGrid('filterToolbar',{stringResult: true,searchOnEnter : true, defaultSearch:"cn"});

 });
</script>
  

<script type="text/javascript">

function addRow() {

 // Get the currently selected row
    jq("#grid").jqGrid('editGridRow','new',
      {  url: "/${baseUrl}/krams/crud/add", 
     editData: {
       },
       recreateForm: true,
       beforeShowForm: function(form) {
       },
    closeAfterAdd: true,
    reloadAfterSubmit:false,
    afterSubmit : function(response, postdata) 
    { 
           var result = eval('(' + response.responseText + ')');
     var errors = "";
     
           if (result.success == false) {
      for (var i = 0; i < result.message.length; i++) {
       errors +=  result.message[i] + "<br/>";
      }
           }  else {
            jq("#dialog").text('Entry has been added successfully');
      jq("#dialog").dialog( 
        { title: 'Success',
         modal: true,
         buttons: {"Ok": function()  {
          jq(this).dialog("close");} 
         }
        });
                 }
        // only used for adding new records
        var new_id = null;
        
     return [result.success, errors, new_id];
    }
      });

}

function editRow() {
 // Get the currently selected row
 var row = jq("#grid").jqGrid('getGridParam','selrow');
 
 if( row != null ) 
  jq("#grid").jqGrid('editGridRow',row,
   { url: "/${baseUrl}/krams/crud/edit", 
    editData: {
          },
          recreateForm: true,
          beforeShowForm: function(form) {
          },
    closeAfterEdit: true,
    reloadAfterSubmit:false,
    afterSubmit : function(response, postdata) 
    { 
              var result = eval('(' + response.responseText + ')');
     var errors = "";
     
              if (result.success == false) {
      for (var i = 0; i < result.message.length; i++) {
       errors +=  result.message[i] + "<br/>";
      }
              }  else {
               jq("#dialog").text('Entry has been edited successfully');
      jq("#dialog").dialog( 
        { title: 'Success',
         modal: true,
         buttons: {"Ok": function()  {
          jq(this).dialog("close");} 
         }
        });
                 }
           
     return [result.success, errors, null];
    }
   });
 else jq( "#dialogSelectRow" ).dialog();
}

function deleteRow() {
 // Get the currently selected row
    var row = jq("#grid").jqGrid('getGridParam','selrow');

    // A pop-up dialog will appear to confirm the selected action
 if( row != null ) 
  jq("#grid").jqGrid( 'delGridRow', row,
           { url: '/${baseUrl}/krams/crud/delete', 
      recreateForm: true,
               beforeShowForm: function(form) {
                 //change title
                 jq(".delmsg").replaceWith('<span style="white-space: pre;">' +
                   'Delete selected record?' + '</span>');
                 
        //hide arrows
                 jq('#pData').hide();  
                 jq('#nData').hide();  
               },
              reloadAfterSubmit:false,
              closeAfterDelete: true,
              afterSubmit : function(response, postdata) 
      { 
                   var result = eval('(' + response.responseText + ')');
       var errors = "";
       
                   if (result.success == false) {
        for (var i = 0; i < result.message.length; i++) {
         errors +=  result.message[i] + "<br/>";
        }
                   }  else {
                    jq("#dialog").text('Entry has been deleted successfully');
        jq("#dialog").dialog( 
          { title: 'Success',
           modal: true,
           buttons: {"Ok": function()  {
            jq(this).dialog("close");} 
           }
          });
                   }
                   // only used for adding new records
                   var new_id = null;
                   
       return [result.success, errors, new_id];
      }
           });
  else jq( "#dialogSelectRow" ).dialog();
}

</script>  
  
<p>Spring MVC 3: jqGrid and MongoDB Integration Tutorial</p>
<div id="jqgrid">
 <table id="grid"></table>
 <div id="pager"></div>
</div>

<div id="dialog" title="Feature not supported" style="display:none">
 <p>That feature is not supported.</p>
</div>

<div id="dialogSelectRow" title="Warning" style="display:none">
 <p>Please select row</p>
</div>

</body>

</html>

Notice for each of the major jqGrid functions, there's a corresponding URL:
/${baseUrl}/krams/crud - retrieves all users in JSON format
/${baseUrl}/krams/crud/add" - adds a new user
/${baseUrl}/krams/crud/edit - edits an existing user
/${baseUrl}/krams/crud/delete - deletes an existing user
These are used by JQuery when performing AJAX calls. A separate controller handles the calls.

UserController

Each URLs are mapped to a specific handler method in the controller. When the controller receives the request, it delegates processing to the service. If the service returns a successful message, a success message is returned. Likewise, if the service returns a failure message, a failure message is returned.


CustomGenericResponse is a simple POJO where can assign our custom responses. When the object is returned, Spring will automatically convert it to a JSON object, which the jqGrid understands

CustomGenericResponse

However, when retrieving all users, we wrapped the response in a CustomUserResponse object instead


CustomUserResponse

Let's finalize our Spring MVC application by declaring the required XML configurations.

To enable Spring MVC we need to add it in the web.xml

web.xml

Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml as well.

spring-servlet.xml

By convention, we must declare an applicationContext.xml as well.

applicationContext.xml

That's it. We've managed to create a simple Spring MVC 3 application that uses MongoDB for its database and jqGrid for its presentation layer. We've seen how we can map our schema from MongoDB to jqGrid easily.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/jqgrid-spring3mvc-integration-tutorial/

You can download the project as a Maven build. Look for the spring-jqgrid-mongo.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3: Dynamic MVC using jqGrid and MongoDB ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3: Using a Document-Oriented Database - MongoDB

In this tutorial we will create a simple Spring MVC 3 application that uses a document-oriented database for its persistence layer. We will be using MongoDB as our database. We will explore and discover how easy it is to integrate MongoDB with Spring MVC 3. Our application is a simple CRUD service for managing a list of Persons. We will provide facilities for adding, deleting, editing, and viewing of all registered persons. This tutorial is similar with my other database integration tutorials: Spring 3 MVC - JDBC Integration Tutorial, Spring 3 MVC - Hibernate 3: Using Annotations Integration Tutorial

Note: An updated version of this tutorial is now accessible at Spring MVC 3.1 - Implement CRUD with Spring Data MongoDB (Part 1)

What is MongoDB?
MongoDB (from "humongous") is a scalable, high-performance, open source, document-oriented database. Written in C++, MongoDB features:
  • Document-oriented storage
  • Full Index Support
  • Replication & High Availability
  • Scale horizontally without compromising functionality.
  • Rich, document-based queries.
  • Atomic modifiers for contention-free performance.
  • Flexible aggregation and data processing.
  • Store files of any size without complicating your stack.
  • Enterprise class support, training, and consulting available.

Source: http://www.mongodb.org/
In a nutshell MongoDB uses JSON instead of SQL There's no static schema to create. All schemas are dynamic, meaning you create them on-the-fly. You can try a real-time online shell for MongoDB at http://try.mongodb.org/. Visit the official MongoDB site for a through discussion.

In order to complete this tutorial, you will be required to install a copy of MongoDB. If you don't have a MongoDB yet, grabe one now by visiting this link http://www.mongodb.org/display/DOCS/Quickstart. The installation is really easy.

Let's begin by defining our MongoDBFactory.

MongoDBFactory
package org.krams.tutorial.mongo;

import java.net.UnknownHostException;

import org.apache.log4j.Logger;

import com.mongodb.Mongo;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.MongoException;

/**
 * A simple factory for returning a MongoDB
 */
public class MongoDBFactory {
 
 protected static Logger logger = Logger.getLogger("mongo");
 
 private static Mongo m;
 
 // Make sure no one can instantiate our factory
 private MongoDBFactory() {}
 
 // Return an instance of Mongo
 public static Mongo getMongo() {
  logger.debug("Retrieving MongoDB");
  if (m == null) {
   try {
    m = new Mongo( "localhost" , 27017 );
   } catch (UnknownHostException e) {
    logger.error(e);
   } catch (MongoException e) {
    logger.error(e);
   }
  }
  
  return m;
 }
 
 // Retrieve a db
 public static DB getDB(String dbname) {
  logger.debug("Retrieving db: " + dbname);
  return getMongo().getDB(dbname);
 }
 
 // Retrieve a collection
 public static DBCollection getCollection(String dbname, String collection) {
  logger.debug("Retrieving collection: " + collection);
  return getDB(dbname).getCollection(collection);
 }
}
MongoDBFactory is simply a factory for retrieving a single instance of your database via getDB() and a single instance of your collection via getCollection(). This is a custom class we created to simplify the retrieval of these items. If you prefer to retrieve them manually instead of using this MongoDBFactory, you're free to do so. Here's an example on how you may retrieve them manually:

Remember our database and collections will be created on-the-fly. Our schema will be based on our domain Person object.

Person


Let's now define a Person service for performing CRUD functions in our application.

PersonService

This service defines our basic CRUD system. We have the following public methods:
getAll() - for retrieving all persons
edit() - for editing
delete() - for deleting
add() - for adding
get() - for retrieving single person
We also have the following private methods:
init() - for initializing our database
getDBObject() - for retrieving a single Mongo object
The database is initialized once in the PersonService's constructor through the init() method:


Notice we're creating a dynamic JSON schema here with the following format:
{  
   id:'',
   firstName:'',
   lastName:'',
   money:''
}
Let's complete our Spring MVC application. We need to define a controller.

MainController

This controller declares the following mappings:
/persons - for retrieving all persons
/persons/add (GET) - displays the Add New form
/persons/add (POST) - saves the new person
/persons/delete - deletes an existing person
/persons/edit (GET) - displays the Edit form
/persons/edit (POST) - saves the edited person

Each mapping delegates the call to the PersonService. When the PersonService is done processing, the controller then forwards the request to a JSP page that displays a confirmation message. Here are the JSP pages.

personspage.jsp

editpage.jsp

addpage.jsp

editedpage.jsp

addedpage.jsp

deletedpage.jsp

To finish our Spring MVC application, we need to declare a couple of required XML configurations.

To enable Spring MVC we need to add it in the web.xml

web.xml

Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml as well.

spring-servlet.xml

By convention, we must declare an applicationContext.xml as well.

applicationContext.xml

 
 
 
 
 
 
  
That's all we need to do to integrate MongoDB and Spring MVC.

Let's examine what happens in the MongoDB console whenever we perform a particular action in our MVC application. When the application is initially run, we mentioned that it will drop and create a new collection. So MongoDB's console should reflect this action as well. Here's the log:
Fri Jan  7 00:34:10 [initandlisten] connection accepted from 127.0.1.1:42747 #1
Fri Jan  7 00:34:10 [conn1] CMD: drop mydb.mycollection
Fri Jan  7 00:34:10 [conn1] building new index on { _id: 1 } for mydb.mycollection
Fri Jan  7 00:34:10 [conn1] done for 0 records 0secs
It did drop and create our collection.

Now let's access the main page that shows all registered persons. To access the main page, enter the following URL in your browser:
http://localhost:8080/spring-mvc-mongodb/krams/main/persons
Here's what you should see:

Here's the log from MongoDB's console:
Fri Jan  7 00:36:48 [initandlisten] connection accepted from 127.0.1.1:53424 #2
Let's edit the first record in our list by clicking the Edit link. We will be redirected to the Edit Person page:

Here's the log from MongoDB's console:
Fri Jan  7 00:39:07 [initandlisten] connection accepted from 127.0.1.1:53427 #3
After editing the person and submitting the changes, we should see the following page:

However the MongoDB's console didn't change. When return back to the main page, we see the edited person.

Let's stop our application, and check the output from MongoDB's console:
Fri Jan  7 00:47:50 [conn3] end connection 127.0.1.1:53427
Fri Jan  7 00:47:50 [conn1] end connection 127.0.1.1:42747
Fri Jan  7 00:47:50 [conn2] end connection 127.0.1.1:53424
It ended three connections which is logical because we made three connections.

That's it. We've managed to create a simple Spring MVC 3 application that uses MongoDB for its database. We've also seen the benefits of a document-oriented database when we created the schema dynamically.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-mongodb/

You can download the project as a Maven build. Look for the spring-mvc-mongodb.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3: Using a Document-Oriented Database - MongoDB ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share