Chào mừng bạn đến với hướng dẫn tích hợp Spring DataSource JNDI với Tomcat. Trước đó, chúng ta đã tìm hiểu cách thực hiện các thao tác với cơ sở dữ liệu bằng cách tích hợp Spring JDBC. Tuy nhiên, trong hầu hết các trường hợp, các ứng dụng doanh nghiệp thường được triển khai trên các servlet container như Tomcat, JBoss,…

Spring DataSource JNDI
Chúng ta đều biết rằng sử dụng DataSource với JNDI là cách ưu tiên để đạt được connection pooling và tận dụng lợi ích từ các container. Hôm nay, chúng ta sẽ cùng tìm hiểu cách cấu hình một ứng dụng web Spring để sử dụng kết nối JNDI do Tomcat cung cấp. Trong ví dụ này, tôi sẽ sử dụng MySQL làm máy chủ cơ sở dữ liệu và tạo một bảng đơn giản với một số dòng dữ liệu. Chúng ta sẽ xây dựng một dịch vụ web Spring Rest trả về dữ liệu dưới dạng JSON.
Thiết lập cơ sở dữ liệu
CREATE TABLE `Employee` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Employee` (`id`, `name`, `role`)
VALUES
(1, 'Pankaj', 'CEO'),
(2, 'David', 'Manager');
commit;
Dự án Spring DataSource MVC
Tạo một dự án Spring MVC trong Spring Tool Suite để khởi tạo bộ khung ứng dụng Spring. Sau khi hoàn thành việc triển khai, cấu trúc dự án của chúng ta sẽ giống như hình ảnh bên dưới.

Spring JDBC và Jackson Dependencies
Chúng ta cần thêm các dependency cho Spring JDBC, Jackson và MySQL Database driver trong file pom.xml. File pom.xml cuối cùng của tôi trông như sau.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<https://maven.apache.org/POM/4.0.0>" xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<https://maven.apache.org/POM/4.0.0> <https://maven.apache.org/maven-v4_0_0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringDataSource</artifactId>
<name>SpringDataSource</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
<jackson.databind-version>2.2.3</jackson.databind-version>
</properties>
<dependencies>
<!-- Spring JDBC Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.databind-version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</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>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Nếu bạn chưa quen với Rest trong Spring, vui lòng đọc bài viết Spring Restful Webservice Example.
Lớp Model
Bean Employee của chúng ta được mô phỏng theo bảng Employee, trông như sau.
package com.journaldev.spring.jdbc.model;
import java.io.Serializable;
public class Employee implements Serializable{
private static final long serialVersionUID = -7788619177798333712L;
private int id;
private String name;
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
Lớp Controller trong Spring
Lớp controller đơn giản của chúng ta trông như sau.
package com.journaldev.spring.jdbc.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.journaldev.spring.jdbc.model.Employee;
/**
* Handles requests for the Employee JDBC Service.
*/
@Controller
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
@Autowired
@Qualifier("dbDataSource")
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@RequestMapping(value = "/rest/emps", method = RequestMethod.GET)
public @ResponseBody List<Employee> getAllEmployees() {
logger.info("Start getAllEmployees.");
List<Employee> empList = new ArrayList<Employee>();
//JDBC Code - Start
String query = "select id, name, role from Employee";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<Map<String,Object>> empRows = jdbcTemplate.queryForList(query);
for(Map<String,Object> empRow : empRows){
Employee emp = new Employee();
emp.setId(Integer.parseInt(String.valueOf(empRow.get("id"))));
emp.setName(String.valueOf(empRow.get("name")));
emp.setRole(String.valueOf(empRow.get("role")));
empList.add(emp);
}
return empList;
}
}
Một số điểm quan trọng về lớp Controller:
- DataSource sẽ được Spring cấu hình và inject thông qua bean có tên dbDataSource.
- Chúng ta sử dụng JdbcTemplate để tránh những lỗi phổ biến như rò rỉ tài nguyên và loại bỏ phần code mẫu (boilerplate) trong JDBC.
- URI để truy xuất danh sách Employee sẽ là: https://{host}:{port}/SpringDataSource/rest/emps
- Chúng ta sử dụng @ResponseBody để trả về danh sách đối tượng Employee dưới dạng phản hồi. Spring sẽ tự động chuyển đổi nó thành JSON.
Cấu hình Spring Bean
Có hai cách để thực hiện tra cứu JNDI và liên kết nó với DataSource trong Controller. File cấu hình Spring Bean của tôi chứa cả hai cách, nhưng một trong số đó được comment. Bạn có thể chuyển đổi qua lại giữa hai cách này và kết quả phản hồi sẽ giống nhau.
- Sử dụng thẻ jee trong namespace để thực hiện tra cứu JNDI và cấu hình như một Spring Bean. Trong trường hợp này, chúng ta cũng cần khai báo namespace và schema tương ứng cho jee.
- Tạo một bean kiểu
org.springframework.jndi.JndiObjectFactoryBeanbằng cách truyền vào tên context JNDI. jndiName là tham số bắt buộc trong cấu hình này.
File cấu hình Spring Bean của tôi trông như sau.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="<https://www.springframework.org/schema/mvc>"
xmlns:jee="<https://www.springframework.org/schema/jee>"
xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>" xmlns:beans="<https://www.springframework.org/schema/beans>"
xmlns:context="<https://www.springframework.org/schema/context>"
xsi:schemaLocation="<https://www.springframework.org/schema/jee> <https://www.springframework.org/schema/jee/spring-jee.xsd>
<https://www.springframework.org/schema/mvc> <https://www.springframework.org/schema/mvc/spring-mvc.xsd>
<https://www.springframework.org/schema/beans> <https://www.springframework.org/schema/beans/spring-beans.xsd>
<https://www.springframework.org/schema/context> <https://www.springframework.org/schema/context/spring-context.xsd>">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/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>
<!-- Configure to plugin JSON as request and response in method handler -->
<beans:bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<beans:property name="messageConverters">
<beans:list>
<beans:ref bean="jsonMessageConverter" />
</beans:list>
</beans:property>
</beans:bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<beans:bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</beans:bean>
<!-- Create DataSource Bean -->
<beans:bean id="dbDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<beans:property name="jndiName" value="java:comp/env/jdbc/MyLocalDB"/>
</beans:bean>
<!-- using JEE namespace for lookup -->
<!--
<jee:jndi-lookup id="dbDataSource" jndi-name="jdbc/MyLocalDB"
expected-type="javax.sql.DataSource" />
-->
<context:component-scan base-package="com.journaldev.spring.jdbc.controller" />
</beans:beans>
Cấu hình Spring DataSource JNDI trong Tomcat
Bây giờ dự án đã hoàn tất, bước cuối cùng là cấu hình JNDI trong container Tomcat để tạo resource JNDI.
<Resource name="jdbc/TestDB"
global="jdbc/TestDB"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/TestDB"
username="pankaj"
password="pankaj123"
maxActive="100"
maxIdle="20"
minIdle="5"
maxWait="10000"/>
Thêm cấu hình trên vào phần GlobalNamingResources trong file server.xml.
<ResourceLink name="jdbc/MyLocalDB"
global="jdbc/TestDB"
auth="Container"
type="javax.sql.DataSource" />
Chúng ta cũng cần tạo một ResourceLink để ứng dụng sử dụng cấu hình JNDI, cách tốt nhất là thêm nó vào file context.xml của Tomcat. Lưu ý rằng tên của ResourceLink phải khớp với tên context JNDI mà chúng ta sử dụng trong ứng dụng. Ngoài ra, hãy đảm bảo rằng file jar của MySQL có trong thư mục lib của Tomcat, nếu không Tomcat sẽ không thể tạo pool kết nối đến cơ sở dữ liệu MySQL.
Chạy dự án Spring DataSource JNDI
Dự án và cấu hình máy chủ của chúng ta đã hoàn tất, giờ có thể tiến hành kiểm tra. Xuất dự án dưới dạng file WAR và đặt nó vào thư mục triển khai của Tomcat. Phản hồi JSON cho lời gọi Rest được hiển thị trong hình dưới đây.

Vậy là xong phần tích hợp Spring với JNDI context của servlet container. Tải về dự án mẫu từ liên kết bên dưới và thử nghiệm để hiểu thêm.