Sunday, May 25, 2014

Spring MVC + Maven + Hibernate CRUD Example Complete


This is a step-by-step tutorial for creating a simple web application starting from scratch using the following technology:
  • Spring MVC 4.0 as a web framework
  • Maven as a build tool and for dependency management
  • Tomcat 7 as a web server
  • Hibernate as an ORM tool
Let's take a look into what we are going to build.

Objective:

I will be explaining Spring MVC basic features by depicting a CRUD operations. Let's create an application called 'Book Store' where you can have the all the CRUD operations such as adding a book, editing a book, deleting a book and listing out all the books. This application can be implemented in projects like Bookstore Management, Library Management etc.

Snapshots:
The following snapshots will give you an idea how it looks like at the end.

Listing out all the book records:


Adding/Editing a book:


Project Setup

Let's begin setting up the project for eclipse. Maven provides several Archetype artifacts to define your project structure. I guess maven-archetype-webapp is the most common one for our purpose. When I was new, I used to google a lot to search which Archetype to use for my every project I create. But these days, due to some personal reasons, I prefer starting from scratch. This way, you can get rid of unwanted files and folders.

First create a root project folder 'bookstore'. And then create inner folders in the following hierarchical structure:


You can omit the 'test' directory if you'd like cause we won't be using that folder in this example. I just added it to show a professional project structure. Now, we're almost done. All you need more is to add pom.xml. Add the following pom.xml under the root directory 'bookstore'.

