拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSP+MySQL个人记事本全解析:从Servlet到WAR部署实践

JSP+MySQL个人记事本全解析:从Servlet到WAR部署实践 简介这是一套基于JSP与MySQL实现的个人记事备忘系统源码适合Java Web初学者、课程设计者及需要快速搭建笔记类应用的开发者。系统围绕在线记事场景实现了笔记的创建、编辑、存储与检索并清晰展示了JSP动态页面、Servlet请求处理、JDBC数据库交互及基础安全防护等关键环节。压缩包共374个文件体积约27.51MB主要包含Java类24个、JSP页面9个、HTML静态页26个、CSS样式28个、JavaScript脚本86个以及PNG界面预览图97个目录结构完整便于按模块阅读与二次开发。资源中还附带数据库配置说明与部署相关文件可帮助理解前后端数据流转和整体架构。目前已有109人学习使用适合用来自学参考、毕业设计或实训项目改进能有效缩短从理论到实践的上手周期。1. 为什么还要折腾一个JSPMySQL的记事本先看清这个选题的落地价值一个人记事备忘多数人第一反应是装个App或记在手机备忘录里但作为JavaWeb学习者自己用JSPMySQL写一套个人记事备忘系统是另一回事。它把用户登录、记事增删改查、分类管理这些核心功能完整串起来是一份可以直接跑的源码打包项目很适合做课程设计、毕设题目或者想快速看懂传统JavaWeb项目结构的入门实践。它能解决的问题很具体本机搭一套能记录日程和备忘的小系统有看得懂的代码、能用的数据库脚本、可复现的部署方案。这篇文章从表设计讲到WAR部署把关键参数和路上容易踩的坑一并说清照着复现即可。2. 拆解系统结构三张表怎么建、请求怎么走2.1 三张核心表怎么设计用户、分类、记事个人记事备忘系统的数据落库常见做法是拆三张表tb_user存账号tb_category做记事分类tb_note记正文和状态。表之间用归属字段关联但物理外键不滥用保持由程序层控制一致性。-- 用户表 CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码MD5或SHA-256后存储, nickname VARCHAR(50) DEFAULT COMMENT 展示昵称, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 分类表 CREATE TABLE tb_category ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 分类ID, user_id INT NOT NULL COMMENT 所属用户ID, category_name VARCHAR(50) NOT NULL COMMENT 分类名称, sort_order INT DEFAULT 0 COMMENT 排序号数字小的排前面, UNIQUE KEY uk_user_category (user_id, category_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT记事分类表; -- 记事表 CREATE TABLE tb_note ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 记事ID, user_id INT NOT NULL COMMENT 所属用户ID, category_id INT DEFAULT NULL COMMENT 分类IDNULL表示未分类, title VARCHAR(200) NOT NULL COMMENT 标题, content TEXT COMMENT 正文内容, is_completed TINYINT(1) DEFAULT 0 COMMENT 完成状态0未完成 1已完成, remind_time DATETIME DEFAULT NULL COMMENT 提醒时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 修改时间, KEY idx_user_create (user_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT记事表;这里有一处容易忽略user_id在tb_note和tb_category里都必须出现且所有查询都要带上“当前登录用户ID”这个条件才能保证多账号数据不串。category_id指向tb_category的主键程序层需要额外校验“这个分类属于当前用户”否则用户A的记事可能引用到用户B的分类。外键约束不写在DDL里主要原因是删除分类时通常先把该分类下的记事批量置为未分类再删分类本身物理外键反而碍事。字段类型方面密码字段不设VARCHAR(32)而是VARCHAR(64)因为MD5是32位十六进制后面升级到SHA-256是64位留出余量。sort_order用INT而不是TINYINT要求不高但排序时不担心溢出。remind_time允许NULL表示该记事不设提醒。这样一套三表结构后面扩展priority、tag字段都很方便不会牵一发动全身。2.2 新增记事的一次完整请求表单如何流转到Servlet、DAO和MySQL一次“新增记事”的操作在传统JavaWeb项目里至少经过四层。先看浏览器端的表单再沿请求链路往下推。form action${pageContext.request.contextPath}/note/add methodpost input typetext nametitle placeholder记事标题 required textarea namecontent rows5 placeholder正文内容/textarea select namecategoryId option value未分类/option !-- 循环渲染当前用户的分类列表 -- /select input typedatetime-local nameremindTime button typesubmit保存/button /form表单提交到项目上下文根下的/note/add这个URL由NoteServlet上的WebServlet(/note/*)映射。Tomcat收到POST请求后把请求交给对应的Servlet。Servlet第一步设置请求字符编码为UTF-8然后通过request.getParameter(title)等接参数组装成Note实体对象调用NoteDao.insert(note)。DAO层使用PreparedStatement执行INSERT执行完毕后Servlet用重定向跳到/note/list避免刷新页面时重复提交。这条链路里JSP负责展示和收集参数Servlet负责流程控制和参数合法性校验DAO负责SQL操作MySQL负责数据持久化。新手容易把SQL直接写在JSP里虽然能跑但维护时极其痛苦改一个字段名要在十几个页面里找这就是典型的“能跑”和“能用”之间的差距。2.3 代码分包与命名照着这套结构不会迷路项目结构建议按职责分包。com.note.entity放User、Note、Category实体类com.note.dao放数据访问层com.note.servlet放控制器com.note.util放DBUtil等工具类JSP放在WebRoot根目录或WEB-INF外的views目录下。WEB-INF下的页面有一个特点不能通过URL直接访问必须由Servlet转发或者重定向访问。登录成功后的note_list.jsp如果放在WEB-INF/views/目录用户直接访问http://localhost:8080/note/WEB-INF/views/note_list.jsp是404必须请求/note/list让Servlet转发过去。这个设计在过滤登录态时很省心因为外部根本拿不到页面路径。我第一次搭项目时把所有JSP都放在WebRoot根目录结果登录过滤器漏掉一个页面用户不登录也能看到别人的记事列表后来改成WEB-INF/views布局才彻底堵上。3. 核心功能实现登录、记事与分类的完整代码3.1 数据库连接工具类与连接串参数说明数据库连接是每次请求都要用的资源最基础的做法是写一个静态工具类统一管理驱动加载、连接创建和资源释放。这里选择的方案是DriverManager直接建连适合学习和单用户低并发场景生产环境请换成连接池第6章会说怎么改。package com.note.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/note_db ?useUnicodetruecharacterEncodingutf8mb4 useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { // 实际项目中应换成日志输出 e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }连接串里有几个参数是关键少一个都可能出诡异问题。useUnicodetrue与characterEncodingutf8mb4组合保证中文按UTF-8编码传输不设置时MySQL默认可能走latin1导致中文乱码useSSLfalse是因为本机开发不需要走SSL握手能省掉一堆证书警告serverTimezoneAsia/Shanghai解决MySQL 8.x驱动要求时区的报错allowPublicKeyRetrievaltrue解决MySQL 8.x下使用caching_sha2_password认证时连接被拒的坑。USER和PASSWORD写死在代码里是图省事正式项目建议放到配置文件中比如db.properties再写一个静态块读取。工具类只提供getConnection()和close()两个方法所有DAO的数据库操作都走这里后续切换数据库只要改URL不用改业务代码。3.2 登录认证与Session处理LoginServlet和UserDao怎么写登录是系统的门面。页面提交用户名和密码Servlet调DAO查询成功则把User对象放进Session失败则回填错误提示。这里展示Servlet和DAO两个关键类。package com.note.servlet; import com.note.dao.UserDao; import com.note.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { request.setAttribute(errorMsg, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功后把用户对象放进Session后续页面通过sessionScope取 request.getSession().setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /note/list); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段Servlet里有两个细节值得注意。一是先做空值校验再查库避免空字符串直接拼SQL二是登录成功用sendRedirect失败用forward。重定向会改变浏览器地址栏URL用户刷新列表页时不会再次提交登录表单转发保留原来的请求参数适合把错误信息带回登录页。request.getSession()不带参数时如果Session不存在会创建一个这正是登录时需要的行为但要注意在未登录状态下访问其他页面时不要随意调用getSession()否则会在后端频繁创建无用的Session对象。package com.note.dao; import com.note.entity.User; import com.note.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class UserDao { public User findByUsernameAndPassword(String username, String password) { // 这里假设密码在Service层已经做过MD5加密DAO只负责比对 String sql SELECT id, username, nickname, create_time FROM tb_user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setNickname(rs.getString(nickname)); user.setCreateTime(rs.getTimestamp(create_time)); return user; } } } catch (SQLException e) { // 实际项目中应记录日志而不是只printStackTrace e.printStackTrace(); } return null; } }这里必须使用PreparedStatement而不是Statement拼SQL。用户如果输入admin or 11这种字符串直接拼接会改变SQL语义甚至把整张表的数据查出来参数化查询把这个输入变成字符串字面量不存在注入的可能。try-with-resources写法在Java 7之后可以直接自动关闭连接和语句不再需要手动在finally里关闭代码结构也更清爽。密码字段建议在注册时用MD5加密存储登录时对输入做同样加密再比对不要明文存密码。3.3 记事增删改查NoteServlet处理参数、NoteDao执行SQL记事模块是系统的核心需要支持列表、详情、新增、修改、删除五个动作。用一个WebServlet(/note/*)接收路径匹配再按request.getRequestURI()分词路由。package com.note.servlet; import com.note.dao.NoteDao; import com.note.entity.Note; import com.note.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; WebServlet(/note/*) public class NoteServlet extends HttpServlet { private NoteDao noteDao new NoteDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path request.getRequestURI().substring(request.getContextPath().length()); if (/note/list.equals(path)) { list(request, response); } else if (/note/delete.equals(path)) { delete(request, response); } else if (/note/edit.equals(path)) { showEdit(request, response); } } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path request.getRequestURI().substring(request.getContextPath().length()); if (/note/add.equals(path)) { add(request, response); } else if (/note/update.equals(path)) { update(request, response); } } private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); // 防御Session里没有用户就回登录页 if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } ListNote noteList noteDao.findByUserId(user.getId()); request.setAttribute(noteList, noteList); request.getRequestDispatcher(/WEB-INF/views/note_list.jsp).forward(request, response); } private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { User user (User) request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } request.setCharacterEncoding(UTF-8); String title request.getParameter(title); String content request.getParameter(content); String categoryIdStr request.getParameter(categoryId); String remindTimeStr request.getParameter(remindTime); Note note new Note(); note.setUserId(user.getId()); note.setTitle(title); note.setContent(content); if (categoryIdStr ! null !categoryIdStr.isEmpty()) { note.setCategoryId(Integer.parseInt(categoryIdStr)); } if (remindTimeStr ! null !remindTimeStr.isEmpty()) { note.setRemindTime(java.sql.Timestamp.valueOf(remindTimeStr.replace(T, ) :00)); } noteDao.insert(note); response.sendRedirect(request.getContextPath() /note/list); } }路由写法上子路径判断直接用request.getRequestURI()去掉上下文前缀比用request.getServletPath()更直观不容易被filter拦截路径干扰。list()方法里每次先取Session里的loginUser为空就重定向回登录页这就是最简单的登录拦截。add()方法里的日期处理是个容易出错的地方input typedatetime-local提交的格式是2025-01-15T14:30但MySQL的DATETIME类型接受的是2025-01-15 14:30:00所以要先替换掉T再追加:00补秒否则Timestamp.valueOf()会抛IllegalArgumentException。public void update(Note note) { String sql UPDATE tb_note SET title ?, content ?, category_id ?, is_completed ?, remind_time ? WHERE id ? AND user_id ?; // 执行PreparedStatement字段按顺序绑定 }UPDATE语句的WHERE条件里必须同时带id和user_id这样即使请求里传来的id是别人的记事也会因为user_id不匹配影响0行等于天然防止越权修改。这是多用户系统里最廉价又最有效的防线。JSP列表页展示时用JSTL标签循环注意c:forEach的items来自Servlet request作用域pageContext.request.contextPath拼出上下文根防止部署路径带项目名后链接全部失效。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html head title我的记事列表/title /head body h2${sessionScope.loginUser.nickname}这是你的记事列表/h2 table border1 cellpadding6 cellspacing0 tr th标题/th th分类/th th更新时间/th th状态/th th操作/th /tr c:forEach varnote items${noteList} tr tda href${pageContext.request.contextPath}/note/edit?id${note.id}${note.title}/a/td td${note.categoryName}/td tdfmt:formatDate value${note.updateTime} patternyyyy-MM-dd HH:mm//td td c:choose c:when test${note.completed}已完成/c:when c:otherwise未完成/c:otherwise /c:choose /td td a href${pageContext.request.contextPath}/note/edit?id${note.id}编辑/a a href${pageContext.request.contextPath}/note/delete?id${note.id} onclickreturn confirm(确定删除这条记事吗);删除/a /td /tr /c:forEach /table /body /html这里${note.categoryName}对应实体类里一个额外字段通常由SQL联表查询带出来SELECT n.*, c.category_name FROM tb_note n LEFT JOIN tb_category c ON n.category_id c.id WHERE n.user_id ?。实体类里加一个categoryName属性不映射数据库列只用于页面展示。这个做法比在JSP里再查一次分类名称要高效得多也是一线项目里常见的查询套路。4. 部署与源码打包从IDEA到Tomcat的WAR生成路径4.1 用IDEA新建传统JSP项目并关联Tomcat拿到“源码打包”后第一步是把项目导进IDEA。如果是从压缩包解压的文件夹用File - Open选择项目根目录IDEA识别出pom.xml或普通Web结构后会提示导入。如果是空项目手动建常见做法是File - New - Project - Java Enterprise勾选Web Application然后配置Tomcat。手动配置Tomcat的步骤在IDEA里是这样的Run - Edit Configurations - - Tomcat Server - LocalApplication server选本机的Tomcat安装目录Deployment选项卡点 - Artifact - 你的项目:war explodedApplication context填/note。这里有个细节war exploded是展开目录模式改JSP直接刷新就能生效war模式是压缩包改代码要重新build。开发阶段用exploded发布阶段再打war。Tomcat本身解压后无需安装设置环境变量CATALINA_HOME指向Tomcat根目录然后bin/startup.shLinux/Mac或bin/startup.batWindows启动。默认端口8080如果被占用改conf/server.xml里的Connector port为8081或8090。本机验证浏览器访问http://localhost:8080/能看到Tomcat首页说明环境就绪。4.2 传统JSP项目打包WARArtifacts与手工jar命令两种路径传统JSP项目打包WARIDEA标准做法是通过Artifacts。点击File - Project Structure - Artifacts点 - Web Application - ArchiveName填note.warOutput directory选择你要输出war的目录。然后执行Build - Build Artifacts - Buildwar就会生成在指定目录。如果不用IDEA也可以手工用jar命令打包。前提是项目编译后的classes目录、WEB-INF/web.xml、所有JSP和静态资源都在一个目录下然后在该目录下执行cd /path/to/project/build jar -cvf note.war .jar -cvf命令中c表示创建新归档v表示输出中打印打包文件列表f note.war指定归档文件名。最后的.表示把当前目录下所有内容打包进去所以执行前一定要确认当前目录结构是Web应用标准结构WEB-INF/classes下是编译好的class文件WEB-INF/lib下有MySQL驱动jar包根目录下有index.jsp等页面。新手最容易忘的是把MySQL驱动jar放进WEB-INF/lib。没放驱动时Tomcat启动项目不会报错但运行时访问任何查库接口都会抛ClassNotFoundException: com.mysql.cj.jdbc.Driver而且这个异常往往藏在控制台的一堆日志里。检查war包内容可以用jar -tf note.war | grep mysql看驱动是否在列。4.3 部署到远程服务器Tomcat的webapps目录与nginx的角色拿到note.war后本机部署最简单的方法是复制到Tomcat的webapps目录然后重启Tomcat。Tomcat启动时会自动解压war包成同名目录。如果是远程Linux服务器用scp或ftp工具把war传上去放入/usr/local/tomcat/webapps/再执行bin/shutdown.sh和bin/startup.sh完成重启。部署后访问路径要注意如果war包名是note.war访问入口是http://服务器IP:8080/note/不是http://服务器IP:8080/。想省掉应用上下文名把war改名为ROOT.warTomcat会把ROOT当作根应用直接访问http://IP:8080/。这个改名操作很简单但很多第一次部署的人在这里卡住明明Tomcat启动成功却打不开页面。类似“nginx支持jsp吗”这类问题答案是否定的。Nginx不解析JSP只处理静态文件或做反向代理。如果服务器上装了nginx正确配置是让nginx监听80端口location / { proxy_pass http://127.0.0.1:8080; }把请求转发给后端的Tomcat处理JSP由Tomcat解析后把生成HTML返回给nginx再回给浏览器。数据库MySQL在同一台服务器或另一台机上都行连接串里的localhost改成对应IP即可但要确认MySQL的bind-address配置允许远程连接否则只能本机连。5. 避坑指南JSPMySQL开发中5个容易反复翻车的点5.1 中文乱码页面、过滤器、连接串三处必须对齐现象登录名或记事标题里带中文存入MySQL后变成???或者页面显示乱码。原因乱码是一个链路上的问题任何一环编码不一致都会翻车。JSP页面本身声明contentTypetext/html;charsetUTF-8但表单POST提交时Servlet没调request.setCharacterEncoding(UTF-8)Tomcat默认按ISO-8859-1解码中文就成了乱码另外MySQL表如果建库时没指定utf8mb4也会丢掉汉字。解决三处必须同时对齐。一是JSP页面声明和meta标签都设UTF-8二是在Servlet的doGet和doPost开头调用request.setCharacterEncoding(UTF-8)或者直接写一个CharacterEncodingFilter过滤器统一设置请求和响应编码三是JDBC连接串加characterEncodingutf8mb4建表时DEFAULT CHARSETutf8mb4。排查顺序先看数据进库前是否乱再看库里存的字节对不对最后看页面渲染编码是否是UTF-8。5.2 JDBC连接不上MySQL从ERROR 2002排查到驱动版本现象启动项目后访问登录页点登录按钮报错日志里出现SQLException: Cannot create PoolableConnectionFactory或Communications link failure。用系统命令连MySQL时可能报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因ERROR 2002通常是MySQL服务没启动或者socket路径不对。JDBC连不上的原因更多端口不是默认3306、MySQL用户只授权了localhost、驱动版本和MySQL版本不匹配、防火墙拦了端口。解决先本机用mysql -uroot -p试连能连上说明服务正常再看Java代码里的URL端口、数据库名、字符集参数逐个核对。MySQL 5.7用com.mysql.jdbc.DriverMySQL 8.0必须用com.mysql.cj.jdbc.Driver驱动包也要换成8.x版本。驱动jar不匹配时通常报ClassNotFoundException或Unable to load authentication plugin caching_sha2_password后者在连接串加allowPublicKeyRetrievaltrue即可解决。5.3 Tomcat版本与javax/jakarta命名空间导致的翻车现象源码里import的是javax.servlet.http.HttpServlet代码编译正常但部署到Tomcat 10后抛java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet或者ClassNotFoundException。原因Tomcat 10开始把Servlet API的包名从javax.*改成jakarta.*这是Oracle和Eclipse Foundation商标问题带来的历史包袱。传统JSP项目大多基于javax编写直接扔到Tomcat 10上容器加载Servlet时根本找不到对应的类。解决两个方案。一是把Tomcat降到9.x版本这是最省事的方案教科书和毕设项目里绝大多数代码都按javax写二是把源码里的javax.servlet全部替换成jakarta.servlet同时替换依赖jar包。第二个方案对源码打包项目来说改动量不小不建议新手折腾。部署前先确认目标Tomcat大版本再决定代码是否需要适配。5.4 Session失效为什么登录一会儿就掉线现象登录成功后操作几分钟刷新页面就跳回登录页。原因Session默认存活时间是Tomcat的web.xml里配置的30分钟但这个时间是“空闲超时”不是“绝对超时”。如果服务器系统时间和实际时间不一致或者把项目的web.xml里Session超时时间设成了1分钟都会出现频繁掉线。另一种情况是浏览器禁用了CookieSessionId没法保存在JSESSIONID里每次请求都是新Session。解决先检查项目WEB-INF/web.xml里有没有session-config段session-timeout的值以分钟为单位建议设30或60。再检查Tomcat的conf/context.xml看是否有破坏Session的配置。浏览器端按F12打开开发者工具查看Network请求里的Cookie是否都带上了JSESSIONID。开发中用localhost访问没问题但如果是用IP访问注意Cookie的domain是否需要配置否则第一次请求种下的Cookie在后续请求中可能不带。5.5 重复执行SQL脚本导致的重复数据与主键撞车现象tb_user或tb_note里出现两条完全相同的数据或者插入时主键自增值跳得离谱比如用户表只有3条数据下一个id却是500多。原因开发时反复执行note.sql脚本脚本里的CREATE TABLE没有DROP TABLE IF EXISTS导致旧数据残留或者脚本本身带INSERT语句每次执行都追加一遍数据。自增主键一旦删除过记录MySQL会把自增值继续往后推不会回收已删除的id。解决给脚本加幂等处理在每张表前写DROP TABLE IF EXISTS tb_user;再CREATE TABLE这样重复执行不会产生脏数据。不想全删的话用TRUNCATE TABLE tb_user;清空表并重置自增。在生产环境千万不要手动执行TRUNCATE只把幂等脚本用在开发环境初始化。这类问题本质上是“部署流程黑匣子”没管好建议一个人维护脚本只用一份初始化SQL别随手造第二份。6. 验证与进阶把记事本从能跑到能用6.1 从DriverManager切换到连接池告别频繁建连用DriverManager每查一次数据就创建一次物理连接在并发请求上来时MySQL会很快达到max_connections上限。Druid连接池是常见的替换方案只改DBUtil里获取连接的部分DAO层代码不用动private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setUrl(URL); dataSource.setUsername(USER); dataSource.setPassword(PASSWORD); dataSource.setInitialSize(5); dataSource.setMaxActive(20); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }核心参数就两个initialSize是启动时预创建的连接数maxActive是最大活跃连接数。maxActive设太小高并发下线程会等待连接池释放接口响应变慢设太大会打满MySQL连接反而拖垮数据库。个人项目给10到20足够。6.2 参数化查询挡住SQL注入和越权前面代码里已经用PreparedStatement替代了Statement这是底线做法。顺手再做一层DAO方法里所有涉及用户ID的SQL一律用WHERE user_id ?带上参数不要相信前端传来的任何隐藏字段。JSP表单里的categoryId可能被恶意改成别人的分类ID接收到后要校验它确实属于当前用户否则数据归属就被绕过了。这两个习惯一起养成多用户系统的数据安全就稳了一半。6.3 多用户隔离验证与方法收尾部署完成后建议用两个账号做验证。注册用户A和用户B分别登录A新建三条记事并加分类B登录后看到列表为空直接访问A的记事编辑页URL应该被“查无此数据”挡住或被重定向。这一步真正验证了所有查询都带user_id条件是比功能验收更重要的数据隔离验收。再查一下更新时间和提醒时间的格式。tb_note里的update_time依赖ON UPDATE CURRENT_TIMESTAMPMySQL 8.0的默认配置就能生效如果是5.7要注意时间字段类型和自动更新是否被建表语句完整保留。这轮验证做完系统才算真的从“能跑”变成“能用”。这套JSPMySQL的记事本结构简单但五脏俱全。我现在遇到简单需求时仍然会选这种传统方式快速出活它的好处是部署链路透明、出问题可排查。在做登录拦截和SQL参数化时多花点功夫后面维护能省很多事。希望帮到你。本文还有配套的精品资源点击获取
返回列表