原文: http://zetcode.com/articles/servletfreemarker/

在本教程中,我们将创建一个具有基本 CRUD 操作的简单 Java Web 应用。 我们使用 FreeMarker 模板引擎,Servlet 技术和JdbcTemplate库。 MySQL 用于存储数据。 该应用最终部署在 Tomcat 服务器上。

该教程的资源可从作者的 Github 存储库中获得。

CRUD(创建,读取,更新和删除)是持久性存储的四个基本功能。 对于关系数据库,它们等效于INSERTSELECTUPDATEDELETE语句。

FreeMarker 是 Java 编程语言的流行模板引擎。 模板以 FreeMarker 模板语言(FTL)编写。 模板引擎将静态数据与动态数据结合起来以生成内容。 模板是内容的中间表示。 它指定如何产生输出。

JDBCTemplate是用于简化 JDBC 编程的 Spring 库。 它处理乏味且容易出错的底层细节,例如处理事务,清理资源以及正确处理异常。 它包含在 Spring 的 spring-jdbc 模块中。

管理用户

我们的应用将管理用户。 它允许添加新用户,对其进行修改,删除它并列出所有可用用户。

  1. mysql> CREATE TABLE Users(Id INTEGER PRIMARY KEY AUTO_INCREMENT,
  2. Firstname TEXT, Lastname TEXT, Email TEXT);

我们在 MySQL testdb数据库中创建Users表。

Excerpt from pom.xml

  1. <dependencies>
  2. <dependency>
  3. <groupId>javax</groupId>
  4. <artifactId>javaee-web-api</artifactId>
  5. <version>7.0</version>
  6. <scope>provided</scope>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-jdbc</artifactId>
  11. <version>4.2.6.RELEASE</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>mysql</groupId>
  15. <artifactId>mysql-connector-java</artifactId>
  16. <version>6.0.2</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.freemarker</groupId>
  20. <artifactId>freemarker</artifactId>
  21. <version>2.3.25-incubating</version>
  22. </dependency>
  23. </dependencies>

在 Maven pom.xml文件中,我们提供四个依赖项:javaee-web-api是用于开发 Java Web 应用的一组库,spring-jdbc模块包含 JDBCTemplate 库,mysql-connector-java是 MySQL Java 驱动程序,而freemarker是 FreeMarker 库。

context.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Context path="/ServletFreemarkerEx">
  3. <Resource name="jdbc/testdb"
  4. auth="Container"
  5. type="javax.sql.DataSource"
  6. username="user7"
  7. password="s$cret"
  8. driverClassName="com.mysql.jdbc.Driver"
  9. url="jdbc:mysql://localhost:3306/testdb"
  10. maxActive="10"
  11. maxIdle="4"/>
  12. </Context>

context.xml文件中,我们定义了应用上下文路径(其名称)和一个 MySQL 数据源。

web.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  5. version="3.1">
  6. <servlet>
  7. <servlet-name>freemarker</servlet-name>
  8. <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
  9. <init-param>
  10. <param-name>TemplatePath</param-name>
  11. <param-value>/WEB-INF/template/</param-value>
  12. </init-param>
  13. <init-param>
  14. <param-name>NoCache</param-name>
  15. <param-value>true</param-value>
  16. </init-param>
  17. <init-param>
  18. <param-name>ResponseCharacterEncoding</param-name>
  19. <param-value>fromTemplate</param-value>
  20. </init-param>
  21. <init-param>
  22. <param-name>ExceptionOnMissingTemplate</param-name>
  23. <param-value>true</param-value>
  24. </init-param>
  25. <init-param>
  26. <param-name>incompatible_improvements</param-name>
  27. <param-value>2.3.25-incubating</param-value>
  28. </init-param>
  29. <init-param>
  30. <param-name>template_exception_handler</param-name>
  31. <param-value>html_debug</param-value>
  32. </init-param>
  33. <init-param>
  34. <param-name>template_update_delay</param-name>
  35. <param-value>0 s</param-value>
  36. </init-param>
  37. <init-param>
  38. <param-name>default_encoding</param-name>
  39. <param-value>UTF-8</param-value>
  40. </init-param>
  41. <init-param>
  42. <param-name>output_encoding</param-name>
  43. <param-value>UTF-8</param-value>
  44. </init-param>
  45. <init-param>
  46. <param-name>locale</param-name>
  47. <param-value>en_US</param-value>
  48. </init-param>
  49. <init-param>
  50. <param-name>number_format</param-name>
  51. <param-value>0.##########</param-value>
  52. </init-param>
  53. <load-on-startup>1</load-on-startup>
  54. </servlet>
  55. <servlet-mapping>
  56. <servlet-name>freemarker</servlet-name>
  57. <url-pattern>*.ftl</url-pattern>
  58. </servlet-mapping>
  59. <security-constraint>
  60. <web-resource-collection>
  61. <web-resource-name>FreeMarker MVC Views</web-resource-name>
  62. <url-pattern>*.ftl</url-pattern>
  63. </web-resource-collection>
  64. <auth-constraint>
  65. </auth-constraint>
  66. </security-constraint>
  67. <session-config>
  68. <session-timeout>
  69. 30
  70. </session-timeout>
  71. </session-config>
  72. </web-app>

