ARTICLE DETAIL

资讯详情

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

简易库存管理系统:JSP+Servlet+数据库核心设计与部署指南

简易库存管理系统:JSP+Servlet+数据库核心设计与部署指南 简介这是一份基于 JSP、Servlet 与数据库的简易库存商品管理系统项目源码包面向正在学习 Java Web 开发的学生或需要完成课程设计的开发者能够帮助理解浏览器请求经过 Servlet 处理后操作数据库再跳转回页面刷新的完整流程。压缩包共包含 38 个文件压缩后大小约为 148KB其中主要有 9 个 Java 源文件、9 个对应的编译后 class 文件、6 个 JSP 页面、2 个 SQL 初始化脚本以及项目配置、样式表和使用文档等辅助类型整体目录简洁方便导入集成开发环境后按模块查看。系统已实现管理员登录、库存商品列表展示、库存记录的增删改以及删除前二次确认、库存少于十件时数字标红、保存前对必填项与数字输入进行校验并且当名称、规格、单价相同的新记录出现时会自动累加原有库存这些细节体现了实际库存管理中的典型规则。目前已有 1488 人学习或下载适合用于积累 JSP 页面与 Servlet 交互、JDBC 数据库读写、表单校验和简单业务逻辑设计的实战经验也可作为相关课程设计或实验报告的参考基础。 前几天一个学弟把“一个简易的库存商品管理系统使用JSPServlet数据库.zip”这个压缩包丢给我说系统部署后登录页一直报错数据库也连不上。我解压后扫了一遍代码发现这类项目在课程设计里实在太有代表性了用户登录、商品资料维护、入库出库登记、库存列表和预警功能不多不少正好覆盖 Java Web 的核心知识点。项目本身不大但 JSP、Servlet、数据库这三者怎么协作、表怎么设计、事务怎么处理、部署有哪些坑基本都浓缩在里面了。这篇文章就把我从定位、建模、编码到调试的完整思路重新整理一遍给正在做类似项目的同学或自学者一个可以直接参考的版本。1. 先划边界一个“简易”库存系统到底要实现什么很多人拿到这种压缩包的第一反应是直接解压跑起来然后发现数据库脚本导入报错、Tomcat 版本不匹配、控制台刷出一堆红色堆栈。我习惯先把功能边界列清楚再动手因为功能边界直接决定了数据库表结构和 Servlet 怎么写。1.1 我按这四个模块拆功能用户登录模块用户名加密码校验登录成功后把用户信息放进 Session后续页面通过 Session 判断是否已登录。没有登录就把请求拦到登录页这是整个系统的入口也是第一个要跑通的部分。商品基础资料模块商品要有一张基本信息表里面至少包括商品名称、分类、单位、规格、当前库存、库存预警线。商品可以新增、编辑、删除、分页查询。分类单独一张表避免商品表里塞一堆重复的分类文字。入库出库模块入库登记时填商品、数量、备注出库时同样填商品和数量。这个模块是整个系统的业务核心因为每一次入库或者出库都会改变当前库存而且必须留下流水记录方便以后对账。库存查询与预警模块库存查询就是把商品列表按条件筛出来重点是多一个预警状态。哪个商品当前库存低于预警线在列表里直接标记出来或者单独提供一个预警列表页面。这四个模块串起来之后一个简易库存系统的闭环就出来了商品建档然后通过入库把库存加进去通过出库把库存减掉用户随时查询当前库存和流水记录。权限方面就做一种角色不做复杂的组织架构这样才符合“简易”两个字。1.2 为什么“简易”不等于粗糙我见过很多课程设计项目功能倒是做了不少但代码全堆在 JSP 里一个 list.jsp 里又写数据库连接又写 SQL又写 Java 逻辑最后页面一打开全是乱码和报错。这种项目表面看着“简易”实际维护起来非常痛苦。真正的做法是哪怕只有五个功能也要把代码分成三层Servlet 负责接收请求和跳转Service 层负责业务逻辑DAO 层负责数据库操作。JSP 只负责把服务器准备好的数据显示出来。分层会多写几个类但换来的好处非常明显登录出问题你只需要看 LoginServlet库存计算出问题你只需要看 StockService数据不对你只需要看 ProductDAO。排查范围缩小分数自然也不会低。1.3 这个系统的典型使用场景这种项目最常见的使用场景就是课程设计、期末大作业或者刚学完 Java Web 打算练手的初学者。它不需要很高的技术含量但足以把 JDBC、Servlet 生命周期、JSP 内置对象、Session、事务这些基础知识点全部过一遍。如果你以后想往商业级系统走这个项目也可以作为起点把 JSP 替换成 Vue 或 React把 JDBC 替换成 MyBatis整体业务模型仍然能复用。2. 数据库建模表结构决定后面写代码的顺不顺畅库存系统最怕表设计拍脑袋。我见过有人把入库出库记录合并成一张流水表导致查询当前库存时 SQL 写得特别别扭也见过根本不建分类表直接在商品表里写字符串结果统计分类时全部要手工处理。表结构没想清楚后面写代码只能不断迁就数据库越写越难受。2.1 五张表的职责划分我这个系统最终用了五张表每一张的职责都很单一user 表存登录账号和密码字段有 id、username、password、real_name。实际项目中密码一定要加密存放课程设计里至少也别用明文用 MD5 加盐处理一下。category 表商品分类字段有 id、category_name、sort_order。分类表独立出来后续加“按分类统计库存”这种报表功能时非常方便。product 表商品主表字段有 id、category_id、product_name、spec、unit、stock、warning_line、create_time。这里的 stock 字段就是当前库存warning_line 是库存预警线。stock_in_record 表入库记录字段有 id、product_id、quantity、remark、operator_id、create_time。每一条记录对应一次入库操作。stock_out_record 表出库记录字段和入库表基本一致只是业务方向不同。这里有一个很容易纠结的问题要不要建一张独立的“库存表”我的结论是在简易系统里没必要直接把库存挂在 product 表的 stock 字段上因为商品和库存本来就是一对一的关系。如果你以后要做多仓库、多批次、先进先出那才需要把库存拆成独立表甚至批次表。2.2 当前库存到底存不存字段关于库存设计有一类观点认为当前库存不应该单独存字段而是通过查询入库流水总和减出库流水总和来实时计算。这个方案在理论上是正确的但放到一个课程设计项目里每次查询商品列表都要做两次聚合计算SQL 复杂不说数据量一大性能也不好看。我采用的方案是“库存字段 流水记录 事务控制”product 表里的 stock 字段始终保存当前库存同时每次入库出库都往流水表里插一条记录。每次变更库存时在一个数据库事务里同时完成“插入流水”和“更新库存”两步操作。这样查询当前库存只需要读取一个字段查流水时又能找到每一次操作的来龙去脉两边都不耽误。2.3 建表 SQL 片段与设计细节建表脚本我摘录几个关键片段完整脚本在项目压缩包里都有。商品表需要注意的几个点category_id 建议加外键约束保证不会出现商品指向一个不存在的分类stock 字段加默认值 0避免插入时没写导致空值warning_line 同样给默认值方便后续直接比较。CREATE TABLE category ( id INT AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50) NOT NULL, sort_order INT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL, product_name VARCHAR(100) NOT NULL, spec VARCHAR(50) DEFAULT , unit VARCHAR(20) DEFAULT 件, stock INT DEFAULT 0, warning_line INT DEFAULT 10, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;入库记录表单独拿出来看核心是 product_id 加 quantity这两列决定了流水能不能准确统计。CREATE TABLE stock_in_record ( id INT AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL, quantity INT NOT NULL, remark VARCHAR(255) DEFAULT , operator_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_stock_in_product FOREIGN KEY (product_id) REFERENCES product(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两个地方需要特别说明。第一所有表都用 InnoDB 并且显式指定 utf8mb4InnoDB 是为了支持事务utf8mb4 是为了中文不乱码。第二quantity 字段千万不要允许负数入库出库记录里的数量永远只存正数业务上的增减通过不同的表体现这样流水才清晰。如果出库时把数量减成负数那流水表里就会出现一堆正负数混合的数据对账时非常痛苦。3. 请求处理链路Servlet 和 JSP 是怎么分工的整个项目跑通之后你会发现 JSP 和 Servlet 的核心关系其实就一句话Servlet 干完活把数据放到 request 或 session 里然后跳转到 JSPJSP 再把数据展示出来。很多新手搞不清这一步是因为没弄明白请求到底先到哪个文件、最后停留在哪个页面。3.1 用 web.xml 注册 Servlet为什么课程设计更推荐虽然 Servlet 3.0 之后可以用 WebServlet 注解但考虑到课程设计的评审习惯和某些老版本教学环境我还是用 web.xml 的方式注册。这种方式的优点是所有 URL 映射都集中在一个文件里一眼就能看明白哪个路径对应哪个 Servlet。servlet servlet-nameLoginServlet/servlet-name servlet-classcom.example.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping请求处理的完整链路是这样用户在 login.jsp 输入用户名密码表单提交到 /loginTomcat 根据 web.xml 找到 LoginServletLoginServlet 调用 UserDAO 查询数据库查询成功就把用户放进 session然后 response.sendRedirect 到 index.jsp查询失败就通过 request.setAttribute 放一条错误提示再 forward 回 login.jsp 展示。这里要特别注意 forward 和 sendRedirect 的区别forward 是一次请求内部的转发浏览器地址栏不会变sendRedirect 是服务器给浏览器返回一个新的地址浏览器重新发起一次请求。登录成功后用 sendRedirect 更合适因为如果用户刷新页面不会把登录请求再提交一次。3.2 一个入库操作的 Servlet 代码骨架入库操作的 Servlet 更能说明分层的重要性。这个 Servlet 的职责是接收前端参数、调用业务方法、控制页面跳转绝对不直接写 JDBC 代码。protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); int productId Integer.parseInt(request.getParameter(productId)); int quantity Integer.parseInt(request.getParameter(quantity)); String remark request.getParameter(remark); StockService stockService new StockService(); boolean success stockService.stockIn(productId, quantity, remark); if (success) { response.sendRedirect(stockInList.jsp?msgsuccess); } else { request.setAttribute(error, 入库失败请检查商品和数量); request.getRequestDispatcher(stockIn.jsp).forward(request, response); } }这里有一个细节我在 doPost 方法第一行写 request.setCharacterEncoding(UTF-8)如果不写这一行表单提交过来的中文在 Servlet 里会变成乱码这是整个项目里最常见的坑。很多人的 JSP 页面声明了 UTF-8数据库连接串也写了 characterEncoding但唯独忘了在 Servlet 里设置请求编码结果前端看着没问题存进数据库之后全是问号。3.3 JSP 页面只做渲染JSP 页面我建议只用 EL 表达式和 JSTL 标签。比如商品列表页面Servlet 查询完数据后通过 request.setAttribute(productList, list) 把结果放进去JSP 页面就直接遍历。c:forEach items${productList} varp tr td${p.id}/td td${p.productName}/td td${p.stock}/td td${p.stock p.warningLine ? 库存不足 : 正常}/td /tr /c:forEach页面里一旦出现 % % 写 Java 代码尤其是出现 Connection、Statement 这种类就要停下来反思了。JSP 的本质是视图它的任务是展示不是处理业务。把这层关系想清楚整个项目代码会干净很多。4. 核心逻辑落地的关键点会话、事务、库存扣减登录、增删改查这些做完之后项目能跑是能跑但能不能拿高分往往要看几个核心逻辑处理得够不够严谨。我在这个项目里最想强调的就是登录拦截、库存变更事务和出库防负数这三个点。4.1 用 Filter 统一做登录校验如果每个 Servlet 都写一段“判断 session 里有没有 user没有就调回登录页”不仅代码重复还容易漏。正确做法是写一个 LoginFilter对需要保护的 URL 做统一拦截。web.xml 里注册 Filter 后所有页面请求都会先经过 Filter再进到 Servlet。public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(); if (session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } }登录校验用 Filter 处理之后后续新增的页面只要在配置里纳入拦截范围登录保护就自动生效不需要再手工写判断。密码校验方面课程设计里很多人直接拿数据库里的密码和用户输入做字符串比较虽然能跑通但属于明显的安全问题。我的建议是至少用 MD5 对密码做一次哈希再存库校验时也哈希之后比较。虽然 MD5 现在不算安全但作为课程设计里的展示点已经足够了而且能把摘要算法这个知识点顺带体现出来。4.2 库存变更必须走事务库存变更的核心逻辑是“插入流水表”和“更新库存表”必须同时成功或者同时失败。如果插入入库流水成功但更新库存失败那库存数据和流水就对不上反过来也一样。所以我用 JDBC 的 setAutoCommit 手动控制事务。Connection conn null; try { conn DBHelper.getConnection(); conn.setAutoCommit(false); String insertSql INSERT INTO stock_in_record(product_id, quantity, remark) VALUES(?, ?, ?); PreparedStatement ps1 conn.prepareStatement(insertSql); ps1.setInt(1, productId); ps1.setInt(2, quantity); ps1.setString(3, remark); ps1.executeUpdate(); String updateSql UPDATE product SET stock stock ? WHERE id ?; PreparedStatement ps2 conn.prepareStatement(updateSql); ps2.setInt(1, quantity); ps2.setInt(2, productId); ps2.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.close(); }这段代码对应的是入库操作出库操作唯一的区别是把更新 SQL 改成 stock - quantity。需要特别注意的是出库扣减库存时一定要防止库存变成负数。我见过不少人直接在代码里先查一次当前库存判断数量够不够再执行更新。这个做法在单用户单线程下没问题但遇到并发出库时会存在两个线程同时读到库存为 10然后都扣到负数的情况。更稳妥的办法是在更新 SQL 里加条件UPDATE product SET stock stock - ? WHERE id ? AND stock ?如果这条 SQL 返回的影响行数是 0说明库存不足直接回滚事务。这个写法虽然只是加了一个 AND 条件但能把并发场景下的重复扣减问题从数据库层面挡住属于非常实用的小技巧。4.3 库存预警和分页查询的思路库存预警实现起来不难查询商品表时把 stock warning_line 的记录筛出来就行。放在某个列表页面里就是商品查询条件加一个“只看预警商品”的选项用 JSP 页面传一个是否勾选的参数Servlet 接收到之后拼 SQL 条件。这个功能的重点是让系统不只停留在“能查”而是能主动发现异常库存评审时能明显加分。分页查询同样必要。商品数量一多全查出来塞到页面里一是加载慢二是页面非常乱。我在这个项目里用的方案是 LIMIT 加页码参数每页显示 10 条当前页码从请求参数里取计算偏移量 protocol 后再执行查询。这样做以后库存列表的响应速度会稳定很多后续扩展搜索条件也方便。5. 部署与调试最容易翻车的几个地方功能写完之后项目真正上手跑起来还会有各种奇奇怪怪的问题。这一章把我实际调试时遇到的坑集中整理一下基本都是新手会踩的高频问题。5.1 先确认这套环境组合这个项目最稳妥的组合是 JDK 8、Tomcat 8.5 或 9、MySQL 5.7。Tomcat 10 之后把 javax.servlet 包名改成了 jakarta.servlet很多老项目里的 import javax.servlet.http.HttpServlet 会在编译阶段直接报“程序包不存在”所以如果用的是新版本 Tomcat要么把代码里的包名全部替换要么直接换回 Tomcat 9。很多“代码没问题但一直报错”的情况根源就是版本问题。JDK 版本也不要盲目装最新的。JDK 17 搭配老版本 Tomcat 时有时会出现 module 访问限制之类的报错处理起来比项目本身还复杂。用 JDK 8 做 Java Web 课程设计是目前最不容易出意外的选择。5.2 数据库连接失败的排查顺序我在帮学弟排查登录报错时十次里有七次是数据库连接问题。这里列一个排查顺序表照着顺序走基本能定位。控制台报错常见原因处理方式Communications link failureMySQL 服务没启动或者连接地址端口不对确认本机 MySQL 是否在运行检查连接串里的 jdbc:mysql://localhost:3306 是否写对Access denied for user用户名或密码错误或当前用户没有访问权限核对数据库账号密码重新授权对应数据库的权限Unknown database数据库名称不存在先确认建库脚本是否执行成功再看连接串里的库名和实际库名是否一致ClassNotFoundException: com.mysql.jdbc.Drivermysql-connector-java.jar 没有放到项目 WEB-INF/lib 目录把连接驱动包复制到 lib 目录并确保部署到 Tomcat 后能看到这里还要提一个容易忽略的点数据库连接驱动 jar 包、数据库本身字符集、项目代码字符集三者要统一。连接串里加上 characterEncodingUTF-8 是最基本的一步不然即使 JSP 页面和 Servlet 都设置了 UTF-8数据经过 JDBC 传输时仍然可能乱码。5.3 JSP 修改不生效和中文乱码“JSP 改了不生效”是搜索热度很高的词实际原因通常是 Tomcat 的工作目录里还存着旧版本的编译结果。在 IDEA 里改了 JSP 之后要重新构建项目并重启 Tomcat。如果是直接部署到外置 Tomcat 的 webapps 目录有时还要去 Tomcat 安装目录的 work 目录下找对应项目的缓存文件删掉之后重启才会重新编译。这个缓存问题非常隐蔽很多人改了半天页面没反应其实重启一次就好。中文乱码要从三个入口统一处理JSP 页面顶部声明 contentType 为 UTF-8Servlet 里调用 request.setCharacterEncoding(UTF-8)数据库连接串追加 characterEncodingUTF-8。三个地方都设置之后前端输入、Servlet 接收、数据库存储三个环节的编码就全对齐了。5.4 拿到压缩包后我建议做的事你如果拿到的是别人整理好的项目压缩包不要急着一键运行。先解压看有没有.sql脚本没有就先建库建表再看有没有 README 或说明文档很多项目里写明了默认账号密码和部署步骤最后确认依赖的 jar 包是不是齐全。把这三件事做在前面能避免大多数启动报错。我自己每做完一个这种项目都会顺手写一个部署说明文本放进去内容包括 JDK 和 Tomcat 版本、数据库建库 SQL、默认账号密码、运行步骤。别小看这几行字过一个月再回头看它能救回你大量回忆时间。最后说句实在话。这类系统的技术点并不算难真正的门槛在于第一次接触时不知道“所有零件怎么拼”。我建议拿到项目后先别急着打开 IDE 改代码找一个下午把表结构和请求流转用纸笔捋一遍理清楚谁在什么时候调用谁再动手调整。我在这个库存系统上反复折腾过很多次最大的体会就是把基础链路跑通后面加什么功能都不会慌。希望这篇整理能帮你把那个压缩包里的项目安安稳稳地变成自己的东西。本文还有配套的精品资源点击获取
返回列表