尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java+MySQL超市管理系统:Servlet/JSP/JDBC分层实现
简介这是一个基于Java与MySQL的Web超市信息管理系统项目源码包主要面向高校计算机专业学生尤其适合毕业设计、课程设计、期末大作业等实战场景。项目经导师指导并通过高分评审代码完整、配置齐全代码注释与层次划分清晰基础薄弱的学习者也能按文档完成本地部署快速理解JavaWeb项目的典型开发流程。压缩包共64个文件包含28个Java源文件、21个JSP页面、10个XML配置文件另有数据库脚本、项目说明文档、界面预览图与Maven工程信息等整体约593KB目录结构简洁标准便于按功能模块定位代码。资源中的SQL脚本可一键导入MySQL配置文件与说明文档能快速构建运行环境文档梳理了系统功能与运行注意事项可支撑二次开发和答辩讲解。目前已有83人学习下载对需要完整可运行课设源码并想弄清实现细节的同学是很实用的参考资料。1. 为什么这个超市管理系统适合做Java期末大作业期末前两周收到一份zip里面就三样东西pom.xml、sms.sql、src目录外加一个README。时间紧任务重最怕的不是代码写不出来而是技术选型飘。有的同学临时换成Spring Boot加Vue演示很好看可答辩老师一句“MyBatis的Mapper代理是如何生成SqlSession的”就把路堵死了。这套基于Java和MySQL的Web超市信息管理系统走的是Servlet加JSP加JDBC的老路线链路短、每一段都看得见摸得着HTTP请求进ServletServlet调ServiceService调DAODAO执行SQLMySQL返回结果。业务上它把商品、分类、订单、库存串成一条完整回路既能演示一个超市的进销存过程也能支撑“库存为什么不会变负数”“订单金额为什么要冗余”这类偏进阶级的追问。适合正在做毕业设计、课程设计、期末大作业的计算机相关专业学生也适合想快速补齐Java Web底子的练习者。2. MySQL数据库脚本sms.sql中的表关系与初始化数据先剖数据因为Java代码写得再规范表结构设计不合理后面所有查询都会被迫绕路。2.1 五张表如何覆盖超市进销存sms.sql的核心由五张表组成user、category、product、orders、order_item。为什么订单要拆成主表和明细两张因为一次结账会包含多个商品主表orders记录“这一单多少钱、谁收银、什么时间”明细表order_item逐行记录“卖了哪个商品、单价多少、数量多少”。这样设计之后统计某段时间各商品的销量直接拿order_item按product_id聚合跟orders表的状态变更互不干扰。表名关键字段在业务流程里的作用userid, username, password, role登录与权限区分管理员和收银员categoryid, name, description商品分类下拉框数据源productid, category_id, name, price, stock, status商品主数据库存字段直接放这ordersid, order_no, user_id, total_amount, status, create_time每次结账生成一条汇总order_itemid, order_id, product_id, quantity, unit_price订单明细冗余下单时的快照user表的role建议直接存“ADMIN”或“CASHIER”字符串不要用0和1不然答辩时还得解释映射关系。product里的stock字段就是库存我见过不少项目把库存单独建一张表对课设来说没必要stock放在product上一次UPDATE就能完成扣减和判断。category_id在product表里只做一个逻辑外键Java层校验分类是否存在而不是用数据库的FOREIGN KEY去强约束这样既能保持数据关系清晰又不会在删除分类时被外键锁死。2.2 建表细节字符集、引擎和索引的三个坑这是第一次开拆项目时最容易忽略的地方。CREATE DATABASE要用utf8mb4而不是utf8utf8在MySQL里是utf8mb3只有3字节商品名里出现生僻字或emoji就会落库失败。引擎统一InnoDB因为订单创建和库存扣减要开事务MyISAM既没有行锁也不支持事务回滚。建product表的DDL通常长这样CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 商品ID, category_id INT NOT NULL COMMENT 分类ID逻辑外键, name VARCHAR(128) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, KEY idx_category_id (category_id), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;字段类型就四种INT、VARCHAR、DECIMAL、TINYINT。price用DECIMAL(10,2)而不是FLOAT否则0.1加0.2会出现精度脏数据。索引方面category_id加普通索引支撑分类筛选name加索引给模糊查询加速。写SQL时要记住LIKE 可乐%这种前缀匹配能走索引LIKE %可乐%不会走索引数据量小感觉不到把测试数据撑到几万行后商品列表页的响应差距一下就出来了这也是答辩时值得主动抛出来的点。2.3 初始化账号与验证导入结果sms.sql末尾一般会带几组INSERT一个admin管理员账号、几组商品分类、二十来条商品记录、一条演示用订单。密码字段放的是MD5摘要不要存明文。这里有个细节如果SQL里默认密码是123456导入后要记得在用户管理页面改掉防止演示时被老师抓到弱口令话题。导入报错时先看两个地方一是SQL文件编码是不是UTF-8Windows下用记事本另存为很容易留成GBK二是MySQL的sql_mode如果开着ONLY_FULL_GROUP_BYGROUP BY查询会把不是全字段的查询直接拒绝。导入完成后先用SQL验证行数SELECT COUNT(*) AS user_cnt FROM user; SELECT COUNT(*) AS product_cnt FROM product; SELECT COUNT(*) AS order_cnt FROM orders;三个数字都对得上再启动Java项目能省掉后面一多半排查时间。3. Java Web分层落地pom.xml、JDBC连接与DAO实现数据层立住之后Java端的结构决定后面每次改需求需要动几个类。zip里的pom.xml说明项目用Maven管理而不是手动拷贝jar包的旧路子。3.1 Maven依赖与provided作用域对比依赖只保留四类Servlet API、JSP API、MySQL驱动、Druid连接池。其中前两个scope要写成provided表示编译时需要、但运行时由Tomcat提供打完的war包不会重复塞进去。Druid是国内Java Web项目里用得很多的连接池之一自带监控页答辩时打开/druid/index.html能看到SQL执行统计比口头说“我用了连接池”具体得多。dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.1.23/version /dependency /dependencies数据库驱动版本和MySQL实例版本要对应。MySQL 5.7用com.mysql.jdbc.DriverMySQL 8.x必须换成com.mysql.cj.jdbc.Driver还要在URL里加serverTimezoneAsia/Shanghai否则连接时直接报时区异常。页面连不上数据库优先查这一条。3.2 db.properties与Druid数据源初始化数据库连接参数不该写死在Java类里常见做法是放到src/main/resources/db.propertiesdriverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8useSSLfalse usernameroot password123456 initialSize2 maxActive10再写一个DbUtils工具类加载属性并初始化连接池public class DbUtils { private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(DbUtils.class.getClassLoader() .getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }初始化放在静态块里类第一次被引用时执行一次不会每个请求都重开连接。initialSize表示启动时预建的物理连接数maxActive是最大活跃连接数课设场景2和10完全够用。createDataSource接收的Properties键名就是driverClassName、url、username、password写错任何一个都会在getConnection时才抛异常所以排查顺序放在Tomcat日志之后、业务代码之前的第二顺位。3.3 DAO层PreparedStatement与ResultSet映射DAO层把一行行ResultSet变成Java对象。商品表的按ID查询是最常用的方法代码风格基本反映了整个项目的数据访问水平public Product findById(int id) { String sql SELECT id, category_id, name, price, stock, status FROM product WHERE id ?; try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setCategoryId(rs.getInt(category_id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); p.setStatus(rs.getByte(status)); return p; } } } catch (SQLException e) { e.printStackTrace(); } return null; }两点要注意。第一PreparedStatement用?占位再setInt绑定不用字符串拼SQL这是防SQL注入的第一道门。第二try-with-resources保证Connection、PreparedStatement、ResultSet三个资源在方法结束后自动关闭比手动finally少一个漏关的风险点。项目里Servlet、Service、DAO的职责分配可以归纳成下表层典型类职责Web层LoginServlet, ProductServlet接收请求参数分发到JSP业务层OrderService开事务、校验库存、调用多个DAO数据层ProductDao, OrderDao, UserDao只负责SQL执行与结果集映射分层不是规定动作直接让Servlet里写JDBC也能跑但那样一来下单要扣库存时就必须在Servlet里拼两个SQL老师追问“事务边界在哪”就答不上来。有Service层挡在中间事务和回滚逻辑才有地方放。4. 页面交互与业务闭环登录会话、商品CRUD与订单库存联动分层代码要跑起来靠三条主线登录、商品管理、下单。每一条线都对应一个Java Web关键点。4.1 登录过滤器与Session会话控制登录成功后把User对象放进Session但每个页面都判断Session里当前用户是否为空太啰嗦应该用一个Filter统一拦WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.endsWith(/login) || uri.endsWith(login.jsp) || uri.endsWith(.css) || uri.endsWith(.js)) { chain.doFilter(req, resp); return; } if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }注意白名单的设计登录接口、登录页、静态资源直接放行其余请求一律检查Session。这里有个细节getSession(false)只在已有会话时返回对象没有就返回null避免给匿名访问者白白创建会话。Filter拦截的是路径放行条件必须覆盖项目实际资源后缀否则CSS加载不出来页面布局全乱。4.2 商品分页与模糊查询的参数计算商品列表不能一次把几千行全塞到页面上JSP项目分页靠LIMIT子句实现SELECT id, category_id, name, price, stock, status FROM product ORDER BY id LIMIT ?, ?;第一个?是偏移量第二个?是每页条数。当前第currentPage页、每页pageSize条时偏移量计算方式为int offset (currentPage - 1) * pageSize;比如第2页每页10条offset就是10即从第11条开始取10条。模糊搜索和分页组合的SQL是SELECT * FROM product WHERE name LIKE CONCAT(%, ?, %) ORDER BY id LIMIT ?, ?;用CONCAT(%, ?, %)而不是Java端先拼好再传这样查询条件里如果出现%或_这类通配符MySQL会按字面量处理不会破坏查询边界。分页参数和计算结果可以在答辩时用表格直接展示参数含义示例值currentPage当前页码2pageSize每页条数10offsetLIMIT偏移量公式为(currentPage-1)*pageSize10totalCountCOUNT(*)查询结果25totalPages总数除以每页条数后向上取整3分页要写两个方法一个返回List一个返回COUNT计数因为MySQL不会在同一个结果集里既返回数据又返回总数。4.3 订单提交库存扣减与事务回滚下单的操作本质是在同一个数据库连接里先减库存、再写订单明细、最后更新订单主表任何一步失败都整体回滚。public boolean createOrder(Connection conn, OrderVO vo) throws SQLException { conn.setAutoCommit(false); try { for (OrderItem item : vo.getItems()) { int rows productDao.decreaseStock(conn, item.getProductId(), item.getQuantity()); if (rows 0) { conn.rollback(); return false; } orderItemDao.insert(conn, item); } orderDao.insert(conn, vo.getOrder()); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } }decreaseStock方法对应的SQL是关键UPDATE product SET stock stock - ? WHERE id ? AND stock ?;受影响行数为0说明当前库存小于扣减数量直接回滚。这种写法没有单独SELECT库存再判断用一行带条件的UPDATE完成“先查后写”是防库存超卖的常用手段。注意事务必须全程在同一个Connection上完成如果decreaseStock和insertOrder各拿一个连接即便开了事务也没有原子性。这一块是整个项目最值得现场演示的部分因为肉眼能在商品列表看到库存数字变化。5. 部署到Tomcat的完整排错路径与答辩演示清单临近验收换一台机器部署环境就变了最容易踩的坑集中在两处SQL没有导对库以及驱动版本与数据库不匹配。5.1 导入SQL与启动环境验证先在MySQL里建库再导入sms.sql。直接从命令行执行mysql -u root -p sms.sql会把表建到默认库如果默认库名和项目里db.properties的url对不上后续怎么调都连不上。CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARSET utf8mb4; USE supermarket; SOURCE /path/to/sms.sql;导入完成后依次执行三个COUNT查询行数正常再启动TomcatSELECT COUNT(*) FROM user; SELECT COUNT(*) FROM product; SELECT COUNT(*) FROM orders;5.2 常见启动报错对照排查顺序固定为先看Tomcat日志再核对数据库连接参数不要一开始就怀疑业务代码。报错特征常见原因处理方式ClassNotFoundException: com.mysql.jdbc.Drivermysql-connector没打进war检查pom.xml依赖后重新打包Access denied for user密码或主机不匹配在MySQL里确认root账号的host和密码Table doesnt exist库选错或SQL没导入核对USE语句与建库语句Communications link failure端口被占用或地址写错telnet localhost 3306验证连通性5.3 演示节奏与一个现场验证技巧演示顺序建议固定成一条有头有尾的业务链路admin登录 → 新增分类 → 新增商品 → 商品列表翻页 → 收银台下单 → 回到商品列表确认库存减少 → 到订单管理查询订单。其中下单前后用SQL验证一次库存与订单明细的联动SELECT p.id, p.name, p.stock, (SELECT SUM(oi.quantity) FROM order_item oi WHERE oi.product_id p.id) AS sold FROM product p WHERE p.id 10;执行前先记录stock值下单后再执行一次stock减少的数量等于sold新增的累加量这个数字对比就是库存联动最好的证据。如果老师追问权限控制可以在LoginFilter里加一个角色判断分支拦截CASHIER访问用户管理接口这比重新开一套权限框架快得多。答辩时把这层逻辑顺着说完比如“把白名单改成一个角色到路径的映射表就能扩展成多角色系统”这个问题就落到了可实现的细节上。本文还有配套的精品资源点击获取
RELATED