pom.xml   ( path: /bookstore/pom.xml )

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>np.com.mshrestha</groupId>
       <artifactId>bookstore</artifactId>
       <name>bookstore</name>
       <packaging>war</packaging>
       <version>1.0.0-BUILD-SNAPSHOT</version>
       <properties>
              <java-version>1.7</java-version>
              <spring-version>4.0.3.RELEASE</spring-version>
              <hibernate-version>4.2.7.Final</hibernate-version>
              <mysql-version>5.1.27</mysql-version>
              <apache-connection-pooling-version>1.4</apache-connection-pooling-version>
       </properties>
       <dependencies>
              <!-- Spring -->
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-context</artifactId>
                     <version>${spring-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-webmvc</artifactId>
                     <version>${spring-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-orm</artifactId>
                     <version>${spring-version}</version>
              </dependency>

              <!-- Hibernate -->
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-core</artifactId>
                     <version>${hibernate-version}</version>
              </dependency>
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-entitymanager</artifactId>
                     <version>${hibernate-version}</version>
              </dependency>
             
              <!--
              <dependency>
                     <groupId>org.hibernate</groupId>
                     <artifactId>hibernate-commons-annotations</artifactId>
                     <version>3.3.0.ga</version>
              </dependency>
              -->

              <!-- Apache's Database Connection Pooling -->
              <dependency>
                     <groupId>commons-dbcp</groupId>
                     <artifactId>commons-dbcp</artifactId>
                     <version>${apache-connection-pooling-version}</version>
              </dependency>
              <dependency>
                     <groupId>commons-pool</groupId>
                     <artifactId>commons-pool</artifactId>
                     <version>${apache-connection-pooling-version}</version>
              </dependency>

              <!-- MySQL -->
              <dependency>
                     <groupId>mysql</groupId>
                     <artifactId>mysql-connector-java</artifactId>
                     <version>${mysql-version}</version>
              </dependency>

              <!-- Servlet -->
              <dependency>
                     <groupId>javax.servlet</groupId>
                     <artifactId>servlet-api</artifactId>
                     <version>2.5</version>
                     <scope>provided</scope>
              </dependency>
              <dependency>
                     <groupId>javax.servlet.jsp</groupId>
                     <artifactId>jsp-api</artifactId>
                     <version>2.1</version>
                     <scope>provided</scope>
              </dependency>
              <dependency>
                     <groupId>javax.servlet</groupId>
                     <artifactId>jstl</artifactId>
                     <version>1.2</version>
              </dependency>

       </dependencies>
       <build>
          <plugins>

              <!-- Tomcat 7 plugin -->
              <plugin>
                   <groupId>org.apache.tomcat.maven</groupId>
                   <artifactId>tomcat7-maven-plugin</artifactId>
                   <version>2.0</version>
              </plugin>

              <plugin>
                  <artifactId>maven-eclipse-plugin</artifactId>
                  <version>2.9</version>
                  <configuration>
                       <additionalProjectnatures>
                           <projectnature>
                               org.springframework.ide.eclipse.core.springnature
                           </projectnature>
                       </additionalProjectnatures>
                       <additionalBuildcommands>
                           <buildcommand>
                               org.springframework.ide.eclipse.core.springbuilder
                           </buildcommand>
                       </additionalBuildcommands>
                       <downloadSources>true</downloadSources>
                       <downloadJavadocs>true</downloadJavadocs>
                  </configuration>
               </plugin>
           </plugins>
       </build>
       <repositories>
              <repository>
                     <id>java.net</id>
                     <url>http://download.java.net/maven/2/</url>
              </repository>
       </repositories>
</project>

Now, open the command prompt, go to the root directory and then type mvn eclipse:eclipse. This will generate the Eclipse configuration files. For instance, you'll see the files .classpath, .project created under the root directory.

Note that, the following segment adds the Tomcat 7 plugin to the project.
<build>
    <plugins>
         <plugin>
              <groupId>org.apache.tomcat.maven</groupId>
              <artifactId>tomcat7-maven-plugin</artifactId>
              <version>2.0</version>
              <!-- version 2.2 if you're using JDK 1.8 -->
          </plugin>
    </plugins>
</build>

Now, let's import the project from eclipse. You can import the project either as 'General -> Existing Projects into Workspace' or as 'Maven -> Existing Maven Projects'.

Time to create some java source packages. Let's create the following packages under the 'src/java/main' directory. I believe the package names are self-descriptive.
  • np.com.mshrestha.bookstore.dao
  • np.com.mshrestha.bookstore.dao.impl
  • np.com.mshrestha.bookstore.service
  • np.com.mshrestha.bookstore.service.impl
  • np.com.mshrestha.bookstore.controller
Then, let's create another folder 'WEB-INF' under webapp directory and add the deployment descriptor web.xml under WEB-INF. The web.xml defines root spring container and the heart of the spring MVC - 'DispatcherServlet'.

web.xml   ( path: /webapp/WEB-INF/web.xml )

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee                 http://java.sun.com/xml/ns/javaee/web-app.xsd">

       <!-- The definition of the Root Spring Container shared by all Servlets
            and Filters -->

       <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>/WEB-INF/spring/root-context.xml</param-value>
       </context-param>

       <!-- Creates the Spring Container shared by all Servlets and Filters -->
       <listener>
              <listener-class>
                   org.springframework.web.context.ContextLoaderListener
              </listener-class>
       </listener>

       <!-- Processes application requests -->
       <servlet>
              <servlet-name>dispatcherServlet</servlet-name>
              <servlet-class>
                   org.springframework.web.servlet.DispatcherServlet
              </servlet-class>
              <init-param>
                     <param-name>contextConfigLocation</param-name>
                     <param-value>
                           /WEB-INF/spring/dispatcherServlet/servlet-context.xml
                     </param-value>
              </init-param>
              <load-on-startup>1</load-on-startup>
       </servlet>

       <servlet-mapping>
              <servlet-name>dispatcherServlet</servlet-name>
              <url-pattern>/</url-pattern>
       </servlet-mapping>
</web-app>


In the above web.xml, we see the two configuration xmls. I intentionally created those two config xmls to let you know that we can initialize parameters in two ways. This will be needed if you have multiple servlets and filters that shares common parameters. You can define only one servlet config xml and put all of contents from both xmls into the one. The tag <context-param> defines parameters that are shared by all servlets and filters whereas <init-param> defines parameters that are accessible by only that <servlet> inside which it is placed. We also defined the DispatcherServlet which is the default request handler for all the requests as defined by the pattern "/" in <url-pattern>.

<web-app>:
  • Defines the root element of the document called Deployment Descriptor
<context-param>:
  • This is an optional element
  • Contains the declaration of a Web application's servlet context initialization parameters available to the entire scope of the web application
  • The methods for accessing context init parameter is as follows:
    • String paramName = getServletContext().getInitParamter("paramName")
<init-param>:
  • An optional element within the servlet
  • Defines servlet configuration initialization parameters only available to the servlet inside which it's defined
  • The methods for accessing servlet init parameter is as follows:
    • String paramName = getServletConfig().getInitParamter("paramName")
<listener>:
  •  Defines a listener class to start up and shut down Spring's root application context
  • You might not need this if you don't define the element <context-param>
<servlet>:
  • Declares a servlet including a refering name, defining class and initialization parameters
  • You can define multiple servlets in the document
  • Interesting thing is you can even declare multiple servlets using the same class with different initialization parameters
  • The name of each servlet must be unique
<servlet-mapping>:
  • Maps a URL pattern to a servlet
  • In the above case, all requests will be handled by the DispatcherServlet named dispatcherServlet

Now, let's create them in the respective locations too. You can put both xmls alongside web.xml under WEB-INF. But I put them one under WEB-INF/spring directory and another under WEB-INF/spring/dispatcherServlet. Let's look at those xmls.

servlet-context.xml   ( path: /webapp/WEB-INF/spring/dispatcherServlet/servlet-context.xml )

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
       default-lazy-init="true">

       <!-- DispatcherServlet Context: defines this servlet's request-processing
              infrastructure -->

       <!-- Enables the Spring MVC @Controller programming model -->
       <annotation-driven />
       <context:component-scan base-package="np.com.mshrestha.bookstore.controller" />

       <!-- Handles HTTP GET requests for /web-resources/** by efficiently serving
              up static resources in the ${webappRoot}/resources/ directory -->
       <resources mapping="/web-resources/**" location="/resources/" />

       <!-- Resolves views selected for rendering by @Controllers to .jsp resources
              in the /WEB-INF/views directory -->
       <beans:bean
              class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <beans:property name="prefix" value="/WEB-INF/views/" />
              <beans:property name="suffix" value=".jsp" />
       </beans:bean>

</beans:beans>


The various components from the above xmls are described as follows:

<annotation-driven />:

  • Initialiazes components required for dispatching the requests to your Controllers
  • Registers a RequestMappingHandlerMapping, a RequestMappingHandlerAdapter, and an ExceptionHandlerExceptionResolver (among others) in support of processing requests with annotated controller methods using annotations such as @RequestMapping, @ExceptionHandler, and others
  • Configures support for new Spring MVC features such as declarative validation with @Valid, HTTP message conversion with @RequestBody/@ResponseBody, formatting Number fields with @NumberFormat etc.
  • This element should be placed in your DispatcherServlet context (or in your root context if you have no DispatcherServlet context defined)

<context:component-scan>:

  • Scans for the classes annotated with @Component, @Service, @Repository and @Controller defined under the base-package and registers their corresponding beans
  • @Component serves as a generic stereotype for any Spring-managed component; whereas, @Repository@Service, and @Controller serve as specializations of @Component for more specific use cases (e.g., in the persistence, service, and presentation layers, respectively)
  • This tag allows static resource requests following a particular URL pattern to be served by a ResourceHttpRequestHandler from any of a list of Resource location(s)
  • This provides a convenient way to serve static resources from locations other than the web application root, including locations on the classpath
  • e.g. <mvc:resources mapping="/resources/**" location="/, classpath:/web-resources/"/>
InternalResourceViewResolver:
  • Spring provides different view resolvers to render models in a browser without tying you with a specific view technology.
  • Out of the box, Spring enables you to use JSPs, Velocity templates and XSLT views.
  • InternalResourceViewResolver is one of them.
  • In the above example, when returning test as a viewname, this view resolver will hand the request over to the RequestDispatcher that will send the request to /WEB-INF/views/test.jsp.
  • For more details:

root-context.xml     ( path: /webapp/WEB-INF/spring/root-context.xml )

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx.xsd">

       <!-- Root Context: defines shared resources visible to all other web components -->

       <bean id="propertyConfigurer"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
           
           <property name="locations">
                 <list>
                      <value>classpath:database.properties</value>
                 </list>
           </property>
       </bean>

       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close" 
              p:driverClassName="${jdbc.driverClassName}"
              p:url="${jdbc.url}" 
              p:username="${jdbc.username}" 
              p:password="${jdbc.password}" />

       <bean id="sessionFactory"
              class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
              <property name="dataSource" ref="dataSource" />

              <property name="packagesToScan">
                     <list>
                           <value>np.com.mshrestha.bookstore.model</value>
                     </list>
              </property>
              <property name="hibernateProperties">
                     <props>
                           <prop key="hibernate.hbm2ddl.auto">update</prop>
                           <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                           <prop key="hibernate.show_sql">false</prop>
                     </props>
              </property>
       </bean>

       <bean id="transactionManager"
              class="org.springframework.orm.hibernate4.HibernateTransactionManager">
              <property name="sessionFactory" ref="sessionFactory" />
       </bean>

       <tx:annotation-driven transaction-manager="transactionManager" />
       <context:component-scan base-package="np.com.mshrestha.bookstore" />
</beans>



The database property file looks like as follows:

database.properties    ( path: /src/main/resources/database.properties )
jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost/bookstore?createDatabaseIfNotExist=true&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;autoReconnect=true

jdbc.username=root
jdbc.password=root

hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


Finally, we're done setting up the project. Let's deal with rest of the parts.

Persistence

Let's create a persistent annotated POJO class 'Book' under the respective model package 'np.com.mshrestha.bookstore.model'. 


package np.com.mshrestha.bookstore.model;

import java.sql.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "book")
public class Book {

       private Long id;
       private String name;
       private String code;
       private String price;
       private String authors;
       private String isbn;
       private String publisher;
       private Date publishedOn;

       @Id
       @GeneratedValue(strategy = GenerationType.AUTO)
       public Long getId() {
              return id;
       }

       @Column(nullable = false)
       public String getName() {
              return name;
       }

       @Column(length = 15, nullable = false)
       public String getCode() {
              return code;
       }

       @Column(length = 10)
       public String getPrice() {
              return price;
       }

       @Column(nullable = false)
       public String getAuthors() {
              return authors;
       }

       @Column
       public String getIsbn() {
              return isbn;
       }

       @Column
       public String getPublisher() {
              return publisher;
       }

       @Column(name = "published_date")
       public Date getPublishedOn() {
              return publishedOn;
       }

       // setter methods...
}


In the above example, I've shown different attributes we can use for @Column annotation such as namelengthnullable.

Couple points to notice in the above POJO. If you look into the import statements, all the annotations are imported from the package javax.persistence rather than from the package org.hibernate.annotations. The package javax.persistence is provided by Java Persistence API (JPA) whereas org.hibernate.annotations is provided by Hibernate. For the reason behind this, please go to:
Another thing is, the annotations are applied on the getter methods rather than on the fields. You can put annotations either all on the fields or all on the getter methods. It's just a matter of preference which approach to follow. Some people prefer annotations on the fields while others prefer annotations on the getter methods. But don't mix up both approaches unless you know well to use the @Access annotation.You can go to the following links to see the discussion regarding which one is better:


DAO Layer

Now, time to work on the Data Access Object (DAO) layer to communicate with database. Let's create an interface BookDao under package np.com.mshrestha.bookstore.dao and it's implementation class BookDaoImpl under package np.com.mshrestha.bookstore.dao.impl.


package np.com.mshrestha.bookstore.dao;

import java.util.List;
import np.com.mshrestha.bookstore.model.Book;

public interface BookDao {

       /*
        * CREATE and UPDATE
        */
       public void saveBook(Book book); // create and update

       /*
        * READ
        */
       public List<Book> listBooks();
       public Book getBook(Long id);

       /*
        * DELETE
        */
       public void deleteBook(Long id);
}


package np.com.mshrestha.bookstore.dao.impl;

import java.util.List;

import np.com.mshrestha.bookstore.dao.BookDao;
import np.com.mshrestha.bookstore.model.Book;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {

       @Autowired
       private SessionFactory sessionFactory;

       public void saveBook(Book book) {
              
              getSession().merge(book);
       }

       public List<Book> listBooks() {

              return getSession().createCriteria(Book.class).list();
       }

       public Book getBook(Long id) {
              return (Book) getSession().get(Book.class, id);
       }

       public void deleteBook(Long id) {

              Book book = getBook(id);

              if (null != book) {
                     getSession().delete(book);
              }
       }

       private Session getSession() {
              Session sess = getSessionFactory().getCurrentSession();
              if (sess == null) {
                     sess = getSessionFactory().openSession();
              }
              return sess;
       }

       private SessionFactory getSessionFactory() {
              return sessionFactory;
       }
}


The method saveBook() calls a hibernate method Sesson.merge(). We're going to call the same method for both saving a new book as well as updating an existing book. Sesson.merge() takes a good care of that. It copies the state of the passed object onto the persistent object with the same identifier. If there is no persistence instance currently associated with the session, it will be loaded. And returns the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The passed object will not be attached to the session.

Other methods implementation seems to be straight-forward.

The class BookDaoImpl contains two Spring annotations @Repository and @Autowired.
  • @Repository
    • Indicates that the annotated class (BookDaoImpl in this case) is a Repository or DAO
    • A stereotype ( a specialization of @Component ) for persistence layer
  • @Autowired
    • Marks the field ( or it could be a constructor or a setter method) as to be autowired by Spring's dependency injection facilities
    • The field is injected right after construction of the bean, before any config methods are invoked
    • Autowires the bean by matching the data type in the configuration metadata
Now, we're done with DAO layer. Let's move ahead to Service layer.

Service Layer

The Service Layer is a bridge between the DAO (Persistence) layer and the Presentation (Web) layer. Just like we did in DAO layer, let's create an interface BookService under  the package np.com.mshrestha.bookstore.service and it's implementation class BookServiceImpl under the package np.com.mshrestha.bookstore.service.impl.

package np.com.mshrestha.bookstore.service;

import java.util.List;

import np.com.mshrestha.bookstore.model.Book;

public interface BookService {

       /*
        * CREATE and UPDATE
        */
       public void saveBook(Book book);

       /*
        * READ
        */
       public List<Book> listBooks();
       public Book getBook(Long id);

       /*
        * DELETE
        */
       public void deleteBook(Long id);

}


package np.com.mshrestha.bookstore.service.impl;

import java.util.List;

import np.com.mshrestha.bookstore.dao.BookDao;
import np.com.mshrestha.bookstore.model.Book;
import np.com.mshrestha.bookstore.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookServiceImpl implements BookService {

       @Autowired
       private BookDao bookDao;

       @Transactional
       public void saveBook(Book book) {
              bookDao.saveBook(book);
       }

       @Transactional( readOnly = true)
       public List<Book> listBooks() {
              return bookDao.listBooks();
       }

       @TransactionalreadOnly = true)
       public Book getBook(Long id) {
              return bookDao.getBook(id);
       }

       @Transactional
       public void deleteBook(Long id) {
              bookDao.deleteBook(id);

       }
}


