这个基于Thymeleaf+Maven+Servlet+MySQL的图书管理系统,是一个典型的Java Web应用开发案例。作为一名有多年Java开发经验的工程师,我认为这个案例很好地展示了如何将传统Servlet技术与现代模板引擎结合使用。系统实现了图书信息的增删改查功能,采用了Maven进行依赖管理,MySQL作为数据存储,Thymeleaf作为前端模板引擎。
在实际开发中,这种技术组合非常实用。Servlet作为Java Web开发的基础,虽然现在有Spring等框架,但理解Servlet的工作原理对深入掌握Web开发至关重要。Thymeleaf则提供了比JSP更优雅的模板解决方案,支持自然模板和静态原型。MySQL作为最流行的开源关系型数据库,在中小型项目中应用广泛。
首先需要创建一个Maven Web项目。在pom.xml中,我们需要配置以下关键依赖:
xml复制<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!-- 其他可能需要的依赖 -->
</dependencies>
注意:在实际项目中,建议使用dependencyManagement管理版本号,便于统一版本控制。
图书表的设计需要考虑以下几个关键字段:
sql复制CREATE TABLE books (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
author VARCHAR(50) NOT NULL,
isbn VARCHAR(20) UNIQUE NOT NULL,
stock INT NOT NULL DEFAULT 0,
publish_date DATE NOT NULL,
category VARCHAR(50) NOT NULL
);
在实际项目中,我通常会添加创建时间、更新时间等审计字段,便于后期维护。同时,ISBN字段设置为UNIQUE可以防止重复录入同一本书。
add.html页面使用了Bootstrap进行美化,这是非常实用的做法。在实际开发中,我有几点建议:
html复制<form action="booksServlet?method=save" method="post" onsubmit="return validateForm()">
<!-- 表单字段 -->
</form>
<script>
function validateForm() {
// 简单的客户端验证
if(document.forms[0].title.value == "") {
alert("书名不能为空");
return false;
}
// 更多验证逻辑...
return true;
}
</script>
save方法中的日期处理需要特别注意。示例中使用了java.sql.Date.valueOf()方法,这在大多数情况下是可行的,但我建议:
java复制try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
Date utilDate = sdf.parse(publishDate);
java.sql.Date publishDate1 = new java.sql.Date(utilDate.getTime());
} catch (ParseException e) {
// 处理日期格式错误
}
在toUpdate方法中,通过ID查询图书信息并回显到表单,这是标准的CRUD操作。在实际项目中,我通常会:
java复制public void toUpdate(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
int id = Integer.parseInt(req.getParameter("id"));
// 添加参数校验
if(id <= 0) {
throw new IllegalArgumentException("无效的图书ID");
}
Books book = booksService.findById(id);
if(book == null) {
throw new RuntimeException("未找到指定图书");
}
req.setAttribute("book", book);
} catch (Exception e) {
// 更细致的错误处理
req.setAttribute("error", "加载图书信息失败: " + e.getMessage());
}
engine.processTemplate("update", req, resp);
}
update方法中的业务逻辑与save方法类似,但需要注意:
java复制public void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
int id = Integer.parseInt(req.getParameter("id"));
// 参数校验...
// 获取表单数据...
Books book = new Books(id, title, author, isbn, stock, publishDate1, category);
// 添加版本控制
int affectedRows = booksService.edit(book);
if(affectedRows == 0) {
throw new RuntimeException("更新失败,可能记录已被删除或修改");
}
// 添加操作日志
logService.logUpdate(req.getSession(), book);
resp.sendRedirect(req.getContextPath()+"/booksServlet?method=list");
} catch (Exception e) {
// 错误处理
req.setAttribute("error", "更新失败: " + e.getMessage());
engine.processTemplate("update", req, resp);
}
}
目前的Servlet包含了所有业务逻辑,随着功能增加会变得臃肿。我建议:
java复制// 示例分层结构
public class BookController {
private BookService bookService;
public void toAdd(HttpServletRequest req, HttpServletResponse resp) {
// 仅处理请求转发
engine.processTemplate("add", req, resp);
}
public void save(HttpServletRequest req, HttpServletResponse resp) {
try {
BookDTO bookDTO = extractBookFromRequest(req);
bookService.addBook(bookDTO);
redirectToList(resp);
} catch (Exception e) {
handleError(req, resp, e);
}
}
// 其他方法...
}
java复制// 在Servlet初始化时配置Thymeleaf
public class YourServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
ServletContext ctx = getServletContext();
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(ctx);
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
// 生产环境开启缓存
resolver.setCacheable(ctx.getInitParameter("production") != null);
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(resolver);
}
}
java复制// 在Thymeleaf中自动转义HTML
<input type="text" name="title" th:value="${#strings.escapeXml(book.title)}">
// 在Servlet中添加CSRF Token
public void toAdd(HttpServletRequest req, HttpServletResponse resp) {
String csrfToken = UUID.randomUUID().toString();
req.getSession().setAttribute("csrfToken", csrfToken);
req.setAttribute("csrfToken", csrfToken);
engine.processTemplate("add", req, resp);
}
// 在表单中添加CSRF Token
<form action="booksServlet?method=save" method="post">
<input type="hidden" name="csrfToken" th:value="${csrfToken}">
<!-- 其他字段 -->
</form>
问题现象:模板无法正确解析,显示原始Thymeleaf表达式
解决方案:
html复制<!-- 确保有正确的命名空间 -->
<html xmlns:th="http://www.thymeleaf.org">
问题现象:无法连接到MySQL数据库
解决方案:
properties复制# db.properties示例配置
jdbc.url=jdbc:mysql://localhost:3306/book_db?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=yourpassword
问题现象:日期转换失败或显示格式不正确
解决方案:
html复制<!-- 在Thymeleaf中格式化日期 -->
<input type="date" name="publishDate" th:value="${#dates.format(book.publishDate, 'yyyy-MM-dd')}">
在实际开发中,我发现使用统一的时间处理工具类可以大大减少日期相关的问题。以下是我常用的日期工具类片段:
java复制public class DateUtils {
private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static Date parseDate(String dateStr) throws ParseException {
return parseDate(dateStr, DEFAULT_DATE_FORMAT);
}
public static Date parseDate(String dateStr, String format) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setLenient(false);
return sdf.parse(dateStr);
}
public static String formatDate(Date date) {
return formatDate(date, DEFAULT_DATE_FORMAT);
}
// 其他实用方法...
}
这个图书管理系统虽然基础,但涵盖了Java Web开发的许多核心概念。通过这个项目,开发者可以掌握Servlet工作原理、Maven依赖管理、数据库操作以及Thymeleaf模板引擎的使用。在实际项目中,可以根据需求逐步扩展功能,如添加用户权限管理、图书借阅功能、数据统计分析等。