拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SSM银行系统毕设全解:从Spring装配到事务与并发

简介这是一套基于SSMSpringSpringMVCMyBatis框架的银行系统Java毕业设计项目具备完整的业务操作功能按前后台分离设计可供高校计算机相关专业学生用于毕业设计、课程设计或SSM整合练习。压缩包共1210个文件整体约46.61MB其中以566个JavaScript脚本、94个CSS样式、56个HTML页面、41个JSP视图和39个Java源文件为主同时包含78个jar依赖库、class编译文件、XML配置、图片与字体资源等目录结构规范便于检索与二次开发。目前已有672人浏览学习适合需要快速理解银行类管理系统业务逻辑的开发者参考。内容涵盖用户信息、银行卡账户、基金产品等典型模块前后台权限与操作流程完整并随包提供IDEA导入、Apache服务部署及MySQL配置说明可帮助读者在本地环境顺利运行项目同时掌握SSM框架整合开发的基本思路与常见配置方法。1. 基于 SSM 的银行系统毕业设计先想清楚它考的是什么银行系统是 SSM 毕业设计里最耐做的题目之一。业务闭环清晰开户、存款取款、转账、流水查询、管理员冻结账户不需要堆功能就能把需求讲明白。这类项目的真正难点不在页面多而在几笔写操作的准确性和并发边界上。框架层面考核的是 Spring 的 IoC 装配、SpringMVC 的请求分发、MyBatis 的 XML 映射外加声明式事务的触发条件。答辩时被问的内容基本就是 Java 面试里 Spring、MyBatis 那一类问题Bean 生命周期、事务失效场景、SQL 注入。下面从工程骨架开始把一套基于 SSM 的银行系统逐步跑通对正在做毕设、或者答辩前想补底层原理的人同样适用。2. SSM 项目的工程骨架与银行模块拆解2.1 毕设选 SSM 而不是 Spring Boot图的是一套可讲解的装配过程现在企业新项目大多直接 Spring Boot但毕业设计被要求基于 SSM图的不是技术陈旧而是把 Spring 的核心机制摆在明面上。Spring Boot 靠自动配置把 IoC 容器、Bean 生命周期、AOP 切面这些概念藏了起来「能跑」很容易答辩时反而说不清。SSM 里数据源、事务管理器、Mapper 扫描都需要手写声明讲到 Bean 怎么被管理、事务切面织在哪个类上都有实际配置可以指给老师看。银行系统业务不复杂正好把精力留给框架本身。整套系统落到实现上是四件事Spring 容器装配 service 与 daoSpringMVC 把 /user/login、/account/transfer 这类请求分发给 controllerMyBatis 把 Java 方法映射成 SQLSpring 事务管理器统一控制转账这类写操作的提交与回滚。把这四件事对应到工程结构上目录就不会乱写。2.2 按三层架构把银行系统拆成包结构答辩也好复述常见做法是把 Maven 工程根包定为 com.bank下面按 controller、service、dao、pojo、interceptor、utils 六类分包。实体类放 pojo数据库访问接口放 dao对应的 MyBatis XML 放 resources/mapper 目录并让 XML 的 namespace 与接口全限定名一致。interceptor 放登录校验utils 放 MD5 加密和分页工具。拿到一个 SSM 毕设项目先看包结构就能把架构复述出来。com.bank ├── controller # 接收请求并返回视图名, 如 LoginController / AccountController ├── service # 业务接口与 impl 实现, 转账事务写在 impl 上 ├── dao # Mapper 接口, 方法名与 XML 中的 statement id 对应 ├── pojo # 实体类, 与表字段一一对应 ├── interceptor # LoginInterceptor, 处理未登录跳转 └── utils # MD5Util / PageUtil 等无状态工具以转账为例controller 只做参数接收和视图跳转service 里先做业务校验再调用两个 dao 方法更新余额、插入流水两个更新都在同一个事务里。这样拆的另一个好处是能回答「为什么 controller 要瘦、service 要厚」参数格式校验放 controller 可以被绕过放到 service 层才是业务上的兜底。2.3 手写 pom.xml 与 Spring 容器装配注意三个依赖组SSM 工程最常见的坑是依赖版本打架。常见做法是 Spring 4.3.x 配 MyBatis 3.4.x、mybatis-spring 1.3.xJDK 用 8Tomcat 用 8 或 9。pom 里最重要的三组依赖是 spring-webmvc、mybatis、mybatis-spring再补 mysql-connector-java、druid 连接池和 jstl。打包插件用 maven-war-plugin 3.2.x否则 classes 路径偶尔会不对。dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version4.3.30.RELEASE/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- mysql 驱动、druid、jstl 按实际环境补充 -- /dependenciesmybatis-spring 的作用是让 SqlSessionFactory 由 Spring 统一管理。Spring 的 XML 装配里要声明三个对象dataSource、sqlSessionFactory、MapperScannerConfigurersqlSessionFactory 的 mapperLocations 配成 classpath:mapper/*.xml扫描器的 basePackage 配成 com.bank.dao。bean iddataSource classcom.alibaba.druid.pool.DruidDataSource p:driverClassNamecom.mysql.jdbc.Driver p:urljdbc:mysql://localhost:3306/bank?useUnicodetrueamp;characterEncodingutf8 p:usernameroot p:password123456/ bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.bank.dao/ /beandriverClassName 要按 MySQL 版本选5.x 用 com.mysql.jdbc.Driver8.x 必须换成 com.mysql.cj.jdbc.Driver。mapperLocations 指 XML 所在目录basePackage 指 Mapper 接口所在包两者缺一个都会在 Tomcat 启动时抛出 BeanCreationException。提示SpringMVC 的注解驱动要单独加mvc:annotation-driven/否则 ResponseBody 返回对象时不会走 JSON 序列化页面上拿到的是 406 错误或者对象默认的 toString。3. 银行系统的库表设计与 MyBatis 映射细节3.1 用户、账户、流水三张表的 DDL 怎么写银行系统最少要三张核心表用户表管登录账户表管余额流水表管每一笔资金变动。账户与用户是一对多关系一个人可以开多个账户。流水表只做追加写入不做 update这是做银行类需求的基本纪律。下面这套 DDL 结构简单但能支撑起开户、存取款、转账、流水分页查询和冻结账户五个功能点。CREATE TABLE bank_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5/SHA-256 后的值 real_name VARCHAR(50), status TINYINT DEFAULT 1 COMMENT 1 正常, 0 冻结, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE account_info ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, account_no VARCHAR(20) NOT NULL UNIQUE, balance DECIMAL(18,2) NOT NULL DEFAULT 0.00, version INT NOT NULL DEFAULT 0, -- 乐观锁版本号, 编辑资料时用 CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES bank_user(id) ); CREATE TABLE trans_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_no VARCHAR(20) NOT NULL, trans_type TINYINT NOT NULL COMMENT 1 存款, 2 取款, 3 转账出, 4 转账入, amount DECIMAL(18,2) NOT NULL, balance_after DECIMAL(18,2), -- 交易后余额, 方便对账展示 target_account VARCHAR(20), -- 转账对手账号 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_account_time (account_no, create_time) );密码列设计成 VARCHAR(64)是因为 MD5 是 32 位十六进制SHA-256 是 64 位留出余量以后升级算法不用改表。流水表对 account_no 和 create_time 建联合索引分页查询和日终对账才不会全表扫。account_no 用唯一索引开户时生成规则常用「银行标识 日期 随机数」插入前先查一次避免撞号。3.2 Mapper XML 里转账扣款与分页查询的 SQL 写法Mapper 接口只声明方法SQL 写在与接口同目录的 XML 里。扣款是银行系统的核心动作常见做法是不先查余额而是把「余额充足」写进 UPDATE 的条件里利用数据库行级锁保证原子性。这样既省一次 SELECT也避免了先查后改之间被其他事务插队的问题。update iddeductBalance UPDATE account_info SET balance balance - #{amount} WHERE account_no #{accountNo} AND balance #{amount} /update update idincreaseBalance UPDATE account_info SET balance balance #{amount} WHERE account_no #{accountNo} /update select idpageRecords resultTypecom.bank.pojo.TransRecord SELECT id, account_no, trans_type, amount, balance_after, target_account, create_time FROM trans_record WHERE account_no #{accountNo} ORDER BY create_time DESC, id DESC LIMIT #{offset}, #{pageSize} /selectdeductBalance 返回受影响行数等于 0 说明账号不存在或余额不足service 层据此抛出业务异常。increaseBalance 不需要额外条件因为入账时金额只会增加。分页查询里注意LIMIT 的参数必须用 #{offset} 和 #{pageSize}不能拼成字符串否则存在注入风险ORDER BY 里补 id DESC避免同一秒多笔交易时排序不稳定。3.3 金额字段用 DECIMAL 存储实体类用 BigDecimal 接收金额字段是银行系统里最容易翻车的地方。float 和 double 是二进制浮点0.1 这类十进制小数无法精确表示累计几万笔流水后对账就对不平。MySQL 侧用 DECIMAL(18,2)Java 侧用 BigDecimal两者对齐后加减乘除都不丢精度。含义MySQL 类型Java 类型常见问题余额DECIMAL(18,2)BigDecimal用 double 会累积精度误差交易金额DECIMAL(18,2)BigDecimal用字符串拼 SQL 有注入风险交易类型TINYINTByte / Integer用字符串难扩展, 且占空间时间DATETIMEjava.util.Date用 TIMESTAMP 有时区换算问题实体类里余额字段声明为 private BigDecimal balance查询结果由 MyBatis 自动映射不需要额外 TypeHandler。做加减时用 amount.compareTo(BigDecimal.ZERO) 判断正负不能拿 equals 判断值相等因为 BigDecimal 的 equals 会连精度一起比较0.00 和 0.0 会返回 false。转账金额校验常用的写法是先判空再判 compareTo(ZERO) 0最后确认保留两位小数。4. 登录拦截、转账扣款与事务控制的 SSM 实现4.1 拦截器加 session 实现未登录跳转静态资源要放行SpringMVC 的拦截器是控制访问权限最轻量的方式。实现 HandlerInterceptorAdapter在 preHandle 里检查 session 中是否有登录标记没有就重定向到登录页。相比过滤器拦截器能拿到 handler 对象可以针对某个 Controller 做更细的控制这正好是答辩时能展开讲的一个点。public class LoginInterceptor extends HandlerInterceptorAdapter { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(loginUser) ! null) { return true; // 已登录, 放行 } response.sendRedirect(request.getContextPath() /login); return false; // 未登录, 拦截并跳转 } }SpringMVC 的 XML 里要显式注册拦截器路径。登录接口和静态资源必须排除否则用户压根进不了登录页。常见配置是 mvc:interceptors 下 mapping path 配 /exclude 配 /login、/user/login、/css/、/js/**。排查问题时先确认 exclude 的路径有没有写对很多「页面循环重定向」都是这里漏配导致的。4.2 Transactional 控制转账回滚注意默认不回滚检查异常转账至少涉及两个账户的余额变更和两条流水插入任何一步失败都要整体回滚。在 service 实现类的方法上标注 TransactionalSpring 用 AOP 在方法前后织入事务逻辑。注意注解写在 impl 上而不是接口上这是 Spring 官方推荐的做法。Service public class AccountServiceImpl implements AccountService { Autowired private AccountDao accountDao; Autowired private TransRecordDao recordDao; Override Transactional(rollbackFor Exception.class) public void transfer(String fromNo, String toNo, BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new BusinessException(转账金额不合法); } int rows accountDao.deductBalance(fromNo, amount); if (rows 0) { throw new BusinessException(余额不足或账户不存在); } accountDao.increaseBalance(toNo, amount); recordDao.insert(new TransRecord(fromNo, (byte) 3, amount.negate(), toNo)); recordDao.insert(new TransRecord(toNo, (byte) 4, amount, fromNo)); } }rollbackFor Exception.class 是必须写的。Spring 默认只对 RuntimeException 回滚如果业务里抛出自定义的检查异常不加这个参数事务不会回滚数据就出现半提交状态。另一个高频坑是自调用同一个类里的方法直接调 transfer()Spring 动态增强的那个包装对象没有被经过事务切面根本没执行。解决办法是把被调方法挪到另一个 service 里必要时也可以从 Spring 容器里重新取出 bean。4.3 并发扣款用 UPDATE 条件更新避免先查后改两个线程同时对同一个账户转账时如果 service 里先 SELECT 余额再判断够不够、然后 UPDATE两次查询可能读到同一个余额最后余额被扣成负数。上一章把 balance #{amount} 写进 WHERE 的做法让数据库在 UPDATE 时对行加锁第二个事务必须等第一个提交后才能执行天然避免了超扣。两种做法的取舍整理成一张对比表答辩时能直接用。方案实现方式并发安全适用场景先查后改SELECT 余额再 UPDATE不安全, 需手动加锁只做演示, 不建议条件更新UPDATE ... WHERE balance amount安全, 行锁由数据库保证转账扣款主方案SELECT FOR UPDATE先锁行再查再改安全, 但锁持有时间长需要读取余额做额外判断乐观锁 versionUPDATE ... WHERE version old安全, 冲突后重试开户信息编辑, 非热点条件更新是最省事的扣款方案但它不返回余额如果业务需要展示扣款后的余额要么再查一次要么在 UPDATE 后跟一条 SELECT。流水表里已经存了 balance_after插入流水前补一次查询即可。service 层不要捕获异常后吞掉事务要靠抛出 RuntimeException 才能触发回滚。5. 部署到 Tomcat 前的 SSM 配置核对与高频报错定位5.1 数据源、事务管理器、扫描路径三处对着检查SSM 项目部署后报错九成出在配置与路径上。把下面五个配置项逐一对一遍比自己瞎猜快得多。配置项常见取值出错特征数据源 URLjdbc:mysql://localhost:3306/bank?useUnicodetruecharacterEncodingutf8useSSLfalse中文乱码、连接拒绝驱动类com.mysql.jdbc.Driver5.x或 com.mysql.cj.jdbc.Driver8.xClassNotFoundExceptionMapper 扫描basePackagecom.bank.dao, mapperLocationsclasspath:mapper/*.xml启动报 sqlSessionFactory 相关异常事务管理器DataSourceTransactionManager tx:annotation-drivenTransactional 完全不生效拦截配置DispatcherServlet 的 url-pattern 配 / 而不是 /*视图解析与静态资源冲突最容易被忽略的是事务管理器必须与 SqlSessionFactory 使用同一个 dataSource否则事务控制的连接与 MyBatis 用的连接不是同一条回滚会静默失效。5.2 mapper 找不到、事务不生效、404、乱码的排查顺序按固定顺序排查能覆盖九成以上的运行时报错。项目启动报 Invalid bound statement先检查 mapper 接口全限定名、方法名、XML 的 namespace 与 statement id 三者是否一致再看 target/classes 里有没有打进包的 xmlMaven 默认不会把 src/main/java 下的 xml 当资源需要补资源过滤配置。异常抛了但数据没回滚确认 rollbackFor 有没有写异常有没有在 controller 里被 try-catch 吞掉以及是不是同类的自调用。访问 Controller 报 404先看 DispatcherServlet 的 url-pattern再看 spring-mvc.xml 的扫描包有没有覆盖 controller。页面中文乱码优先检查 CharacterEncodingFilter 是否放在过滤器链最前面JDBC URL 里的 characterEncodingutf8 是否写全。四类问题按这个顺序通常三分钟内能定位。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门