In this example, we will learn to configure Spring MVC and Hibernate with JPA along with few other things e.g. validation and custom property editors. I have been asked quite enough times to write this tutorial in past, so here it is. Feel free to post your doubts, comments and suggestions. If you want to learn about using hibernate’s session factory then you may want to read Spring and hibernate integration tutorial.
Table of Contents 1) Application Overview 2) Spring's application context configuration 3) DAO classes 4) Maven dependencies 5) Other application files
1) Application Overview
This demo application is an employee management screen which lists down all employee records in database, and a form which you can use to add more employees. There is validation functionality added to form, so if you try to add empty form then you will get validation errors. To store the record in database, Hibernate JPA implementation is used. Front end is implemented using Spring MVC.
The all files involved in this application are as below:
2) Spring’s application context configuration
This file is center piece of application and require your most attention.
spring-servlet.xml
<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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx/ http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <!-- It register the beans in context and scan the annotations inside beans and activate them --> <context:component-scan base-package="com.howtodoinjava.demo" /> <!-- This allow for dispatching requests to Controllers --> <mvc:annotation-driven /> <!-- This helps in mapping the logical view names to directly view files under a certain pre-configured directory --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!-- This resolves messages from resource bundles for different locales --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages" /> </bean> <!-- To validate the posted add employee form --> <bean id="employeeValidator" class="com.howtodoinjava.demo.validator.EmployeeValidator" /> <!-- This produces a container-managed EntityManagerFactory; rather than application-managed EntityManagerFactory as in case of LocalEntityManagerFactoryBean--> <bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- This makes /META-INF/persistence.xml is no longer necessary --> <property name="packagesToScan" value="com.howtodoinjava.demo.model" /> <!-- JpaVendorAdapter implementation for Hibernate EntityManager. Exposes Hibernate's persistence provider and EntityManager extension interface --> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="jpaProperties"> <props> <prop key="hibernate.hbm2ddl.auto">validate</prop> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> </props> </property> </bean> <!-- Simple implementation of the standard JDBC DataSource interface, configuring the plain old JDBC DriverManager via bean properties --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean> <!-- This transaction manager is appropriate for applications that use a single JPA EntityManagerFactory for transactional data access. JTA (usually through JtaTransactionManager) is necessary for accessing multiple transactional resources within the same transaction. --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactoryBean" /> </bean> <!-- responsible for registering the necessary Spring components that power annotation-driven transaction management; such as when @Transactional methods are invoked --> <tx:annotation-driven /> </beans>
Let’s note down few important points.
- context:component-scan : register the beans in context; and it also scans the annotations inside beans and activate them. So
context:component-scan
does whatcontext:annotation-config
does, but additionally it scan the packages and register the beans in application context.
Read More : context:annotation-config vs context:component-scan
- mvc:annotation-driven : tag essentially sets you your Spring context to allow for dispatching requests with annotated controller methods using annotations such as
@RequestMapping
,@ExceptionHandler
, and others.
Read More : Spring MVC Config
- InternalResourceViewResolver : view template decides that which view should be rendered based on returned logical view name by mapping the logical view names to directly view files under a certain pre-configured directory.
Read More : InternalResourceViewResolver Configuration Example
- entityManagerFactoryBean :
LocalEntityManagerFactoryBean
produces an application-managedEntityManagerFactory
whereasLocalContainerEntityManagerFactoryBean
produces a container-managedEntityManagerFactory
. It supports links to an existing JDBC DataSource, supports both local and global transactions. - JpaTransactionManager : This transaction manager is appropriate for applications that use a single JPA
EntityManagerFactory
for transactional data access. JTA (usually throughJtaTransactionManager
) is necessary for accessing multiple transactional resources within the same transaction. Note that you need to configure your JPA provider accordingly in order to make it participate in JTA transactions. Of course,JtaTransactionManager
does require a full JTA-supporting application server, rather than a vanilla servlet engine like Tomcat. - tx:annotation-driven : enable the configuration of transactional behavior based on annotations e.g.
@Transactional
. The@EnableTransactionManagement
annotation provides equivalent support if you are using Java based configuration. To do this. simply add the annotation to a@Configuration
class.
3) DAO classes
Next important classes are DAO classes which use the entity manager to perform CRUD operations using hibernate entities, and specifying methods which support transactions through @Transactional
annotation. In our case, we have applied @Transactional
annotation at class level, making all public methods transactional.
EmployeeDAO.java
public interface EmployeeDAO { public List<EmployeeEntity> getAllEmployees(); public List<DepartmentEntity> getAllDepartments(); public void addEmployee(EmployeeEntity employee); }
EmployeeDAOImpl.java
import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.howtodoinjava.demo.model.DepartmentEntity; import com.howtodoinjava.demo.model.EmployeeEntity; @Repository @Transactional public class EmployeeDAOImpl implements EmployeeDAO { @PersistenceContext private EntityManager manager; public List<EmployeeEntity> getAllEmployees() { List<EmployeeEntity> employees = manager.createQuery("Select a From EmployeeEntity a", EmployeeEntity.class).getResultList(); return employees; } public List<DepartmentEntity> getAllDepartments() { List<DepartmentEntity> depts = manager.createQuery("Select a From DepartmentEntity a", DepartmentEntity.class).getResultList(); return depts; } public DepartmentEntity getDepartmentById(Integer id) { return manager.find(DepartmentEntity.class, id); } public void addEmployee(EmployeeEntity employee) { //Use null checks and handle them employee.setDepartment(getDepartmentById(employee.getDepartment().getId())); manager.persist(employee); } }
@Transactiona
l annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ if you need to annotate non-public methods.@PersistenceContext
expresses a dependency on a container-managed EntityManager
and its associated persistence context. @Repository
is usually applied on DAO layer.
Read More : @Component, @Repository, @Service and @Controller Annotations
4) Maven dependencies
The last thing, you should be interested in this example is maven dependencies for spring, hibernate and JPA extension. Here are all dependencies for this example.
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- Spring MVC support --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!-- Tag libs support for view layer --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.2.0.Final</version> </dependency> <dependency> <groupId>org.hibernate.common</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>4.0.1.Final</version> <classifier>tests</classifier> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>1.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.0.1.Final</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.4</version> </dependency> <dependency> <groupId>org.jboss.logging</groupId> <artifactId>jboss-logging</artifactId> <version>3.1.0.CR2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.10</version> </dependency> </dependencies>
5) Other application files
Now list down, other files also which are used in this example.
web.xml
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee https://www.oracle.com/java/technologies/; <display-name>Spring Hibernate JPA Hello World Application</display-name> <!-- Configuration file for the root application context --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-servlet.xml </param-value> </context-param> <!-- Configuration for the DispatcherServlet --> <servlet> <servlet-name>spring</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
listEmployeeView.jsp
<%@ page contentType="text/html;charset=UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Employee Management</title> <style> .error { color: #ff0000; font-weight: bold; } #listOfEmployees tr:first-child{ font-weight: bold; } </style> </head> <body> <h2><spring:message code="lbl.page.list" text="lbl.page.list" /></h2> <br/> <table id="listOfEmployees" border="1"> <tr> <td>ID</td> <td>First Name</td> <td>Last Name</td> <td>Email</td> <td>Department</td> </tr> <c:forEach items="${allEmployees}" var="employee"> <tr> <td>${employee.id}</td> <td>${employee.firstName}</td> <td>${employee.lastName}</td> <td>${employee.email}</td> <td>${employee.department.name}</td> </tr> </c:forEach> </table> <h2><spring:message code="lbl.page" text="Add New Employee" /></h2> <br/> <form:form method="post" modelAttribute="employee"> <table> <tr> <td><spring:message code="lbl.firstName" text="First Name" /></td> <td><form:input path="firstName" /></td> <td><form:errors path="firstName" cssClass="error" /></td> </tr> <tr> <td><spring:message code="lbl.lastName" text="Last Name" /></td> <td><form:input path="lastName" /></td> <td><form:errors path="lastName" cssClass="error" /></td> </tr> <tr> <td><spring:message code="lbl.email" text="Email Id" /></td> <td><form:input path="email" /></td> <td><form:errors path="email" cssClass="error" /></td> </tr> <tr> <td><spring:message code="lbl.department" text="Department" /></td> <td><form:select path="department" items="${allDepartments}" itemValue="id" itemLabel="name" /></td> <td><form:errors path="department" cssClass="error" /></td> </tr> <tr> <td colspan="3"><input type="submit" value="Add Employee"/></td> </tr> </table> </form:form> </body> </html>
messages.properties
lbl.page=Add New Employee lbl.page.list=List of Employees lbl.firstName=First Name lbl.lastName=Last Name lbl.email=Email Id error.firstName=First Name can not be blank error.lastName=Last Name can not be blank error.email=Email Id can not be blank
EmployeeValidator.java
@Component public class EmployeeValidator implements Validator { public boolean supports(Class clazz) { return EmployeeEntity.class.isAssignableFrom(clazz); } public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "error.firstName", "First name is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "error.lastName", "Last name is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "error.email", "Email is required."); } }
EmployeeManager.java
public interface EmployeeManager { public List<EmployeeEntity> getAllEmployees(); public List<DepartmentEntity> getAllDepartments(); public void addEmployee(EmployeeEntity employee); }
EmployeeManagerImpl.java
@Service public class EmployeeManagerImpl implements EmployeeManager { @Autowired EmployeeDAO dao; public List<EmployeeEntity> getAllEmployees() { return dao.getAllEmployees(); } public List<DepartmentEntity> getAllDepartments() { return dao.getAllDepartments(); } public void addEmployee(EmployeeEntity employee) { dao.addEmployee(employee); } }
EmployeeEntity.java
@Entity @Table (name="employee") public class EmployeeEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Integer id; @NotEmpty private String firstName; private String lastName; private String email; @NotNull @ManyToOne private DepartmentEntity department; public EmployeeEntity() {} public EmployeeEntity(String name, DepartmentEntity department) { this.firstName = name; this.department = department; } public EmployeeEntity(String name) { this.firstName = name; } //Setters and Getters @Override public String toString() { return "EmployeeVO [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ", department=" + department + "]"; } }
DepartmentEntity.java
@Entity @Table (name="department") public class DepartmentEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Integer id; private String name; public DepartmentEntity(){ } public DepartmentEntity(Integer id, String name) { super(); this.id = id; this.name = name; } @OneToMany(mappedBy="department",cascade=CascadeType.PERSIST) private List<EmployeeEntity> employees = new ArrayList<EmployeeEntity>(); //Setters and Getters @Override public String toString() { return "DepartmentVO [id=" + id + ", name=" + name + "]"; } }
EmployeeController.java
@Controller @RequestMapping("/employee-module") @SessionAttributes("employee") public class EmployeeController { @Autowired EmployeeManager manager; private Validator validator; //Bind custom validator for submitted form public EmployeeController() { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); validator = validatorFactory.getValidator(); } /** * Bind DepartmentEditor to DepartmentEntity; Look at JSP file for clearer picture * */ @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(DepartmentEntity.class, new DepartmentEditor()); } /** * Bind all departments * */ @ModelAttribute("allDepartments") public List<DepartmentEntity> populateDepartments() { List<DepartmentEntity> departments = manager.getAllDepartments(); return departments; } /** * Bind all employees list * */ @ModelAttribute("allEmployees") public List<EmployeeEntity> populateEmployees() { List<EmployeeEntity> employees = manager.getAllEmployees(); return employees; } /** * Method will be called in initial page load at GET /employee-module * */ @RequestMapping(method = RequestMethod.GET) public String setupForm(Model model) { EmployeeEntity employeeVO = new EmployeeEntity(); model.addAttribute("employee", employeeVO); return "listEmployeeView"; } /** * Method will be called on submitting add employee form POST /employee-module * */ @RequestMapping(method = RequestMethod.POST) public String submitForm(@ModelAttribute("employee") EmployeeEntity employeeVO, BindingResult result, SessionStatus status) { Set<ConstraintViolation<EmployeeEntity>> violations = validator.validate(employeeVO); for (ConstraintViolation<EmployeeEntity> violation : violations) { String propertyPath = violation.getPropertyPath().toString(); String message = violation.getMessage(); // Add JSR-303 errors to BindingResult // This allows Spring to display them in view via a FieldError result.addError(new FieldError("employee", propertyPath, "Invalid "+ propertyPath + "(" + message + ")")); } if (result.hasErrors()) { return "listEmployeeView"; } // Store the employee information in database manager.addEmployee(employeeVO); // Mark Session Complete and redirect to URL so that page refresh do not re-submit the form status.setComplete(); return "redirect:employee-module"; } }
Please feel free to comment with your questions and suggestions.
Happy Learning !!
Leave a Reply