發(fā)實(shí)戰(zhàn):Thymeleaf+Servlet+MySQL圖書管理系統(tǒng))
1. 項(xiàng)目背景與技術(shù)選型解析這個(gè)基于ThymeleafMavenServletMySQL的圖書管理系統(tǒng)框架是典型的Java Web應(yīng)用開(kāi)發(fā)實(shí)踐案例。作為系列教程的第三部分共九章它聚焦于企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中最核心的技術(shù)棧組合。為什么選擇這個(gè)技術(shù)組合我在實(shí)際項(xiàng)目中多次驗(yàn)證過(guò)它的可靠性Thymeleaf相比JSP它天然支持HTML5能與前端開(kāi)發(fā)工具無(wú)縫配合且不依賴Servlet容器Maven解決依賴管理的噩夢(mèng)特別是處理MySQL Connector這類經(jīng)常需要版本適配的庫(kù)Servlet 3.1保持輕量級(jí)的同時(shí)支持注解配置避免web.xml的繁瑣MySQL 5.7事務(wù)完整性和JSON字段支持已足夠成熟提示初學(xué)者常犯的錯(cuò)誤是直接使用最新版本組件。建議初學(xué)者采用穩(wěn)定組合Thymeleaf 3.0 Servlet 4.0 MySQL 5.7 Maven 3.62. 環(huán)境準(zhǔn)備與項(xiàng)目初始化2.1 Maven環(huán)境配置要點(diǎn)在IDEA中創(chuàng)建Maven項(xiàng)目時(shí)我推薦使用以下原型archetype groupIdorg.apache.maven.archetypes/groupId artifactIdmaven-archetype-webapp/artifactId version1.4/version /archetype關(guān)鍵配置項(xiàng)說(shuō)明settings.xml鏡像配置解決國(guó)內(nèi)下載慢問(wèn)題mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共倉(cāng)庫(kù)/name urlhttps://maven.aliyun.com/repository/public/url /mirrorpom.xml基礎(chǔ)依賴dependencies !-- Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- Thymeleaf -- dependency groupIdorg.thymeleaf/groupId artifactIdthymeleaf/artifactId version3.0.12.RELEASE/version /dependency !-- MySQL Connector -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.27/version /dependency /dependencies2.2 數(shù)據(jù)庫(kù)初始化腳本創(chuàng)建圖書管理核心表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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 添加測(cè)試數(shù)據(jù) INSERT INTO books VALUES (1,Java編程思想,Bruce Eckel,9787111213826,2007-06-01,108.00,50), (2,Spring實(shí)戰(zhàn),Craig Walls,9787115480661,2018-10-01,89.00,30);3. Servlet與Thymeleaf集成架構(gòu)3.1 視圖解析器配置創(chuàng)建ThymeleafViewResolver是整合的關(guān)鍵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 基礎(chǔ)Servlet實(shí)現(xiàn)圖書列表展示的Servlet示例WebServlet(/book/list) public class BookListServlet extends HttpServlet { private BookService bookService new BookServiceImpl(); protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListBook 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()); } }對(duì)應(yīng)的Thymeleaf模板/WEB-INF/templates/book/list.html!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title圖書列表/title /head body table tr th:eachbook : ${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 /html4. 數(shù)據(jù)訪問(wèn)層實(shí)現(xiàn)4.1 JDBC工具類封裝避免在每個(gè)DAO中重復(fù)創(chuàng)建連接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(數(shù)據(jù)源配置錯(cuò)誤, 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模式實(shí)現(xiàn)圖書DAO接口與實(shí)現(xiàn)public interface BookDao { ListBook 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 ListBook findAll() throws SQLException { Connection conn null; PreparedStatement stmt null; ResultSet rs null; ListBook 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; } // 其他方法實(shí)現(xiàn)類似... }5. 常見(jiàn)問(wèn)題排查指南5.1 Thymeleaf模板不渲染問(wèn)題癥狀頁(yè)面直接顯示Thymeleaf表達(dá)式如${books}排查步驟檢查模板文件是否放在/WEB-INF/templates/目錄下確認(rèn)HTML標(biāo)簽添加了命名空間html xmlns:thhttp://www.thymeleaf.org查看Servlet是否正確設(shè)置了模板引擎檢查IDEA是否識(shí)別Thymeleaf語(yǔ)法需安裝Thymeleaf插件5.2 MySQL連接失敗處理典型錯(cuò)誤信息Communications link failure解決方案矩陣錯(cuò)誤類型可能原因解決方法連接超時(shí)數(shù)據(jù)庫(kù)地址/端口錯(cuò)誤檢查jdbc.url中的IP和端口拒絕訪問(wèn)用戶名密碼錯(cuò)誤核對(duì)application.properties配置SSL錯(cuò)誤MySQL 8.0默認(rèn)啟用SSL在連接字符串添加?useSSLfalse時(shí)區(qū)問(wèn)題服務(wù)器時(shí)區(qū)未設(shè)置添加serverTimezoneAsia/Shanghai5.3 Maven依賴沖突解決使用以下命令分析依賴樹(shù)mvn dependency:tree -Dverbose常見(jiàn)沖突處理Servlet API沖突確保scope為provided日志框架沖突排除沖突的logging依賴exclusions exclusion groupIdcommons-logging/groupId artifactIdcommons-logging/artifactId /exclusion /exclusions6. 性能優(yōu)化實(shí)踐6.1 數(shù)據(jù)庫(kù)連接池配置推薦使用HikariCP替代傳統(tǒng)DBCPdependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version4.0.3/version /dependency配置示例context.xmlResource namejdbc/BookDB authContainer typecom.zaxxer.hikari.HikariDataSource factorycom.zaxxer.hikari.HikariJNDIFactory jdbcUrljdbc:mysql://localhost:3306/bookdb usernameroot password123456 maximumPoolSize20 connectionTimeout30000/6.2 Thymeleaf模板緩存開(kāi)發(fā)階段應(yīng)禁用緩存resolver.setCacheable(false); // 開(kāi)發(fā)環(huán)境設(shè)置為false生產(chǎn)環(huán)境配置resolver.setCacheTTLMs(3600000L); // 1小時(shí)緩存6.3 靜態(tài)資源處理正確配置靜態(tài)資源映射web.xmlservlet-mapping servlet-namedefault/servlet-name url-pattern/static/*/url-pattern /servlet-mapping7. 安全防護(hù)措施7.1 SQL注入防護(hù)必須使用PreparedStatement// 錯(cuò)誤示范存在注入風(fēng)險(xiǎn) String sql SELECT * FROM books WHERE title title ; // 正確做法 PreparedStatement stmt conn.prepareStatement( SELECT * FROM books WHERE title ?); stmt.setString(1, title);7.2 XSS防護(hù)Thymeleaf默認(rèn)會(huì)進(jìn)行HTML轉(zhuǎn)義特殊場(chǎng)景需要取消轉(zhuǎn)義時(shí)div th:text${#strings.escapeXml(book.title)}/div !-- 或 -- div th:utext${book.title}/div !-- 慎用 --7.3 CSRF防護(hù)實(shí)現(xiàn)簡(jiǎn)單的CSRF Token機(jī)制// 生成Token String csrfToken UUID.randomUUID().toString(); session.setAttribute(csrfToken, csrfToken); // 驗(yàn)證Token String requestToken request.getParameter(csrfToken); String sessionToken (String) session.getAttribute(csrfToken); if(!requestToken.equals(sessionToken)) { throw new SecurityException(CSRF驗(yàn)證失敗); }8. 項(xiàng)目結(jié)構(gòu)規(guī)范建議推薦的標(biāo)準(zhǔn)目錄結(jié)構(gòu)src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── config/ # 配置類 │ │ ├── controller/ # Servlet │ │ ├── dao/ # 數(shù)據(jù)訪問(wèn)層 │ │ ├── model/ # 實(shí)體類 │ │ ├── service/ # 業(yè)務(wù)邏輯 │ │ └── util/ # 工具類 │ ├── resources/ │ │ ├── db/ # SQL腳本 │ │ └── config.properties # 配置文件 │ └── webapp/ │ ├── static/ # 靜態(tài)資源 │ │ ├── css/ │ │ ├── js/ │ │ └── images/ │ └── WEB-INF/ │ ├── templates/ # Thymeleaf模板 │ └── web.xml └── test/ # 測(cè)試代碼9. 擴(kuò)展功能實(shí)現(xiàn)思路9.1 分頁(yè)查詢優(yōu)化前端傳遞分頁(yè)參數(shù)int page Integer.parseInt(request.getParameter(page)); int size Integer.parseInt(request.getParameter(size)); ListBook books bookService.findByPage(page, size);MySQL分頁(yè)SQLSELECT * 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 日志系統(tǒng)集成Logback配置示例logback.xmlconfiguration appender nameFILE classch.qos.logback.core.FileAppender filelogs/bookstore.log/file encoder pattern%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refFILE / /root /configuration在項(xiàng)目開(kāi)發(fā)過(guò)程中我發(fā)現(xiàn)很多初學(xué)者容易在Maven依賴版本管理上栽跟頭。建議鎖定關(guān)鍵依賴的版本號(hào)而不是使用動(dòng)態(tài)版本如RELEASE、LATEST。對(duì)于數(shù)據(jù)庫(kù)操作一定要記得在finally塊中關(guān)閉資源否則隨著系統(tǒng)運(yùn)行時(shí)間增長(zhǎng)會(huì)出現(xiàn)連接泄漏問(wèn)題。Thymeleaf的模板緩存機(jī)制在開(kāi)發(fā)階段應(yīng)該關(guān)閉否則每次修改模板都需要重啟服務(wù)器才能生效