Saturday, February 19, 2011

Spring MVC(Simple CRUD)

Hey
In my previouse post we could setup and configure Spring. Now we are ready to develope our own applications. since a large amount of usecases are derived a simple crud use case so in this post I am going to explain a perfect crud and present my own patern on spring mvc. during it I will descibe much aspects of spring mvc which developers deals with them.
A simple crud use case includes 3 major parts.
  1. List page with search and some other operations.
  2. Edit page with save and cancel operations.
  3. View page with edit and back operations.

In this sample we use annotation base version of hibernate to work with DBMS. The domain model is Student. We use DAO pattern to handle this use case. In DAO pattern we have three layers:
  1. controller: to handle the requests and responses
  2. service: to handle business and interface between controller and Dao.
  3. Dao: to access to DBMS.
here is the class diagram:

 the BaseController class is a super class to make some Spring MVC facilities. we use "@ModelAttribute" in this class. this annotation makes spring mvc call the function which has this annotation first in any request then calls related method which handles the request action. The return value of the "@ModelAttribute" method will be set into the model with specified name. Here is the code:
package com.base.controller;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.ui.Model;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BaseController {
    Model model;
    HttpServletRequest request;
    HttpServletResponse response;

    public Model getModel() {
        return model;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

    @ModelAttribute("command")
    public BaseController init(HttpServletRequest request, HttpServletResponse response, Model model) {
        this.model = model;
        this.request = request;
        this.response = response;
        ServletRequestDataBinder binder = new ServletRequestDataBinder(this, "command");
        binder.bind(request);
        return this;
    }
}
 
In "init" method we first initiate the class fields from the method parameters. It make for sub-classes to access these objects easier against to define theme in their methods parameters. second we make "ServletRequestDataBinder" to bind any request parameters to the controller fields. finally we return the current object to be set in the model (by "@ModelAttribute") with the name "command" to use it in our jsp pages. on the other hand in my pattern I use controller class both to handle actions and also to be a kind of super model to be used in JSPs by preparing any getter methods which is needed in it. so if we have a parameter name "student.id" in request, it will be bound to this.student.id. Notice, do not worry about creating objects. spring will handle it.
We can have different kinds of variables in any spring mvc methods like:
  • HttpServletRequest
  • HttpServletResponse
  • Model model
  • ...
and even annotated variables:
  • @ModelAttribute: will binds to the parameter object from request parameters. For example:
    public String save(@ModelAttribute("student") Student student){}
    first a student will be created then any request parameters fields with name of "student." will be bound to this object. for example if we have a request parameter with the name "student.name" will be set into the student.name field and so on.
  • @RequestParam: we can use request parameters on this way. It will converts any data types if compatibility is available. for example:
    public String delete(@RequestParam(value = "ids", required = true) Long[] ids) {}
    It means any request parameters with name "ids" should be set into ids. "required = true" means the request should contain parameter name to call this method. if not, this method will not be called even we map it directly by "@RequestMapping".
"StudentController" extends "BaseController". It is the major controller and maps any request action containing "/student".
package com.simplecrud;

import com.base.controller.BaseController;
import com.simplecrud.domain.Student;
import com.simplecrud.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@Scope("prototype")
@RequestMapping("/student")
public class StudentController extends BaseController {
    @Autowired
    StudentService studentService;

    Student student;

    public StudentService getStudentService() {
        return studentService;
    }

    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    @RequestMapping("/list")
    public String list() {
        if (student == null) {
            student = new Student();
            student.setName("");
        }

        getModel().addAttribute("studentList", studentService.findStudents(student.getName()));
        return "student.list";
    }

    @RequestMapping("/edit")
    public String edit() {
        student = studentService.findById(student.getId());
        return "student.edit";
    }

    @RequestMapping("/add")
    public String add() {
        student = null;
        return "student.edit";
    }

    @RequestMapping("/view")
    public String view() {
        student = studentService.findById(student.getId());
        return "student.view";
    }

    @RequestMapping("/save")
    public String save() {
        studentService.saveOrUpdate(student);
        getModel().addAttribute("student.id", student.getId());
        return "redirect:view.html";
    }

    @RequestMapping("/delete")
    public String delete(@RequestParam(value = "ids", required = true) Long[] ids) {
        studentService.deleteAll(ids);
        return list();
    }
}
we have two new annotations here:
  1. @Scope("prototype"): the default scope of spring components are singleton. it means we have 1 instance of it in the whole spring context. but if we set this annotation, we make spring to create this component in any requests. so we will not have concurrency conflicts in object field.
  2.  @Autowired: this annotation makes spring to create the the field object or call method in any time the containing object is creating. for example in this sample "StudentService studentService" will be fed from spring context any time "StudentController" is creating.
"StudentServiceImpl" is a class which implements "StudentService". As I mentioned, in service layer, we can handle our business. spring has an annotation to make it as a component called "@Service". here is the code:
package com.simplecrud.service;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import com.simplecrud.domain.Student;
import com.simplecrud.dao.StudentDao;
import java.util.List;
import java.util.ArrayList;

@Service
public class StudentServiceImpl implements StudentService {
    @Autowired
    StudentDao studentDao;

    public Student findById(Long id) {
        return studentDao.findById(id);
    }

    public List<Student> findStudents(String name) {
        return studentDao.findStudents(name);
    }

    public void saveOrUpdate(Student student) {
        studentDao.saveOrUpdate(student);
    }

    public void deleteAll(Long[] ids) {
        List<Student> studentList = new ArrayList();
        for (Long id : ids) {
            studentList.add(studentDao.findById(id));
        }
        studentDao.deleteAll(studentList);
    }
}
"StudentDaoImpl" implements "StudentDao" and also extends "BaseDao". "BaseDao" is super class for any Dao class to prepare a "HibernateTemplate" object. here is the code:
package com.base.dao;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;

public class BaseDao {
    private HibernateTemplate hibernateTemplate;

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        hibernateTemplate = new HibernateTemplate(sessionFactory);
    }
}
"HibernateTemplate" is a spring framework ORM class to make us some facilities to work with hibernate. so spring will handle hibernate sessions on this way so we can focus on the code. it will be created from the session factory. the session factory is a spring context bean. so we can get it from the spring context and initiate "hibernateTemplate" by that. "@Autowired" on top of a method means this method should be called whenever the current object is created.
So in "StudentDaoImpl" class we will have "hibernateTemplate" and we can apply it. here is the code:
package com.simplecrud.dao;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import com.base.dao.BaseDao;
import com.simplecrud.domain.Student;
import java.util.List;

