1. 项目背景与技术选型解析
这个基于Thymeleaf+Maven+Servlet+MySQL的图书管理系统框架,是典型的Java Web应用开发实践案例。作为系列教程的第三部分(共九章),它聚焦于企业级应用开发中最核心的技术栈组合。
为什么选择这个技术组合?我在实际项目中多次验证过它的可靠性:
- Thymeleaf:相比JSP,它天然支持HTML5,能与前端开发工具无缝配合,且不依赖Servlet容器
- Maven:解决依赖管理的噩梦,特别是处理MySQL Connector这类经常需要版本适配的库
- Servlet 3.1+:保持轻量级的同时支持注解配置,避免web.xml的繁琐
- MySQL 5.7+:事务完整性和JSON字段支持已足够成熟
提示:初学者常犯的错误是直接使用最新版本组件。建议初学者采用稳定组合:Thymeleaf 3.0 + Servlet 4.0 + MySQL 5.7 + Maven 3.6
2. 环境准备与项目初始化
2.1 Maven环境配置要点
在IDEA中创建Maven项目时,我推荐使用以下原型:
<archetype> <groupId>org.apache.maven.archetypes</groupId> <artifactId>maven-archetype-webapp</artifactId> <version>1.4</version> </archetype>关键配置项说明:
- settings.xml镜像配置(解决国内下载慢问题):
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>- pom.xml基础依赖:
<dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- Thymeleaf --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.12.RELEASE</version> </dependency> <!-- MySQL Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> </dependencies>2.2 数据库初始化脚本
创建图书管理核心表:
CREATE TABLE `books` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL, `author` varchar(50) NOT NULL, `isbn` varchar(20) UNIQUE, `publish_date` date, `price` decimal(10,2), `stock` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 添加测试数据 INSERT INTO `books` VALUES (1,'Java编程思想','Bruce Eckel','9787111213826','2007-06-01',108.00,50), (2,'Spring实战','Craig Walls','9787115480661','2018-10-01',89.00,30);3. Servlet与Thymeleaf集成架构
3.1 视图解析器配置
创建ThymeleafViewResolver是整合的关键:
public class ThymeleafConfig { private static TemplateEngine templateEngine; static { ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(); resolver.setPrefix("/WEB-INF/templates/"); resolver.setSuffix(".html"); resolver.setTemplateMode(TemplateMode.HTML); resolver.setCharacterEncoding("UTF-8"); templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(resolver); } public static TemplateEngine getTemplateEngine() { return templateEngine; } }3.2 基础Servlet实现
图书列表展示的Servlet示例:
@WebServlet("/book/list") public class BookListServlet extends HttpServlet { private BookService bookService = new BookServiceImpl(); protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Book> books = bookService.getAllBooks(); req.setAttribute("books", books); WebContext ctx = new WebContext( req, resp, req.getServletContext(), req.getLocale()); ctx.setVariable("books", books); ThymeleafConfig.getTemplateEngine() .process("book/list", ctx, resp.getWriter()); } }对应的Thymeleaf模板(/WEB-INF/templates/book/list.html):
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>图书列表</title> </head> <body> <table> <tr th:each="book : ${books}"> <td th:text="${book.id}"></td> <td th:text="${book.title}"></td> <td th:text="${book.author}"></td> <td th:text="${#numbers.formatDecimal(book.price,1,2)}"></td> </tr> </table> </body> </html>4. 数据访问层实现
4.1 JDBC工具类封装
避免在每个DAO中重复创建连接:
public class JdbcUtil { private static DataSource dataSource; static { try { Context ctx = new InitialContext(); dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/BookDB"); } catch (NamingException e) { throw new RuntimeException("数据源配置错误", e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if(rs != null) rs.close(); } catch (SQLException ignored) {} try { if(stmt != null) stmt.close(); } catch (SQLException ignored) {} try { if(conn != null) conn.close(); } catch (SQLException ignored) {} } }4.2 DAO模式实现
图书DAO接口与实现:
public interface BookDao { List<Book> findAll() throws SQLException; Book findById(int id) throws SQLException; int insert(Book book) throws SQLException; int update(Book book) throws SQLException; int delete(int id) throws SQLException; } public class BookDaoImpl implements BookDao { @Override public List<Book> findAll() throws SQLException { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; List<Book> books = new ArrayList<>(); try { conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("SELECT * FROM books"); rs = stmt.executeQuery(); while(rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setIsbn(rs.getString("isbn")); book.setPublishDate(rs.getDate("publish_date")); book.setPrice(rs.getBigDecimal("price")); book.setStock(rs.getInt("stock")); books.add(book); } } finally { JdbcUtil.close(rs, stmt, conn); } return books; } // 其他方法实现类似... }5. 常见问题排查指南
5.1 Thymeleaf模板不渲染问题
症状:页面直接显示Thymeleaf表达式(如${books})
排查步骤:
- 检查模板文件是否放在
/WEB-INF/templates/目录下 - 确认HTML标签添加了命名空间:
<html xmlns:th="http://www.thymeleaf.org"> - 查看Servlet是否正确设置了模板引擎
- 检查IDEA是否识别Thymeleaf语法(需安装Thymeleaf插件)
5.2 MySQL连接失败处理
典型错误信息:Communications link failure
解决方案矩阵:
| 错误类型 | 可能原因 | 解决方法 |
|---|---|---|
| 连接超时 | 数据库地址/端口错误 | 检查jdbc.url中的IP和端口 |
| 拒绝访问 | 用户名密码错误 | 核对application.properties配置 |
| SSL错误 | MySQL 8.0+默认启用SSL | 在连接字符串添加?useSSL=false |
| 时区问题 | 服务器时区未设置 | 添加serverTimezone=Asia/Shanghai |
5.3 Maven依赖冲突解决
使用以下命令分析依赖树:
mvn dependency:tree -Dverbose常见冲突处理:
- Servlet API冲突:确保scope为provided
- 日志框架冲突:排除冲突的logging依赖
<exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions>6. 性能优化实践
6.1 数据库连接池配置
推荐使用HikariCP替代传统DBCP:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>4.0.3</version> </dependency>配置示例(context.xml):
<Resource name="jdbc/BookDB" auth="Container" type="com.zaxxer.hikari.HikariDataSource" factory="com.zaxxer.hikari.HikariJNDIFactory" jdbcUrl="jdbc:mysql://localhost:3306/bookdb" username="root" password="123456" maximumPoolSize="20" connectionTimeout="30000"/>6.2 Thymeleaf模板缓存
开发阶段应禁用缓存:
resolver.setCacheable(false); // 开发环境设置为false生产环境配置:
resolver.setCacheTTLMs(3600000L); // 1小时缓存6.3 静态资源处理
正确配置静态资源映射(web.xml):
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/static/*</url-pattern> </servlet-mapping>7. 安全防护措施
7.1 SQL注入防护
必须使用PreparedStatement:
// 错误示范(存在注入风险) String sql = "SELECT * FROM books WHERE title = '" + title + "'"; // 正确做法 PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM books WHERE title = ?"); stmt.setString(1, title);7.2 XSS防护
Thymeleaf默认会进行HTML转义,特殊场景需要取消转义时:
<div th:text="${#strings.escapeXml(book.title)}"></div> <!-- 或 --> <div th:utext="${book.title}"></div> <!-- 慎用 -->7.3 CSRF防护
实现简单的CSRF Token机制:
// 生成Token String csrfToken = UUID.randomUUID().toString(); session.setAttribute("csrfToken", csrfToken); // 验证Token String requestToken = request.getParameter("csrfToken"); String sessionToken = (String) session.getAttribute("csrfToken"); if(!requestToken.equals(sessionToken)) { throw new SecurityException("CSRF验证失败"); }8. 项目结构规范建议
推荐的标准目录结构:
src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── config/ # 配置类 │ │ ├── controller/ # Servlet │ │ ├── dao/ # 数据访问层 │ │ ├── model/ # 实体类 │ │ ├── service/ # 业务逻辑 │ │ └── util/ # 工具类 │ ├── resources/ │ │ ├── db/ # SQL脚本 │ │ └── config.properties # 配置文件 │ └── webapp/ │ ├── static/ # 静态资源 │ │ ├── css/ │ │ ├── js/ │ │ └── images/ │ └── WEB-INF/ │ ├── templates/ # Thymeleaf模板 │ └── web.xml └── test/ # 测试代码9. 扩展功能实现思路
9.1 分页查询优化
前端传递分页参数:
int page = Integer.parseInt(request.getParameter("page")); int size = Integer.parseInt(request.getParameter("size")); List<Book> books = bookService.findByPage(page, size);MySQL分页SQL:
SELECT * FROM books LIMIT ?, ?9.2 文件上传功能
Servlet 3.0+的文件上传处理:
@WebServlet("/upload") @MultipartConfig public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part filePart = req.getPart("file"); String fileName = Paths.get(filePart.getSubmittedFileName()) .getFileName().toString(); InputStream fileContent = filePart.getInputStream(); // 保存文件到指定目录 Files.copy(fileContent, Paths.get("/uploads", fileName)); } }9.3 日志系统集成
Logback配置示例(logback.xml):
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>logs/bookstore.log</file> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="FILE" /> </root> </configuration>在项目开发过程中,我发现很多初学者容易在Maven依赖版本管理上栽跟头。建议锁定关键依赖的版本号,而不是使用动态版本(如RELEASE、LATEST)。对于数据库操作,一定要记得在finally块中关闭资源,否则随着系统运行时间增长,会出现连接泄漏问题。Thymeleaf的模板缓存机制在开发阶段应该关闭,否则每次修改模板都需要重启服务器才能生效