相关推荐

图像增强实战:自适应MSR+引导滤波实现详解

图像增强实战:自适应MSR+引导滤波实现详解

简介:这是一套结合引导滤波的自适应MSR图像增强算法Matlab代码,面向图像处理、计算机视觉领域的研究者与开发者,用于解决低光照、高动态范围等场景下的图像对比度不足与细节丢失问题。压缩包内共有1个文件,即MSR.m源码文件&#x…

📅 2026/9/15 6:04:12
ESP32-C3外挂16MB W25Q128实现嵌入式图像本地化存储

ESP32-C3外挂16MB W25Q128实现嵌入式图像本地化存储

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/15 6:04:12
塞梅普雷斯 如是说 (第二部/9.关系的极致)

塞梅普雷斯 如是说 (第二部/9.关系的极致)

//2017-11-14 23:199.关系的极致一日,塞梅普雷斯与朋友一起在大榕树下喝茶乘凉.朋友轻轻嘬了一口飘香的铁观音,问道:"之前,你说人与人之间相识一定要有一个黏合点.那么我想问,如果继续发展下去,人与人关系的极致是什么呢?"清风拂面,塞梅普雷斯用食指轻轻拨开挡在左眼…

📅 2026/9/15 5:59:12
MORE NEWS

更多资讯

