ARTICLE DETAIL

资讯详情

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

JavaWeb银行账户系统:Spring Boot+MyBatis-Plus实战与事务安全设计

JavaWeb银行账户系统:Spring Boot+MyBatis-Plus实战与事务安全设计 简介企业级Web应用开发的核心在于构建稳定、安全且可维护的系统这通常涉及对数据一致性、业务原子性和系统安全性的深刻理解。其基本原理是通过合理的架构设计和技术选型确保在高并发场景下业务逻辑的正确执行例如通过数据库事务和原子操作来保障金融交易的准确性。在技术价值层面一个设计良好的系统不仅能高效处理核心业务如账户管理、资金交易还能通过权限控制和数据加密等手段提升整体安全性。这类技术广泛应用于需要高可靠性的业务系统例如银行、金融和电商平台的后台管理。本文以银行账户管理系统为具体场景深入探讨了如何使用Spring Boot快速搭建项目骨架并利用MyBatis-Plus的QueryWrapper和原子更新特性结合Transactional注解优雅地解决存款、取款及转账过程中的并发与数据一致性问题为开发类似企业级应用提供了清晰的工程实践路径。1. 项目缘起一个“老掉牙”的选题如何做出新意与深度又到了一年一度的毕业季相信不少计算机相关专业的同学正对着“银行帐目管理系统”这个看似经典到有些“烂大街”的毕设选题发愁。我当年做毕设时也差点选了它后来在导师的“点拨”下才意识到这个选题的陷阱与机遇并存。很多人觉得不就是个增删改查CRUD吗网上源码一大堆找个改改就能交差。但如果你真这么想可能就错过了通过毕设系统展示自己技术深度、工程思维和解决问题能力的最佳机会。这个基于JavaWeb的银行帐目管理系统其核心价值远不止于实现一个功能列表。它本质上是一个典型的企业级Web应用微缩模型涵盖了用户认证、权限控制、核心业务逻辑存款、取款、转账、事务一致性、数据安全、前后端交互、数据库设计等几乎所有后端开发工程师的必备技能点。评审老师看重的也恰恰是你如何在这些“老生常谈”的功能背后体现出对技术选型、架构设计、异常处理和代码规范的思考。所以本文的目的不是给你一个可以“一键运行”的源码包虽然我会提供完整的、可运行的源码和数据库脚本而是带你深度拆解这个项目的每一个技术决策、每一行关键代码背后的“为什么”并分享我在实际开发和企业级项目中积累的、那些教科书和普通博客里不会写的“踩坑经验”和“性能优化技巧”。无论你是想快速完成一个高质量的毕设还是希望通过这个项目夯实自己的JavaWeb技能栈这篇文章都将为你提供一条清晰的、可复现的路径。2. 技术栈选型与项目骨架搭建为什么是它们在动手写第一行代码之前确定技术栈是至关重要的一步。一个合理的技术选型能让开发事半功倍也更能体现你的技术视野。对于这个银行帐目管理系统我推荐并采用以下组合并逐一解释其必要性。2.1 后端核心Spring Boot MyBatis-Plus为什么不是纯粹的Servlet/JSP直接用Servlet/JSP是很多教学视频的起点但它繁琐的配置、大量的样板代码Boilerplate Code并不适合现代毕设追求效率和规范性的要求。Spring Boot通过“约定大于配置”的理念极大简化了初始搭建过程。为什么选择MyBatis-Plus而不是JPA或原生MyBatisMyBatis-Plus简称MP是一个MyBatis的增强工具。对于毕设项目而言它提供了两个巨大优势极简的CRUD操作内置通用Mapper对于单表操作你几乎可以不写SQL。例如用户表的查询、分页MP都能通过userService.page(page, queryWrapper)一行代码搞定这能让你把精力集中在复杂的业务逻辑如跨账户转账上。强大的查询构造器它的QueryWrapper可以让你以Java Lambda表达式的方式安全地构建动态查询条件避免SQL注入同时代码可读性极高。实操心得很多同学在引入MP后还是习惯为每个实体手动编写基础的增删改查Mapper.xml这完全是画蛇添足。正确做法是让你的Mapper接口直接继承MP的BaseMapper即可免费获得数十个通用方法。只有涉及多表关联查询或特别复杂的SQL时才使用注解或XML编写自定义SQL。2.2 前端视图层Thymeleaf Bootstrap jQuery为什么不用前后端分离Vue/React对于毕设尤其是管理系统类前后端分离固然时髦但会引入额外的复杂度需要独立部署前端项目、处理跨域、管理两套技术栈。使用Thymeleaf模板引擎可以在服务端直接渲染HTML数据通过Model传递开发调试链路更短更符合“一个工程搞定所有”的毕设场景。Bootstrap和jQuery的组合Bootstrap提供了现成的、响应式的UI组件表格、表单、模态框能让你在几乎不写CSS的情况下快速搭建出一个美观且专业的管理界面。jQuery则用于处理简单的DOM操作和Ajax请求例如异步提交表单、局部刷新数据。这个组合成熟、稳定、资料丰富是快速开发的利器。2.3 数据库MySQL 8.0选择MySQL几乎无需理由免费、流行、稳定是学习关系型数据库的事实标准。使用8.0版本可以体验窗口函数、CTE公共表表达式等高级特性虽然本项目可能用不到但能体现你对技术版本的关注。关键点数据库连接池在生产环境中为每个请求创建新的数据库连接是灾难性的。Spring Boot默认集成了HikariCP这是目前性能最好的连接池之一。你只需要在application.yml中简单配置即可这是企业级项目的标配务必在毕设中体现。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bank_account_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: yourpassword hikari: connection-timeout: 30000 # 连接超时时间毫秒 maximum-pool-size: 10 # 最大连接数 minimum-idle: 5 # 最小空闲连接2.4 项目初始化实战使用IntelliJ IDEA这也是热搜词里的主流工具初始化项目通过Spring Initializr创建项目依赖选择Spring Web,Thymeleaf,MyBatis Framework,MySQL Driver。在pom.xml中手动添加MyBatis-Plus的依赖Spring Initializr可能没有直接选项。创建标准的Maven项目结构controller,service,mapper,entity,config。踩坑记录在添加MyBatis-Plus依赖时务必排除掉其内部可能自带的MyBatis核心包避免与Spring Boot初始化的MyBatis版本冲突。通常这样写dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version最新版本/version exclusions exclusion groupIdorg.mybatis/groupId artifactIdmybatis/artifactId /exclusion exclusion groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId /exclusion /exclusions /dependency3. 数据库设计精要不只是建表那么简单数据库设计是系统的基石一个糟糕的设计会让后续编码举步维艰。对于银行系统核心在于保证数据的准确性、一致性、可追溯性。3.1 核心表结构设计我们至少需要以下五张表用户表 (sys_user)存储系统操作员柜员、管理员信息。id(主键),username(用户名),password(加密后的密码),real_name,role(角色如admin, teller),status(状态如启用/禁用)。关键点密码绝不能明文存储。使用Spring Security的BCryptPasswordEncoder进行单向哈希加密这是行业标准。客户表 (customer)存储银行客户信息。id(主键),id_card(身份证号唯一索引),name,phone,address等。关键点身份证号需建立唯一索引防止重复开户并在业务层做合法性校验简单的格式校验。账户表 (account)核心表关联客户。id(主键),account_number(账号唯一索引通常有特定规则如分行号序列号),customer_id(外键关联客户),account_type(账户类型如储蓄户、支票户),balance(余额DECIMAL类型),status(账户状态如正常、冻结、销户),open_date。关键点balance余额字段必须使用DECIMAL(19, 4)这种类型19代表总位数4代表小数点后4位即精确到分厘。严禁使用FLOAT或DOUBLE它们有精度损失在金融计算中是致命的。交易流水表 (transaction)最重要的表记录每一笔资金的变动确保可审计。id(主键),transaction_number(交易流水号全局唯一),from_account_id(转出账户ID可为空如存款),to_account_id(转入账户ID可为空如取款),amount(交易金额DECIMAL),transaction_type(交易类型存款、取款、转账),remark(备注),create_time。关键点流水号需要生成全局唯一的、有一定业务意义的编号例如日期时间随机数。这张表是“铁证”任何资金变动都必须先插入流水记录再更新账户余额。操作日志表 (oper_log)记录管理员的关键操作用于安全审计。id,user_id,operation(操作内容),ip,create_time。关键点通过AOP面向切面编程统一记录避免业务代码污染。3.2 数据库脚本的“坑”与技巧提供的database.sql脚本不应只是简单的CREATE TABLE。一个专业的脚本应该包含以下内容-- 1. 创建数据库并指定字符集 CREATE DATABASE IF NOT EXISTS bank_account_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE bank_account_db; -- 2. 删除表如果存在- 创建表 DROP TABLE IF EXISTS transaction; DROP TABLE IF EXISTS account; -- ... 其他表 -- 3. 建表语句包含注释 CREATE TABLE account ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, account_number varchar(32) NOT NULL COMMENT 账号, balance decimal(19,4) NOT NULL DEFAULT 0.0000 COMMENT 账户余额, -- ... 其他字段 PRIMARY KEY (id), UNIQUE KEY uk_account_number (account_number), -- 唯一索引 KEY idx_customer_id (customer_id) -- 外键关联索引 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账户表; -- 4. 插入初始数据管理员账号、基础参数等 INSERT INTO sys_user (username, password, real_name, role) VALUES (admin, $2a$10$你的BCrypt加密密码, 系统管理员, admin);重要提示utf8mb4字符集可以支持完整的UTF-8包括表情符号是现在的默认推荐。InnoDB引擎支持事务和外键必须使用。4. 核心业务逻辑实现存款、取款与转账的“原子性”保卫战这是整个系统的灵魂所在也是最容易出错的地方。核心原则所有涉及资金变动的操作必须是事务性的并且要先记录流水再更新余额。4.1 存款与取款相对简单但陷阱犹在以存款为例看似只是account.balance amount然后插入一条流水。但在并发环境下这可能出问题。错误示范伪代码public void deposit(Long accountId, BigDecimal amount) { Account account accountMapper.selectById(accountId); account.setBalance(account.getBalance().add(amount)); accountMapper.updateById(account); // 此时balance可能已被其他线程修改 // 插入流水... }这里存在“丢失更新”问题。两个线程同时读到相同的余额分别加上存款额后更新后一次更新会覆盖前一次。正确做法在SQL层面进行原子更新// Service层方法添加 Transactional 注解 Transactional(rollbackFor Exception.class) public boolean deposit(Long accountId, BigDecimal amount, String remark) { // 1. 插入交易流水记录意图 Transaction transaction new Transaction(); transaction.setToAccountId(accountId); transaction.setAmount(amount); transaction.setType(DEPOSIT); // ... 设置其他字段 transactionMapper.insert(transaction); // 2. 使用MP的UpdateWrapper进行原子更新 UpdateWrapperAccount updateWrapper new UpdateWrapper(); updateWrapper.eq(id, accountId) .setSql(balance balance amount); // 直接使用SQL表达式原子操作 int rows accountMapper.update(null, updateWrapper); // 3. 验证更新是否成功 if (rows ! 1) { // 更新失败可能是账户不存在或已销户事务会回滚 throw new RuntimeException(存款失败账户状态异常或不存在); } return true; }通过setSql直接在数据库执行balance balance amount这个操作在数据库层面是原子的完美解决了并发问题。取款逻辑类似但需要额外检查余额是否充足。4.2 跨账户转账分布式事务的微缩演练转账是银行系统的核心涉及两个账户的更新必须在一个事务内完成要么全部成功要么全部失败。Transactional(rollbackFor Exception.class) // 这是关键 public boolean transfer(Long fromAccountId, Long toAccountId, BigDecimal amount, String remark) { // 0. 基础校验账户是否存在、是否冻结、是否同户等 Account fromAccount accountMapper.selectById(fromAccountId); Account toAccount accountMapper.selectById(toAccountId); // ... 各种校验 // 1. 检查转出账户余额是否充足 (在数据库层面再次校验更安全) if (fromAccount.getBalance().compareTo(amount) 0) { throw new RuntimeException(转账失败转出账户余额不足); } // 2. 插入转账流水一条记录包含进出或两条记录均可 Transaction transaction new Transaction(); transaction.setFromAccountId(fromAccountId); transaction.setToAccountId(toAccountId); transaction.setAmount(amount); transaction.setType(TRANSFER); transaction.setRemark(remark); transactionMapper.insert(transaction); // 3. 原子更新转出账户余额减少 UpdateWrapperAccount deductWrapper new UpdateWrapper(); deductWrapper.eq(id, fromAccountId) .ge(balance, amount) // 乐观锁在更新时再次确认余额充足 .setSql(balance balance - amount); int deductRows accountMapper.update(null, deductWrapper); if (deductRows ! 1) { throw new RuntimeException(转账失败转出账户余额不足或状态异常); } // 4. 原子更新转入账户余额增加 UpdateWrapperAccount addWrapper new UpdateWrapper(); addWrapper.eq(id, toAccountId) .setSql(balance balance amount); int addRows accountMapper.update(null, addWrapper); if (addRows ! 1) { throw new RuntimeException(转账失败转入账户状态异常); // 由于有Transactional步骤3的扣款会被回滚 } // 5. 记录操作日志可选可通过AOP实现不影响主事务 logService.recordTransferLog(...); return true; }核心经验Transactional注解确保了从方法开始到结束所有数据库操作在一个事务中。任何一步抛出RuntimeException或Error所有已执行的数据修改都会回滚。这就是本地事务它是实现业务原子性的基础。在更复杂的分布式系统中才会用到Seata等分布式事务框架但对于单数据库的毕设项目本地事务完全足够且正确。5. 权限控制与安全性设计不只是登录拦截一个管理系统权限是骨架。我们不能让一个普通柜员有权限修改利率参数或查看所有客户的敏感信息。5.1 基于角色的访问控制RBAC我们设计简单的RBAC用户 - 角色 - 权限菜单/按钮。权限表 (sys_permission)存储所有需要控制的资源如/account/create创建账户、/transaction/transfer执行转账。角色-权限关联表建立角色和权限的多对多关系。用户登录时查询其角色对应的所有权限存入Session或Token。5.2 使用拦截器Interceptor实现权限校验在Spring Boot中实现一个拦截器来检查每个请求的路径是否在当前用户的权限集合内。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 获取请求URI String uri request.getRequestURI(); // 2. 放行登录、注销等公开路径 if (isPublicUri(uri)) { return true; } // 3. 从Session中获取当前用户权限列表 HttpSession session request.getSession(); SetString permissions (SetString) session.getAttribute(userPermissions); // 4. 校验 if (permissions null || !permissions.contains(uri)) { // 无权限返回错误页面或JSON response.sendRedirect(/error/403); return false; } return true; } }然后在配置类中注册这个拦截器并设置拦截路径如/admin/**。5.3 关键安全加固点密码加密如前所述使用BCryptPasswordEncoder。Session管理设置合理的Session超时时间。防止用户离开后会话长期有效。防止SQL注入坚持使用MyBatis-Plus的QueryWrapper或#{}参数绑定绝对禁止在代码中拼接SQL字符串。XSS防护Thymeleaf默认会对输出进行HTML转义这很好。对于富文本等需要保留格式的内容可以使用白名单过滤库如Jsoup进行清洗。CSRF防护对于重要的操作如转账应使用CSRF Token。Spring Security提供了开箱即用的支持即使你不完全集成Spring Security也可以手动实现一个简单的Token校验。踩坑实录我曾在一个早期项目中因为偷懒直接在查询中拼接了用户输入的关键字导致了SQL注入漏洞。被测试同学用‘ or ‘1’‘1轻松攻破。教训是无论多简单的查询只要参数来自用户就必须使用参数化查询。MyBatis-Plus的eq、like等方法内部已经做了处理这是选择它的又一个重要理由。6. 前端页面与交互打造一个“能用”且“好用”的管理界面前端的目标是清晰、易用、反馈及时。利用Bootstrap我们可以快速搭建。6.1 列表页与分页查询账户列表页是高频操作页面。需要支持按账号、客户名、账户类型等多条件查询以及分页显示。后端ControllerGetMapping(/account/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, AccountQuery query, Model model) { // 1. 构建分页对象 PageAccount page new Page(pageNum, pageSize); // 2. 构建查询条件 QueryWrapperAccount wrapper new QueryWrapper(); if (StringUtils.isNotBlank(query.getAccountNumber())) { wrapper.like(account_number, query.getAccountNumber()); } if (query.getCustomerId() ! null) { wrapper.eq(customer_id, query.getCustomerId()); } // ... 其他条件 wrapper.orderByDesc(update_time); // 3. 执行分页查询 PageAccount accountPage accountService.page(page, wrapper); // 4. 将数据放入Model供Thymeleaf渲染 model.addAttribute(pageData, accountPage); return account/list; // 对应 src/main/resources/templates/account/list.html }前端Thymeleaf页面关键片段table classtable table-hover theadtrth账号/thth客户姓名/thth余额/thth操作/th/tr/thead tbody tr th:eachaccount : ${pageData.records} td th:text${account.accountNumber}/td td th:text${account.customer.name}/td td th:text${#numbers.formatDecimal(account.balance, 1, 2)}/td td a th:href{/account/detail/{id}(id${account.id})} classbtn btn-sm btn-info详情/a button classbtn btn-sm btn-warning onclickshowDepositModal(${account.id})存款/button /td /tr /tbody /table !-- 分页组件 -- nav ul classpagination li th:class${pageData.current 1} ? page-item disabled : page-item a classpage-link th:href{/account/list(pageNum${pageData.current-1})}上一页/a /li !-- 页码循环 -- li th:eachi : ${#numbers.sequence(1, pageData.pages)} th:class${i pageData.current} ? page-item active : page-item a classpage-link th:href{/account/list(pageNum${i})} th:text${i}/a /li li th:class${pageData.current pageData.pages} ? page-item disabled : page-item a classpage-link th:href{/account/list(pageNum${pageData.current1})}下一页/a /li /ul /nav6.2 使用jQuery Ajax实现无刷新操作对于存款、取款这类操作使用模态框Modal和Ajax提交能极大提升体验。// 存款模态框的提交函数 function submitDeposit() { var accountId $(#depositAccountId).val(); var amount $(#depositAmount).val(); var remark $(#depositRemark).val(); // 前端校验... $.ajax({ url: /transaction/deposit, type: POST, contentType: application/json, data: JSON.stringify({accountId: accountId, amount: amount, remark: remark}), success: function(result) { if (result.success) { $(#depositModal).modal(hide); alert(存款成功); location.reload(); // 刷新列表 } else { alert(存款失败 result.message); } }, error: function(xhr) { alert(请求异常请稍后重试。); } }); }界面优化技巧Bootstrap的表格类table-hover可以增加鼠标悬停效果table-striped可以增加斑马纹让表格更易读。对于金额使用Thymeleaf的#numbers.formatDecimal进行格式化统一显示为两位小数。所有按钮操作尤其是删除和资金交易必须在前端通过弹窗confirm()进行二次确认防止误操作。7. 项目部署、测试与答辩准备最后的临门一脚代码写完只是第一步让项目能稳定运行并清晰展示才是赢得高分的关键。7.1 本地运行与打包配置多环境在application.yml旁创建application-dev.yml开发环境和application-prod.yml生产环境分别配置不同的数据库连接、日志级别等。通过Spring Boot的spring.profiles.active属性切换。打包在项目根目录下执行Maven命令mvn clean package -DskipTests会在target目录下生成一个可执行的jar文件如bank-system-0.0.1-SNAPSHOT.jar。这个jar包内嵌了Tomcat服务器直接通过java -jar bank-system-0.0.1-SNAPSHOT.jar即可运行。7.2 基础功能测试清单在答辩前务必自己完整测试一遍。可以准备一个检查清单模块测试点预期结果用户登录正确用户名/密码登录成功跳转主页错误密码提示“用户名或密码错误”禁用账户登录提示“账户已被禁用”账户管理创建新账户成功账号唯一查询账户支持模糊查询和分页冻结/解冻账户状态更新冻结账户不能交易存款取款正常存款余额增加流水生成取款余额不足提示失败余额不变大额存取款金额格式正确支持两位小数转账正常转账双方余额正确变动一条流水转出余额不足整体失败双方余额不变转入账户不存在整体失败转出余额不变权限管理员登录能看到所有菜单柜员登录只能看到业务操作菜单无系统管理7.3 答辩演示技巧与可能的问题演示脚本提前写好一个演示流程。例如“首先我以管理员身份登录创建一个新客户‘张三’并为他开立一个储蓄账户。然后我切换为柜员角色为张三的账户存入10000元。接着模拟客户‘李四’前来我从张三账户向李四账户转账5000元。最后查看交易流水确认所有操作都有记录。”准备技术问答Q你的系统如何保证转账过程不出错A我使用了Spring的Transactional声明式事务。在转账方法中先记录流水再原子性地更新两个账户的余额。任何一步失败整个事务都会回滚确保数据一致性。Q如果多人同时给同一个账户存款余额会错乱吗A不会。我在更新余额时使用的是setSql(balance balance #{amount})这样的原子SQL操作数据库本身会处理并发写入保证了最终结果的正确性。Q密码是怎么存储的安全吗A使用BCryptPasswordEncoder进行单向哈希加密并加盐。即使数据库泄露攻击者也无法反向破解出明文密码。Q为什么选择MyBatis-Plus而不是JPAAMyBatis-Plus在保留MyBatis灵活SQL能力的同时极大地简化了单表CRUD操作。对于银行交易这类对SQL可控性要求较高的业务我觉得它更合适。而且它的QueryWrapper写动态查询非常方便。展示代码亮点提前在IDE里标记好几处关键代码如事务注解的使用、原子更新的SQL、权限拦截器、密码加密配置等答辩时可以直接切换过去讲解。最后将完整的项目源码包括清晰的目录结构、数据库脚本、一份简明的README.md说明如何配置和启动打包。这份完整的、可运行的作品加上你对其中技术要点的深入理解就是一个足以获得优秀评价的毕业设计。记住老师想看到的不是你实现了多少功能而是你如何思考并解决实现过程中遇到的问题。本文还有配套的精品资源点击获取
返回列表