@Repository
public class StudentDaoImpl extends BaseDao implements StudentDao {

    public Student findById(Long id) {
        List<Student> studentList = getHibernateTemplate().findByCriteria(DetachedCriteria.forClass(Student.class).add(Restrictions.eq("id", id)));
        if(studentList.size()>0){
            return studentList.get(0);
        } else {
            return null;
        }

    }

    public List<Student> findStudents(String name) {
        return getHibernateTemplate().findByCriteria(DetachedCriteria.forClass(Student.class).add(Restrictions.like("name", name, MatchMode.ANYWHERE)));
    }

    @Transactional
    public void saveOrUpdate(Student student) {
        getHibernateTemplate().saveOrUpdate(student);
    }

    @Transactional
    public void deleteAll(List<Student> studentList) {
        getHibernateTemplate().deleteAll(studentList);
    }
}
spring has an annotation to make it as a component called "@Repository". "@Service" and "@Repository" both are spring "@Component" and they are the same in this version. But its better to specify them separately for future use of newer spring versions.
"@Transactional" annotation make the method be called transactional. if exception is thrown from the middle of the method, the transaction will rollback so all saved date during this transaction will be rolled back. else the transaction will be committed. we will assign the transaction management to the spring on this way. we just set this annotation on the top of the transactional method. Transaction semantics such as propagation settings, the isolation level, the rollback rules, etc are all defined in the annotation also. it has extensive consents so I will explain it in my future posts.
the last configuration which is needed is to create a session factory in spring configuration. we do it in hibernate-config.xml file:
<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

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

    <beans:bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <beans:property name="packagesToScan" value="com.simplecrud"/>
        <beans:property name="hibernateProperties">
            <beans:props>
                <beans:prop key="hibernate.dialect">${jdbc.dialect}</beans:prop>
                <!--<prop key="connection.pool_size">${jdbc.connectionPoll}</prop>-->
                <beans:prop key="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</beans:prop>
                <beans:prop key="hibernate.c3p0.min_size">3</beans:prop>
                <beans:prop key="hibernate.c3p0.max_size">15</beans:prop>
                <beans:prop key="hibernate.c3p0.timeout">10</beans:prop>
                <beans:prop key="hibernate.c3p0.max_statements">10</beans:prop>
                <beans:prop key="hibernate.c3p0.idle_test_period">120</beans:prop>
                <beans:prop key="hibernate.show_sql">true</beans:prop>
            </beans:props>
        </beans:property>
        <beans:property name="dataSource">
            <beans:ref bean="dataSource"/>
        </beans:property>
    </beans:bean>

    <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <beans:property name="driverClassName">
            <beans:value>${jdbc.driverClassName}</beans:value>
        </beans:property>
        <beans:property name="url">
            <beans:value>${jdbc.url}</beans:value>
        </beans:property>
        <beans:property name="username">
            <beans:value>${jdbc.username}</beans:value>
        </beans:property>
        <beans:property name="password">
            <beans:value>${jdbc.password}</beans:value>
        </beans:property>
    </beans:bean>
    <beans:bean id="transactionManager"
                class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <beans:property name="sessionFactory">
            <beans:ref bean="sessionFactory"/>
        </beans:property>
    </beans:bean>
    <tx:annotation-driven/>

</beans:beans>
since the data source properties are in database.properties:
jdbc.dialect=org.hibernate.dialect.MySQLDialect
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/simplecruddb?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.username=root
jdbc.password=root
so we should define a "PropertyPlaceholderConfigurer" bean in spring context and specify its location property to "classpath:database.properties". From the here we can use this property file keys in our configuration. then we create "dataSource" and "sessionFactory" beans and configure their properties and also create "transactionManager" bean by "org.springframework.orm.hibernate3.HibernateTransactionManager" to assign transaction management to it. finally by "<tx:annotation-driven/>" tag we can use "@Transactional" annotation on our method so spring will manage the transactions by "org.springframework.orm.hibernate3.HibernateTransactionManager".
Then in "spring-servlet.xml" we import "hibernate-config.xml" file. here is the code:
<?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: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">

    <context:component-scan base-package="com.simplecrud"/>

    <!--Tiles 2-->
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/layout/tiles-config.xml</value>
            </list>
        </property>
    </bean>

    <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
        <property name="order" value="1"/>
    </bean>

    <!--hibernate-->
    <import resource="classpath:hibernate-config.xml"/>
</beans>
The other configurations is passed in my previouse post.
You can download the full source code. A sample database is avalable in "project-root/db/simplecrud.sql" file. you can restore it in mysql serve 5.0+. you can deploy your project in application server like tomcat so the start page will be: http://localhost:8080/student/list.html


all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

No comments: