ARTICLE DETAIL

资讯详情

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

从MVC到事务:JavaWeb个人网银系统开发与转账限额实现

从MVC到事务:JavaWeb个人网银系统开发与转账限额实现 简介基于JavaWeb与MVC模式开发的个人网上银行系统毕业设计/课程设计完整项目包围绕用户登录、存取款、转账、定期存款、活期转定期、操作日志等核心银行功能展开适合计算机相关专业学生完成毕设、课程大作业或初期项目演示。资源共368个文件约64.8MB包含52个Java源码、31个JSP页面、84个JavaScript脚本、48个CSS样式、11个XML配置以及SQL数据库脚本、设计报告文档和系统截图源码采用分层设计目录结构清晰便于定位和二次开发。配套报告完整覆盖开发环境配置、需求分析、总体与详细设计、数据库设计、功能实现和测试过程可辅助理解MVC分层架构在银行业务中的落地方式。项目功能已验证稳定运行目前已有147人学习使用是从入门进阶到扩展完善银行系统功能的实用参考资料。1. 这套网银系统值不值得花一周先看MVC模式怎么兜住复杂度每到课程设计节点你大概率会搜到这样一个 javaweb 个人网上银行系统源码包。它自带数据库、报告和系统截图解压后俨然是一个“交作业专用全家桶”。但这类资源真正的价值不在那几个页面截图上而在于它把登录、开户、存款、取款、转账拆成了三层结构这就是标题里 MVC 模式的意义让 JSP、Servlet、Service 和 DAO 各管一段哪一层坏了只改哪一层而不是把几百行业务逻辑全堆在 JSP 里。对正在做毕设或课程设计的读者来说这套包能省下从零搭框架的时间但前提是你能把数据库跑通、把 Tomcat 挂上、把三条核心链路走明白。本篇就按“先拆 MVC 的三层分工再从头配环境、建库、跑通登录最后亲手加一个转账限额功能”的顺序把这套代码变成你自己写得出来、讲得清楚的项目。2. MVC 在 javaweb 个人网银里的三层分工对照源码目录认包名最快很多人拿到源码第一时间就开 IDEA 找 main 方法然后在 Tomcat 配置里碰运气。其实跑这套项目之前先花半小时把包结构读一遍后面排错会快得多。常见做法是entity放表的映射对象dao放增删改查service放交易规则servlet接收请求和转发视图jsp页面只负责显示数据。这套包大概率也是按这个思路组织的只是包名长短不同。2.1 模型层表、DAO 和 service别把它们全塞进 entity模型层在 MVC 里不是一张表对应一个 JavaBean 那么简单。很多 javaweb 网银项目的模型层内部会再拆两小层数据访问层 DAO 和业务逻辑层 Service。以登录功能为例DAO 负责拼 SQL 查用户表Service 负责判断“密码错了三次要不要锁定账户”。这两件事混在一起最直接的后果就是事务没法圈定转账扣账和加流水中间一旦抛异常两边数据就对不上。我一般会先看src目录里有没有独立的service包名。如果只有 DAO 类说明项目把所有业务判断都堆在了 Servlet 里这种实现不能算完整的 MVC后续二开也会特别痛苦。对应到代码上DAO 的写法往往长这样// UserDao.java public class UserDao { private DBHelper db new DBHelper(); // 登录查询参数化查询防止 SQL 注入这是网银类项目的底线 public User findByUsername(String username) throws SQLException { String sql SELECT user_id, username, balance, password_hash FROM user_info WHERE username ?; try (Connection conn db.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setUserId(rs.getInt(user_id)); u.setUsername(rs.getString(username)); u.setBalance(rs.getBigDecimal(balance)); return u; } } } return null; } }这段代码的关键点有两个。一是PreparedStatement参数占位符绝对不要用字符串拼接WHERE username username 网银系统是练手项目里最容易被人拿 SQL 注入开刀的目标。二是try-with-resources写法连接、预处理语句、结果集全部自动关闭否则 Tomcat 跑两天连接池必爆。实际项目里 DAO 层除了 findByUsername 还会有 findByCardId、updateBalance 这些方法它们共同构成对user_info和account两张表的基础增删改查先看这几个方法能快速判断项目的数据表设计是否合理。2.2 视图层JSP 负责渲染MVC 模式下别写业务逻辑视图层在传统 javaweb 项目里就是 JSP。项目里常见的页面大概这些登录页、开户页、存款/取款页、转账页、交易流水页以及一个未登录跳转的 401 页面。合格的 JSP 应该只做两件事从 request 域里取数据用标签或 JSTL 渲染成 HTML。如果打开transfer.jsp看到%里面直接new Dao().doTransfer(...)这说明原作者把 MVC 写成了 VM 混合体这种页面千万不能照着抄否则每次传参错位都找不到原因。视图层真正需要关注的参数是表单的name属性和 JSP 的编码声明。以登录页为例% page contentTypetext/html;charsetUTF-8 languagejava % form action${pageContext.request.contextPath}/login methodpost input typetext nameusername placeholder用户名/ input typepassword namepassword placeholder密码/ button typesubmit登录/button /form注意两点。第一action用的是 EL 表达式拼了contextPath这样部署后不管项目名怎么改表单都能提交到正确的/login路径如果写成/bank/login这种绝对路径换 Tomcat 部署名后必然 404。第二这里的username和password必须和LoginServlet里request.getParameter(username)的参数名逐一对应拼错一个就等着收空指针登录失败。JSP 页面顶部那一行charsetUTF-8不是摆设它同时影响 post 提交的中文参数解码后面乱码排查会再提到。2.3 控制层Servlet 的转发与重定向决定“跳转”还是“刷新”控制层是 MVC 的调度中心。/login、/transfer、/withdraw这些路径背后各有一个 Servlet 的doPost方法。LoginServlet的职责只有三个取参数、调 Service 做校验、按结果决定跳到哪里。这里最容易犯的错是登录成功后用forward跳首页导致用户按 F5 就把登录请求再提交一遍网银项目里这就是重复扣款的直接入口。正确做法是“POST 后重定向”。登录成功用response.sendRedirect(main.jsp)让浏览器重新走一次 GET 请求失败才用forward把错误信息带回登录页。对应到 web.xml 里的映射关系长这样servlet servlet-nameloginServlet/servlet-name servlet-classbank.web.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameloginServlet/servlet-name url-pattern/login/url-pattern /servlet-mappingurl-pattern就是浏览器地址栏里那个/login之前 JSP 表单提交的路径最终落在这里。调servlet-mapping是排查 404 的第一站类名拼写、url-pattern 少写斜杠、web.xml 放错位置是三个高频问题。搞清这三层后再去看压缩包里的报告和截图才会有感觉——报告里的流程图描述的是控制层路由ER 图对应模型层数据表截图则是验证视图层有没有把数据正确渲染出来。把这三点对上了项目才算真正读懂了。3. 把源码压缩包跑成本地可运行项目IDEA 配置 Tomcat、MySQL 建库完整步骤读懂结构之后就是动手跑通。这里最忌讳一上来就开 IDEA 点绿色三角。javaweb 项目能不能跑起来取决于 JDK、Tomcat、MySQL 三个版本的“性格是否合得来”。常见做法是把版本先固定在某个被反复验证过的组合上再谈导入源码。3.1 版本闭环JDK、Tomcat 和 MySQL 不建议都选最新很多同学在本机装的是 JDK 17 或更高但课程设计源码多数按 JDK 8 的语法写的比如javax.servlet包在 JDK 11 之后就从默认模块里移除了直接导入会报一堆 “Package javax.servlet does not exist”。我的建议是本机常备 JDK 8 和 Tomcat 8.5这是大量 javaweb 源码能直接运行的底线配置。Tomcat 10 也尽量不要碰因为它把javax.servlet改成了jakarta.servlet老项目的 import 语句全会翻车。MySQL 版本相对宽容5.7 和 8.0 都能跑。需要注意的只有驱动类名和认证插件后面避坑章节专门写。JDK、Tomcat 和 MySQL 版本推荐搭配见下表组件推荐版本不推荐的原因JDK1.8老代码兼容性最好Tomcat8.5Tomcat 9 对 javax 兼容开始出问题MySQL5.7 或 8.05.7 稳妥8.0 要注意驱动和认证IDEA2020 以后任意版跟版本冲突关系不大3.2 导入源码与挂载 Tomcat先看有没有 pom.xml解压压缩包后先看根目录有没有pom.xml。如果项目是 Maven 管理的恭喜依赖会自动下载但更常见的情况是普通 JSP Servlet 项目只有src目录和web目录连.idea都没有。这种项目导入 IDEA 时选择File - New - Project from Existing Sources直接选源码根目录IDEA 会把它识别成普通 Java 项目随后手动把lib目录里的 jar 包加入 Libraries 即可。如果漏了这一步启动后必报ClassNotFoundException最常见的是少了servlet-api.jar或mysql-connector-java.jar。配 Tomcat 时要注意Deployment 里要部署的是war exploded而不是空项目名。很多 javaweb 项目没有 Maven 结构IDEA 不知道 web 根目录在哪需要在 Project Structure 里指定 Web Resource Directory 指向web或WebContent目录。这一步配错Tomcat 能启动但全是对着空白页 404而且不报任何错。配置完成后访问路径通常是http://localhost:8080/项目名/或http://localhost:8080/取决于你挂在 Tomcat 的应用名。控制层路由和 web.xml 的对齐关系前面 2.3 小节已经讲过。把 Servlet 类放对位置把 web.xml 的 mapping 路径核对一遍再启动就不至于一头雾水。如果项目里用了WebServlet注解就别在 web.xml 里重复声明同一个路径否则 Tomcat 启动直接报java.lang.IllegalArgumentException: The servlet name ... is already defined这个错特别容易让人误判成 JDK 问题。3.3 数据库初始化SQL 脚本导入、JDBC 连接配置双管齐下数据库是这类网银项目的命门。压缩包里通常带一个.sql脚本有的是全量建表脚本有的是导出带数据的备份。不要直接在 MySQL 图形工具里复制粘贴因为脚本里可能有DELIMITER //这样的存储过程语法图形工具粘贴会直接断章取义。推荐在命令行里按顺序手动建库再导入mysql -u root -p进入 MySQL 命令行后执行-- 先建库指定 utf8mb4 编码避免后面中文乱码 CREATE DATABASE IF NOT EXISTS bank_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bank_db; -- 导入压缩包里的建表脚本这里按实际路径写 SOURCE /Users/yourname/desktop/bank.sql; SHOW TABLES;SHOW TABLES输出里应该有user_info、account、transaction_record这类常见网银表。看到表之后再检查一下account表里是否有初始测试数据如果没有手动补两条不然启动网站后登录会直接空指针。导入完成后下一步就是让 Java 代码能找到这个库。老项目里常见的连接配置长这样位置通常在src/jdbc.properties或db.properties里drivercom.mysql.cj.jdbc.Driver # serverTimezone 必须写否则 mysql8 驱动会认为时区未设置 urljdbc:mysql://localhost:3306/bank_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password你的密码characterEncodingutf8解决中文参数写入数据库变成问号的问题serverTimezone解决 mysql 8.0 驱动连接时报The server time zone value Öйú±ê׼ʱ¼ä的错。如果你用的是 mysql 5.7驱动类还是com.mysql.jdbc.Driver这是 8.0 和 5.7 之间最容易眼花了脑子短路的地方。还有一类情况压缩包里只有数据库备份文件.bak或者根本没有现成 SQL 脚本只有几张表结构截图。这时你要做的是打开 IDEA 右侧 Database 面板从连接好的 MySQL 里直接把远程库同步出来或者用命令mysqldump导出成 SQL 脚本再导入本地。IDEA 导出数据库脚本时会自动带DROP TABLE IF EXISTS开头这类脚本在本地重复执行不会报错比手动改表结构轻松得多。3.4 启动后的自检链路先走通登录再看余额变动环境全部配好后启动 Tomcat浏览器打开首页。不要急着点所有菜单先走一条完整链路登录 → 查余额 → 存款 → 再看余额。这条链路验证了 JSP、Servlet、Service、DAO、MySQL 全链路是否通。如果登录就卡住九成是数据库连接配置问题直接看 Tomcat 日志里的Caused by后面跟的第一行异常比对着页面干猜高效得多。4. MySQL 初始化与 javaweb 联动五个让人翻车的坑对照排查这一章把我在这类网银项目里踩过的数据库相关坑集中写出来。每一条都是“现象 → 原因 → 解决”的顺序排错时可以按索引直接对号入座。4.1 mysql8 驱动类名变了Class.forName 直接给你脸色看现象Tomcat 启动没有报错但一登录就抛java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 8.0 的 JDBC 驱动把旧类名com.mysql.jdbc.Driver移除了取而代之的是com.mysql.cj.jdbc.Driver。老项目jdbc.properties里写的是旧类名而lib下放的 jar 包是 mysql-connector-java 8.x两者对不上。解决把驱动配置改成drivercom.mysql.cj.jdbc.Driver同时删掉lib目录里旧版本的 mysql-connector-java.jar避免两个驱动打架。检查方法是在 IDE 里双击 jar 包看META-INF/services/java.sql.Driver内容它能直接告诉你这个 jar 支持哪些驱动类。4.2 MySQL 8 认证插件不兼容建立连接时提示 Public Key Retrieval 错误现象连接 URL 配好后IDEA 测试连接时报Public Key Retrieval is not allowed。原因MySQL 8.0 默认用的caching_sha2_password认证插件老版本连接器在非 SSL 连接下第一次握手拿不到公钥。这属于新库老驱动之间的典型代沟。解决两个方案任选。一是连接 URL 里加allowPublicKeyRetrievaltrue适合开发环境二是把该用户的认证插件改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;网银类课程设计在本地跑没有安全敏感问题建议直接把第一个参数加上代码改动最小也避免影响后续别的项目使用。4.3 连接 URL 缺了时区参数时间字段全部变成午夜现象交易流水表查出来的时间全是00:00:00或者连接直接报The server time zone value is unrecognized。原因mysql8 连接器要求显式指定时区而很多老项目代码里连接 URL 只写到jdbc:mysql://localhost:3306/bank_db。解决统一在jdbc.properties里改成urljdbc:mysql://localhost:3306/bank_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8补充一个细节characterEncodingutf8在 mysql8 下建议写成utf8mb4因为 utf8 在 MySQL 里其实是 utf8mb3遇到生僻字和 emoji 存不进去。但改编码不只是改 URL建表时字段的 charset 也要对齐。如果表已经建完了可以用一条命令批量改ALTER TABLE transaction_record CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;4.4 页面中文全是问号JSP 编码和数据库编码没串起来现象登录用户名显示正常但从数据库读出来的用户姓名、开户行地址全是???。原因这不是数据库单方面的问题而是三层编码不一致。JSP 页面用charsetUTF-8Tomcat 的 POST 参数解析默认用ISO-8859-1MySQL 表又是latin1或gbk。三个环节差一个中文就变问号。解决先统一 MySQL 建库时用utf8mb4再在 JSP 顶部加pageEncoding最后给 Servlet 的request设置编码。如果你不想在每个 Servlet 里重复写可以加一个过滤器优先级放在最前面WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }这个过滤器覆盖所有路径包括静态资源启动后请求参数和响应输出统一成 UTF-8整个过程不需要在业务代码里再处理编码。乱码排查时还可以在 MySQL 命令行执行SHOW VARIABLES LIKE character_set%;确认数据库服务端的默认编码如果 server 端显示latin1那就别只在应用层较劲了。4.5 表结构改坏了学会用事务回滚而不是删库重来现象想给转账表加一个手续费字段结果 ALTER 语句报错或者字段加错位置导致页面查询结果列错位。原因很多课程设计项目改表结构前不备份。表里本来就放着测试数据删掉重建最省事但如果你已经在这个库里跑了若干次测试删库会连测试记录一起清掉等会写报告时又得重新录截图。解决尽量用ALTER TABLE增量修改而不是删表。加字段的语句一试便知ALTER TABLE account ADD COLUMN daily_transfer_limit DECIMAL(10,2) DEFAULT 5000.00;万一 ALTER 报错先BEGIN;开启事务执行后确认没问题再COMMIT;。如果你要改的数据量不小最好先mysqldump备份一份再动手备份命令不需要登录 mysql 命令行直接在系统终端执行mysqldump -u root -p bank_db bank_db_backup.sql这个文件就是后悔药。折腾坏了直接mysql -u root -p bank_db bank_db_backup.sql恢复到改动前的状态比重新录测试数据省出两个小时。5. 给系统加一个转账限额功能MVC 全流程改动指南跑通原项目不算本事能在这个基础上加需求才算把 MVC 吃透了。这一章以“单日转账限额”为例完整走一遍从数据库到 JSP 的四层改动。这个需求选得刻意它涉及金额判断、数据库累加、错误回显每一项都能检验你对三层结构的理解。5.1 表结构落地给 account 加限额字段先在account表加一个daily_limit字段表示该账户单日累计转账上限ALTER TABLE account ADD COLUMN daily_limit DECIMAL(10,2) DEFAULT 5000.00;这里用DECIMAL(10,2)而不是FLOAT因为金额字段用浮点类型会在多次累加后产生精度失真比如显示 5000.01 实际却是 5000.009999。网银场景里钱算错一位小数都是事故级别的问题所以 Java 侧对应字段也要用BigDecimal类型对应。同时建一张每日转账累计表比单靠查流水再求和更高效CREATE TABLE daily_transfer_sum ( user_id INT PRIMARY KEY, sum_amount DECIMAL(12,2) DEFAULT 0.00, stat_date DATE );这张表以用户和日期为粒度每次转账成功后更新累计值。如果当天跨天先判断stat_date是否等于当前日期不等就把累计值清零重置。做完这一步模型层的数据基础就有了。5.2 Service 层写限额校验Servlet 只做传话筒控制层最忌讳把金额累加和比较逻辑写在doPost里。新增一个TransferService方法接收转账人和收款人标识、转账金额返回业务结果// TransferService.java public boolean transfer(int fromUserId, int toUserId, BigDecimal amount) throws SQLException { Connection conn db.getConnection(); try { conn.setAutoCommit(false); // 开启事务扣款、加款、写流水必须同生共死 // 1. 查当天已累计转账金额 BigDecimal todaySum dailySumDao.getTodaySum(fromUserId); // 2. 获取该用户的单日限额 BigDecimal dailyLimit userDao.getDailyLimit(fromUserId); // 3. 判断本次转账后是否超限 if (todaySum.add(amount).compareTo(dailyLimit) 0) { return false; // 超过限额回滚且不往下执行 } // 4. 扣款、加款、插入流水 accountDao.decreaseBalance(fromUserId, amount); accountDao.increaseBalance(toUserId, amount); transferDao.insertRecord(fromUserId, toUserId, amount); dailySumDao.updateTodaySum(fromUserId, amount); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { conn.rollback(); // 任何一步失败所有操作全部撤销 throw e; } }setAutoCommit(false)是网银项目里必须有的动作。如果不开启事务扣款成功但加款失败时钱就凭空消失在系统里了。compareTo返回大于 0 说明超限这行判断是新增需求的落点。控制层对应代码就干净很多WebServlet(/transfer) public class TransferServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int fromUserId Integer.parseInt(req.getParameter(fromUserId)); BigDecimal amount new BigDecimal(req.getParameter(amount)); boolean ok new TransferService().transfer(fromUserId, 2, amount); if (ok) { resp.sendRedirect(req.getContextPath() /transferSuccess.jsp); } else { req.setAttribute(error, 今日转账已达限额); req.getRequestDispatcher(/transfer.jsp).forward(req, resp); } } }注意成功用了重定向失败用了转发。重定向让浏览器换 URL刷新时不会再提交一次表单转发则保留 request 里的 error 字段让页面能显示提示。这正是 2.3 节讲的 POST 后重定向原则的实战版。5.3 视图层回显错误信息JSP 别刷新丢失提示transfer.jsp页面需要预留一个错误信息展示区域并保留用户已填写的金额避免失败后表单清空重填。常见的实现方式如下c:if test${not empty error} div stylecolor:red;${error}/div /c:if form action${pageContext.request.contextPath}/transfer methodpost input typetext nameamount value${param.amount} placeholder转账金额/ button typesubmit转账/button /form${param.amount}取的正是上一次请求提交的参数值省去了在 Servlet 里回填表单的额外代码。注意这里用 JSTL 的c:if做判断要确保 JSP 页面上有% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %引入否则这段代码会原样输出。前端的input也可以加一层required和正则校验但记住前端校验只是用户体验Service 层校验才是安全防线两者不能互相替代。5.4 报告和截图怎么补把新增功能写成“需求实现说明”压缩包里那份报告是你最容易偷懒也最应该更新的东西。新增功能后报告里至少补三块内容一是需求分析章里加一条“单日转账限额”的功能点描述二是数据库设计章贴ALTER TABLE后的表结构截图三是测试章写清楚两个用例——限额内转账成功、超限额转账失败并提示。系统截图不要拿老页面凑数重新跑一遍转账链路把失败弹窗和成功跳转都截下来放进测试章对应位置。这就是从“源码的搬运工”变成“项目的二次开发者”的最直接证据。6. 收尾用三个验证步骤判断项目是真跑通还是看起来跑通项目启动、页面点开就算跑通了吗差得远。当年我自己做类似项目时吃过“能登录但一转账余额不动”的哑巴亏所以后来养成了一个习惯收尾阶段用三张表做验收而不是用浏览器截图。第一张表是功能验证表逐项核对并记录真实现象功能点操作路径预期结果实际观察登录首页输入用户名密码跳转主页面显示余额观察 URL 是否变化存款存款页输入 100 元余额增加 100回数据库查 account 表转账转账页输入金额余额减少对方余额增加查 transaction_record 表限额设为 5000尝试转 6000提示超限余额不变查 daily_transfer_sum第二张表是 MVC 组件对应检查每个页面 action 路径能否在 Servlet 映射里找到每个 Servlet 调用的 Service 方法是否存在每个 Service 里引用的 DAO 方法是否都在对应类有定义。三层断一条页面就会 500 或空白。第三张表是数据库一致性检查转账前后两个账户的余额总和不变流水条数增加一条日累计金额等于今日转账之和。这能直接暴露事务没提交或重复提交的问题。最后加一个 SQL 注入用例试试防线。在登录页用户名框输入 OR 11密码随便填如果居然登录成功说明原项目 DAO 层用了字符串拼接 SQL这是网银系统最致命的结构性问题必须重写为PreparedStatement。在本地测试库验证这件事花不了五分钟却能在答辩时让你讲出“安全设计”的真实细节。我现在的习惯是拿到任何 javaweb 项目包第一件事先看 web.xml 的 Servlet 映射和 jdbc 配置这两个位置决定了路径和数据流也是最容易翻车的黑匣子。提前半小时在这上面较劲后面能少熬一个通宵。希望这篇从拆解到改功能的完整路径能帮你把这套源码真正变成自己的项目。本文还有配套的精品资源点击获取
返回列表