web.xml文件中,我们设置了FreemarkerServlet,该文件用于处理 FreeMarker .ftl文件。

User.java

  1. package com.zetcode.bean;
  2. public class User {
  3. private Long id;
  4. private String firstName;
  5. private String lastName;
  6. private String email;
  7. public Long getId() {
  8. return id;
  9. }
  10. public void setId(Long id) {
  11. this.id = id;
  12. }
  13. public String getFirstName() {
  14. return firstName;
  15. }
  16. public void setFirstName(String firstName) {
  17. this.firstName = firstName;
  18. }
  19. public String getLastName() {
  20. return lastName;
  21. }
  22. public void setLastName(String lastName) {
  23. this.lastName = lastName;
  24. }
  25. public String getEmail() {
  26. return email;
  27. }
  28. public void setEmail(String email) {
  29. this.email = email;
  30. }
  31. }

我们有一个User bean,其中包含四个属性:idfirstNamelastNameemail

ServiceLocator.java

  1. package com.zetcode.util;
  2. import java.util.logging.Level;
  3. import java.util.logging.Logger;
  4. import javax.naming.Context;
  5. import javax.naming.InitialContext;
  6. import javax.naming.NamingException;
  7. import javax.sql.DataSource;
  8. public class ServiceLocator {
  9. public static DataSource getDataSource(String jndiName) {
  10. Context ctx = null;
  11. DataSource ds = null;
  12. try {
  13. ctx = new InitialContext();
  14. ds = (DataSource) ctx.lookup(jndiName);
  15. } catch (NamingException ex) {
  16. Logger.getLogger(ServiceLocator.class.getName()).log(
  17. Level.SEVERE, null, ex);
  18. }
  19. return ds;
  20. }
  21. }

ServiceLocator包含用于查找数据源的代码。 它使用 JNDI API 来完成这项工作。

DatabaseService.java

  1. package com.zetcode.service;
  2. import com.zetcode.bean.User;
  3. import com.zetcode.util.ServiceLocator;
  4. import java.util.List;
  5. import javax.sql.DataSource;
  6. import org.springframework.jdbc.core.BeanPropertyRowMapper;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. public class DatabaseService {
  9. public static User getUserById(Long id) {
  10. String sql = "SELECT * FROM Users WHERE Id = ?";
  11. JdbcTemplate jtm = getJDBCTempate();
  12. User user = (User) jtm.queryForObject(sql, new Object[]{id},
  13. new BeanPropertyRowMapper(User.class));
  14. return user;
  15. }
  16. public static void addUser(User user) {
  17. String sql = "INSERT INTO Users(Firstname, Lastname, Email) VALUES(?, ?, ?)";
  18. JdbcTemplate jtm = getJDBCTempate();
  19. jtm.update(sql, new Object[] {user.getFirstName(),
  20. user.getLastName(), user.getEmail()});
  21. }
  22. public static void deleteUser(Long id) {
  23. String sql = "DELETE From Users WHERE Id = ?";
  24. JdbcTemplate jtm = getJDBCTempate();
  25. jtm.update(sql, new Object[] {id});
  26. }
  27. public static void updateUser(User user) {
  28. String sql = "UPDATE Users SET Firstname=?, Lastname=?, Email=? WHERE Id=?";
  29. JdbcTemplate jtm = getJDBCTempate();
  30. jtm.update(sql, new Object[] {user.getFirstName(),
  31. user.getLastName(), user.getEmail(), user.getId()});
  32. }
  33. public static List<User> getAllUsers() {
  34. String sql = "SELECT * FROM Users";
  35. JdbcTemplate jtm = getJDBCTempate();
  36. List<User> users = (List<User>) jtm.query(sql,
  37. new BeanPropertyRowMapper(User.class));
  38. return users;
  39. }
  40. private static JdbcTemplate getJDBCTempate() {
  41. DataSource ds = ServiceLocator.getDataSource("java:comp/env/jdbc/testdb");
  42. JdbcTemplate jtm = new JdbcTemplate(ds);
  43. return jtm;
  44. }
  45. }