In the above class, we can see three Spring annotations: @Service @Autowired @Transactional
  • @Service
    • Indicates that the annotated class (BookServiceImpl in this case) is a "Service"
    • A stereotype (a specialiation of @Component) for service layer
  • @Autowired
    • Marks the field ( or it could be a constructor or a setter method) as to be autowired by Spring's dependency injection facilities
    • The field is injected right after construction of the bean, before any config methods are invoked
    • Autowires the bean by matching the data type in the configuration metadata
  • @Transactional
    • Enables Spring's transactional behaviour
    • Can be applied to an interface, a class or a method
    • This annotation is enabled by putting <tx:annotation-driven/> in the context configuration file.
    • The attribute readOnly = true sets the transaction to read only mode so that by any chance it cannot modify data.

Presentation ( Web ) Layer

Controller 

Let's create a controller that handles the web requests dispatched via. DispatcherServlet. Let the name of the controller be BookController and put it under the package np.com.mshrestha.bookstore.controller.

BookController.java

package np.com.mshrestha.bookstore.controller;

import java.util.Map;

import np.com.mshrestha.bookstore.model.Book;
import np.com.mshrestha.bookstore.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/book")
public class BookController {

       @Autowired
       private BookService bookService;

       @RequestMapping(value = { "/", "/listBooks" })
       public String listBooks(Map<String, Object> map) {

              map.put("book", new Book());

              map.put("bookList", bookService.listBooks());

              return "/book/listBooks";
       }

