Tuesday, 8 November 2016

5. Hibernate Spring DataSource JNDI with Tomcat

In all previous Hibernate tutorials, we were using 'DriverManagerDataSource' provided by Spring to connect to database. However this approach is not very convenient for obvious reasons like database schema password needs to be configured in application itself which is visible to everyone who has access to application deployable file. Also if password changes, application needs to be redeployed.

So other approach which is recommended and widely used is to use 'DataSource' which is configured on web container/application server and connect to that database using JNDI name of datasource.

We will use tomcat to configure datasource and then using Spring MVC application integrated with Hibernate to connect to that datasource to fetch records.

Below will be final project structure once finished.


Let's first configure datasource in tomcat.
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<Resource name="jdbc/rmsystem" 
      auth="Container" 
      type="javax.sql.DataSource" 
      driverClassName="org.postgresql.Driver" 
      url="jdbc:postgresql://localhost:5432/rmsystem" 
      username="postgres" 
      password="password"      
      maxTotal="10" 
      maxIdle="4" 
      minIdle="2" 
      maxWaitMillis="10000"
      validationQuery="SELECT 1"/>

Add this element inside the root element  in a context.xml file. This file is located inside $CATALINA_BASE/conf directory: this is the preferred place because the JNDI DataSource will be available to all web applications and it’s independent of any applications.

Once done, configure this datasource in Spring bean configuration file like below,
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?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:tx="http://www.springframework.org/schema/tx"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

 <context:property-placeholder location="classpath:hibernate.properties"/>
 
 <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="jdbc/rmsystem"></property>
  <property name="resourceRef" value="true"></property>
 </bean>

<!--  <jee:jndi-lookup id="myDataSource" jndi-name="jdbc/rmsystem" expected-type="javax.sql.DataSource" /> -->
 
 <bean id="sessionFactory"
  class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
  <property name="packagesToScan"
   value="in.blogspot.ashish4java.employeesystem.model"></property>
  <property name="dataSource" ref="myDataSource" />
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">${hibernate.dialect}</prop>
    <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
    <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
    <prop key="current_session_context_class">${hibernate.current_session_context_class}</prop>
    <prop key="cache.provider_class">${hibernate.cache.provider_class}</prop>
    <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
    <prop key="hibernate.autocommit">${hibernate.autocommit}</prop>
    <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>  
   </props>
  </property>
 </bean>

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

Note, there are two ways in which we can configure this. I have commented out the second way. Difference in these way is with property 'resourceRef'. This property set whether the look up occurs in a J2EE container, i.e. if the prefix "java:comp/env/" needs to be added if the JNDI name doesn't already contain it. If configured as bean shown in first way then default value is 'false'. So we need to set that property to 'true'. However if datasource is configured using jee:jndi-lookup then default value is 'true' so need to set explicitly to true. After this, this datasource is passed to SessionFactory bean to create and configure  SessionFactory object.

mvc-context.xml
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

 <mvc:annotation-driven />
 <context:component-scan base-package="in.blogspot.ashish4java.employeesystem" />

 <tx:annotation-driven />

 <bean
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
 </bean>

</beans>

EmployeeDao.java
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package in.blogspot.ashish4java.employeesystem.dao;

import java.util.List;

import in.blogspot.ashish4java.employeesystem.model.Employee;

public interface EmployeeDao {

 List<Employee> findAllEmployees();

}

EmployeeDaoImpl.java
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package in.blogspot.ashish4java.employeesystem.dao;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import in.blogspot.ashish4java.employeesystem.model.Employee;

@Repository
public class EmployeeDaoImpl implements EmployeeDao {

 @Autowired
 SessionFactory sessionFactory;

 protected Session getsession() {
  return sessionFactory.getCurrentSession();
 }

 @SuppressWarnings("unchecked")
 @Override
 public List<Employee> findAllEmployees() {
  Session session = getsession();
  Criteria criteria = session.createCriteria(Employee.class);
  return (List<Employee>) criteria.list();
 }

}

EmployeeService.java
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package in.blogspot.ashish4java.employeesystem.service;

import java.util.List;

import in.blogspot.ashish4java.employeesystem.model.Employee;

public interface EmployeeService {

 List<Employee> findAllEmployees();

}

