💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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](https://img.kancloud.cn/0a/f0/0af0980c1e371596883992e47e1f8710_654x368.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/)。