SSM+JSP小工程预算系统毕设实战:从环境搭建到预算汇总全流程

发布时间:2026/11/2 10:32:10
SSM+JSP小工程预算系统毕设实战:从环境搭建到预算汇总全流程 简介这份资源是面向Java方向毕业设计与课程设计学生的SSMJSP小工程预算系统完整项目包适合需要完成预算管理类课题、缺乏完整项目经验的中高级学习者参考。项目采用B/S架构以Java为开发语言结合SSM框架与JSP页面底层使用MySQL数据库通过Eclipse工具开发实现了用户管理、装修材料管理、订单信息管理、工程信息管理、工程订单管理及材料分类管理等核心模块覆盖预算类系统的主要业务场景。压缩包共1272个文件包含120个Java源码、109个JSP页面、364个JavaScript脚本、146个CSS样式及172张PNG图片等另有SQL脚本与配置文件整体约32.88MB目录结构清晰便于按模块查阅与二次开发。资源同时附带演示录像与说明文档已有100人学习下载可帮助读者快速理解系统架构、梳理功能实现思路并完成课题搭建。1. 小工程预算系统为什么还在用 SSMJSP一个被低估的毕设选题每年到选题季计算机毕业设计里最容易被嫌弃的就是XX管理系统尤其是基于 SSMJSP 的小工程预算系统很多人一看技术栈就觉得老土。但如果你真去翻招聘网站和实际外包项目会发现大量中小型企业内部系统、区县级工程队的预算台账、装修公司的报价工具跑的还是 Spring SpringMVC MyBatis 加 JSP 这套组合。原因很直接部署简单、依赖少、一台低配服务器就能跑改起来不用重新学一套前端工程化链路。这个选题真正要解决的问题是把工程项目从立项到结算过程中的材料费、人工费、机械费、间接费按分项工程录入、汇总、对比预算与实际支出并输出可打印的预算表。适合谁做适合 Java 基础刚过、想稳扎稳打拿下一个能演示、能答辩、能讲清楚数据流转的毕业设计的同学也适合需要一套可二次开发脚手架的小团队。它不炫技但每一个环节都能讲出为什么这么设计这恰恰是答辩老师最想听到的东西。2. 把 SSMJSP 跑起来环境、依赖与最小可运行骨架2.1 技术选型为什么是 SSM 而不是 SpringBoot先把这个纠结讲清楚。SpringBoot 集成 JSP 在官方文档里一直属于不推荐但支持的状态打包成可执行 jar 后 JSP 解析经常出问题必须打成 war 包丢进外部 Tomcat 才稳。而传统 SSM 项目本身就是 war 结构Tomcat 直接部署没有内嵌容器的路径玄学。对于毕业设计这种要交演示录像、老师可能在自己电脑上跑一遍的场景war 包 外部 Tomcat 的确定性远高于 SpringBoot 内嵌方案。另一个现实原因是资料密度。SSM 的配置虽然啰嗦但每一段 XML 都能找到对应解释出问题时有大量可检索的报错记录。SpringBoot 把配置藏进自动装配一旦版本对不上新手连从哪查都不知道。所以我的建议是毕设阶段用 SSM 打基础把 IoC、AOP、MVC 请求流转、MyBatis 映射这几件事吃透比直接上 SpringBoot 更有收获。依赖版本上JDK 用 8 或 11 都行Tomcat 用 8.5 或 9Spring 用 5.x 系列MyBatis 用 3.5.xMySQL 用 5.7 或 8.0。注意 MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver连接串要带时区和 SSL 参数这是新手第一个翻车点。2.2 Maven 依赖与目录结构一个能跑的最小 SSM 项目pom.xml 里核心依赖如下!-- Spring 核心与 Web MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.30/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.30/version /dependency !-- MyBatis 与 Spring 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.2/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency !-- 数据库驱动MySQL 8 用 cj 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- JSP 与 JSTL缺一不可 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency这里servlet-api的 scope 必须是provided因为 Tomcat 自带这份 jar如果打进 war 包会和容器冲突启动时报ClassCastException或NoSuchMethodError。JSTL 则必须打进包否则 JSP 里的c:forEach标签直接不解析页面显示原始标签文本。目录结构按 Maven 标准走src/main/java放 controller、service、mapper 接口和实体src/main/resources放applicationContext.xml、spring-mvc.xml、mybatis-config.xml、jdbc.properties和 mapper 的 XML 文件src/main/webapp/WEB-INF下放web.xml和jsp目录。JSP 放在 WEB-INF 下是刻意的这样浏览器不能直接访问必须经过 Controller 转发权限控制才有意义。2.3 web.xml 与两个 Spring 容器的分工SSM 最容易讲不清的就是为什么有两个 Spring 容器。ContextLoaderListener加载的父容器管 Service、DAO、数据源、事务DispatcherServlet加载的子容器管 Controller、视图解析器、拦截器。子容器能拿到父容器的 Bean反过来不行。所以 Service 里注入 Controller 会失败Controller 里注入 Service 正常。!-- web.xml 关键片段 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping !-- 解决 POST 中文乱码 -- filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern写/而不是/*区别在于/只拦截静态资源之外的请求/*会把 JSP 也拦下来导致死循环。这个细节答辩时被问到能加分。字符编码过滤器要放在所有过滤器最前面否则参数已经被按默认编码解析过了再设也没用。3. 预算业务建模从分项工程到费用汇总的数据表设计3.1 核心表结构与字段含义小工程预算的领域模型不复杂但字段设计要经得起推敲。核心是四张表项目表、分项工程表、费用明细表、用户表。表名关键字段说明projectid, name, location, total_budget, status, create_time一个工程项目一条记录status 区分立项/施工/结算sub_itemid, project_id, item_name, unit, quantity, unit_price分项工程如土方开挖quantity 与 unit_price 相乘得合价cost_detailid, sub_item_id, cost_type, amount, remark费用明细cost_type 区分材料/人工/机械/间接sys_userid, username, password, rolerole 区分管理员和普通录入员sub_item里不直接存合价而是存数量和单价合价在查询时用 SQL 计算或 Java 端计算。这样做的好处是修改单价后合价自动更新避免数据不一致。如果为了查询快把合价冗余存下来就必须在每次更新时同步维护这是典型的空间换一致性权衡毕设阶段建议不冗余。cost_detail的cost_type用枚举值存储比如 1 材料、2 人工、3 机械、4 间接不要直接存中文否则后期改文案要动数据。前端展示时用 JSTL 的c:choose或后端字典转换。3.2 MyBatis 映射与动态 SQL 汇总预算汇总的核心是一条带GROUP BY的查询按分项工程汇总各类费用!-- CostDetailMapper.xml -- select idsumByProject resultTypemap SELECT s.item_name AS itemName, s.quantity * s.unit_price AS budgetAmount, SUM(CASE WHEN c.cost_type 1 THEN c.amount ELSE 0 END) AS materialCost, SUM(CASE WHEN c.cost_type 2 THEN c.amount ELSE 0 END) AS laborCost, SUM(CASE WHEN c.cost_type 3 THEN c.amount ELSE 0 END) AS machineCost FROM sub_item s LEFT JOIN cost_detail c ON c.sub_item_id s.id WHERE s.project_id #{projectId} GROUP BY s.id, s.item_name, s.quantity, s.unit_price /select用LEFT JOIN而不是INNER JOIN是因为某个分项工程可能还没录入任何费用明细但预算表里仍要显示它的预算金额。如果用内连接这些行会消失汇总结果就少了。CASE WHEN做行转列把不同费用类型摊到不同列比在 Java 里循环累加更高效也更好维护。参数#{projectId}用#{}而不是${}前者是预编译占位符能防 SQL 注入后者是字符串拼接。这条规则在任何涉及用户输入的查询里都不能破例。3.3 Service 层事务与预算校验录入费用明细时要同时更新分项工程的已用金额并校验是否超预算这两步必须在同一个事务里Service public class CostServiceImpl implements CostService { Autowired private CostDetailMapper costDetailMapper; Autowired private SubItemMapper subItemMapper; Override Transactional(rollbackFor Exception.class) public void addCost(CostDetail detail) { // 先插入明细 costDetailMapper.insert(detail); // 再汇总该项目已用金额 BigDecimal used costDetailMapper.sumUsedBySubItem(detail.getSubItemId()); SubItem item subItemMapper.selectById(detail.getSubItemId()); BigDecimal budget item.getQuantity().multiply(item.getUnitPrice()); // 超预算只警告不阻断实际工程中允许调整 if (used.compareTo(budget) 0) { item.setOverBudget(true); subItemMapper.updateById(item); } } }Transactional的rollbackFor Exception.class很关键。Spring 默认只对运行时异常回滚受检异常不回滚。如果这里抛的是受检异常而没配这个属性插入成功了但后续校验失败数据就脏了。金额一律用BigDecimal不要用double浮点误差在预算场景里是致命的0.10.2 不等于 0.3 这种事在财务报表上会被人追着问。4. JSP 页面与交互把预算表做成能演示的样子4.1 列表页与 JSTL 循环渲染JSP 页面现在被很多人当成过时技术但在毕设演示里它有个优势服务端渲染打开就是完整页面不依赖前端构建工具老师电脑上不用装 node。列表页用 JSTL 渲染分项工程% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % table classtable tr th分项名称/thth单位/thth数量/th th单价/thth预算合价/thth操作/th /tr c:forEach items${subItems} varitem tr td${item.itemName}/td td${item.unit}/td td${item.quantity}/td tdfmt:formatNumber value${item.unitPrice} pattern#,##0.00//td tdfmt:formatNumber value${item.quantity * item.unitPrice} pattern#,##0.00//td tda hrefcost?subItemId${item.id}录入费用/a/td /tr /c:forEach /tablefmt:formatNumber做千分位和两位小数格式化比在 Java 端拼字符串干净。${item.quantity * item.unitPrice}在 EL 表达式里直接算注意这里如果 quantity 是整数、unitPrice 是 BigDecimalEL 会自动做类型转换但精度可能受影响严谨做法是在后端算好放进 VO。演示场景下 EL 直接算够用但答辩被问到要能说清这个边界。4.2 表单提交与后端接收新增分项工程的表单提交到 Controller用RequestMapping接收Controller RequestMapping(/subItem) public class SubItemController { Autowired private SubItemService subItemService; RequestMapping(/add) public String add(SubItem subItem, Model model) { // 参数校验数量单价必须为正 if (subItem.getQuantity() null || subItem.getQuantity().compareTo(BigDecimal.ZERO) 0) { model.addAttribute(msg, 数量必须大于0); return subItem/add; } subItemService.save(subItem); return redirect:/subItem/list?projectId subItem.getProjectId(); } }返回redirect:前缀会触发重定向避免用户刷新页面重复提交。这是 POST-Redirect-GET 模式表单提交后跳转到列表页刷新不会重复插入。如果直接返回视图名刷新时浏览器会重发 POST数据就重复了这是新手最常见的坑之一。4.3 拦截器做登录校验未登录用户不能直接访问预算页面用 SpringMVC 拦截器统一处理public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } return true; } }拦截器在spring-mvc.xml里注册mvc:exclude-mapping排除登录页和静态资源路径。注意拦截器只拦 Controller 请求如果 JSP 直接放在 webapp 根目录下能被 URL 直接访问拦截器管不到。所以前面强调 JSP 要放 WEB-INF 下这是配套的。5. 避坑与排查SSMJSP 项目最容易翻车的五个地方5.1 启动报 NoClassDefFoundError 或 ClassNotFoundException现象Tomcat 启动时报找不到某个类或者运行到某个功能才报。原因通常是依赖没打进 war 包或者 scope 设成了 provided 但容器里没有。解决先看WEB-INF/lib下有没有对应 jar没有就检查 pom 的 scope有还报错就检查是不是版本冲突用mvn dependency:tree看依赖树排除重复引入的低版本。5.2 JSP 页面显示 404 或直接下载源码现象访问某个 JSP 返回 404或者浏览器把 JSP 当文件下载。原因一是 JSP 放在 WEB-INF 下但 Controller 没转发二是url-pattern配成了/*把 JSP 请求也拦了三是 Tomcat 的conf/web.xml里 JSP Servlet 映射被改过。解决确认 Controller 返回的视图名和视图解析器前缀后缀拼出来的路径真实存在把 DispatcherServlet 的 url-pattern 改回/。5.3 中文乱码POST 和 GET 表现还不一样现象表单提交的中文存进数据库变成问号或者页面显示乱码。原因POST 乱码靠 CharacterEncodingFilter 解决GET 乱码要靠 Tomcat 的server.xml里 Connector 加URIEncodingUTF-8两者机制不同。解决过滤器配 UTF-8 且放最前数据库连接串加useUnicodetruecharacterEncodingutf8Tomcat 8 以上默认 URI 编码已是 UTF-8但 7 及以下要手动加。5.4 MyBatis 查询返回 null 或字段全空现象SQL 在数据库客户端能查出数据程序里返回的对象字段全是 null。原因实体类属性名和表字段名没对上且没开驼峰映射。比如item_name对应itemNameMyBatis 默认不会自动转换。解决在mybatis-config.xml里加setting namemapUnderscoreToCamelCase valuetrue/或者在 SQL 里用AS起别名。前者一劳永逸推荐。5.5 事务不生效插入成功但回滚失败现象Service 方法抛异常了但前面插入的数据还在。原因Spring 事务基于代理同类内部方法调用不走代理Transactional失效或者方法不是 public或者异常类型不在回滚范围内。解决确保事务方法由外部调用配rollbackFor Exception.classService 类不要自己 new 自己。这个坑在答辩演示时如果被问到事务答不上来很尴尬。6. 让预算系统更耐问导出、校验与二次开发方向把基础功能跑通只是及格线想让这个毕设真正拿得出手得在几个细节上做深。第一个是预算表导出。答辩老师很可能会问这个表能不能打印用 Apache POI 生成 Excel 是常见做法但要注意 POI 生成图表的能力有限柱状图饼图这类需要 XSSFChart配置繁琐。更稳的方案是导出 Excel 数据图表交给前端 ECharts 在页面展示各司其职。// 用 POI 导出预算汇总表的核心逻辑 public void exportBudget(HttpServletResponse response, Long projectId) throws IOException { Workbook wb new XSSFWorkbook(); Sheet sheet wb.createSheet(预算汇总); // 表头 Row header sheet.createRow(0); String[] cols {分项名称, 预算合价, 材料费, 人工费, 机械费}; for (int i 0; i cols.length; i) { header.createCell(i).setCellValue(cols[i]); } // 数据行由 service 查询后填充 ListMapString, Object data costService.sumByProject(projectId); int rowIdx 1; for (MapString, Object row : data) { Row r sheet.createRow(rowIdx); r.createCell(0).setCellValue(String.valueOf(row.get(itemName))); r.createCell(1).setCellValue(toDouble(row.get(budgetAmount))); // 其余列同理 } response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenamebudget.xlsx); wb.write(response.getOutputStream()); wb.close(); }Content-Disposition里的文件名如果含中文要做 URL 编码否则不同浏览器表现不一致有的乱码有的直接截断。toDouble是自定义的 null 安全转换因为SUM查询在无数据时返回 null直接强转会空指针。第二个是数据校验的层次。前端 JSP 用 JavaScript 做即时提示后端 Controller 做参数合法性校验数据库层用非空约束和唯一索引兜底。三层缺一不可只靠前端校验等于没校验绕过浏览器直接发请求就破了。答辩时能讲清这三层说明你对不可信输入有概念。第三个是二次开发方向。这套骨架换成设备维修预算活动经费预算装修报价都只是改表名和字段的事Controller 和 Service 的分层结构不用动。如果想升级可以把 JSP 换成 Thymeleaf 或前后端分离但那是另一个工作量级毕设阶段不建议动把现有链路讲透比堆技术栈更有说服力。我自己做这类项目最大的教训是别一上来就想着把功能做全先把登录→建项目→录分项→录费用→看汇总→导出这条主链路跑通能演示、能讲清数据怎么流剩下的边角功能都是加分项。主链路不通功能再多也是散的。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询