ARTICLE DETAIL

资讯详情

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

JSP+MySQL人事管理系统毕设实战:从环境搭建到避坑指南

JSP+MySQL人事管理系统毕设实战:从环境搭建到避坑指南 简介这是一套面向高校毕业设计、课程设计与JavaWeb实训的人事管理系统完整项目基于JSPServletMySQL实现可视为ERP人力资源模块的简化落地适合需要完成毕设或巩固JavaWeb开发技能的读者。压缩包共108个文件主要包括Java源码与class文件、JSP页面、HTML/CSS/JS前端资源、SQL数据库脚本、图片截图及配置文档整体仅2.04MB数据库文件放在lib包下便于快速导入使用。资源覆盖管理员登录校验、员工信息新增等核心功能代码以及对应页面样式与截图能够帮助读者理解人事管理场景下的增删改查、权限控制和数据库交互流程包内目录按代码、页面、配置等分层组织方便按需查阅。目前已有1447人学习下载适合作毕业设计选题参考、课程设计代码研读或JavaWeb项目实战练习的配套资料。1. 人事管理系统毕设JSPMysql它不是过时项目而是把JavaWeb全链路串起来的最小可演示方案人事管理系统毕设JSPMysql听着像十年前的技术组合但它恰恰是本科毕设和课程设计里最不容易翻车的全栈链路。它要求你把Servlet控制层、JSP展示层、Jdbc数据访问、Mysql存储全部串起来最终能跑出登录、员工增删改查、部门维护、考勤与薪资管理。如果你现在的状态是“学了JavaWeb但没串成项目”这个方向比直接抄Spring Boot项目更值得投入。正因为它短每一层的坑都会暴露得很充分环境、编码、连接池、会话、SQL、文件上传一个都躲不掉。这篇文章不渲染复杂只给你一条能复现的路线。2. 搭一套能跑的环境JDK/Tomcat/Mysql版本选型与Druid连接池落地2.1 JSPServletMysql这套组合毕设选它到底图什么先回答一个常被问的问题都用Spring Boot了为什么毕设还要受JSP和Mysql的“折磨”我的看法是毕设的目标不是证明你会用最新框架而是证明你能讲清楚一次HTTP请求从浏览器走到数据库再回来的完整路径。JSP负责展示、Servlet负责接收请求和控制跳转、Jdbc负责和Mysql交互三层边界非常清晰。你用Spring Boot时很多细节被自动装配盖住了答辩被问“拦截器在哪个环节生效”“SqlSession是怎么打开的”容易变成黑匣子用JSP这套反而每一个概念都能指给老师看。版本选择也不需要追新JDK 1.8、Tomcat 8.5/9.0、Mysql 5.7或8.0这几样放到今天依然稳定。JDK 17甚至21在部分老Tomcat和JSTL标签库组合里会遇到模块访问限制毕设没必要时不时给自己挖坑。Mysql用8.0的同学多因为安装教程多、驱动兼容也好但要注意8.0默认认证插件和连接参数都和5.7不一样后面避坑章节会专门讲。还有一个边界必须说清楚这套方案适合几千人规模的中小型人事系统。你要在高并发、微服务这些方向上做文章它不合适。但毕设评审更看重“功能完整、逻辑自洽、能讲清楚”。你非要在答辩PPT里写“支持万人并发”那谁也没办法帮你圆场。2.2 Windows下安装Mysql 8.0的实操my.ini、初始化与net start我一般推荐用zip解压版而不是msi安装版。zip版的好处是你能看到Mysql的完整安装过程卸载也干净删目录、删服务、删环境变量就行。下载mysql-8.0.x-winx64.zip之后解压到纯英文目录比如D:/mysql-8.0.36-winx64路径里不要出现中文和空格否则后面初始化大概率报错。在Mysql根目录新建一个my.ini这是整个安装过程最容易出错的地方[mysqld] basedirD:/mysql-8.0.36-winx64 datadirD:/mysql-8.0.36-winx64/data port3306 character-set-serverutf8mb4 collation-serverutf8mb4_general_ci default-storage-engineINNODB [client] default-character-setutf8mb4这里有几个参数我强调一下basedir和datadir必须用绝对路径正斜杠和双反斜杠都行但别写成单反斜杠导致转义问题datadir目录不用手动创建后续的mysqld --initialize会生成port3306如果被占用后面连接会一直失败排查时要习惯性看一下端口。character-set-serverutf8mb4是给后续表结构统一编码打底的我见过不少人只改客户端编码结果建表默认还是latin1插入中文直接变问号。初始化与启动Mysql以管理员身份打开cmd按顺序执行mysqld --initialize --console net start mysql mysql -u root -p第一句会在data目录生成初始数据并在控制台打印临时密码记得立刻复制保存后面第一次登录要用。如果漏看了临时密码也可以删除data目录重新初始化这是Mysql给你的后悔药。如果你看到“net start mysql”提示服务无法启动不要急着重装先检查my.ini路径、data目录权限、3306端口占用这三项第5章会展开写排查路径。顺便说一句有人图省事想用Docker装Mysql结果在端口映射、容器卷、时区上翻车然后回到Windows本机安装又重新踩一遍。毕设本地开发没必要多引入一层容器本机安装反而是最好讲清原理的。Linux服务器上部署时再用yum或rpm装也不迟。2.3 用Druid连接池救场JdbcUtils类与三个必调参数人事管理系统里每一次列表查询、登录校验都要访问数据库。如果每次new一个DriverManager.getConnection并发一上来马上卡死。我一般用Druid连接池理由是它自带监控页答辩时可以现场展示“当前连接数、活跃连接数、SQL执行次数”这是很实在的加分项。引入druid-1.2.x.jar和mysql-connector-java-8.0.x.jar放进WEB-INF/lib即可。在src目录下建db.propertiesdrivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/hr_db?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai usernameroot password123456 initialSize2 maxActive10 maxWait3000 validationQuerySELECT 1这段url是血泪经验的浓缩。mysql-connector-java 8.x把驱动类从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver网上老教程里的旧类名会让你直接ClassNotFoundException。useSSLfalse和allowPublicKeyRetrievaltrue是为了绕开Mysql 8.0默认SSL握手和公钥检索报错serverTimezoneAsia/Shanghai解决时区报错。这几个参数缺一不可第5章会逐个展开。再写一个JdbcUtils工具类让所有DAO从这里拿连接public class JdbcUtils { private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(JdbcUtils.class.getClassLoader() .getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(初始化连接池失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement st, ResultSet rs) { // 先关rs再关st最后关conn if (rs ! null) { try { rs.close(); } catch (SQLException e) { } } if (st ! null) { try { st.close(); } catch (SQLException e) { } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { } } } }三个必调参数我单独拎出来说initialSize控制启动时预创建几个连接我习惯设2够用且不大maxActive控制最大连接数毕设单机10足够设太大反而浪费资源maxWait3000是拿连接的超时时间如果数据库连不上3000毫秒后快速失败而不是让页面卡半分钟。还有validationQuerySELECT 1让Druid在空闲连接被回收时自动检测连接是否有效Mysql断过网、休眠过一段时间后这条语句能帮你自动剔除死连接。你有没有专门写close方法直接影响系统跑半天后连接耗尽。连接池里的close其实是“归还连接”这个口径面试时也值得讲。3. 建表先于编码人事管理系统的表结构与初始化SQL3.1 五张表怎么设计员工、部门、账号、考勤与薪资人事管理系统的核心业务是“员工档案 组织部门 考勤 工资”再外挂一个登录账号。我拆成五张表t_admin登录账号、t_dept部门、t_employee员工、t_attendance考勤、t_salary薪资。别把管理员塞进员工表虽然管理员也是人但职责完全不同单独建表以后扩展角色权限也方便。字段设计要注意几个常被问到的点主键统一用INT AUTO_INCREMENT不用UUID。面试题经常问“为什么不用UUID做主键”因为自增主键在InnoDB的B树里是顺序写入页分裂少分页排序稳定UUID离散写入索引树会频繁分裂数据量一大性能明显下降。毕设虽然感受不到差异但这是能讲出逻辑的地方。性别字段我建议用TINYINT存0/1再加注释而不是直接存“男/女”。虽然直接存中文也能跑但用数字存更规范JSP页面上用三元表达式或者枚举转一下就行。工资金额不要用FLOAT或DOUBLE用DECIMAL(10,2)否则算完工资出现0.10.20.30000000000000004这种场面就尴尬了。日期字段统一用DATETIME比TIMESTAMP更直白不需要考虑时区转换问题。外键关系上t_employee的dept_id关联t_dept.idt_attendance和t_salary的emp_id关联t_employee.id。删除员工策略我一般用逻辑删除也就是给员工表加一个status字段0正常、1离职、2删除而不是物理DELETE这样考勤和工资历史记录不会被破坏。外键在物理删除时会因为关联记录报错逻辑删除就没有这个麻烦。3.2 建表SQL脚本用外键、索引和事务把关系讲明白下面这段SQL可以直接当成初始化脚本使用建议把建库、建表、插初始数据分开写方便以后重新执行CREATE DATABASE IF NOT EXISTS hr_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hr_db; CREATE TABLE t_dept ( id INT AUTO_INCREMENT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称, manager VARCHAR(20) COMMENT 部门负责人, phone VARCHAR(20) COMMENT 联系电话 ) ENGINEInnoDB COMMENT部门表; CREATE TABLE t_admin ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加密后的密码, real_name VARCHAR(20) COMMENT 真实姓名, role VARCHAR(20) DEFAULT ADMIN COMMENT 角色 ) ENGINEInnoDB COMMENT登录账号表; CREATE TABLE t_employee ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, name VARCHAR(20) NOT NULL COMMENT 姓名, gender TINYINT COMMENT 性别 0男 1女, birth_date DATE COMMENT 出生日期, dept_id INT COMMENT 部门ID, position VARCHAR(30) COMMENT 岗位, phone VARCHAR(20) COMMENT 手机号, email VARCHAR(50) COMMENT 邮箱, avatar VARCHAR(200) COMMENT 头像路径, entry_date DATE COMMENT 入职日期, status TINYINT DEFAULT 0 COMMENT 0在职 1离职 2删除, KEY idx_emp_dept (dept_id), KEY idx_emp_name (name) ) ENGINEInnoDB COMMENT员工表; CREATE TABLE t_attendance ( id INT AUTO_INCREMENT PRIMARY KEY, emp_id INT NOT NULL COMMENT 员工ID, work_date DATE NOT NULL COMMENT 考勤日期, check_in DATETIME COMMENT 上班打卡, check_out DATETIME COMMENT 下班打卡, status TINYINT DEFAULT 0 COMMENT 0正常 1迟到 2早退 3缺勤, KEY idx_att_emp (emp_id, work_date) ) ENGINEInnoDB COMMENT考勤表; CREATE TABLE t_salary ( id INT AUTO_INCREMENT PRIMARY KEY, emp_id INT NOT NULL COMMENT 员工ID, salary_month CHAR(7) NOT NULL COMMENT 工资月份 如2026-06, basic_salary DECIMAL(10,2) COMMENT 基本工资, bonus DECIMAL(10,2) DEFAULT 0 COMMENT 奖金, deduction DECIMAL(10,2) DEFAULT 0 COMMENT 扣款, final_salary DECIMAL(10,2) COMMENT 实发工资, KEY idx_sal_emp (emp_id, salary_month) ) ENGINEInnoDB COMMENT薪资表; ALTER TABLE t_employee ADD CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES t_dept(id) ON DELETE SET NULL;几个参数必须交代清楚。ENGINEInnoDB是底线只有它能支持事务和行级锁如果哪张表漏写了变成默认MyISAM后面做事务回滚演示时会翻车。utf8mb4比utf8多支持emoji和生僻字人事系统里员工姓名有生僻字是常态。索引方面t_employee的dept_id和name都建了普通索引用来加速按部门筛选和按姓名模糊查询考勤表建了emp_idwork_date的联合索引因为最常用的查询是“查某个人某段时间的打卡记录”。排序走索引字段会更稳。建表之后执行计划可以顺手验证一下EXPLAIN SELECT e.*, d.dept_name FROM t_employee e LEFT JOIN t_dept d ON e.dept_id d.id ORDER BY e.id DESC LIMIT 0, 10;看结果里type不是ALL、key列用上了索引说明这条查询能利用索引如果type是ALL就说明索引没生效去检查字段类型是否一致、有没有在索引列上做函数运算。毕设里能做一次EXPLAIN比写十个功能点更能证明你懂SQL。3.3 初始化数据与“个人信息展示页面”的联表查询数据库空表没法演示初始化数据要尽量贴近真实。用事务包住批量插入好处是中途出错可以整体回滚START TRANSACTION; INSERT INTO t_dept (dept_name, manager, phone) VALUES (技术部, 张伟, 13800000001), (人事部, 李芳, 13800000002), (财务部, 王强, 13800000003); INSERT INTO t_admin (username, password, real_name) VALUES (admin, MD5(123456), 系统管理员); INSERT INTO t_employee (emp_no, name, gender, dept_id, position, phone, entry_date) VALUES (E001, 赵敏, 0, 1, Java开发, 13900000001, 2025-03-01), (E002, 钱进, 0, 1, 前端开发, 13900000002, 2025-04-15), (E003, 孙丽, 1, 2, 招聘专员, 13900000003, 2025-05-20); COMMIT;MD5(123456)是Mysql内置函数直接用SQL初始化密码比在Java代码里生成方便。这个事务块也回答了“mysql执行sql脚本”怎么用把上面所有SQL保存成init.sql再执行mysql -u root -p hr_db init.sql或者用Navicat直接运行SQL文件。个人信息展示页面要查“当前登录人自己的档案”员工表只存dept_id页面要显示部门名就必须联表SELECT e.*, d.dept_name AS deptName FROM t_employee e LEFT JOIN t_dept d ON e.dept_id d.id WHERE e.id ?;这里用LEFT JOIN而不是INNER JOIN是因为员工可能还没分配部门dept_id为NULL时也要能查出员工本人。查询结果放request域JSP里用EL表达式输出。典型的“jsp个人信息展示页面”片段% page contentTypetext/html;charsetUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % div classprofile p工号${emp.empNo}/p p姓名${emp.name}/p p部门${emp.deptName}/p p岗位${emp.position}/p img src${emp.avatar} alt头像 width120 /divEL表达式能取到emp.deptName的前提是Employee实体里有getDeptName()方法或者查询结果封装时手动set一个deptName属性。页面显示不出来时不要怀疑JSP语法先看控制台有没有报“Property deptName not found”有就是实体类缺getter。这个错误我在新手项目里见得太多了。4. 核心功能编码登录、分页、头像上传和图片坐标定位4.1 用Filter做登录拦截Session超时与放行规则登录是人事系统第一个要写的功能但比LoginServlet更重要的是登录拦截器。如果不做拦截员工列表、薪资查询这些页面可以直接通过URL访问答辩时被老师指出来很掉价。我用一个Filter统一处理WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); String uri req.getRequestURI(); boolean loggedIn session ! null session.getAttribute(loginUser) ! null; boolean isStatic uri.endsWith(.css) || uri.endsWith(.js) || uri.contains(/images/) || uri.contains(/upload/); boolean isLogin uri.endsWith(login.jsp) || uri.endsWith(loginServlet); if (loggedIn || isStatic || isLogin) { chain.doFilter(req, resp); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } } }逻辑上就一句话没登录的人一律回登录页但CSS、JS、图片这些静态资源必须放行。很多新手忘了放行静态资源结果登录页打开后样式全丢头像也加载不出来。req.getSession(false)不会自动创建Session避免了每次请求都生成一个无用会话。登录成功后的会话超时也要设置建议在登录Servlet里加上session.setMaxInactiveInterval(30 * 60)表示30分钟无操作自动失效。Session失效后Filter里session.getAttribute(loginUser)取出来是null自动跳回登录页。这就是会话管理完整闭环。密码不要明文存库至少用MD5加盐答辩PPT里敢写“密码加密存储”这比空谈安全意识实在。4.2 员工分页列表与排序LIMIT offset,size 动态WHERE员工列表是人事系统的主页面条件一般是“按姓名模糊搜索 按部门筛选 分页”。用Mysql的LIMIT做物理分页而不是把数据全查出来在Java里截取。核心DAO方法这样写public ListEmployee findByPage(String name, Integer deptId, int offset, int pageSize) { String sql SELECT e.*, d.dept_name AS deptName FROM t_employee e LEFT JOIN t_dept d ON e.dept_id d.id WHERE 11 ; ListObject params new ArrayList(); if (name ! null !name.trim().isEmpty()) { sql AND e.name LIKE ?; params.add(% name.trim() %); } if (deptId ! null deptId 0) { sql AND e.dept_id ?; params.add(deptId); } sql ORDER BY e.id DESC LIMIT ?, ?; params.add(offset); params.add(pageSize); try (Connection conn JdbcUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } try (ResultSet rs ps.executeQuery()) { // 封装为Employee对象并返回List } } catch (SQLException e) { throw new RuntimeException(分页查询失败, e); } }为什么拼SQL用WHERE 11因为后续条件都是AND开头11相当于一个恒真的占位让拼接逻辑统一。所有用户输入都用?占位符不要直接拼进SQL防止SQL注入。LIKE查询要自己拼好%通配符再传参否则PreparedStatement不会替你加。ORDER BY排序字段不能直接拼接用户传入的列名最好在后端做一个白名单映射用户传“按入职时间排”就转成entry_date“按工号排”就转成emp_no。JSP页面用JSTL循环输出上一页下一页的页码直接拼URLtable classemp-table trth工号/thth姓名/thth部门/thth岗位/thth操作/th/tr c:forEach items${page.list} varemp tr td${emp.empNo}/td td${emp.name}/td td${emp.deptName}/td td${emp.position}/td tda hrefempEdit?id${emp.id}编辑/a/td /tr /c:forEach /table div classpager c:if test${page.currentPage 1} a hrefempList?page${page.currentPage - 1}name${keyword}上一页/a /c:if span第 ${page.currentPage} / ${page.totalPage} 页/span c:if test${page.currentPage page.totalPage} a hrefempList?page${page.currentPage 1}name${keyword}下一页/a /c:if /div分页参数里有三个值必须一致currentPage、pageSize、totalCount。totalCount来自SELECT COUNT(*)的统计SQLtotalPage等于totalCount除以pageSize向上取整。新手容易把totalCount算成当前页查出来的条数最后一页只有两条分页就乱了。另外跳转上一页下一页时要带上搜索条件否则翻页后搜索条件丢失列表变回全量数据。4.3 头像上传与坐标定位用Servlet 3.0 Part和鼠标事件员工信息通常要上传头像Servlet 3.0以后不需要再引入第三方上传组件直接在Servlet上加MultipartConfig就能接收文件。上传第一个坑是保存路径我建议用外部绝对路径WebServlet(/uploadAvatar) MultipartConfig(maxFileSize 2 * 1024 * 1024, maxRequestSize 5 * 1024 * 1024) public class UploadAvatarServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); Part part req.getPart(avatar); if (part null || part.getSize() 0) { resp.sendRedirect(personalInfo.jsp?msg请选择图片); return; } String submitted part.getSubmittedFileName(); String suffix ; if (submitted ! null submitted.contains(.)) { suffix submitted.substring(submitted.lastIndexOf(.)); } String fileName UUID.randomUUID().toString() .replace(-, ) suffix; String uploadDir D:/hr_uploads; File dir new File(uploadDir); if (!dir.exists()) dir.mkdirs(); part.write(uploadDir File.separator fileName); Employee emp (Employee) req.getSession().getAttribute(loginUser); emp.setAvatar(/upload/ fileName); // 调用DAO更新该员工的avatar字段 resp.sendRedirect(personalInfo.jsp); } }MultipartConfig的maxFileSize限制单文件2MBmaxRequestSize限制整个请求5MB超了会抛异常页面要统一处理。UUID重命名是为了避免两个用户上传同名文件互相覆盖也避开中文文件名在Tomcat下的乱码问题。不用request.getServletContext().getRealPath(/upload)保存因为IDE内置Tomcat的目录是临时的重启一次文件就没了外部绝对路径D:/hr_uploads保存后还需要在Tomcat里加一个虚拟路径映射或者在项目里配一个资源映射让/upload/xxx能访问到磁盘文件。再来说热搜里很具体的“jsp图片如何对坐标定位”。这个需求在人事系统里可以落地为在员工头像照片上让管理员点击标记“人脸位置”或“签名位置”然后把坐标存进数据库。前端思路是用JS监听图片点击事件计算点击点相对图片左上角的偏移var img document.getElementById(avatarImg); var coordInput document.getElementById(coord); img.addEventListener(click, function (e) { var rect img.getBoundingClientRect(); var x Math.round(e.clientX - rect.left); var y Math.round(e.clientY - rect.top); coordInput.value x , y; // 在图片上画一个红点提示 });页面里放一个隐藏域表单提交时把“x,y”字符串带到后台后台用split(,)拆成两个字段保存。注意getBoundingClientRect获取的是图片在页面上的实际显示尺寸如果CSS把图片缩放过算出来的坐标和原图坐标不一致。要精确对应原图需要用img.naturalWidth除以rect.width做比例换算var scaleX img.naturalWidth / rect.width; var scaleY img.naturalHeight / rect.height; var realX Math.round((e.clientX - rect.left) * scaleX); var realY Math.round((e.clientY - rect.top) * scaleY);这个细节容易被忽略但做过图片标注的人都知道坐标定位不准十有八九是缩放比没换算。5. 避坑记录Mysql服务起不来、SSL报错、JSP乱码等5个修复现场5.1 net start mysql 服务无法启动99% 是my.ini和目录权限现象以管理员身份执行net start mysql提示“服务名无效”或者“服务无法启动”查看Windows事件日志也只有一个笼统的“服务没有及时响应”。原因三种情况最常见。一是service没有真正安装成功mysqld --install这步被跳过二是my.ini里的basedir、datadir路径不对或data目录没有正确初始化三是3306端口被占用Mysql起不来。解决先看Mysql自己的报错执行mysqld --console它会把错误直接打在控制台比看系统日志有用。检查my.ini的路径是不是纯英文把data目录整个删掉重新执行一遍mysqld --initialize --console再用net start mysql启动。如果提示端口占用执行netstat -ano | findstr 3306看到占用进程后把它处理掉或者换一个port。服务本身装坏了就用sc delete mysql清掉服务再重新mysqld --install。我见过不少在Docker里装Mysql失败转回Windows本机又卡在同一处的人归根结底是没看原始日志、凭感觉瞎试。5.2 SSL连接错误与Public Key Retrieval is not allowed现象驱动加载成功但创建连接时报Communications link failure最后一行写着Public Key Retrieval is not allowed有时还会看到SSL connection error。原因Mysql 8.0默认认证插件是caching_sha2_password客户端第一次连接需要向服务器请求公钥来做密码传输加密而Connector/J 8.0的useSSL默认也是true两个默认叠加在一起就卡在握手阶段。解决在连接URL上加两个参数即可jdbc:mysql://localhost:3306/hr_db?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiuseSSLfalse表示本地开发不用SSL加密避免TLS握手allowPublicKeyRetrievaltrue允许客户端向服务器拿公钥。这两个参数都只适合开发和内网环境生产环境应该反过来用SSL证书。如果你不想改URL也可以执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;把认证插件降级但8.0的后续版本对native_password的支持在弱化改URL是更通用的解法。5.3 JSP乱码与“页面必须刷新一次才出现新数据”现象表单POST提交中文保存到Mysql后查出来是问号或者新增一条员工记录后跳转回列表页页面要手动刷新一次才能看到新数据。原因乱码是编码链路断了。Tomcat对POST请求默认按ISO-8859-1解析没设置request.setCharacterEncoding(UTF-8)中文进来就已经变成乱码响应端如果没有设置Content-Type:text/html;charsetUTF-8浏览器又会按本机默认编码显示。至于要刷新才看到新数据往往是你新增成功后用的是forward转发浏览器地址栏还是新增页刷新时重复提交。解决写一个编码过滤器让所有请求在进入Servlet前统一处理WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }新增成功后的跳转使用resp.sendRedirect(empList?page1)也就是Post-Redirect-Get模式提交一次后浏览器重新请求列表页刷新行为变成正常的GET重复提交问题也没了。热搜里的“jsp页面让加载完后刷新一次”有些人会用或setTimeout来定时刷新这只能算是兜底做法真正该做的是让服务器正确跳转加上统一编码。浏览器缓存也会造成“手动刷新才看到新头像”可以在关键响应头加Cache-Control: no-cache缓解。5.4 中文文件名乱码和头像上传后不显示现象上传一张“张三.jpg”控制台不报错但页面上头像裂开或者文件名变成一段乱码在Mysql里存的路径看着就不对。原因文件名的锅。Part.getSubmittedFileName()返回的文件名包含中文如果请求编码没设置对取出来就是乱码即便编码对了直接拿中文名存磁盘后续URL访问还要再做一层URL编码非常容易出问题。解决不要用原始文件名改成UUID重命名后缀从原始文件名里截出来就好。上传目录也要独立不要用getRealPath。Tomcat里需要把外部目录映射成虚拟路径在conf/server.xml的Host节点下加Context docBaseD:/hr_uploads path/upload reloadabletrue/配置好之后数据库存/upload/uuid.jpg页面直接用相对路径就能访问。这里有个教训如果项目部署时Tomcat的docBase和webapps路径混在一起重复部署会把Context配置覆盖掉改动前后最好备份server.xml。5.5 JDBC驱动版本与“Server returns invalid timezone”时区报错现象控制台报java.sql.SQLException: The server time zone value йʱ is unrecognized或者ClassNotFoundException: com.mysql.jdbc.Driver再或者Unknown initial character set index 255。原因前一个是连接URL没写serverTimezone参数Connector/J 8.x拿不到Mysql的时区信息中间一个是驱动类名写错5.x的驱动类是com.mysql.jdbc.Driver8.x改成了com.mysql.cj.jdbc.Driver最后一个是数据库字符集是utf8mb4但驱动太老不认这个字符集。解决把WEB-INF/lib清理干净只放一个mysql-connector-java-8.0.x的jar避免多个版本冲突驱动类用com.mysql.cj.jdbc.Driver连接URL统一带上时区和字符集。给一个可以直接复制的完整模板jdbc:mysql://localhost:3306/hr_db?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai这类问题集中在“版本换了但配置还是老一套”上。项目里如果有人复制了旧教程的db.properties第一件事就是看驱动类名和URL参数不要先去怀疑Tomcat配置。6. 从“能跑”到“能答辩”事务验证、备份恢复与演示前的小习惯系统跑通以后答辩前我习惯做一次“冷启动验证”把Tomcat和Mysql全部停掉再按顺序启动从头登录走一遍员工增删改查。这一步能抓出八成环境型翻车。然后再准备两个能现场讲的技术点。第一个是事务回滚演示。人事系统里最容易讲清事务的场景是“入职新员工并扣减部门编制”两步操作必须同时成功或同时失败。答辩时直接切换到Mysql命令行执行一个手工事务START TRANSACTION; INSERT INTO t_employee (emp_no, name, dept_id, position) VALUES (E099, 测试, 1, 实习生); UPDATE t_dept SET manager 测试 WHERE id 1; ROLLBACK;ROLLBACK之后再查SELECT * FROM t_employee WHERE emp_noE099会发现没有数据。这个演示比讲十句“事务保证一致性”都有效。如果你在Service层也写了同样的逻辑可以现场把第二步改成故意抛异常让代码里的rollback生效。第二个是数据库备份恢复。用mysqldump导出用mysql导入这两个命令毕业设计答辩前一定要会mysqldump -u root -p hr_db D:/hr_backup_$(date %Y%m%d).sql mysql -u root -p hr_db D:/hr_backup.sql演示前备份一次演示过程中就算手滑删了数据也能用导入命令救回来。恢复之前先DROP DATABASE hr_db再CREATE DATABASE hr_db确保是全新状态否则旧数据和新数据混在一起会出现主键冲突。还有两个演示前的小习惯我吃了不少亏才养成浏览器用无痕窗口打开避免上次登录的Session态和缓存干扰判断演示数据库用干净的初始化脚本重新灌一遍不要拿调试过程中改乱的脏数据上屏。我的固定四步是“重建库、重启Tomcat、开无痕、跑一遍主流程”每一步做下来心里才踏实。这四步也希望能帮到你祝你答辩顺利。本文还有配套的精品资源点击获取
返回列表