DatabaseService中,我们有利用JDBCTemplate库执行数据库操作的方法。

  1. public static User getUserById(Long id) {
  2. String sql = "SELECT * FROM Users WHERE Id = ?";
  3. JdbcTemplate jtm = getJDBCTempate();
  4. User user = (User) jtm.queryForObject(sql, new Object[]{id},
  5. new BeanPropertyRowMapper(User.class));
  6. return user;
  7. }

getUserById()方法返回由其 ID 标识的用户。 queryForObject()运行指定的 SQL 语句并返回一个对象。 BeanPropertyRowMapper将返回的行转换为目标类(用户)。

  1. public static List<User> getAllUsers() {
  2. String sql = "SELECT * FROM Users";
  3. JdbcTemplate jtm = getJDBCTempate();
  4. List<User> users = (List<User>) jtm.query(sql,
  5. new BeanPropertyRowMapper(User.class));
  6. return users;
  7. }

getAllUsers()方法返回表中的所有用户。 query()方法返回类型列表。

MyController.java

  1. package com.zetcode.web;
  2. import com.zetcode.bean.User;
  3. import com.zetcode.service.DatabaseService;
  4. import java.io.IOException;
  5. import java.util.List;
  6. import javax.servlet.RequestDispatcher;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.annotation.WebServlet;
  9. import javax.servlet.http.HttpServlet;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. @WebServlet(name = "MyController", urlPatterns = {"/MyController"})
  13. public class MyController extends HttpServlet {
  14. private static final String ADD_USER_VIEW = "addUser.ftl";
  15. private static final String UPDATE_USER_VIEW = "updateUser.ftl";
  16. private static final String LIST_USERS_VIEW = "listUsers.ftl";
  17. private static final String USER_ADDED_VIEW = "userAdded.html";
  18. private static final String USER_DELETED_VIEW = "userDeleted.html";
  19. private static final String USER_MODIFIED_VIEW = "userUpdated.html";
  20. private static final String DELETE_ACTION = "deleteUser";
  21. private static final String ADD_ACTION = "addUser";
  22. private static final String UPDATE_ACTION = "updateUser";
  23. private static final String LIST_ACTION = "listUsers";
  24. @Override
  25. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  26. throws ServletException, IOException {
  27. response.setContentType("text/html;charset=UTF-8");
  28. String path = "";
  29. String action = request.getParameter("action");
  30. if (DELETE_ACTION.equals(action)) {
  31. Long userId = Long.parseLong(request.getParameter("userId"));
  32. DatabaseService.deleteUser(userId);
  33. path = USER_DELETED_VIEW;
  34. } else if (ADD_ACTION.equals(action)) {
  35. path = ADD_USER_VIEW;
  36. } else if (UPDATE_ACTION.equals(action)) {
  37. Long userId = Long.parseLong(request.getParameter("userId"));
  38. User user = DatabaseService.getUserById(userId);
  39. request.setAttribute("user", user);
  40. path = UPDATE_USER_VIEW;
  41. } else if (LIST_ACTION.equals(action)) {
  42. List<User> users = DatabaseService.getAllUsers();
  43. request.setAttribute("users", users);
  44. path = LIST_USERS_VIEW;
  45. }
  46. RequestDispatcher dispatcher = request.getRequestDispatcher(path);
  47. dispatcher.forward(request, response);
  48. }
  49. @Override
  50. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  51. throws ServletException, IOException {
  52. String path = "";
  53. response.setContentType("text/html;charset=UTF-8");
  54. String action = request.getParameter("action");
  55. if (ADD_ACTION.equals(action)) {
  56. User user = new User();
  57. user.setFirstName(request.getParameter("firstName"));
  58. user.setLastName(request.getParameter("lastName"));
  59. user.setEmail(request.getParameter("email"));
  60. DatabaseService.addUser(user);
  61. path = USER_ADDED_VIEW;
  62. } else if (UPDATE_ACTION.equals(action)) {
  63. User user = new User();
  64. user.setId(Long.parseLong(request.getParameter("id")));
  65. user.setFirstName(request.getParameter("firstName"));
  66. user.setLastName(request.getParameter("lastName"));
  67. user.setEmail(request.getParameter("email"));
  68. DatabaseService.updateUser(user);
  69. path = USER_MODIFIED_VIEW;
  70. }
  71. response.sendRedirect(path);
  72. }
  73. }

