尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP记账管理系统拆解:从Servlet到Tomcat部署的JavaWeb全链路实践

JSP记账管理系统拆解:从Servlet到Tomcat部署的JavaWeb全链路实践 简介面向Java Web课程设计与毕业设计的记账管理系统完整资料包针对计算机专业学生及毕业设计人员可用于快速完成项目开发、论文撰写与答辩准备。压缩包约91.24MB采用通用zip格式内含项目报告、答辩PPT、源代码、数据库脚本及部署演示视频覆盖从环境配置到系统上线的完整流程。源码基于JSP、Servlet与MySQL实现重点包括登录鉴权、收支记账、分类统计、账单明细等核心模块有助于理解Java Web经典分层结构。项目报告可作为论文写作蓝本PPT可直接用于现场汇报数据库脚本提供建表与示例数据部署视频逐步演示导入、配置和运行过程便于排查常见报错。目前已有254人学习下载适合需要高性价比毕业设计方案、对完整流程不熟悉的本科及专科学生能够显著减少从零搭建和整理材料的时间。1. 从一个毕设 zip 说起这套 JSP 记账管理系统值不值得拆先说结论这个压缩包打开之后你大概率会看到一份项目报告、一份答辩 PPT、一套 Java 源码、一个数据库脚本和一段部署视频。第一反应可能是又是老掉牙的 JSP 项目但真正动手的人会发现它把 JavaWeb 三层架构里最核心的链路完整走了一遍。很多基于 jsp 的毕设选题列表里都能看到它的身影因为功能边界清晰用户注册登录、记一笔、查账单、看统计、改密码没有复杂的权限分配没有微服务拆分的负担就是一个标准的增删改查加报表聚合。它适合马上要交毕业设计的学生也适合想快速找回 JSPServlet 手感的后端开发如果你需要一个带数据库脚本和部署演示的 JavaWeb 教学案例这套东西同样值得拆一遍。下面的内容我会按为什么这么设计 → 怎么跑起来 → 哪些地方会翻车 → 怎么让演示更体面这个顺序来拆。2. JSP 记账管理系统的技术栈拆解为什么这套老代码还能打2.1 业务闭环与数据库设计从两张表开始的账本模型先把业务跑通再谈技术。这个记账管理系统的用户角色只有一种普通用户。没有管理员后台这对毕设来说反而友好因为你不用花大量篇幅解释角色权限模型。核心流程是注册账号登录后进入主页面左侧菜单通常是记一笔、账单查询、统计报表、个人信息。记一笔时需要选择收入还是支出填金额、分类、备注、日期保存后进入账单查询按时间范围筛选统计报表里按月汇总。数据库设计决定了代码怎么写。老 JSP 项目里的表通常只有两张一张用户表一张账单表SQL 长这样CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_account ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, type TINYINT NOT NULL COMMENT 0收入, 1支出, amount DECIMAL(10,2) NOT NULL, category VARCHAR(20), remark VARCHAR(200), create_time DATETIME, FOREIGN KEY (user_id) REFERENCES t_user(id) );这段 SQL 里有几个字段是答辩时的高频看点。type用 TINYINT 而不是 VARCHAR是为了统计时方便SUM(CASE WHEN type0 THEN amount ELSE 0 END)如果存成收入/支出这种字符串SQL 聚合要写一堆 LIKE 或者 GROUP BY 后二次处理。amount用 DECIMAL(10,2) 而不是 FLOAT是为了避免浮点误差金额类数据用 DECIMAL 是常识但很多新手会踩 FLOAT 的坑。user_id外键把账单归属到用户保证统计报表是根据当前登录用户来过滤而不是拉全表数据。拿到压缩包的数据库脚本后先别急着导打开看一眼表结构是否和上面一致。有些毕设包为了简化没有外键约束user_id 只是一个普通字段功能上没问题但报告里如果写着存在外键关联就要被打脸。如果脚本里没有初始数据导入后 t_user 是空表你需要先去注册页面造一个用户再开始录账单。2.2 请求处理链路login.jsp 按下登录按钮后发生了什么这个系统的技术核心是 JSPServletJavaBean 三层结构。我们拿登录功能拆一条完整链路这条链在答辩时几乎必问。login.jsp 里有一个表单action 指向login这个路径form actionlogin methodpost input typetext nameusername placeholder用户名 input typepassword namepassword placeholder密码 button typesubmit登录/button /form浏览器提交后web.xml 里的 servlet-mapping 决定谁来接servlet servlet-nameloginServlet/servlet-name servlet-classcom.account.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameloginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping这里要注意的是url-pattern配置的/login是逻辑路径JSP 表单里 action 写的是loginTomcat 会自动拼接 application context所以不会出问题。真正容易翻车的地方在 Servlet 内部我们来看 LoginServlet.doPost 的标准写法protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置请求编码必须在 getParameter 之前 req.setCharacterEncoding(utf-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findUserByUsernameAndPassword(username, password); if (user ! null) { // 登录成功把用户对象放进 session供后续页面和 Filter 使用 req.getSession().setAttribute(user, user); resp.sendRedirect(main.jsp); } else { // 失败则回传错误提示forward 保持 request 对象可用 req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } }这段代码里的forward和sendRedirect是答辩高频考点。forward是服务端跳转地址栏不变request 里的属性还能传给下一个 JSPsendRedirect是浏览器端重定向地址栏变化request 属性丢失所以错误消息必须放在req.setAttribute里用 forward 带回。如果改成 redirect就取不到msg了需要换成 session 传递或者 URL 参数。UserDao 里对应的查询方法是这条链的最后一环通常用 PreparedStatementpublic User findUserByUsernameAndPassword(String username, String password) { String sql SELECT * FROM t_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()) { return new User(rs.getInt(id), rs.getString(username), rs.getString(password)); } } } catch (Exception e) { e.printStackTrace(); } return null; }注意这段里的 try-with-resources 写法Connection、PreparedStatement、ResultSet 在离开代码块时自动关闭。很多老教程写的是 finally 手动 close这个写法更干净。PreparedStatement的作用是预编译 SQL参数用?占位从根上避免字符串拼接注入问题。密码这块很多毕设源码存的是明文你要在报告里承认这一点并可以提出用 MD5 或 BCrypt 加密这本身就是个不错的改进点。2.3 从源码包反推结构目录布局、Servlet 清单与配置文件拿到源码包后第一件事不是启动 Tomcat而是先列目录。传统的 Eclipse 时代 JSP 项目一般是 WebRoot 结构MySQL 驱动放在WebRoot/WEB-INF/lib下面。目录结构大概长这样account-manage/ ├── src/com/account/ │ ├── entity/ │ │ ├── User.java │ │ └── AccountBill.java │ ├── dao/ │ │ ├── UserDao.java │ │ └── AccountDao.java │ ├── servlet/ │ │ ├── LoginServlet.java │ │ ├── RegisterServlet.java │ │ ├── AddAccountServlet.java │ │ ├── QueryAccountServlet.java │ │ ├── StatisticsServlet.java │ │ └── ChangePwdServlet.java │ ├── util/ │ │ ├── DBUtil.java │ │ └── DateUtil.java │ └── filter/ │ └── LoginFilter.java ├── sql/ │ └── account.sql └── WebRoot/ ├── login.jsp ├── register.jsp ├── main.jsp ├── addAccount.jsp ├── queryAccount.jsp ├── statistics.jsp ├── changePwd.jsp └── WEB-INF/ ├── web.xml └── lib/ └── mysql-connector-java.jar你手里的压缩包可能不完全是这个结构但大体逃不出这个框架。重点检查三个地方。第一util/DBUtil.java它决定你本地连哪个数据库密码是什么第二filter/LoginFilter.java它负责登录拦截没有它你直接访问main.jsp也能看到数据答辩时老师一试就穿帮第三web.xml里的 servlet 映射把每个 url-pattern 对应的类名记一下调试时看到 404 就能快速定位。我记得有一次帮人排查一个毕设项目登录页能打开但一提交表单就 404检查发现表单 action 写的是/login.do而 web.xml 里映射的是/login差一个后缀对不上。这类路径问题在手工从 Eclipse 迁移到 IDEA 时特别常见每次拿到新源码先加一个grep -R servlet-mapping把所有映射关系过一遍能省很多时间。3. 把源代码跑起来数据库导入、JDBC 配置与 IDEA 部署细节3.1 数据库导入的完整操作和验证方法解压 zip 后先确认里面有没有 sql 目录或.sql文件。如果有account.sql按下面的命令导入注意数据库字符集一定要写成 utf8mb4不然后面中文乱码会浪费你两小时mysql -u root -p create database if not exists account_db default charset utf8mb4; use account_db; source C:/temp/account.sql;代码里的source是 MySQL 客户端的命令不是 SQL 标准语法。路径要写绝对路径Windows 下最稳的做法是把 SQL 文件先复制到C:/temp/account.sql因为命令行对带空格和中文的路径支持很差反斜杠也会被转义。导入成功后执行show tables; select * from t_user; select count(*) from t_account;如果t_user是空表你需要到注册页面手动创建一个用户。如果t_account没有数据统计报表页面会空荡荡你可以先插入一条测试数据让页面不至于白屏INSERT INTO t_account(user_id, type, amount, category, remark, create_time) VALUES (1, 0, 6800, 工资, 7月工资, 2024-07-01 09:00:00), (1, 1, 1200, 餐饮, 聚餐, 2024-07-05 12:00:00);这条 INSERT 里的user_id必须是t_user表里真实存在的 id否则外键约束会报错如果源项目里没写外键约束那随便填个 1 也能表面上跑通但查不到明细。我自己习惯在用 script 导入后用mysqldump备份一份万一后面把数据搞乱了还能恢复。3.2 JDBC 连接配置db.properties、DBUtil 和驱动 jar 的三角关系大多数 JSP 项目的连接配置写在src下的db.properties或config.properties里内容长这样drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/account_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456逐参数说明driver在 mysql-connector-java 5.1.x 时用com.mysql.jdbc.Driver如果你本机用的是 8.0.x 驱动要改成com.mysql.cj.jdbc.Driver两个类名对应两代驱动混着写必报 ClassNotFoundException。characterEncodingutf8和页面编码配套防止中文入参变成乱码。useSSLfalse是因为 MySQL 8 默认打开 SSL 证书校验本地开发每次连库都警告关掉最省心。serverTimezoneAsia/Shanghai是 MySQL 8 的硬性要求不加它会直接报时区错误。源码里的 DBUtil 是连接入口老项目里最常见的硬编码写法是public class DBUtil { private static Connection conn; public static Connection getConnection() throws Exception { if (conn null) { Class.forName(com.mysql.jdbc.Driver); conn DriverManager.getConnection(jdbc:mysql://localhost:3306/account_db?useUnicodetruecharacterEncodingutf8, root, 123456); } return conn; } }这段代码的问题很明显单连接共享Tomcat 多线程访问时同一个 Connection 被多个请求并发使用容易出现数据混乱。但它能跑通。如果你想让项目结构更规范可以把它改成每次调用都新建连接或者直接上连接池。第四章会讲连接池改造。另外压缩包里如果没有db.properties这个文件连接参数大概率直接写死在 DBUtil 里你需要全局搜索jdbc:mysql找到它。改完配置后启动项目前先确认WEB-INF/lib下面有 mysql 驱动 jar没有的话即使密码全对也会在第一次点击登录时才报错。3.3 在 IDEA 里导入 JSP 项目并让 Tomcat 正常启动一份可抄作业的步骤在 IDEA 里导入 JSP 项目最烦的是 IDEA 默认的 Java 后端模板不带 Web 支持手动配置容易漏。我一般按这个顺序来第一步Open 源码目录信任项目。如果源码包里有.idea或.projectIDEA 会直接用如果没有就把根目录当成普通项目打开。第二步右键项目根目录选择Add Framework Support勾选 WebIDEA 会生成 web 目录并识别 web.xml。第三步检查 Project Structure → Artifacts确认有一个war exploded类型的 ArtifactOutput Layout 里包含 source 和 lib。第四步Run → Edit Configurations → 加号 → Tomcat Server → Local选好 Tomcat home在 Deployment 页签把 Artifact 加进去。启动前别忘了看 Artifact 的 Output Layout。很多毕设包的WEB-INF/lib没有自动加载启动页面能打开但一调用数据库就报ClassNotFoundException: com.mysql.jdbc.Driver。这种问题不会在日志启动阶段暴露属于典型的页面白屏查不到原因黑匣子。正确做法是在 Project Structure 里把驱动 jar 手动加进 Libraries确认 Output Layout 的 WEB-INF/lib 里有这个 jar。还有一个决定生死的配置Deployment 页签里的 Application context。IDEA 默认会用项目名加_war_exploded比如/account_war_exploded。你按部署视频里的http://localhost:8080/account/login.jsp访问就会 404因为实际路径是/account_war_exploded/login.jsp。所以启动后先看控制台日志里的deployment descriptor确认 context path再决定访问哪个 URL。4. 部署与运行环境war 包、Tomcat 版本和连接池的匹配问题4.1 JDK 与 Tomcat 版本匹配基石不牢后面全白干JSP 项目对运行环境的敏感程度在技术圈里已经快变成玄学。同一个成品JDK 8 Tomcat 8.5 跑得风生水起换成 JDK 17 Tomcat 10代码里的所有javax.servlet全部标红。原因很简单Tomcat 10 引入了jakarta.servlet命名空间老项目的 import 都是javax两者不兼容。所以拿到这个资源的第一天先看源码里的 import 语句grep -r import src/com/account | grep servlet如果输出全是javax.servlet.http.HttpServlet你就老老实实用 JDK 8 Tomcat 8.5。JDK 8 在 IDEA 里没有内置下载安装后在 Project Structure → SDKs 里添加再把 Project SDK 和 Module SDK 都改成 8。Tomcat Server 配置页面里也要把 JRE 选成 JDK 8。这套组合测试过很多次兼容性最稳。4.2 传统 JSP 项目打包 war没有 Maven 也能部署到裸 Tomcat这压缩包大概率不是 Maven 工程所以别指望mvn package。用 IDEA 的 Build Artifacts 功能打包。步骤Project Structure → Artifacts → 加号 → Web Application → Archive选择 WebRoot 作为 Web Resource Directory确认 lib 会被打包。然后 Build → Build Artifacts → Rebuildwar 文件生成在out/artifacts/目录下。手动部署到独立 Tomcat 的命令行流程cp account.war ${TOMCAT_HOME}/webapps/ ${TOMCAT_HOME}/bin/startup.sh tail -f ${TOMCAT_HOME}/logs/catalina.outwar 包放在 webapps 下Tomcat 启动时会自动解压解压后的目录名就是 context path。比如你放account.war访问根路径就是http://localhost:8080/account/。这个特点意味着 war 包名字不能乱起部署视频里如果叫account你本地打包也叫account演示时才能对得上。部署时最常见的问题是 500 错误。War 包模式下WEB-INF/classes必须包含db.properties如果没有可以把 properties 文件放到src目录下IDE 编译时会自动进 classes。另一个坑是 MySQL 驱动 jar 在 war 包里丢失检查WEB-INF/lib下是否有.jar文件。修完后清空 Tomcat 的 work 目录再重启有些人改了代码但 restricted 缓存页面还是旧逻辑。4.3 数据库连接池改造把 JDBC 直连升级成 DBCP答辩加分项大部分 JSP 毕设用的都是 DriverManager 直连这在单用户演示时没问题但答辩老师一旦问多用户并发怎么办就会很尴尬。改造方式不复杂把 DBUtil 换成 DBCP 连接池。首先把commons-dbcp2-2.9.0.jar、commons-pool2-2.11.1.jar和 mysql 驱动一起放进WEB-INF/lib。然后重写 DBUtilimport org.apache.commons.dbcp2.BasicDataSource; public class DBUtil { private static BasicDataSource dataSource; static { dataSource new BasicDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/account_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxIdle(10); dataSource.setMaxWaitMillis(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }参数解释setInitialSize(5)表示应用启动时预创建 5 个连接避免第一个请求太慢setMaxTotal(20)限制最大连接数防止数据库连接被打满setMaxIdle(10)是空闲连接上限超过会被回收setMaxWaitMillis(3000)是连接池内没有可用连接时最长等待时间超过 3 秒抛异常而不是无限阻塞。改完以后所有调用DBUtil.getConnection()的地方不用变因为方法签名还是原来的。验证连接池是否生效可以做一个简单的并发测试启动 Tomcat 后连续刷新页面 50 次再执行show status like Threads_connected;如果连接数稳定在个位数说明连接在复用如果一路暴涨说明连接池没启作用多半是改动没被加载。5. 避坑记录这套 JSP 记账系统最容易翻车的四个地方5.1 数据库连不上时区报错和驱动类名不匹配并发出现现象Tomcat 启动成功页面打开也正常但点击登录按钮后请求卡住最终白屏控制台出现一行刺眼的报错The server time zone value Öйú±ê׼ʱ¼ä is unrecognized紧接着还有ClassNotFound: com.mysql.jdbc.Driver。原因这两条错误同时出现基本就是 mysql-connector-java 8.x 驱动被写在 5.1 的配置里。8.x 的 driver 类名是com.mysql.cj.jdbc.Driver而且必须指定 serverTimezone5.1 的类名是com.mysql.jdbc.Driver不需要 serverTimezone。把配置里的 driver 和 jar 对应上问题就消了。解决打开db.properties如果 lib 里有mysql-connector-java-8.0.x.jar就把 driver 改成com.mysql.cj.jdbc.Driverurl 后面加上serverTimezoneAsia/Shanghai。如果改完后还报Public Key Retrieval is not allowed在 url 末尾追加allowPublicKeyRetrievaltrue。这个参数允许本地开发时从服务器获取公钥专治 MySQL 8 的 RSA 公钥检索错误。5.2 中文乱码三层编码不一致查起来像无头苍蝇现象注册时输入中文用户名列表页显示成??或者 JSP 页面右上角的菜单名直接变成菱形问号。原因JSP 页面响应编码、Servlet 请求编码、数据库表字符集三个维度只要有一个不是 UTF-8就会乱码。最常见的组合是页面写了 UTF-8数据库连接串里没有characterEncodingutf8结果中文从 Servlet 存进数据库时按默认编码转换了。解决按顺序检查三层。第一JSP 页面头部必须写% page contentTypetext/html; charsetutf-8 pageEncodingutf-8 %。第二Servlet 在读取第一个参数前调用req.setCharacterEncoding(utf-8)注意顺序错就无效。第三数据库连接串加useUnicodetruecharacterEncodingutf8。如果表已经建出来了但字符集不对执行ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE t_account CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这里用 utf8mb4 而不是 utf8是因为 MySQL 的 utf8 最多支持三字节生僻字和 Emoji 存不进去。改完表结构后老数据如果已经乱码需要删掉重录。所以一开始就建好 utf8mb4 才是省钱的办法。5.3 部署视频里能跑自己打开就 404现象跟着部署视频做视频里访问http://localhost:8080/account/login.jsp正常你照做却 404。原因IDEA 的 Application context 不是account而是自动生成的account_war_exploded或者 war 包名是myaccount.war部署后 context path 变成了myaccount。这两者都会导致路径对不上。解决在 Run/Debug Configurations 的 Deployment 页签里把 Application context 改成/account然后重启。如果是裸 Tomcat 部署直接把 war 包改名成account.war再丢进 webapps。另外还要检查项目有没有欢迎页如果 web.xml 里没配 welcome-file-list访问根路径/account会 404必须访问/account/login.jsp。5.4 登录成功后还是被弹回登录页现象用户名密码确认无误数据库里也有记录但登录成功后跳转 main.jsp又被过滤器拦回 login.jsp页面提示需要先登录。原因LoginFilter 从 session 取用户对象时key 匹配不上。登录代码里写的是session.setAttribute(user, user)过滤器里写的却是session.getAttribute(userId)两边对不上过滤器认为你一定是没登录。解决全局搜索setAttribute和getAttribute把涉及 session 用户信息的 key 统一成一个常量。老毕设源码经常拼凑自不同项目这个坑概率不低。我自己的习惯是搜完以后直接在 filter 里加一行日志System.out.println(session user: session.getAttribute(user));启动后看控制台如果输出null说明 key 确实不对如果输出有对象说明过滤器逻辑有问题不在 key 上。6. 进阶玩法造数脚本、报表验证和 war 部署让答辩现场不冷场6.1 快速造数脚本让统计报表页有真实数据可看答辩最尴尬的时刻是点击统计报表页面显示暂无数据。手动一条条录入太慢而且金额分布不合理。我一般会写一个造数 SQL一次性生成几百条测试数据覆盖近 12 个月的收支INSERT INTO t_account (user_id, type, amount, category, remark, create_time) SELECT 1, IF(RAND()0.5, 0, 1), ROUND(RAND()*1000, 2), CASE ROUND(RAND()*4) WHEN 0 THEN 餐饮 WHEN 1 THEN 交通 WHEN 2 THEN 购物 ELSE 其他 END, 自动生成, DATE_SUB(NOW(), INTERVAL FLOOR(RAND()*12) MONTH) FROM information_schema.tables LIMIT 500;这条 SQL 的妙处在于用information_schema.tables作为行来源不用单独建数字表。IF(RAND()0.5, 0, 1)让收支比例接近一比一ROUND(RAND()*1000, 2)生成 0 到 1000 之间的金额DATE_SUB(NOW(), INTERVAL FLOOR(RAND()*12) MONTH)把时间打散到过去一年。生成完执行统计页的查询确认按月分组有柱状图可看。要注意的是造数前先确认user_id1的用户存在否则外键报错。6.2 用独立 Tomcat 验证部署一致性把演示脚本固定下来本地 IDEA 跑通不算真跑通war 包部署到裸 Tomcat 才能覆盖完整链路。我的验证流程是把 war 拷贝到 Tomcat 的 webapps用catalina.bat run前台启动看到Server startup in [x] ms后再打开浏览器。然后按照答辩顺序走一遍注册 → 登录 → 记一笔收入 → 记一笔支出 → 查询近一个月账单 → 打开统计报表 → 修改密码 → 退出登录。每一步截图留存对应到 PPT 的功能演示章节。这个流程走完之后我会再看一眼 Tomcat 的 access log确认没有 500 和 404。如果发现统计报表页报错优先查是不是 SQL 里用到了 MySQL 8 不支持的关键字或者在报表 SQL 中用了FORBIDDEN的别名。从那以后我每次拿到 JSP 毕设项目都会强制自己先按部署视频盲打一遍不看源码打不通再看代码。这个习惯救了我好几次希望也能帮你在答辩前就把雷排干净把演示状态调到最稳希望帮到你。本文还有配套的精品资源点击获取
返回列表