Sử dụng DataSource JNDI trong Tomcat sẽ giúp ứng dụng web của bạn chuyển đổi cơ sở dữ liệu một cách linh hoạt mà không cần nhiều sự thay đổi trong code. Trong tài liệu này, chúng ta sẽ học cách cách triển khai nó với một ví dụ đơn giản.

DataSource JNDI trong Tomcat
Lợi ích thực sự của DataSource thể hiện rõ nhất khi ta sử dụng nó cùng với ngữ cảnh JNDI (JNDI context). Ví dụ ta có connection pool (nhóm kết nối database có thể được tái sử dụng để tối ưu hiệu suất) trong một ứng dụng web được triển khai trên servlet container. Hầu hết các servlet container phổ biến đều hỗ trợ sẵn DataSource thông qua cấu hình ngữ cảnh Resource và JNDI. Nhờ đó, chúng ta có thể tạo và sử dụng DataSource connection pool chỉ với vài dòng cấu hình.
Bài hướng dẫn này sẽ giới thiệu một ví dụ về cấu hình DataSource JNDI trong Tomcat. Nó hỗ trợ ba cách để cấu hình DataSource trong JNDI context.
- Application context.xml: Đây là cách cấu hình DataSource đơn giản nhất. Tất cả những gì chúng ta cần là một file
context.xmltrong thư mụcMETA-INF. Ta cần định nghĩa một phần tử Resource trong file context này, và container sẽ tự động tải và cấu hình nó. Phương pháp này tuy đơn giản nhưng có một số hạn chế:- File context được đóng gói cùng với file WAR. Dù có một thay đổi nhỏ trong cấu hình, chúng ta đều phải build và deploy lại file WAR mới. Vấn đề tương tự cũng sẽ phát sinh nếu ứng dụng hoạt động trong môi trường phân tán (distributed) hoặc cần triển khai trên nhiều môi trường thử nghiệm khác nhau như QA, IT, PROD, v.v.
- Datasource được container tạo ra chỉ dành riêng cho ứng dụng đó sử dụng, do đó không thể dùng chung trên phạm vi toàn cục. Ta không thể chia sẻ datasource này giữa nhiều ứng dụng khác nhau.
- Nếu một datasource toàn cục được định nghĩa trung tên trong trong
server.xml, datasource của ứng dụng sẽ bị bỏ qua.
- Server context.xml: Nếu server có nhiều ứng dụng và bạn muốn chia sẻ DataSource giữa chúng, ta có thể định nghĩa cấu hình này trong file
context.xmlcủa server. File này thường nằm trong thư mụcconfcủa Tomcat (ví dụ:$CATALINA_BASE/conf/context.xml). Tuy nhiên, phạm vi của filecontext.xmlnày là ở cấp độ ứng dụng. Điều này có nghĩa là nếu bạn định nghĩa một DataSource connection pool với 100 kết nối và server có 20 ứng dụng sử dụng nó, thì datasource sẽ được tạo riêng cho từng ứng dụng. Kết quả là sẽ có 2000 kết nối được tạo ra. Điều này rõ ràng sẽ tiêu tốn toàn bộ tài nguyên của database server và làm giảm hiệu suất ứng dụng rất nhiều. - server.xml và context.xml: Ta có thể định nghĩa DataSource ở cấp độ toàn cục bằng cách khai báo chúng trong phần
<GlobalNamingResources>của fileserver.xml. Khi sử dụng phương pháp này, ta cần định nghĩa một<ResourceLink>trỏ đến tài nguyên toàn cục đó từ filecontext.xmlcủa server hoặccontext.xmlcủa từng ứng dụng cụ thể. Đây là phương pháp được khuyến khích khi bạn muốn chia sẻ một pool tài nguyên chung cho nhiều ứng dụng chạy trên cùng một server. Việc định nghĩa liên kết tài nguyên (resource link) trong filecontext.xmlở cấp server hay cấp ứng dụng sẽ tùy thuộc vào yêu cầu cụ thể của bạn.
Bây giờ, chúng ta hãy cùng xem qua ví dụ về DataSource JNDI của Tomcat trong một ứng dụng web Java. Bạn có thể tham khảo bài viết trước của chúng tôi về ví dụ JDBC DataSource để biết thêm về việc chuẩn bị dữ liệu thử nghiệm.
Ví dụ cấu hình server.xml cho DataSource JNDI trong Tomcat
Hãy thêm đoạn dưới đây vào file server.xml của Tomcat. Nó nên được đặt bên trong phần <GlobalNamingResources>. Đảm bảo rằng driver của cơ sở dữ liệu đã có trong thư mục lib của Tomcat. Ví dụ trong trường hợp này, file JAR của MySQL JDBC driver phải nằm trong thư mục lib.
<Resource name="jdbc/MyDB"
global="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/UserDB"
username="pankaj"
password="pankaj123"
maxActive="100"
maxIdle="20"
minIdle="5"
maxWait="10000"/>
Ở đây, ta đang tạo một JNDI context với tên là jdbc/TestDB thuộc loại DataSource. Ta truyền các thông tin cấu hình cơ sở dữ liệu thông qua các thuộc tính như url, username, password và driverClassName. Các thuộc tính cho connection pool được định nghĩa trong maxActive, maxIdle và minIdle.
Cấu hình resource link trong context.xml
Thêm đoạn dưới đây vào file context.xml của server.
<ResourceLink name="jdbc/MyLocalDB"
global="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource" />
Lưu ý rằng tên của resource link (thuộc tính name trong <ResourceLink>) khác với tên của global link (liên kết JNDI toàn cục đã định nghĩa trong server.xml). Chúng ta phải sử dụng tên resource link này trong chương trình Java để lấy về đúng đối tượng DataSource.
Ví dụ DataSource JNDI trong Tomcat
Hãy tạo một ứng dụng web động với tên là JDBCDataSourceTomcat, sau đó tạo một Servlet với đoạn code như bên dưới.
package com.journaldev.jdbc.datasource;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
@WebServlet("/JDBCDataSourceExample")
public class JDBCDataSourceExample extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Context ctx = null;
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try{
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/MyLocalDB");
con = ds.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select empid, name from Employee");
PrintWriter out = response.getWriter();
response.setContentType("text/html");
out.print("<html><body><h2>Employee Details</h2>");
out.print("<table border=\\"1\\" cellspacing=10 cellpadding=5>");
out.print("<th>Employee ID</th>");
out.print("<th>Employee Name</th>");
while(rs.next())
{
out.print("<tr>");
out.print("<td>" + rs.getInt("empid") + "</td>");
out.print("<td>" + rs.getString("name") + "</td>");
out.print("</tr>");
}
out.print("</table></body><br/>");
//lets print some DB information
out.print("<h3>Database Details</h3>");
out.print("Database Product: "+con.getMetaData().getDatabaseProductName()+"<br/>");
out.print("Database Driver: "+con.getMetaData().getDriverName());
out.print("</html>");
}catch(NamingException e){
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
rs.close();
stmt.close();
con.close();
ctx.close();
} catch (SQLException e) {
System.out.println("Exception in closing DB resources");
} catch (NamingException e) {
System.out.println("Exception in closing Context");
}
}
}
}
Lưu ý rằng ở đây chúng tôi đang sử dụng cấu hình dựa trên Annotation của Servlet 3 (chỉ hoạt động trên Tomcat 7 trở lên).
Với phiên bản Tomcat cũ hơn, bạn cần chỉnh sửa một chút trong code của servlet, cụ thể là xóa annotation @WebServlet và thay bằng cách cấu hình trong file web.xml. Đoạn code mà bạn cần chú ý:
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/MyLocalDB");
Đây là cách để ứng dụng lấy về các JNDI resource đã được định nghĩa bởi ứng dụng. Ta cũng có thể viết theo cách khác như sau:
ctx = new InitialContext();
Context initCtx = (Context) ctx.lookup("java:/comp/env");
DataSource ds = (DataSource) initCtx.lookup("jdbc/MyLocalDB");
Ở đây ta cho in ra một số thông tin về cơ sở dữ liệu để kiểm tra xem ứng dụng đang kết nối tới cơ sở dữ liệu nào. Khi bạn chạy ứng dụng, bạn sẽ thấy kết quả tương tự như sau.

Việc chuyển đổi sang một database server khác là rất dễ khi sử dụng Tomcat DataSource. Tất cả những gì bạn cần làm là thay đổi các thuộc tính cấu hình Database. Ví dụ, nếu muốn chuyển sang sử dụng Oracle database, cấu hình Resource của bạn sẽ trông như thế này:
<Resource name="jdbc/MyDB"
global="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@localhost:1521:orcl"
username="hr"
password="oracle"
maxActive="100"
maxIdle="20"
minIdle="5"
maxWait="10000"/>
Khi bạn khởi động lại server rồi chạy ứng dụng, nó sẽ kết nối tới Oracle database và hiển thị kết quả như bên dưới.

Tổng kết
Vậy là chúng ta đã cùng nhau khám phá cách cấu hình một DataSource JNDI trong Tomcat. Với nguyên tắc tương tự, bạn hoàn toàn có thể định nghĩa thêm nhiều loại tài nguyên khác nữa trong các file context.xml của mình.