MyController是一个控制器类,用于管理传入的请求并委托给特定的服务方法。 我们有两种方法:doGet()处理 HTTP GET 请求,doPost()处理 HTTP POST 请求。

  1. private static final String ADD_USER_VIEW = "addUser.ftl";
  2. private static final String UPDATE_USER_VIEW = "updateUser.ftl";
  3. private static final String LIST_USERS_VIEW = "listUsers.ftl";

这是三个 FreeMarker 模板视图。

  1. private static final String USER_ADDED_VIEW = "userAdded.html";
  2. private static final String USER_DELETED_VIEW = "userDeleted.html";
  3. private static final String USER_MODIFIED_VIEW = "userUpdated.html";

在这里,我们有三个 HTML 视图。 它们用于确认我们的任务。

  1. private static final String DELETE_ACTION = "deleteUser";
  2. private static final String ADD_ACTION = "addUser";
  3. private static final String UPDATE_ACTION = "updateUser";
  4. private static final String LIST_ACTION = "listUsers";

我们有四个不同的操作:删除用户,添加新用户,更新用户以及列出所有用户。

  1. if (DELETE_ACTION.equals(action)) {
  2. Long userId = Long.parseLong(request.getParameter("userId"));
  3. DatabaseService.deleteUser(userId);
  4. path = USER_DELETED_VIEW;
  5. }

收到删除操作后,我们从请求中找到 ID,然后调用DatabaseServicedeleteUser()方法。 然后选择一个视图。

  1. } else if (UPDATE_ACTION.equals(action)) {
  2. Long userId = Long.parseLong(request.getParameter("userId"));
  3. User user = DatabaseService.getUserById(userId);
  4. request.setAttribute("user", user);
  5. path = UPDATE_USER_VIEW;
  6. }

当我们单击更新链接时,将执行此代码。 从数据库中检索用户,并将User对象添加到请求对象。 UPDATE_USER_VIEW将应用转发到模板文件,该文件具有用于更新用户的形式。 提交表单后,将 POST 请求发送到控制器,并执行其doPost()方法。

  1. } else if (LIST_ACTION.equals(action)) {
  2. List<User> users = DatabaseService.getAllUsers();
  3. request.setAttribute("users", users);
  4. path = LIST_USERS_VIEW;
  5. }

在这里,我们检索所有用户并将用户列表设置为请求对象。 我们选择LIST_USERS_VIEW视图。

  1. response.sendRedirect(path);

遵循发布/重定向/获取模式,我们将重定向到doPost()方法中的视图。 这样可以防止提交多个表单。 (例如,我们可能不小心多次添加了一个用户)。

addUser.ftl

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Add new user</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <form action="MyController?action=addUser" method="post">
  10. <label for="fname">First name:</label>
  11. <input id="fname" type="text" name="firstName">
  12. <br>
  13. <label for="lname">Last name:</label>
  14. <input id="lname" type="text" name="lastName">
  15. <br>
  16. <label for="email">Email:</label>
  17. <input id="email" type="text" name="email">
  18. <br>
  19. <button type="submit">Submit</button>
  20. </form>
  21. </body>
  22. </html>

addUser.ftl是一个模板文件,其中包含用于添加新用户的表单。

  1. <form action="MyController?action=addUser" method="post">

该表单调用MyController servlet,并将action参数设置为addUser

