在本教程中,我们将创建一个具有基本 CRUD 操作的简单 Java Web 应用。 我们使用 FreeMarker 模板引擎,Servlet 技术和JdbcTemplate
库。 MySQL 用于存储数据。 该应用最终部署在 Tomcat 服务器上。
该教程的资源可从作者的 Github 存储库中获得。
CRUD(创建,读取,更新和删除)是持久性存储的四个基本功能。 对于关系数据库,它们等效于INSERT
,SELECT
,UPDATE
和DELETE
语句。
FreeMarker 是 Java 编程语言的流行模板引擎。 模板以 FreeMarker 模板语言(FTL)编写。 模板引擎将静态数据与动态数据结合起来以生成内容。 模板是内容的中间表示。 它指定如何产生输出。
JDBCTemplate
是用于简化 JDBC 编程的 Spring 库。 它处理乏味且容易出错的底层细节,例如处理事务,清理资源以及正确处理异常。 它包含在 Spring 的 spring-jdbc 模块中。
管理用户
我们的应用将管理用户。 它允许添加新用户,对其进行修改,删除它并列出所有可用用户。
mysql> CREATE TABLE Users(Id INTEGER PRIMARY KEY AUTO_INCREMENT,
Firstname TEXT, Lastname TEXT, Email TEXT);
我们在 MySQL testdb
数据库中创建Users
表。
Excerpt from pom.xml
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.25-incubating</version>
</dependency>
</dependencies>
在 Maven pom.xml
文件中,我们提供四个依赖项:javaee-web-api
是用于开发 Java Web 应用的一组库,spring-jdbc
模块包含 JDBCTemplate 库,mysql-connector-java
是 MySQL Java 驱动程序,而freemarker
是 FreeMarker 库。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/ServletFreemarkerEx">
<Resource name="jdbc/testdb"
auth="Container"
type="javax.sql.DataSource"
username="user7"
password="s$cret"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/testdb"
maxActive="10"
maxIdle="4"/>
</Context>
在context.xml
文件中,我们定义了应用上下文路径(其名称)和一个 MySQL 数据源。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<init-param>
<param-name>TemplatePath</param-name>
<param-value>/WEB-INF/template/</param-value>
</init-param>
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>ResponseCharacterEncoding</param-name>
<param-value>fromTemplate</param-value>
</init-param>
<init-param>
<param-name>ExceptionOnMissingTemplate</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>incompatible_improvements</param-name>
<param-value>2.3.25-incubating</param-value>
</init-param>
<init-param>
<param-name>template_exception_handler</param-name>
<param-value>html_debug</param-value>
</init-param>
<init-param>
<param-name>template_update_delay</param-name>
<param-value>0 s</param-value>
</init-param>
<init-param>
<param-name>default_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>output_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>locale</param-name>
<param-value>en_US</param-value>
</init-param>
<init-param>
<param-name>number_format</param-name>
<param-value>0.##########</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>FreeMarker MVC Views</web-resource-name>
<url-pattern>*.ftl</url-pattern>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
在web.xml
文件中,我们设置了FreemarkerServlet
,该文件用于处理 FreeMarker .ftl
文件。
User.java
package com.zetcode.bean;
public class User {
private Long id;
private String firstName;
private String lastName;
private String email;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
我们有一个User
bean,其中包含四个属性:id
,firstName
,lastName
和email
。
ServiceLocator.java
package com.zetcode.util;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class ServiceLocator {
public static DataSource getDataSource(String jndiName) {
Context ctx = null;
DataSource ds = null;
try {
ctx = new InitialContext();
ds = (DataSource) ctx.lookup(jndiName);
} catch (NamingException ex) {
Logger.getLogger(ServiceLocator.class.getName()).log(
Level.SEVERE, null, ex);
}
return ds;
}
}
ServiceLocator
包含用于查找数据源的代码。 它使用 JNDI API 来完成这项工作。
DatabaseService.java
package com.zetcode.service;
import com.zetcode.bean.User;
import com.zetcode.util.ServiceLocator;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class DatabaseService {
public static User getUserById(Long id) {
String sql = "SELECT * FROM Users WHERE Id = ?";
JdbcTemplate jtm = getJDBCTempate();
User user = (User) jtm.queryForObject(sql, new Object[]{id},
new BeanPropertyRowMapper(User.class));
return user;
}
public static void addUser(User user) {
String sql = "INSERT INTO Users(Firstname, Lastname, Email) VALUES(?, ?, ?)";
JdbcTemplate jtm = getJDBCTempate();
jtm.update(sql, new Object[] {user.getFirstName(),
user.getLastName(), user.getEmail()});
}
public static void deleteUser(Long id) {
String sql = "DELETE From Users WHERE Id = ?";
JdbcTemplate jtm = getJDBCTempate();
jtm.update(sql, new Object[] {id});
}
public static void updateUser(User user) {
String sql = "UPDATE Users SET Firstname=?, Lastname=?, Email=? WHERE Id=?";
JdbcTemplate jtm = getJDBCTempate();
jtm.update(sql, new Object[] {user.getFirstName(),
user.getLastName(), user.getEmail(), user.getId()});
}
public static List<User> getAllUsers() {
String sql = "SELECT * FROM Users";
JdbcTemplate jtm = getJDBCTempate();
List<User> users = (List<User>) jtm.query(sql,
new BeanPropertyRowMapper(User.class));
return users;
}
private static JdbcTemplate getJDBCTempate() {
DataSource ds = ServiceLocator.getDataSource("java:comp/env/jdbc/testdb");
JdbcTemplate jtm = new JdbcTemplate(ds);
return jtm;
}
}
在DatabaseService
中,我们有利用JDBCTemplate
库执行数据库操作的方法。
public static User getUserById(Long id) {
String sql = "SELECT * FROM Users WHERE Id = ?";
JdbcTemplate jtm = getJDBCTempate();
User user = (User) jtm.queryForObject(sql, new Object[]{id},
new BeanPropertyRowMapper(User.class));
return user;
}
getUserById()
方法返回由其 ID 标识的用户。 queryForObject()
运行指定的 SQL 语句并返回一个对象。 BeanPropertyRowMapper
将返回的行转换为目标类(用户)。
public static List<User> getAllUsers() {
String sql = "SELECT * FROM Users";
JdbcTemplate jtm = getJDBCTempate();
List<User> users = (List<User>) jtm.query(sql,
new BeanPropertyRowMapper(User.class));
return users;
}
getAllUsers()
方法返回表中的所有用户。 query()
方法返回类型列表。
MyController.java
package com.zetcode.web;
import com.zetcode.bean.User;
import com.zetcode.service.DatabaseService;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "MyController", urlPatterns = {"/MyController"})
public class MyController extends HttpServlet {
private static final String ADD_USER_VIEW = "addUser.ftl";
private static final String UPDATE_USER_VIEW = "updateUser.ftl";
private static final String LIST_USERS_VIEW = "listUsers.ftl";
private static final String USER_ADDED_VIEW = "userAdded.html";
private static final String USER_DELETED_VIEW = "userDeleted.html";
private static final String USER_MODIFIED_VIEW = "userUpdated.html";
private static final String DELETE_ACTION = "deleteUser";
private static final String ADD_ACTION = "addUser";
private static final String UPDATE_ACTION = "updateUser";
private static final String LIST_ACTION = "listUsers";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String path = "";
String action = request.getParameter("action");
if (DELETE_ACTION.equals(action)) {
Long userId = Long.parseLong(request.getParameter("userId"));
DatabaseService.deleteUser(userId);
path = USER_DELETED_VIEW;
} else if (ADD_ACTION.equals(action)) {
path = ADD_USER_VIEW;
} else if (UPDATE_ACTION.equals(action)) {
Long userId = Long.parseLong(request.getParameter("userId"));
User user = DatabaseService.getUserById(userId);
request.setAttribute("user", user);
path = UPDATE_USER_VIEW;
} else if (LIST_ACTION.equals(action)) {
List<User> users = DatabaseService.getAllUsers();
request.setAttribute("users", users);
path = LIST_USERS_VIEW;
}
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String path = "";
response.setContentType("text/html;charset=UTF-8");
String action = request.getParameter("action");
if (ADD_ACTION.equals(action)) {
User user = new User();
user.setFirstName(request.getParameter("firstName"));
user.setLastName(request.getParameter("lastName"));
user.setEmail(request.getParameter("email"));
DatabaseService.addUser(user);
path = USER_ADDED_VIEW;
} else if (UPDATE_ACTION.equals(action)) {
User user = new User();
user.setId(Long.parseLong(request.getParameter("id")));
user.setFirstName(request.getParameter("firstName"));
user.setLastName(request.getParameter("lastName"));
user.setEmail(request.getParameter("email"));
DatabaseService.updateUser(user);
path = USER_MODIFIED_VIEW;
}
response.sendRedirect(path);
}
}
MyController
是一个控制器类,用于管理传入的请求并委托给特定的服务方法。 我们有两种方法:doGet()
处理 HTTP GET 请求,doPost()
处理 HTTP POST 请求。
private static final String ADD_USER_VIEW = "addUser.ftl";
private static final String UPDATE_USER_VIEW = "updateUser.ftl";
private static final String LIST_USERS_VIEW = "listUsers.ftl";
这是三个 FreeMarker 模板视图。
private static final String USER_ADDED_VIEW = "userAdded.html";
private static final String USER_DELETED_VIEW = "userDeleted.html";
private static final String USER_MODIFIED_VIEW = "userUpdated.html";
在这里,我们有三个 HTML 视图。 它们用于确认我们的任务。
private static final String DELETE_ACTION = "deleteUser";
private static final String ADD_ACTION = "addUser";
private static final String UPDATE_ACTION = "updateUser";
private static final String LIST_ACTION = "listUsers";
我们有四个不同的操作:删除用户,添加新用户,更新用户以及列出所有用户。
if (DELETE_ACTION.equals(action)) {
Long userId = Long.parseLong(request.getParameter("userId"));
DatabaseService.deleteUser(userId);
path = USER_DELETED_VIEW;
}
收到删除操作后,我们从请求中找到 ID,然后调用DatabaseService
的deleteUser()
方法。 然后选择一个视图。
} else if (UPDATE_ACTION.equals(action)) {
Long userId = Long.parseLong(request.getParameter("userId"));
User user = DatabaseService.getUserById(userId);
request.setAttribute("user", user);
path = UPDATE_USER_VIEW;
}
当我们单击更新链接时,将执行此代码。 从数据库中检索用户,并将User
对象添加到请求对象。 UPDATE_USER_VIEW
将应用转发到模板文件,该文件具有用于更新用户的形式。 提交表单后,将 POST 请求发送到控制器,并执行其doPost()
方法。
} else if (LIST_ACTION.equals(action)) {
List<User> users = DatabaseService.getAllUsers();
request.setAttribute("users", users);
path = LIST_USERS_VIEW;
}
在这里,我们检索所有用户并将用户列表设置为请求对象。 我们选择LIST_USERS_VIEW
视图。
response.sendRedirect(path);
遵循发布/重定向/获取模式,我们将重定向到doPost()
方法中的视图。 这样可以防止提交多个表单。 (例如,我们可能不小心多次添加了一个用户)。
addUser.ftl
<!DOCTYPE html>
<html>
<head>
<title>Add new user</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form action="MyController?action=addUser" method="post">
<label for="fname">First name:</label>
<input id="fname" type="text" name="firstName">
<br>
<label for="lname">Last name:</label>
<input id="lname" type="text" name="lastName">
<br>
<label for="email">Email:</label>
<input id="email" type="text" name="email">
<br>
<button type="submit">Submit</button>
</form>
</body>
</html>
addUser.ftl
是一个模板文件,其中包含用于添加新用户的表单。
<form action="MyController?action=addUser" method="post">
该表单调用MyController
servlet,并将action
参数设置为addUser
。
updateUser.ftl
<!DOCTYPE html>
<html>
<head>
<title>Update user</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
input[readonly] {
background-color:lightGray;
}
</style>
</head>
<body>
<form action="MyController?action=updateUser" method="post">
<label for="id">Id:</label>
<input id="id" type="text" name="id" readonly value="${user.id}">
<br>
<label for="fname">First name:</label>
<input id="fname" type="text" name="firstName" value="${user.firstName}">
<br>
<label for="lname">Last name:</label>
<input id="lname" type="text" name="lastName" value="${user.lastName}">
<br>
<label for="email">Email:</label>
<input id="email" type="text" name="email" value="${user.email}">
<br>
<button type="submit">Submit</button>
</form>
</body>
</html>
addUser.ftl
模板文件包含用于更新特定用户的表单。
<style>
input[readonly] {
background-color:lightGray;
}
</style>
这种 CSS 样式将浅色的只读输入标签的背景绘制为灰色。
<label for="id">Id:</label>
<input id="id" type="text" name="id" readonly value="${user.id}">
<br>
ID 是一个只读参数。 ${user.id}
是一个 FreeMarker 插值,可解析为用户 ID。 在到达updateUser.ftl
文件之前,该请求获得一个将要修改的用户对象。
listUsers.ftl
<!DOCTYPE html>
<html>
<head>
<title>List users</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<table>
<thead>
<tr>
<th>User Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th colspan="2">Action</th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.firstName}</td>
<td>${user.lastName}</td>
<td>${user.email}</td>
<td><a href="MyController?action=updateUser&userId=${user.id}">Update</a></td>
<td><a href="MyController?action=deleteUser&userId=${user.id}">Delete</a></td>
</tr>
</#list>
</tbody>
</table>
<p>
<a href="MyController?action=addUser">Add new user</a>
</p>
</body>
</html>
listUsers.ftl
模板文件列出了Users
数据库表中的所有用户。
<#list users as user>
FreeMarker #list
指令遍历users
集合的所有元素。
<td>${user.id}</td>
<td>${user.firstName}</td>
<td>${user.lastName}</td>
<td>${user.email}</td>
这些 FreeMarker 插值显示用户数据。
<td><a href="MyController?action=updateUser&userId=${user.id}">Update</a></td>
该链接将更新操作发送到应用控制器; 它还发送要修改的用户的 ID。
<td><a href="MyController?action=deleteUser&userId=${user.id}">Delete</a></td>
该链接将删除操作发送到应用控制器。 它还发送要删除的用户的 ID。
<a href="MyController?action=addUser">Add new user</a>
该链接将添加用户操作发送到控制器。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Main page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<a href="MyController?action=listUsers">List all users</a>
</body>
</html>
这是一个带有链接的主页,该链接将列表用户的操作发送到控制器。
userAdded.html
<!DOCTYPE html>
<html>
<head>
<title>User added</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>
New user has been added successfully.
<a href="MyController?action=listUsers">List all users</a>
</p>
</body>
</html>
该视图通知用户已成功添加到数据库表中。
userDeleted.html
<!DOCTYPE html>
<html>
<head>
<title>User deleted</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>
User has been successfully deleted.
<a href="MyController?action=listUsers">List all users</a>
</p>
</body>
</html>
此视图通知用户删除。
userUpdated.html
<!DOCTYPE html>
<html>
<head>
<title>User modified</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>
User has been modified successfully.
<a href="MyController?action=listUsers">List all users</a>
</p>
</body>
</html>
该视图通知用户的修改。
图:用户 Web 应用
在上面的屏幕截图中,我们可以看到用户列表。 该应用部署在 NetBeans 内置的 Tomcat 服务器上,该服务器正在监听端口 8084。
在本教程中,我们创建了一个执行 CRUD 操作的 Java Web 应用。 它使用了 FreeMarker,Servlet 和JDBCTemplate
。 您可能也对相关教程感兴趣: JdbcTemplate
教程, FreeMarker 教程, Java 教程,游戏入门, Spark 简介或 Strips 简介。