Servlet+JSP+MySQL贷款管理系统课设:从表设计到事务实现

发布时间:2026/10/18 1:46:58
Servlet+JSP+MySQL贷款管理系统课设:从表设计到事务实现 简介面向数据库课程设计的在校大学生贷款管理系统提供完整Java源码与配套说明书适合本科或高职学生作为期末项目参考。系统基于JavaSQL实现采用MVC模式分离业务、界面与数据访问核心功能涵盖用户登录注册、贷款申请、管理员审批、还款计划与数据统计。数据库设计包含学生信息、贷款申请、贷款还款、用户权限四类主要表结构体现典型管理信息系统开发思路。压缩包共46个文件其中包含18个java源文件、20个class编译文件、1个SQL建库脚本、1个doc格式说明书以及Eclipse工程配置整体仅1.1MB可直接导入开发工具运行调试。已有1510人学习下载实用性得到验证。借助源码与说明书可快速理解从建表到功能实现的全过程并可在此基础上扩展或改造完成自己的课程设计报告与演示。1. 贷款管理系统不是黑匣子两个角色、三张表、一条审批链如果你做过数据库课设多少会有这种感觉选题一听就懂一动手就卡在「数据怎么流、状态怎么翻、页面怎么串」上。在校大学生贷款管理系统就是这样一个典型课程设计选题——学生端能注册、登录、提交贷款申请、查审批结果和还款计划管理员端能审核申请、管理用户、生成每期应还明细。整个系统从功能上拆开就是「两个角色 三张核心表 一条审批链路」没有高深算法没有黑科技但它把数据库表设计、JDBC 操作、Servlet 请求流转、JSP 页面展示全部串了一遍。本文就用一套可复现的源码加说明书来拆解这个系统适合正在做课设或者想拿一个完整项目练手的开发者照着重写一遍。2. 技术栈与架构设计Servlet JSP MySQL 的分层分工2.1 为什么不用 Spring Boot课设场景下的技术选型很多同学拿到课设第一反应是「上 Spring Boot」但仔细想想答辩时被问的问题Maven 依赖谁下载的、自动配置干了什么、MyBatis 的代理接口怎么生成的——答不上来反而扣分。这个项目走的是 Java Web 最朴素的一条路JDK 8 Tomcat 8.5 Servlet 3.1 JSP JSTL MySQL 5.7数据库访问用原生 JDBC 封装。这套组合的好处是每一行代码都能解释清楚整个请求链路从 Tomcat 接收 HTTP 请求到 Servlet 里取参数、调 Service、操作 DAO、返回 JSP没有任何黑匣子。同时它也不是完全原始的裸 JDBC。项目里做了基础的 MVC 分层Servlet 只做参数接收和页面转发业务校验放在 Service 层SQL 全部收拢在 DAO 层实体类单独一个 entity 包。这样分工对一个课设来说恰到好处——前端 JSP 里的% %脚本只做循环展示不写业务Service 层里只处理状态判断和金额计算不碰 Connection。前端没有上框架用 JSP Bootstrap jQuery因为演示场景里用户操作集中在表单提交和列表刷新原生写法维护成本最低答辩时也最好讲。2.2 目录结构与分层职责源码包拿到手先看什么拿到源码包第一件事不是双击运行而是先对着目录结构确认「哪个文件管什么」。这套系统的目录长这样loan-system/ ├── sql/ │ └── loan_system.sql # 建库建表脚本 演示账号 ├── src/ │ └── com/loan/demo/ │ ├── entity/ # User、LoanApplication、RepaymentPlan │ ├── dao/ # UserDao、LoanDao、RepaymentDao │ ├── service/ # AuthService、LoanService │ ├── servlet/ # 登录、注册、申请、列表、审核 │ ├── filter/ # EncodingFilter、AuthFilter │ └── util/ # DBUtil、Md5Util └── web/ ├── static/ # css、js、images ├── WEB-INF/ │ ├── web.xml │ └── lib/ # mysql-connector-java 等 jar 包 ├── login.jsp、register.jsp ├── student/ # 学生端页面apply.jsp、myLoans.jsp └── admin/ # 管理端页面auditList.jsp、userList.jspentity 包里三个类对应数据库三张表字段和表结构一一映射没有用 ORM 框架所以字段名必须手写对齐。dao 包里每个类只负责一张表的增删改查方法粒度很细findById、findByUsername、insertApplication、updateStatus、listByUserId。service 包负责组合这些 DAO 方法比如 LoanService 里的apply方法会先校验金额再调 LoanDao 插入申请audit方法会更新状态再生成还款计划。filter 包解决两个横切问题字符编码和登录拦截。util 包里的 DBUtil 是全局共用的数据库连接工厂Md5Util 对用户密码做哈希避免明文入库。如果拿到手的源码版本里有额外的包比如vo/或dto/通常是作者把「页面展示用的组合字段」单独抽出来了不影响整体分层理解。课设项目改包名时记得同步改 web.xml 里 servlet-class 的完整类名这是最容易漏的一步。2.3 数据库设计三张表撑起两个状态机这套系统的核心是数据模型设计好了业务代码就是一层皮。三张表的关系可以概括成用户表存「谁在操作」贷款申请表存「借了多少钱、借多久、批没批」还款计划表存「批了之后分几期还、每期还多少」。建表脚本大致长这样CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 哈希不存明文 role VARCHAR(16) NOT NULL DEFAULT student, real_name VARCHAR(32), student_no VARCHAR(20), major VARCHAR(64), phone VARCHAR(20), status TINYINT DEFAULT 1, -- 1 正常0 禁用 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE loan_application ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, -- 金额用 DECIMAL不用 FLOAT term_months INT NOT NULL, -- 贷款期限单位月 purpose VARCHAR(255), -- 贷款用途 annual_rate DECIMAL(5,2) NOT NULL DEFAULT 4.75, status VARCHAR(16) NOT NULL DEFAULT PENDING, reject_reason VARCHAR(255), apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, audit_time DATETIME, auditor_id INT, CONSTRAINT fk_loan_user FOREIGN KEY (user_id) REFERENCES user(id) ); CREATE TABLE repayment_plan ( id INT PRIMARY KEY AUTO_INCREMENT, application_id INT NOT NULL, period_no INT NOT NULL, -- 第几期 due_date DATE NOT NULL, principal DECIMAL(10,2), -- 本期本金 interest DECIMAL(10,2), -- 本期利息 total DECIMAL(10,2), -- 本期应还总额 status VARCHAR(16) DEFAULT UNPAID, -- UNPAID / PAID CONSTRAINT fk_plan_loan FOREIGN KEY (application_id) REFERENCES loan_application(id) );字段类型选择为什么这么定amountDECIMAL(10,2)用 FLOAT 会有精度漂移答辩时演示 0.1 0.2 就翻车statusVARCHAR比 TINYINT 可读性好PENDING/APPROVED/REJECTED 直接看库就懂annual_rateDECIMAL(5,2)利率固定到两位小数课设不做浮动利率created_atDATETIMECURRENT_TIMESTAMP 自动填充减少代码里手动塞时间这里隐藏了两个状态机。第一个在 loan_application 表上贷款申请从 PENDING待审核出发只能往 APPROVED通过或 REJECTED驳回两条路走驳回时必须同时记录 reject_reason。第二个在 repayment_plan 表上审核通过时按贷款期限一次性生成 N 期还款记录状态统一为 UNPAID后续按期手动标记为 PAID。两个状态机的切换逻辑全部放在 Service 层DAO 不做状态判断这样 DBA 看表结构、开发看业务逻辑互不干扰。默认利率 4.75% 是写死在 application 表里的字段而非代码常量原因是利率属于贷款合同数据——今天申请的按 4.75%下周利率调整后新申请应该记 5.2%后续对账要以申请时记录的利率为准。这个细节在答辩时说出来会让老师觉得你想过业务场景。3. 核心链路拆解一笔贷款从提交申请到生成还款计划3.1 登录拦截与字符编码两个 Filter 解决 80% 的课设翻车点课设系统最常见的两个「玄学问题」——中文乱码和未登录却能访问学生页面——都靠 Filter 在请求进入 Servlet 之前统一处理。先看编码过滤器public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; request.setCharacterEncoding(UTF-8); // 处理 POST 表单参数 resp.setContentType(text/html;charsetUTF-8); chain.doFilter(request, resp); // 放行到后续 Servlet } }这个过滤器必须在 web.xml 里注册并且filter-mapping的 url-pattern 写成/*保证所有请求都过一遍编码设置。注意setCharacterEncoding必须在读取 request 参数之前调用否则已经按 ISO-8859-1 解析过的中文就回不来了。POST 表单乱码靠它解决但 GET 请求的 URL 参数乱码得去 Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8这个后面避坑章节会细说。登录拦截器是另一个关键 Filter它检查 session 里有没有登录用户没有就重定向到 login.jsp。但要注意放行名单里除了登录页和注册接口还必须把 css、js、图片等静态资源也放出去否则页面样式全部丢失public void doFilter(...) { HttpSession session request.getSession(false); String path request.getRequestURI(); boolean isStatic path.startsWith(request.getContextPath() /static/); boolean isPublic path.endsWith(/login.jsp) || path.endsWith(/register.jsp) || path.contains(/login) || path.contains(/register); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); // 已登录直接放行 } else if (isStatic || isPublic) { chain.doFilter(request, response); // 公共资源放行 } else { response.sendRedirect(request.getContextPath() /login.jsp); } }逻辑说明先判断 session 是否存在且有效再判断请求路径是否属于放行名单两者都不满足才踢回登录页。参数上getSession(false)的 false 是关键——不主动创建新 session避免未登录用户每次请求都产生一个无用的 Session 对象。静态资源路径的放行判断统一走/static/前缀这也是为什么静态文件不放 WEB-INF 下的原因因为 WEB-INF 目录天然拒绝外部直接访问但 Filter 拦截的是所有请求两种方式结合最稳妥。3.2 学生提交贷款申请参数校验、业务判断、数据落库三段式贷款申请是系统的核心写入操作。从前端表单提交到数据落库链路是 apply.jsp 表单 → ApplyServlet → LoanService.apply() → LoanDao.insert()。Servlet 只做三件事取参数、包成对象、调用 Service 后决定跳转页面WebServlet(/student/apply) public class ApplyServlet extends HttpServlet { private LoanService loanService new LoanService(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String amountStr req.getParameter(amount); String termStr req.getParameter(termMonths); String purpose req.getParameter(purpose); User loginUser (User) req.getSession().getAttribute(loginUser); try { loanService.apply(loginUser.getId(), new BigDecimal(amountStr), Integer.parseInt(termStr), purpose); resp.sendRedirect(req.getContextPath() /student/myLoans.jsp); } catch (IllegalArgumentException e) { req.setAttribute(error, e.getMessage()); req.getRequestDispatcher(/student/apply.jsp).forward(req, resp); } } }参数说明amount前端传来的金额字符串用 BigDecimal 构造避免 double 精度问题termMonths是贷款期限单位月loginUser从 session 取绝不信任前端传来的 userId 参数——这是权限安全的基础意识课设答辩常问到这里。业务异常用 IllegalArgumentException 抛出后转发回申请页并在表单上方显示错误信息非法数据不会进入下一层。真正的业务校验在 Service这也是看这个项目最值得学的一段public void apply(int userId, BigDecimal amount, int termMonths, String purpose) { if (amount.compareTo(new BigDecimal(1000)) 0 || amount.compareTo(new BigDecimal(50000)) 0) { throw new IllegalArgumentException(贷款金额必须在 1000 ~ 50000 之间); } if (termMonths ! 12 termMonths ! 24 termMonths ! 36 termMonths ! 48) { throw new IllegalArgumentException(贷款期限只支持 12/24/36/48 个月); } LoanApplication app new LoanApplication(); app.setUserId(userId); app.setAmount(amount); app.setTermMonths(termMonths); app.setPurpose(purpose); app.setStatus(PENDING); loanDao.insert(app); }校验注意点金额比较用compareTo而不是equals因为 BigDecimal 的 equals 会连带比较精度——new BigDecimal(10.0)和new BigDecimal(10.00)会被判定为不相等而 compareTo 只看数值大小。期限判断限制死四档这样后续生成还款计划的期数可穷举避免出现 13 期、47 期这类没法画计划表的脏数据。3.3 管理员审核状态翻转与还款计划生成的事务边界审核是整个系统最考验事务功底的地方。管理员在 auditList.jsp 点击「通过」后后端要做三件必须同时成功的事把申请状态改成 APPROVED、记录审核时间和审核人、按等额本息生成全部期数的还款计划。任何一步失败都要回滚否则会出现「状态是通过但还款计划缺失」的脏数据。核心代码public void approve(int applicationId, int auditorId) { Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 第一步翻转状态 int rows loanDao.updateStatus(conn, applicationId, APPROVED, auditorId); if (rows ! 1) { throw new RuntimeException(申请状态已变更请刷新后重试); } // 第二步读取申请信息用于计算还款计划 LoanApplication app loanDao.findById(conn, applicationId); // 第三步删除旧计划防重复审核产生的脏数据再重新生成 repaymentDao.deleteByApplicationId(conn, applicationId); ListRepaymentPlan plans calcRepaymentPlan(app); for (RepaymentPlan plan : plans) { repaymentDao.insert(conn, plan); } conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException(审核失败已回滚事务, e); } finally { DBUtil.close(conn); } }逻辑说明conn.setAutoCommit(false)之后手动控制 commit 和 rollbackDAO 方法全部改为接收 Connection 参数保证它们使用的是同一个数据库连接。updateStatus返回影响行数如果不等于 1说明这条申请的状态在管理员点「通过」之前已经被别人改过比如另一台浏览器设备刚好点了驳回此时直接抛异常回滚而不是硬覆盖这就是最简单的乐观锁思想。spring 里叫 Transactional 注解课设里用 try-catch 手写回滚反而更看得懂。等额本息计算是这段代码的数学核心也是金融业务里最容易算错的地方private ListRepaymentPlan calcRepaymentPlan(LoanApplication app) { BigDecimal principal app.getAmount(); BigDecimal monthlyRate app.getAnnualRate() .divide(new BigDecimal(12), 6, RoundingMode.HALF_UP) .divide(new BigDecimal(100)); int months app.getTermMonths(); // 等额本息每期还款额 本金 * 月利率 * (1月利率)^期数 / [(1月利率)^期数 - 1] BigDecimal pow BigDecimal.valueOf(Math.pow(1 monthlyRate.doubleValue(), months)); BigDecimal numerator principal.multiply(monthlyRate).multiply(pow); BigDecimal denominator pow.subtract(BigDecimal.ONE); BigDecimal monthlyPayment numerator.divide(denominator, 2, RoundingMode.HALF_UP); // ...逐期拆分本金和利息最后一期做尾差修正 }参数说明年利率 4.75 先除以 12 换算成月利率除以 100 是把百分比数字变成小数除法指定 6 位小数精度防止中间计算溢出Math.pow计算复利系数。这里有个「最后一期尾差修正」的细节——按照等额本息公式每期都四舍五入到分最后一期会因为之前每期舍入误差累积导致总和差几分钱所以最后一期的本金用应还总额 - 已还本金反推保证所有期数的本金加起来严格等于贷款金额。答辩时说出这个细节老师会高看你一眼。4. 运行指南建库、连接、部署与一次完整的演示走位4.1 初始化数据库用脚本而不是手动建表源码包的 sql 目录下有一个完整脚本包含建库、建表、插入演示数据。优先用命令行导入比在 Navicat 里一句句复制粘贴更接近真实工作流mysql -u root -p loan_system.sql脚本里除了三张表的 CREATE 语句还有两条演示账号学生账号student01密码默认为123456和管理员账号admin01密码默认为admin123。导入完成后可以进 MySQL 验证一下USE loan_system; SHOW TABLES; -- 应该看到三张表 SELECT * FROM user; -- 检查演示账号是否插入成功注意脚本开头如果有CREATE DATABASE语句执行时需要确保当前 MySQL 账号有建库权限。课设环境里 root 肯定没问题但如果学校机房用的受限账号可以手动只执行后面的建表部分。脚本里所有表都带DROP TABLE IF EXISTS前缀所以重复执行不会报错这点对反复调试很友好。4.2 改数据库连接DBUtil 是第一个要动的文件连接信息集中在 DBUtil.java 里也是拿到源码后你必然要修改的文件。这是 JDBC 连接 MySQL 的核心代码public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/loan_system ?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }三个参数逐个解释useSSLfalse关掉 MySQL 8.x 默认的 SSL 握手避免本地开发输出大段 SSL 证书警告characterEncodingutf8让 JDBC 连接层和数据库字符集保持一致这是中文数据不乱码的前提之一serverTimezoneAsia/Shanghai是针对新版驱动和 MySQL 5.7 时区差异的修复不写的话通常报The server time zone value й׼ʱ is unrecognized——这个报错信息本身也是乱码很容易误导排查方向。如果本机 MySQL 的 root 密码不是 123456改这一处即可。如果在 IDEA 里用 Maven 或 Gradle 导入驱动依赖已经由 pom 管理如果收到的是非 Maven 源码包驱动 jar 直接放在 web/WEB-INF/lib 目录下DBUtil 里也不需要手动Class.forName——JDBC 4.0 起驱动类通过 SPI 自动注册只要 jar 在 classpath 里就行。4.3 IDEA 部署到 Tomcatwar exploded 是调试最快的模式这个项目是 Web 应用不是普通 Java 程序运行方式是在 IDEA 里配置 Tomcat。先说一个容易被忽略的顺序问题先装好本地 Tomcat再在 IDEA 里配置。步骤建议严格按下面的顺序走打开项目后确认Project Structure里 Project SDK 选的是 JDK 8Tomcat 8.5 不支持 JDK 11 以下的某些运行参数组合JDK 8 最稳。如果源码包没带 Maven 配置需要手动把 src 目录 Mark asSources Root把 web 目录 Mark asWeb Resources Directory。如果 lib 目录下 jar 包没有蓝色小茶壶图标全选后右键Add as Library。Run - Edit Configurations - - Tomcat Server - LocalServer 选项卡里指向本地 Tomcat 安装目录。Deployment选项卡里点 - Artifact选择loan-system:war explodedApplication context 填/loan。启动前确认下方Before launch里有Build Artifact的步骤否则修改 Java 代码后热部署不生效。war exploded 是解压目录形式相比 war 包它允许 IDEA 增量更新 class 和资源文件。修改 JSP 后刷新浏览器即可修改 Java 类后点 Tomcat 面板的Update按钮就能重新加载不用反复重启——课设调试期能省大量时间。如果选择打包 war 再部署每次都走完整构建周期JSP 改一处就要重打一次包血泪教训。4.4 演示走位把整个业务流串一遍系统跑起来后建议按这条路径做一次完整的「半分钟演示」它覆盖了除注册外的所有核心功能浏览器访问http://localhost:8080/loan/login.jsp系统自动跳转到登录页说明 AuthFilter 正常拦截。用student01登录进学生端首页提交一笔金额 12000、期限 24 期的贷款申请用途写「学费缴纳」。退出登录销毁 session用admin01登录进入待审核列表能看到刚才提交的 PENDING 状态的申请。点击「通过」系统生成 24 条还款计划记录。退出管理员再用student01登录进入「我的贷款」页面能看到状态变成 APPROVED下方出现完整的还款计划表每期金额相同等额本息最后一期可能与前面差几分钱——这是尾差修正后的正常现象。演示过程中如果某一步跳转异常优先看 IDEA 控制台的异常堆栈而不是先怀疑代码逻辑。课设阶段遇到的 80% 问题都能从「哪一行抛异常、SQL 语句和预编译参数是什么、数据库当前数据是什么状态」这三个方向定位出来。5. 避坑与排查六个让课设当场翻车的现场5.1 中文乱码页面、Tomcat、JDBC 三层编码不一致现象登录后学生姓名和贷款用途全是问号或乱码入库后 Navicat 里看到的是???。原因编码在三层各管一摊——JSP 页面声明的编码、Tomcat 解析 HTTP 请求的编码、JDBC 连接 MySQL 时声明的编码任何一层不一致都会乱码。最常见的是漏配 EncodingFilter或者 Tomcat 没有配置 URI 编码。解决三层统一 UTF-8。JSP 顶部加% page contentTypetext/html;charsetUTF-8 %Tomcat 的 server.xml 里 Connector 加URIEncodingUTF-8JDBC URL 带上characterEncodingutf8。改完这三处还乱检查一下 MySQL 建库时的 DEFAULT CHARSET 是不是 utf8mb4这三层对齐后乱码基本绝迹。5.2 一启动就报 ClassNotFoundException: com.mysql.jdbc.Driver现象Tomcat 启动或首次访问数据库相关页面时抛出找不到驱动类。原因分两种一是驱动 jar 没有添加到模块依赖里IDEA 里表现为 jar 包没有显示成可展开的库二是代码里写了Class.forName(com.mysql.jdbc.Driver)但 MySQL 8.x 驱动已经把类迁移到了com.mysql.cj.jdbc.Driver。解决检查 web/WEB-INF/lib 下是否有 mysql-connector-java 的 jar并且 IDEA 的 Artifacts 输出里包含它。如果是 MySQL 8 的驱动推荐直接干掉Class.forName那行JDBC 4.0 后驱动会自动注册。排查完依然报错去Project Structure - Artifacts - 选中项目 - Output Layout里确认 lib 目录下的 jar 是不是在打包范围内——IDEA 有时不会自动带上手动 add 的库。5.3 SQLException: Zero date value prohibited现象查询还款计划时抛Zero date value prohibited或者看到日期字段全是0000-00-00。原因MySQL 里某些记录的时间字段是零值常见于被手工改过数据或批量脚本导入而 JDBC 8 驱动默认把 0000-00-00 当非法日期处理。另外 serverTimezone 配错也会给出误导性的报错信息。解决JDBC URL 加参数zeroDateTimeBehaviorconvertToNull把零值日期自动转成 null加上前面提过的serverTimezoneAsia/Shanghai两个参数一起加。更彻底的修法是写 SQL 时对允许为空的 DATETIME 字段用IFNULL(字段, NULL)包一层前端展示时对 null 输出「未填写」。5.4 外键约束删不掉用户删除前先看子表现象管理员在用户管理里删除一个学生用户报Cannot delete or update a parent row。原因loan_application 表的外键fk_loan_user引用了 user.id学生如果提交过贷款申请直接删父表记录就会撞外键约束。这是数据库设计有意为之的完整性保护不是 bug。解决两种方向要么业务上不允许删有历史记录的用户改用 status 字段置 0 做逻辑禁用要么删除时先删掉该用户的申请、还款计划等子表数据再删 user 记录。课设阶段用逻辑禁用最省事——状态置 0 后登录接口直接拒绝历史数据还能保留在库里做统计演示报表时反而有内容可看。5.5 登录后页面样式全丢Filter 把静态资源也拦截了现象跳到学生首页后页面是纯 HTML 文字没有 CSS 样式和 JS 效果控制台一堆 404。原因AuthFilter 判断未登录直接重定向或者已登录用户访问的路径被误判成未授权导致 /static 下的 css、js 请求全部被踢回登录页。浏览器里按 F12 看 Network 面板静态资源请求的响应应该是 302 跳转。解决在 Filter 的放行名单里加静态目录前缀判断代码写法参考 3.1 节中的isStatic判断。注意/static/要带着前缀判断而不是只判断后缀.css因为你还要给 js、图片一起放行统一目录前缀最省心。5.6 Tomcat 控制台报 Address already in use: 8080现象启动 Tomcat 时控制台提示端口已占用几秒后自动退出。原因上一次启动的 Tomcat 实例没有完全关闭或者机器上其他进程占了 8080。IDEA 里表现为 Tomcat 面板的 Stop 按钮是灰的但端口还活着。解决优先找到未关闭的 java 进程结束掉临时快速解法是改 Tomcat 端口——IDEA 的 Tomcat 配置里 HTTP port 改成 8081同时把部署后的 Application context 路径和浏览器访问地址同步改掉。治本办法是杀掉残留进程因为如果同时开着多个 Tomcat 实例互相干扰后续 session 和日志排查会更乱。6. 二次开发方向先把金额改成 BigDecimal再谈报表如果你准备拿这个项目做改进版答辩我建议改造顺序是「先动钱再动数据最后动展示」。动钱指的是把可能还残留在代码里的 double 计算全部替换成 BigDecimal比如有些旧版本源码在计算利息时用的是double变量直接改成BigDecimal interest principal.multiply(monthlyRate) .setScale(2, RoundingMode.HALF_UP);这个改造看起来小但直接决定了系统在金额运算上是否可靠。改完之后跑一遍第 4.4 节的完整演示核对还款计划表里的总额是否等于本金加利息总和这就是最直接的验证方法。接着可以加一个「统计报表」功能用一条 SQL 按状态分组统计申请数量和金额总量前端用 ECharts 画饼图管理员一看就知道当前放款结构。SQL 就写在这SELECT status, COUNT(*) AS cnt, SUM(amount) AS total FROM loan_application GROUP BY status;最后加一个真正有用的后端防护把审核里的乐观锁从「先查询再更新」改成「条件更新」——一条 UPDATE 语句带上 status 条件影响行数是 0 就说明状态已被别人改过。比如UPDATE loan_application SET statusAPPROVED, audit_timeNOW() WHERE id? AND statusPENDING这比先查后改更安全也更高效答辩时可以把这条 SQL 的 WHERE 条件讲给老师听说明你理解了并发环境下状态一致性的价值。我从一个课设项目里学到的教训是拿到别人的源码第一反应永远是先看 SQL 脚本和数据模型因为表结构反映了设计者对整个业务的理解如果表和表之间的关联、状态流转说不通那代码写再花哨也没意义。从那以后我每次复现项目都强制先把建库脚本和实体类字段逐一核对再启动编译器。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询