# Servlet FreeMarker `JdbcTemplate`教程 - CRUD 操作

> 原文： [http://zetcode.com/articles/servletfreemarker/](http://zetcode.com/articles/servletfreemarker/)

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

该教程的资源可从作者的 Github [存储库](https://github.com/janbodnar/ServletFreemarkerJdbcTemplate)中获得。

CRUD（创建，读取，更新和删除）是持久性存储的四个基本功能。 对于关系数据库，它们等效于`INSERT`，`SELECT`，`UPDATE`和`DELETE`语句。

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

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

## 管理用户

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

```java
mysql> CREATE TABLE Users(Id INTEGER PRIMARY KEY AUTO_INCREMENT, 
                          Firstname TEXT, Lastname TEXT, Email TEXT);

```

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

`Excerpt from pom.xml`

```java
<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`

```java
<?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`

```java
<?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`

```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`

```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`

```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`库执行数据库操作的方法。

```java
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`将返回的行转换为目标类（用户）。

```java
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`

```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 请求。

```java
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 模板视图。

```java
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 视图。 它们用于确认我们的任务。

```java
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";

```

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

```java
if (DELETE_ACTION.equals(action)) {
    Long userId = Long.parseLong(request.getParameter("userId"));
    DatabaseService.deleteUser(userId);

    path = USER_DELETED_VIEW;
}

```

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

```java
} 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()`方法。

```java
} else if (LIST_ACTION.equals(action)) {

    List<User> users = DatabaseService.getAllUsers();
    request.setAttribute("users", users);
    path = LIST_USERS_VIEW;
}

```

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

```java
response.sendRedirect(path);

```

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

`addUser.ftl`

```java
<!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`是一个模板文件，其中包含用于添加新用户的表单。

```java
<form action="MyController?action=addUser" method="post"> 

```

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

`updateUser.ftl`

```java
<!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`模板文件包含用于更新特定用户的表单。

```java
<style>
    input[readonly] {
        background-color:lightGray;
    }
</style>

```

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

```java
<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`

```java
<!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`数据库表中的所有用户。

```java
<#list users as user>

```

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

```java
<td>${user.id}</td>
<td>${user.firstName}</td>
<td>${user.lastName}</td>
<td>${user.email}</td>

```

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

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

```

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

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

```

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

```java
<a href="MyController?action=addUser">Add new user</a>  

```

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

`index.html`

```java
<!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`

```java
<!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`

```java
<!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`

```java
<!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>

```

该视图通知用户的修改。

![Users web application](img/ee293d8525f759cf682d04eb692d8475.jpg)

图：用户 Web 应用

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

在本教程中，我们创建了一个执行 CRUD 操作的 Java Web 应用。 它使用了 FreeMarker，Servlet 和`JDBCTemplate`。 您可能也对相关教程感兴趣： [`JdbcTemplate`教程](/db/jdbctemplate/)， [FreeMarker 教程](/java/freemarker/)， [Java 教程](/lang/java/)，[游戏入门](/java/play/)， [Spark 简介](/java/spark/)或 [Strips 简介](/java/stripes/)。