📰

提示词工程实战:10个技巧让大语言模型输出更精准

很多朋友第一次接触提示词工程,都会把它理解成“怎么向AI提问”。这个理解不算错,但远远不够。我自己做了大半年大模型应用相关的工作,最深的感受是:提示词工程本质上不是话术技巧,而是需求表达能力的升级。你给模型的…

📰

从超时到限流:GPT API 稳定调用的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

基于LLM的科学文献文化双关语检测:从披头士歌名到智能语义理解

“让它在科学论文的标题里识别出‘All You Need Is Love’其实是在玩披头士的梗”——这不是科幻,而是一个把大语言模型(LLMs)用到文献知识挖掘里的真实项目。具体来说,这个项目名字叫The Beatles: Detecting cultural wordplay i…

📰

STM32图书馆环境监测系统:原理图、仿真与裸机实时实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

OpenClaw企业级二次发行版构建与优化实践

1. OpenClaw企业级二次发行版构建指南OpenClaw作为当前最热门的开源AI平台之一,其模块化架构和开放生态使其成为企业构建专属AI平台的首选。我在金融、制造等多个行业落地OpenClaw私有化部署的过程中,发现原版系统往往需要深度定制才能满足企业级需求。本…

📰

RAG离线入库全流程实战:从PDF解析到向量库的完整指南

说实话,RAG 项目我前后折腾了两年多,踩过的坑比写过的代码还多。最讽刺的是,真正让检索效果崩盘的,往往不是 prompt 写得不好,也不是 rerank 没调明白,而是最容易被忽略的“入库”环节——文档从 PDF 变成向…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