updateUser.ftl

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Update user</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <style>
  8. input[readonly] {
  9. background-color:lightGray;
  10. }
  11. </style>
  12. </head>
  13. <body>
  14. <form action="MyController?action=updateUser" method="post">
  15. <label for="id">Id:</label>
  16. <input id="id" type="text" name="id" readonly value="${user.id}">
  17. <br>
  18. <label for="fname">First name:</label>
  19. <input id="fname" type="text" name="firstName" value="${user.firstName}">
  20. <br>
  21. <label for="lname">Last name:</label>
  22. <input id="lname" type="text" name="lastName" value="${user.lastName}">
  23. <br>
  24. <label for="email">Email:</label>
  25. <input id="email" type="text" name="email" value="${user.email}">
  26. <br>
  27. <button type="submit">Submit</button>
  28. </form>
  29. </body>
  30. </html>

addUser.ftl模板文件包含用于更新特定用户的表单。

  1. <style>
  2. input[readonly] {
  3. background-color:lightGray;
  4. }
  5. </style>

这种 CSS 样式将浅色的只读输入标签的背景绘制为灰色。

  1. <label for="id">Id:</label>
  2. <input id="id" type="text" name="id" readonly value="${user.id}">
  3. <br>

ID 是一个只读参数。 ${user.id}是一个 FreeMarker 插值,可解析为用户 ID。 在到达updateUser.ftl文件之前,该请求获得一个将要修改的用户对象。

listUsers.ftl

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>List users</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <table>
  10. <thead>
  11. <tr>
  12. <th>User Id</th>
  13. <th>First Name</th>
  14. <th>Last Name</th>
  15. <th>Email</th>
  16. <th colspan="2">Action</th>
  17. </tr>
  18. </thead>
  19. <tbody>
  20. <#list users as user>
  21. <tr>
  22. <td>${user.id}</td>
  23. <td>${user.firstName}</td>
  24. <td>${user.lastName}</td>
  25. <td>${user.email}</td>
  26. <td><a href="MyController?action=updateUser&userId=${user.id}">Update</a></td>
  27. <td><a href="MyController?action=deleteUser&userId=${user.id}">Delete</a></td>
  28. </tr>
  29. </#list>
  30. </tbody>
  31. </table>
  32. <p>
  33. <a href="MyController?action=addUser">Add new user</a>
  34. </p>
  35. </body>
  36. </html>

listUsers.ftl模板文件列出了Users数据库表中的所有用户。

  1. <#list users as user>

FreeMarker #list指令遍历users集合的所有元素。

  1. <td>${user.id}</td>
  2. <td>${user.firstName}</td>
  3. <td>${user.lastName}</td>
  4. <td>${user.email}</td>

这些 FreeMarker 插值显示用户数据。

  1. <td><a href="MyController?action=updateUser&userId=${user.id}">Update</a></td>

该链接将更新操作发送到应用控制器; 它还发送要修改的用户的 ID。

  1. <td><a href="MyController?action=deleteUser&userId=${user.id}">Delete</a></td>

该链接将删除操作发送到应用控制器。 它还发送要删除的用户的 ID。

  1. <a href="MyController?action=addUser">Add new user</a>

该链接将添加用户操作发送到控制器。

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Main page</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <a href="MyController?action=listUsers">List all users</a>
  10. </body>
  11. </html>

这是一个带有链接的主页,该链接将列表用户的操作发送到控制器。

userAdded.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>User added</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <p>
  10. New user has been added successfully.
  11. <a href="MyController?action=listUsers">List all users</a>
  12. </p>
  13. </body>
  14. </html>

该视图通知用户已成功添加到数据库表中。

userDeleted.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>User deleted</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <p>
  10. User has been successfully deleted.
  11. <a href="MyController?action=listUsers">List all users</a>
  12. </p>
  13. </body>
  14. </html>

此视图通知用户删除。

userUpdated.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>User modified</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. </head>
  8. <body>
  9. <p>
  10. User has been modified successfully.
  11. <a href="MyController?action=listUsers">List all users</a>
  12. </p>
  13. </body>
  14. </html>

该视图通知用户的修改。

Servlet FreeMarker `JdbcTemplate`教程 - CRUD 操作 - 图1

图:用户 Web 应用

在上面的屏幕截图中,我们可以看到用户列表。 该应用部署在 NetBeans 内置的 Tomcat 服务器上,该服务器正在监听端口 8084。

在本教程中,我们创建了一个执行 CRUD 操作的 Java Web 应用。 它使用了 FreeMarker,Servlet 和JDBCTemplate。 您可能也对相关教程感兴趣: JdbcTemplate教程FreeMarker 教程Java 教程游戏入门Spark 简介Strips 简介