       @RequestMapping("/get/{bookId}")
       public String getBook(@PathVariable Long bookId, Map<String, Object> map) {

              Book book = bookService.getBook(bookId);

              map.put("book", book);

              return "/book/bookForm";
       }

       @RequestMapping(value = "/save", method = RequestMethod.POST)
       public String saveBook(@ModelAttribute("book") Book book,
                     BindingResult result) {

              bookService.saveBook(book);

              /*
               * Note that there is no slash "/" right after "redirect:"
               * So, it redirects to the path relative to the current path
               */
              return "redirect:listBooks";
       }

       @RequestMapping("/delete/{bookId}")
       public String deleteBook(@PathVariable("bookId") Long id) {

              bookService.deleteBook(id);

              /*
               * redirects to the path relative to the current path
               */
              // return "redirect:../listBooks";

              /*
               * Note that there is the slash "/" right after "redirect:"
               * So, it redirects to the path relative to the project root path
               */
              return "redirect:/book/listBooks";
       }
}

Let's go through the Spring annotations in the above controller:
  • @Controller
    • Indicates that the annotated class (BookController) serves the role of a "Controller"
    • A stereotype (a specialiation of @Component) for web layer
    • To enable autodetection of such annotated controllers, you add component scanning to your configuration ( see: servlet-context.xml )
    • <context:component-scan base-package="np.com.mshrestha.bookstore.controller" />
  • @Autowired
    • Marks the field ( or it could be a constructor or a setter method) as to be autowired by Spring's dependency injection facilities
    • The field is injected right after construction of the bean, before any config methods are invoked
    • Autowires the bean by matching the data type in the configuration metadata
  • @RequestMapping
    • DispatcherServlet uses this annotation to dispatch the requests to the correct controller and the handler method.
    • Maps URLS or web requests onto specific handler classes (e.g. in the above controller class @RequestMapping("/book") and/or handler methods ( e.g. in the above example @RequestMapping("/get/{bookId}")
    • RequestMapping element value is a String array ( String[] ) so it can accept multiple URL paths
    • e.g. @RequestMapping(value = { "/""/listBooks" })
    • The handler methods which are annotated with this annotation are allowed to have very flexible return types and method signatures. Please go through the spring documentation for that.
I want to go little further on the following annotations. 


It binds the method parameter to a URI template variable. URI template is a parameterized URI i.e. URI having one or more variables. For instance, the URI template http://localhost:8080/bookstore/book/delete/{bookId} contains the variable bookId. Assigning the value 10 to the variable yields http://localhost:8080/bookstore/book/delete/10


       @RequestMapping("/delete/{bookId}")
       public String deleteBook(@PathVariable("bookId") Long id) {

              bookService.deleteBook(id);

              return "redirect:/book/listBooks";
       }

The URI Template "/delete/{bookId}" specifies the variable name bookId. When the controller handles this request, the value of bookId is set to the value found in the appropriate part of the URI. For example, when a request comes in for /delete/10, the value of bookId is 10.

If the URI template variable name matches the method argument name, we can omit the value element of PathVariable. As long as your code is not compiled without debugging information, Spring MVC will match the method argument name to the URI template variable name. This scenario has been depicted in the method BookController.getBook(). Or you could write the above example as follows: 


       @RequestMapping("/delete/{bookId}")
       public String deleteBook(@PathVariable Long bookId) {

              bookService.deleteBook(bookId);

              return "redirect:/book/listBooks";
       }




In a nutshell, this annotation is used for preparing the model data and also to define the command object that would be bound with the HTTP request data. This annotation can be applied on the methods as well as on the method arguments. In the above controller example, we used it on the argument of the saveBook() method. So, I'll be explaing its usage only on the method arguments.

An @ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument's fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.


       @RequestMapping(value = "/save", method = RequestMethod.POST)
       public String saveBook(@ModelAttribute("book") Book book,
                     BindingResult result) {

              bookService.saveBook(book);

              /*
               * redirects to the path w.r.t the current path
               */
              return "redirect:listBooks";
       }

In the above example, the Book instance can come from several options as follows:
  • It may already be in the model due to use of @SessionAttributes storing model attributes in the HTTP session between requests
  • It may already be in the model due to an @ModelAttribute method in the same controller
  • It may be retrieved based on a URI template variable in conjunction with Type Converter
  • It may be instantiated using its default constructor
I won't go through each of the above points which is beyong the scope of this tutorial. If you would like to go through all of the above points, here's the Spring documentation for that.
Finally, we're done with the Controller class too. Now, let's move ahead to Views i.e. JSPs.

JSPs

Time to create some JSPs. We can have separate JSP pages for creating, editing and listing out the book records. However, in this tutorial, I'm gonna show you a little different way where adding and editing is done in a JQuery Dialog. We'll be creating two JSP pages:

  • bookForm.jsp
    • contains a form common for both Add and Edit actions.
  • listBooks.jsp
    • lists out all the book records
    • deletes specific record from the list
    • includes bookForm.jsp for adding and editing
bookForm.jsp    ( path: /webapp/WEB-INF/views/book/bookForm.jsp )

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

<c:url var="actionUrl" value="save" />

<form:form id="bookForm" commandName="book" method="post"
       action="${actionUrl }" class="pure-form pure-form-aligned">

     <fieldset>
          <legend></legend>

          <div class="pure-control-group">
              <label for="name">Name</label>
              <form:input path="name" placeholder="Book Name" />
          </div>
          <div class="pure-control-group">
               <label for="code">Code</label>
               <form:input path="code" placeholder="Code" maxlength="15" />
          </div>
          <div class="pure-control-group">
              <label for="price">Price</label>
              <form:input path="price" placeholder="Price" maxlength="10" />
          </div>
          <div class="pure-control-group">
              <label for="authors">Author(s)</label>
              <form:input path="authors" placeholder="Authors" />
          </div>
          <div class="pure-control-group">
              <label for="isbn">ISBN</label>
              <form:input path="isbn" placeholder="ISBN" />
          </div>
          <div class="pure-control-group">
              <label for="publisher">Publisher</label>
              <form:input path="publisher" placeholder="Publisher" />
          </div>
          <div class="pure-control-group">
              <label for="publishedOn">Published On</label>
              <form:input path="publishedOn" placeholder="YYYY-MM-DD" class="datepicker" />
          </div>

          <form:input path="id" type="hidden" />
      </fieldset>
</form:form>

The various components of the above jsp are explained below:
  • Added taglib directive to use spring's Form tag and JSTL core tag.
  • The attribute commandName="book" puts the book object in the PageContext so that is it can be accessed by inner tags like <form:input>
  • You might have seen another form attribute modelAttribute. There is no difference between them, two different attributes exist for some historical reasons. 
  • Remember that it is the book object that we put as a model in the controller. Please refer the methods BookController.listBooks() and BookController.getBook()
  • When a page is requested, the controller puts the command object, book in this case, in the PageContext. Then Spring binds the object properties with the form elements using path attribute.
  • By default Spring sets the id and name of the input field from the path attribute value. Having said that you can explicitly set the id. Name attribute can not be overridden though. 
  • A command class can basically be any Java class; the only requirement is a no-arg constructor. The command class should preferably be a JavaBean in order to be able to populate bean properties with request parameters.
  • <form:input path="id" type="hidden" />
    • The hidden field holds the id of a book object. Required for editing a book record.
  • The classes pure-formpure-form-aligned and pure-control-group have nothing to do with Spring. They used merely used for styling purpose. To be more specifix, they came from purecss.io.
listBooks.jsp   ( path: /webapp/WEB-INF/views/book/listBooks.jsp )

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<html>
<head>
<title>List Of Books</title>

<link rel="stylesheet" href='<c:url value="/web-resources/css/pure-0.4.2.css"/>'>

<link rel="stylesheet"
       href='<c:url value="/web-resources/css/font-awesome-4.0.3/css/font-awesome.css"/>'>
<link rel="stylesheet" 
       href='<c:url value="/web-resources/css/jquery-ui-1.10.4.custom.css"/>'>
<style type="text/css">
  th {
       text-align: left
  }

</style>
</head>

<body>
   <div style="width: 95%; margin: 0 auto;">
       
        <div id="bookDialog" style="display: none;">
            <%@ include file="bookForm.jsp"%>
        </div>

        <h1>List Of Books</h1>

        <button class="pure-button pure-button-primary" onclick="addBook()">
            <i class="fa fa-plus"></i> Add Book
         </button>
         <br>
         <table class="pure-table pure-table-bordered pure-table-striped">
            <thead>
               <tr>
                  <th width="4%">S.N</th>
                  <th width="12%">Name</th>
                  <th width="12%">Code</th>
                  <th width="12%">Price</th>
                  <th width="12%">Authors</th>
                  <th width="12%">ISBN</th>
                  <th width="12%">Publisher</th>
                  <th width="12%">Published On</th>
                  <th width="12%"></th>
               </tr>
            </thead>
            <tbody>
               <c:forEach items="${bookList}" var="book" varStatus="loopCounter">
               <tr>
                   <td><c:out value="${loopCounter.count}" /></td>
                   <td><c:out value="${book.name}" /></td>
                   <td><c:out value="${book.code}" /></td>
                   <td><c:out value="${book.price}" /></td>
                   <td><c:out value="${book.authors}" /></td>
                   <td><c:out value="${book.isbn}" /></td>
                   <td><c:out value="${book.publisher}" /></td>
                   <td><c:out value="${book.publishedOn}" /></td>

                   <td>
                     <nobr>
                        <button onclick="editBook(${book.id});"
                                class="pure-button pure-button-primary">
                             <i class="fa fa-pencil"></i> Edit
                        </button>

                        <a href="delete/${book.id}class="pure-button pure-button-primary"
                   onclick="return confirm('Are you sure you want to delete this book?');">
                             <i class="fa fa-times"></i>Delete
                        </a>
                      </nobr>
                   </td>
                </tr>
                </c:forEach>
            </tbody>
         </table>
     </div>

     <!--  It is advised to put the <script> tags at the end of the document body so that they don't block rendering of the page -->
     
    <script type="text/javascript"
       src='<c:url value="/web-resources/js/lib/jquery-1.10.2.js"/>'></script>
    <script type="text/javascript"
       src='<c:url value="/web-resources/js/lib/jquery-ui-1.10.4.custom.js"/>'></script>
    <script type="text/javascript"
       src='<c:url value="/web-resources/js/lib/jquery.ui.datepicker.js"/>'></script>
    <script type="text/javascript"
       src='<c:url value="/web-resources/js/js-for-listBooks.js"/>'></script>
</body>
</html> 

Also, let's define the following JS file that contains the methods we defined in the above JSP page.

js-for-listBooks.js    ( path: /webapp/resources/js/js-for-listBooks.js )

function addBook() {
       $('#bookDialog').dialog("option", "title", 'Add Book');
       $('#bookDialog').dialog('open');
}

function editBook(id) {

       $.get("get/" + id, function(result) {

              $("#bookDialog").html(result);
              $('#bookDialog').dialog("option", "title", 'Edit Book');
              $("#bookDialog").dialog('open');

              initializeDatePicker();
       });
}

function initializeDatePicker() {
       $(".datepicker").datepicker({
              dateFormat : "yy-mm-dd",
              changeMonth : true,
              changeYear : true,
              showButtonPanel : true
       });
}

function resetDialog(form) {

       form.find("input").val("");
}

$(document).ready(function() {

       $('#bookDialog').dialog({

              autoOpen : false,
              position : 'center',
              modal : true,
              resizable : false,
              width : 440,
              buttons : {
                     "Save" : function() {
                           $('#bookForm').submit();
                     },
                     "Cancel" : function() {
                           $(this).dialog('close');
                     }
              },
              close : function() {

                     resetDialog($('#bookForm'));
                     $(this).dialog('close');
              }
       });

       initializeDatePicker();
});



The above listBooks.jsp basically lists out all the book records. When this page is requested, the request is handled by BookController.listBooks(). If you look into the method, it fetches all the book records and puts them in a map with the model name bookList which will be exposed to the web view.

map.put("bookList"bookService.listBooks());
We used JSTL core tag to iterate over the map model and listed them in a table.

<c:forEach items="${bookList}" var="book" varStatus="loopCounter">

Also, you must have noticed that we also created a hidden div that includes bookForm.jsp.

<div id="bookDialog" style="displaynone;">
     <%@ include file="bookForm.jsp"%>
</div>

The above hidden div has been initialized as a JQuery Dialog in document ready method in js-for-listBooks.js. The dialog has been provided with Save button and Cancel button. The Save button submits the form (id="bookForm") and the Cancel button simply closes the form and resets all the input field values.

Add Book:  When the page listBooks.jsp is requested, the method BookController.listBooks() also instantiate a new Book object and puts it in the map with the model name book.

map.put("book", new Book());
When the Add Book is clicked, the bookDialog pops up. Since it's a new object, the dialog form will be of all empty input fields, including the hidden "id" field. As defined by form action action="save", when the form is submitted, the controller method BookController.saveBook() is called to save the new book record. Since, the id field is empty, Hibernate's merge() method saves it as a new record.

Edit:  Each rows has been provided with an Edit button. When the the button is clicked, it calles the method editBook() method and also passing the corresponding book id as the method argument.  If you look into the method, you'll see that it makes an AJAX call to fetch the corresponding book record by calling the controller method BookController.getBook(). With the book id in the request parameter acquired via. @PathVariable, the controller fetches the corresponding book record, puts it in the map with the model name book and returns the view bookForm.jsp. Then the response is set as the content of bookDialog div and which is then displayed as a dialog. Note that this time the hidden id field will hold the id of the persistent book object. So, when you save the form, Hibernate's merge() method updates the record.

Delete:  When you hit the delete button, it sends the corresponding book id to the controller which retrieves the id via @PathVariable. Note that the delete request is GET method, rather than POST.

Running the Application
Now, running the app is quite simple enough.

  1. Open the Command Prompt
  2. Go to the root project directory ( bookstore )
  3. Run the mvn command to download all dependent JARs.
    • Type mvn and hit <Enter>.
  4. Run Tomcat server 
    • mvn tomcat7:run
  5. Go to the browser and enter the following URL: 
    • http://localhost:8080/bookstore/book/
    • The port number might be different in your case. Please have a look at the tomcat log in console for that.
Download

The source code is available in GitHub. You can either download the zip or directly import the project in eclipse from the following location:

Questions?

I must have missed many things to explain. If you have any questions or feedback, feel free to put them in the comment below.