ARTICLE DETAIL

资讯详情

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

Java Web简历管理系统:Servlet+DAO分层架构与招聘状态流转实战

Java Web简历管理系统:Servlet+DAO分层架构与招聘状态流转实战 简介面向毕业设计或课程设计场景的Java Web简历管理系统项目包采用JSP、Servlet、Service层与Dao层分层设计并以MySQL作为数据存储构成一个可运行的简历管理平台。整套资源共162个文件压缩包大小约2.75MB其中包含55个Java源程序、61个编译生成的Class字节码、17个JSP页面模板、10个第三方依赖库文件以及2个数据库建表脚本另附工程导入所需的配置信息方便在集成开发环境中直接部署。目前已有301人参与学习与下载。系统实现了用户注册与登录、简历的创建编辑与删除、简历信息的浏览查看、关键词搜索与条件筛选以及普通用户和管理员的不同权限控制等功能代码结构按控制层、业务层、数据访问层、视图层清晰划分能够帮助学习者理解MVC模式在真实项目中的应用方式。项目对简历所含教育背景、工作经历、技能特长等字段进行了结构化设计可直观感受从前端表单提交到数据库落库的完整过程。借助源码还可以掌握Servlet请求处理流程、JSP页面渲染、DAO封装、SQL表设计等核心知识点适合作为Java Web开发能力的综合训练项目。1. 这套企业简历管理系统的真正价值在排期与评分闭环这套项目的价值点和大部分人的第一反应不一样登录注册、简历增删改查只是外壳真正体现设计功力的是“排期 笔试/面试成绩录入 状态流转”这条招聘闭环。从类名就能看出来——RMSResumeBrowsingServlet 管简历浏览、RMSPaperExamEnteringServlet 管笔试成绩录入、RMSInterviewEnteringServlet 管面试成绩录入、RMSResumeSchedulingServlet 管考核排期四条链路串起来的是一份简历从投递到录用或淘汰的完整生命周期。对准备 Java Web 课程设计、毕业设计答辩的人来说这套架构比单纯做个 CRUD 系统耐问得多。适合三类人急需 Java Web 完整项目做课设的学生、想搞懂 Servlet Service DAO 分层实战的初学者、以及想快速改造出招聘流程演示系统的开发者。2. 项目架构与数据模型先看懂分层再谈复现2.1 三层架构拆解Servlet 到底在替谁干活先说结论这个系统是典型的 JSP Servlet Service DAO 手工分层没用 Spring也没用 MyBatis。对课程设计来说这反而是优点——答辩时考官问“请求是怎么走的”你能从浏览器一路讲到数据库每一层都拿得出代码。硬套 SSM 反而容易把自己绕进去。我按名称把 Servlet 的责任范围理了一下大致是这样Servlet 类名对应的 urlPatterns常见映射职责RMSInitServlet/init初始化公共数据例如基础配置、字典项加载到 application 作用域RMSNavigationServlet/navigation统一页面跳转根据参数转发到对应 JSPRMSResumeBrowsingServlet/resumeBrowser简历分页列表、按条件搜索RMSResumeSchedulingServlet/resumeSchedule给应聘者安排笔试/面试轮次修改状态RMSPaperExamEnteringServlet/paperExamEntering录入笔试成绩RMSInterviewEnteringServlet/interviewEntering录入面试成绩并推进招聘状态这里的请求链路是固定的浏览器请求 → web.xml 或 WebServlet 注解匹配 Servlet → Servlet 调用 Service → Service 调 DAO → DAO 用 JDBC 访问 MySQL → 结果一层层返回最后 Servlet 用 request.setAttribute 把数据塞给 JSP再 forward 过去。代码走查时我建议你重点看 RMSInitServlet 和 RMSNavigationServlet 的初始化逻辑它们决定了系统启动后能不能正确跳转。在实际项目中我会在 init 方法里读数据库配置放到 application 里避免每个 Servlet 重复读取连接参数。这套代码如果也这么做你找数据库连接配置时先看它的 init 段public void init() throws ServletException { // 读取数据库连接和系统参数放入application作用域 String dbUrl getServletContext().getInitParameter(jdbcUrl); String dbUser getServletContext().getInitParameter(dbUser); String dbPassword getServletContext().getInitParameter(dbPassword); getServletContext().setAttribute(dbUrl, dbUrl); getServletContext().setAttribute(dbUser, dbUser); getServletContext().setAttribute(dbPassword, dbPassword); }这里我说明一下把连接信息放 application 作用域是课程项目的常见做法能让你在一个地方改配置、全局生效。但生产环境绝不要这样干密码应该放在加密配置中心或环境变量里这是我需要提醒你的边界。判断一个 Java Web 项目值不值得下载我的习惯是先看它有没有“业务深度”。纯 CRUD 的课程项目到处都是但带多状态流转、多轮考核评分这种业务逻辑的才值得你花时间拆。这套系统明显属于后者。2.2 数据模型梳理排期、轮次和成绩表的关系接下来是数据库。这套包里的 SQL 脚本和 DAO 类名已经透露了表结构方向——有应聘者表、考核轮次表、成绩表、用户表。我先给你一个按常见实现还原出的建表参照你导入 SQL 后可以对照检查字段-- 应聘者主表一份简历的核心信息 CREATE TABLE rms_applicant ( applicant_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 姓名, gender VARCHAR(10) COMMENT 性别, phone VARCHAR(20) COMMENT 联系电话, email VARCHAR(50) COMMENT 邮箱, education VARCHAR(20) COMMENT 最高学历, experience TEXT COMMENT 工作经验描述, skill VARCHAR(255) COMMENT 技能特长, resume_text TEXT COMMENT 简历全文内容, current_status INT DEFAULT 0 COMMENT 状态: 0待筛选 1笔试中 2面试中 3已录用 4已淘汰, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 考核轮次表记录笔试、一面、二面等排期 CREATE TABLE rms_round ( round_id INT PRIMARY KEY AUTO_INCREMENT, applicant_id INT NOT NULL COMMENT 关联应聘者, round_type INT COMMENT 1笔试 2面试 3终面, round_name VARCHAR(50) COMMENT 轮次名称, round_date DATETIME COMMENT 考核时间, location VARCHAR(100) COMMENT 地点/线上链接, remark VARCHAR(255) COMMENT 备注, FOREIGN KEY (applicant_id) REFERENCES rms_applicant(applicant_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 成绩记录表分数与评价 CREATE TABLE rms_score ( score_id INT PRIMARY KEY AUTO_INCREMENT, round_id INT NOT NULL COMMENT 关联轮次, applicant_id INT NOT NULL COMMENT 关联应聘者, score DECIMAL(5,2) COMMENT 得分例如85.50, evaluator VARCHAR(50) COMMENT 评分人, comment VARCHAR(500) COMMENT 评价意见, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (round_id) REFERENCES rms_round(round_id), FOREIGN KEY (applicant_id) REFERENCES rms_applicant(applicant_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 管理员表 CREATE TABLE rms_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, role INT DEFAULT 1 COMMENT 1普通用户 2管理员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表 SQL 是这套系统数据关系的核心概括。你需要这样读rms_applicant 是主表current_status 字段控制整条招聘流程走到哪一步rms_round 表记录了考核排期每次“排期”操作就是向这张表插一条记录rms_score 表存分数它挂在 round 下面一个轮次可以有多人打分。理解了这个关系你再看 DAO 层代码就不会晕。有个地方要特别留意rms_score 和 rms_round 用外键做了关联但某些版本的 SQL 脚本里外键约束可能没写完整。你在 MySQL 里SHOW CREATE TABLE rms_score;看一眼外键在不在没有的话建议手动补上否则后期录入成绩时可能出现悬空数据。我在课设复现时最容易在这块翻车。3. 核心业务实现拆解从简历浏览到成绩录入的完整链路3.1 简历浏览分页查询的 condition 拼装简历浏览是系统的最前端入口。RMSResumeBrowsingServlet 承担分页列表和多条件筛选DA 层里对应的是 ApplicantDaoImpl。核心逻辑是“动态拼 SQL”根据用户勾选的学历、输入的姓名关键词决定 WHERE 子句长什么样。protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 请求和响应编码统一成UTF-8防止中文参数变乱码 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); int pageNum 1; int pageSize 10; String keyword request.getParameter(keyword); String education request.getParameter(education); // 页码从请求中获取但要校验非法值 String pageParam request.getParameter(page); if (pageParam ! null !.equals(pageParam)) { pageNum Integer.parseInt(pageParam); } // 保证pageNum最小为1避免负数导致SQL异常 if (pageNum 1) pageNum 1; // 封装查询条件 MapString, Object condition new HashMap(); condition.put(keyword, keyword); condition.put(education, education); ApplicantDao dao new ApplicantDaoImpl(); PageResultApplicant pageResult dao.pageQuery(pageNum, pageSize, condition); request.setAttribute(pageResult, pageResult); request.setAttribute(keyword, keyword); request.setAttribute(education, education); request.getRequestDispatcher(/WEB-INF/jsp/resume_list.jsp).forward(request, response); }这段代码的逻辑我拆开讲pageNum 和 pageSize 是分页的两根支柱pageSize 决定每页多少条简历一般课程项目用 10如果你演示的数据量大可以调到 20但注意 JSP 页面里分页按钮的显示逻辑。condition Map 是传参的容器把 keyword 和 education 打包传给 DAO这样的好处是后续加筛选条件不用改方法签名只改 Map 的内容。最后 forward 到 JSP而不是 sendRedirect因为 forward 能把 request 里的属性带过去JSP 直接pageResult.list就能迭代出数据列表。对应 DAO 层的实现逻辑是这个系统的样板我建议你把这段当模板背下来public PageResultApplicant pageQuery(int pageNum, intPageSize, MapString, Object condition) { ListObject params new ArrayList(); StringBuilder sql new StringBuilder(SELECT * FROM rms_applicant WHERE 11 ); // 名字、技能、简历内容三个字段做模糊匹配 if (condition.get(keyword) ! null !.equals(condition.get(keyword))) { String keyword % condition.get(keyword) %; sql.append( AND (name LIKE ? OR skill LIKE ? OR resume_text LIKE ?) ); params.add(keyword); params.add(keyword); params.add(keyword); } // 学历精确匹配 if (condition.get(education) ! null !.equals(condition.get(education))) { sql.append( AND education ? ); params.add(condition.get(education)); } // 统计总数、查当前页数据 // SELECT COUNT(*) FROM rms_applicant 同样的where条件 // SELECT * FROM rms_applicant where条件 LIMIT ?, ? int offset (pageNum - 1) * pageSize; sql.append( ORDER BY create_time DESC LIMIT ?, ?); params.add(offset); params.add(pageSize); // 执行两条SQL后封装成PageResult对象返回 }这里的关键参数是 LIMIT 的 offset 和 end 计算——第一条记录的偏移量永远是 (pageNum-1) × pageSize这是 JDBC 手写分页最常见的数学逻辑面试也常问。你再看这个 DAO 里用?占位符而不是字符串拼接就是为了防止 SQL 注入输入的关键词里带单引号也不会把 SQL 搞坏。很多改作业的学生会把这里写成字符串拼接然后被注入攻击演示打穿答辩前一定要改成 PreparedStatement 风格。3.2 笔试与面试成绩录入round_id 与 score 联动成绩录入环节是本系统最有业务厚度的一块。笔试成绩走 RMSPaperExamEnteringServlet面试成绩走 RMSInterviewEnteringServlet两者最终都落到 ScoreDaoImpl。数据关系上一份成绩必须挂在某个 round 下面所以录入成绩前你得先确定轮次 IDpublic int saveScore(Score score) { String sql INSERT INTO rms_score (round_id, applicant_id, score, evaluator, comment) VALUES (?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, score.getRoundId()); ps.setInt(2, score.getApplicantId()); // 分数可能是小数用setDouble而不是setInt ps.setDouble(3, score.getScore()); ps.setString(4, score.getEvaluator()); ps.setString(5, score.getComment()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }这里我特别强调一个参数坑分数字段我用 setDouble 是因为 SQL 表里定义的是 DECIMAL(5,2)支持 85.50 这种带小数的笔试分。你要是图省事全用 setString数据库虽然能隐式转换但遇到非数字字符串会在 DAO 层直接抛异常而且这种 bug 很难查因为你看到的报错信息是那一长串 SQLException 而不是“你传错类型了”。录入后正常情况返回 1返回 0 就是在告诉你连数据库都没写进去先查连接。面试成绩录入的 Servlet 除了调 saveScore通常还会连带更新应聘者状态——面试通过就把 current_status 改成 3 已录用或者进入下一轮。这一步涉及事务public boolean passInterview(int applicantId, int nextStatus, Score score) { Connection conn null; try { conn DBUtil.getConnection(); // 关闭自动提交让多条操作绑成一个事务 conn.setAutoCommit(false); // 第一步插入成绩记录 String insertScore INSERT INTO rms_score (round_id, applicant_id, score, evaluator, comment) VALUES (?, ?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(insertScore)) { ps.setInt(1, score.getRoundId()); ps.setInt(2, applicantId); ps.setDouble(3, score.getScore()); ps.setString(4, score.getEvaluator()); ps.setString(5, score.getComment()); ps.executeUpdate(); } // 第二步更新应聘者状态 String updateStatus UPDATE rms_applicant SET current_status ? WHERE applicant_id ?; try (PreparedStatement ps conn.prepareStatement(updateStatus)) { ps.setInt(1, nextStatus); ps.setInt(2, applicantId); ps.executeUpdate(); } // 都成功才提交 conn.commit(); return true; } catch (SQLException e) { // 只要有一个失败就回滚 if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DBUtil.close(conn, null, null); } }这段代码要讲的参数细节有两个。conn.setAutoCommit(false) 是把 JDBC 的默认自动提交关掉让两条 SQL 形成一个原子操作——成绩写成功但状态没改成功时全部回滚不会出现“分数在但简历状态卡住”这种脏数据。DBUtil.close 是自定义工具类负责把 Connection、Statement、ResultSet 按顺序关掉顺序反了会内存泄漏。你答辩时如果被问到“事务你怎么控制的”这段就是标准答案。3.3 状态流转排期 Servlet 的调度逻辑RMSResumeSchedulingServlet 是这套系统里最容易被忽略但最关键的组件。它做的事情是给应聘者排考核当前状态是“待筛选”排笔试状态推进到“笔试中”笔试通过后安排面试状态改“面试中”。它本质上是一个状态机控制器用 current_status 字段的值驱动流程。我在读这种排期代码时有一个固定的看码路径先找状态字段的赋值点再看有没有前置校验。好的排期逻辑应该拒绝“从待筛选直接跳到已录用”这种非法操作因为中间少了笔试和面试招进来的人是什么水平都没验证过。这属于业务规则应该写在 Service 层而不是 JSP 里硬判。实务中我会这样设计 Service 的排期方法public boolean scheduleRound(int applicantId, int roundType, String roundName, Date roundDate, String location) { // 根据应聘者当前状态判断该轮次是否合法 Applicant applicant applicantDao.findById(applicantId); if (applicant null) { return false; } int current applicant.getCurrentStatus(); // 状态机校验1笔试中 - 2面试中 - 3已录用不允许逆向跳转 if (roundType 1 current ! 0) { return false; } if (roundType 2 current ! 1) { return false; } if (roundType 3 current ! 2) { return false; } // 插入轮次记录 Round round new Round(); round.setApplicantId(applicantId); round.setRoundType(roundType); round.setRoundName(roundName); round.setRoundDate(roundDate); round.setLocation(location); int insertResult roundDao.insert(round); // 推进应聘者状态 int nextStatus roundType; // 笔试中1 面试中2 终面3 int updateResult applicantDao.updateStatus(applicantId, nextStatus); return insertResult 0 updateResult 0; }你注意看 roundType 同时是轮次类型和下一状态的映射这种写法在课程项目里很常见也够用。它的问题是不够通用——如果你要加“性格测试”“背调”这种阶段状态机硬编码就得改好几个地方这是它作为课设的边界。扩产思路是建一张 rms_status_dict 表存状态流转规则但那是给人日后的项目用的课程设计做到这版已经算超纲了。理解这个边界很重要这也是我推荐你读这套代码的原因——它展示了业务逻辑怎么从代码里长出来而不是停留在增删改查层面。4. 把项目跑起来JDK、Tomcat、MySQL 的版本搭配与部署4.1 环境搭配别让 Tomcat 版本卡住你下载完源码包第一时间不是解压看代码而是确认环境。很多学生卡在第一步不是因为代码错而是 Tomcat 10 把包名从 javax.servlet 换成了 jakarta.servlet旧代码直接编译不通过。我建议你按这个组合来配组件推荐版本说明JDK8 或 11不要用 17某些老依赖可能不兼容Tomcat8.5 或 9.0关键必须 9 及以下避免包名地狱MySQL5.7 或 8.08.0 需要新驱动连接串多一个时区参数EclipseEclipse IDE for Enterprise Java and Web Developers自带 WTP 插件直接右键 Run on ServerJDK 版本这里我说一下我的习惯JDK 8 最稳因为绝大多数 Java Web 课设源码都是在 JDK 8 下写的你在 JDK 11 下编译通常也没问题但遇到 JavaMail 这类老库时可能报模块访问错误犯不上为版本折腾。确认 JDK 的方式是命令行java -version输出里有 1.8 字样就是 JDK 8不要装 JRE 就完事Servlet 编译需要 javac 工具。Tomcat 这里有个更隐蔽的坑如果你用的是集成环境比如 IDEA Ultimate 自带的 Tomcat 或者某些绿色版它可能内置了 Tomcat 10 而你毫无感知。第一次编译出The import javax.servlet cannot be resolved时先查这个别急着改代码。4.2 初始化数据库导入 SQL 和连接串配置数据库是这套系统能跑起来的地基。解压包里应该带 SQL 脚本文件按文件名排序找到它用命令行导入或者 Navicat 执行都行。命令行导入的方式mysql -u root -p -h 127.0.0.1 rms_db.sql执行完成后进入 MySQL 验证mysql -u root -p USE rms_db; SHOW TABLES;SHOW TABLES 能看到前面建表语句对应的那几张表。如果一张表都没建出来先检查你执行 SQL 时有没有先创建同名数据库——有的脚本第一行有 CREATE DATABASE有的没有没有的话你得手动执行一次CREATE DATABASE rms_db DEFAULT CHARACTER SET utf8mb4;再导。然后改数据库连接。数据库连接配置在源码里通常是 DBUtil.java 或者 db.properties 文件你要找 Class.forName 和 DriverManager.getConnection 这两个位置。以 MySQL 8.0 为例static { try { // MySQL8必须用cj驱动类名5.7时代的老类名已废弃 Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/rms_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; conn DriverManager.getConnection(url, root, 你的密码); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } }这里的参数我给逐项说明com.mysql.cj.jdbc.Driver 是 MySQL Connector/J 8 指定的驱动入口写旧版的 com.mysql.jdbc.Driver 在 MySQL 8 下会直接报找不到类useSSLfalse 关闭 SSL 握手本地开发时能省去证书配置serverTimezoneAsia/Shanghai 是 MySQL 8 刚需因为新版驱动默认用 UTC 时区不指定它你和数据库的时间对不上。characterEncodingutf8 这个参数负责让数据库连接的字符串传输是 UTF-8 编码少了它后面往数据库插中文大概率变问号。连接串改完别急着运行先把 MySQL Connector/J 的 jar 包放进项目的 WebContent/WEB-INF/lib 目录。很多新手只改了代码没放驱动包启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver完全不知道怎么解决——就是这一步漏了。驱动版本和 MySQL 服务端版本不必严格一致大版本匹配就行8.0.x 的驱动连 5.7 数据库也没问题。4.3 用 Eclipse 导入项目并启动首屏导入项目的操作路径是菜单 File Import Existing Projects into Workspace选择解压后的项目文件夹注意勾选 Copy projects into workspace这样 Eclipse 会在自己的工作空间里生成一份副本避免直接污染源码包。导入后项目列表里出现红叉时优先检查两处。第一处是 JRE System Library 的版本。右键项目 Properties Java Build Path Libraries确认 JRE 是你本地装的 JDK 8 而不是 Eclipse 默认的 JRE 17。这个错很阴间代码本身没毛病但编译器版本不对报的全是莫名其妙的语法错误。第二处是 Targeted Runtime 有没有选 Tomcat。Window Preferences Server Runtime Environment 里添加你的 Tomcat 安装目录然后项目右键 Properties Targeted Runtimes 勾上。没勾的话 Project Facets 校验会失败一键 Run on Server 的按钮是灰色。配置完成后把项目加到 Tomcat 里启动启动成功后浏览器访问 login.jsp 或首页路径。如果看到 Tomcat 默认的 404 页面大概率是欢迎页没配置对。在 web.xml 里加一段welcome-file-list welcome-filelogin.jsp/welcome-file welcome-fileindex.jsp/welcome-file /welcome-file-list顺序表示找 login.jsp 不行就找 index.jsp。这一步做完再看浏览器能不能正常进到登录页。进到这个页面数据库连接基本就是通的因为登录页的数据校验要查 rms_user 表查不到表时系统会直接抛异常而不是乖乖渲染页面。到这一步部署就算成功了一大半。5. 避坑记录四个最容易把课设卡死的真实故障这里的每一条都是我在实操和帮人调课设时翻过的车。现象都是实际报错原因和解决办法可以直接照抄。5.1 Tomcat 10 的包名地狱现象项目导入后在 Problems 视图全是红色叉号提示The import javax.servlet cannot be resolved但你的 JDK 没问题、Tomcat 也添加了。原因Tomcat 10 开始把 Jakarta EE 的包名从 javax.servlet 迁移到 jakarta.servlet这个源码是老的 javax.servlet 时代写的两套包名互不兼容。你装了 Tomcat 10 就相当于拿新版容器跑旧代码编译期直接死掉。解决确认你的服务器版本卸载 Tomcat 10换成 Tomcat 9.0.x 或 8.5.x。装完之后在 Eclipse 的 Server 视图里把旧 Server 删掉重新建一个注意新 Server 的 Runtime Environment 要指向新 Tomcat 目录。装完 Tomcat 9 后代码一行都不用改就能编译通过。5.2 MySQL 8 驱动类名与时区问题现象启动时抛java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或者报The server time zone value й׼ʱ is unrecognized。原因驱动类名写的是老版 MySQL 5 时代的 com.mysql.jdbc.Driver而 MySQL 8 的驱动必须写 com.mysql.cj.jdbc.Driver。时区报错是因为新版驱动默认走 UTC 时区而你的系统时区是 Asia/Shanghai两边不一致。另外要注意!号那个й׼ʱ是乱码实际是“中国标准时间”说明连接串没指定编码。解决驱动类名改成com.mysql.cj.jdbc.Driver连接串末尾加?serverTimezoneAsia/ShanghaiuseSSLfalsecharacterEncodingutf8。改完清一下 Tomcat 的缓存再重启因为 Tomcat 有时会缓存旧的类路径你不清理它还会用旧配置启动导致同样的报错。5.3 中文乱码请求、响应、连接串三层凑齐现象简历列表页打开后名字和技能显示成??或者一堆乱码字符数据库里存的也是???。原因中文乱码从来不是单点问题。第一层是请求参数编码form 表单提交时没指定 accept-charsetTomcat 默认按 ISO-8859-1 解码中文就坏了第二层是响应编码JSP 页面没写 pageEncoding浏览器按默认编码渲染第三层是数据库连接串没带 characterEncodingutf8即使前端不乱MySQL 存储时也会乱。解决三层一起修。JSP 文件头加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%在 Java 里对 request 设置request.setCharacterEncoding(UTF-8)数据库连接串里加characterEncodingutf8。注意表单提交是 POST 时没问题如果是 GET 请求Tomcat 8 以上默认 UTF-8 不用改Tomcat 7 需要在 conf/server.xml 里配置 URIEncodingUTF-8。5.4 表名大小写Windows 能跑、Linux 上就崩现象本地 Windows 上系统运行完美但把项目 数据库导到服务器Linux 环境后一查表就报Table rms_db.RMS_APPLICANT doesnt exist。原因Windows 的 MySQL 默认把表名大小写不敏感而 Linux 下lower_case_table_names0是严格区分大小写的。如果 SQL 脚本里建表用的 RMS_APPLICANT全大写而 Java 代码里查的是 rms_applicant小写或者反过来Windows 上没事Linux 上直接找不到表。解决统一表名风格。我习惯的做法是把 SQL 脚本里的所有表名都改成小写Java 代码里的 SQL 语句也全部用小写表名字段名保持原样。改完后重新导一次数据库建表时字段名也尽量用下划线分隔的小写命名这样最保险。同时把 MySQL 配置文件里的 lower_case_table_names1 设上也行但那是数据库层面的妥协不如代码统一来得干净。6. 进阶用法给系统加一个 Excel 名单导出功能系统跑通后大部分人交完作业就停了。但答辩时评委最容易被“加分项”打动——比如把录用名单导出成 Excel。这里我用 Apache POI 给你演示一条最快见效的加功能路径。原理很简单新增一个导出 Servlet从数据库查出状态为“已录用”的应聘者用 POI 生成 .xlsx 工作簿通过 HTTP 响应流直接下载。protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置导出文件的响应头 response.setContentType(application/vnd.ms-excel;charsetUTF-8); String fileName URLEncoder.encode(录用名单.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment;filename fileName); // 查已录用应聘者 ApplicantDao dao new ApplicantDaoImpl(); ListApplicant hiredList dao.listByStatus(3); // 创建工作簿和表格 try (Workbook workbook new XSSFWorkbook(); OutputStream out response.getOutputStream()) { Sheet sheet workbook.createSheet(录用名单); Row header sheet.createRow(0); header.createCell(0).setCellValue(姓名); header.createCell(1).setCellValue(学历); header.createCell(2).setCellValue(联系方式); for (int i 0; i hiredList.size(); i) { Applicant a hiredList.get(i); Row row sheet.createRow(i 1); row.createCell(0).setCellValue(a.getName()); row.createCell(1).setCellValue(a.getEducation()); row.createCell(2).setCellValue(a.getPhone()); } workbook.write(out); out.flush(); } }写完之后在 JSP 页面放一个“导出录用名单”的链接指向这个 Servlet 的映射路径就行。要注意的是 response.getOutputStream 不能在获取之后再调 getWriter否则会报 IllegalStateException因为你已经拿了输出流就不能再要字符流了。文件名用 URLEncoder.encode 包一层是为了防止浏览器把中文文件名变成乱码或者截断这在 IE 时代是个大坑现代浏览器反而宽容很多。做这个功能最有价值的不是代码本身而是它帮你建立了一条完整的“查库 → 数据组装 → 响应输出”的链路心智。从那以后我每次做 Java Web 功能都会强制先画一遍请求到响应的数据流草图再落代码。这个习惯帮我避开了至少一半的“我明明写对了为什么没反应”的尴尬。希望这套系统和你加上去的导出功能能让你在答辩时讲出一条完整有深度的业务故事——同样的东西在不同人手里价值差好几倍差别往往就在这种看不见的小功能上。希望帮到你。本文还有配套的精品资源点击获取
返回列表