EmployeeServiceImpl.java
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package in.blogspot.ashish4java.employeesystem.service;

import java.util.List;

import org.springframework.transaction.annotation.Transactional;

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

import in.blogspot.ashish4java.employeesystem.dao.EmployeeDao;
import in.blogspot.ashish4java.employeesystem.model.Employee;

@Service
@Transactional
public class EmployeeServiceImpl implements EmployeeService {

 @Autowired
 EmployeeDao employeeDao;

 @Override
 public List<Employee> findAllEmployees() {
  return employeeDao.findAllEmployees();
 }

}

finally, WelcomeController.java
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package in.blogspot.ashish4java.employeesystem.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import in.blogspot.ashish4java.employeesystem.model.Employee;
import in.blogspot.ashish4java.employeesystem.service.EmployeeService;

@Controller
public class WelcomeController {

 @Autowired
 private EmployeeService service;
 
 @RequestMapping(path = "/employeelist", method = RequestMethod.GET)
 public String sendMessage(ModelMap map) {
  List<Employee> empList = service.findAllEmployees();
  map.addAttribute("employeeList", empList);
  return "EmployeeList";
 }

}

As mentioned in controller, this controller accepts GET request at {context-root}/employeeList url and returns 'EmployeeList' String which will translate to 'WEB-INF/jsp/EmployeeList.jsp'

EmployeeList.jsp
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ page session="false"%>
<html>
<head>
<title>Employee System</title>
<style type="text/css">
.tg {
 border-collapse: collapse;
 border-spacing: 0;
 border-color: #ccc;
}

.tg td {
 font-family: Arial, sans-serif;
 font-size: 14px;
 padding: 10px 5px;
 border-style: solid;
 border-width: 1px;
 overflow: hidden;
 word-break: normal;
 border-color: #ccc;
 color: #333;
 background-color: #fff;
 text-align: center;
}

.tg th {
 font-family: Arial, sans-serif;
 font-size: 14px;
 font-weight: normal;
 padding: 10px 5px;
 border-style: solid;
 border-width: 1px;
 overflow: hidden;
 word-break: normal;
 border-color: #ccc;
 color: #333;
 background-color: #f0f0f0;
 text-align: center;
}

.tg .tg-4eph {
 background-color: #f9f9f9
}
</style>
</head>
<body>
 <h1>List of Employees</h1>
 <c:if test="${!empty employeeList}">
  <table class="tg">
   <tr>
    <th width="80">Employee ID</th>
    <th width="120">First Name</th>
    <th width="120">Last Name</th>
    <th width="220">Designation</th>
    <th width="180">Joining Date</th>
   </tr>
   <c:forEach items="${employeeList}" var="employee">
    <tr>
     <td>${employee.employeeId}</td>
     <td>${employee.firstName}</td>
     <td>${employee.lastName}</td>
     <td>${employee.designation}</td>
     <td>${employee.joiningDate}</td>
    </tr>
   </c:forEach>
  </table>
 </c:if>
</body>
</html>

Apart from this, to load some initial data in database once hibernate drops and create new schema, create 'import.sql' file on classpath. Hibernate will execute this file after new schema is created by it.

import.sql

1
2
3
4
INSERT INTO public.employee( employeeid, designation, firstname, joiningdate, lastname, leavingdate) VALUES (1, 'Full Stack Java Developer', 'Ashish', '01/01/2011', 'Gajabi', null);
INSERT INTO public.employee( employeeid, designation, firstname, joiningdate, lastname, leavingdate) VALUES (2, 'CTO', 'John', '09/12/2001', 'Varma', null);
INSERT INTO public.employee( employeeid, designation, firstname, joiningdate, lastname, leavingdate) VALUES (3, 'Solution Designer', 'Richard', '06/14/2007', 'Hank', null);
INSERT INTO public.employee( employeeid, designation, firstname, joiningdate, lastname, leavingdate) VALUES (4, 'Senior Tester', 'Sachin', '03/28/2015', 'Singh', null);

Once all this done, deploy this application on tomcat where we configured datasource and hit the url http://localhost:8080/SpringDataSourceExample/employeelist

Output clearly shows Spring MVC application was successfully able to connect to datasource configured on tomcat and able to fetch list of employees through Hibernate.

No comments:

Post a Comment