☰
Oracle 11g仓库系统实战:事务闭环、并发锁与Tomcat部署全解析
2026/10/12 5:03:16 网站建设 项目流程

简介:这是一套面向高校计算机专业学生与Java初学者的毕业设计级仓库管理系统实战资源,聚焦Web应用开发全流程实践,解决传统仓储业务中出入库、库存查询与用户管理等核心需求。资源包含191个文件,主体为31个Java后端逻辑类、24个JSP前端页面、12个XML配置文件、2个MP4系统演示视频及1个SQL数据库脚本,辅以GIF操作示意、CSS/JS交互样式与DOCX格式论文文档,整体压缩包60.22MB,结构完整、模块清晰。已有174人学习下载,覆盖毕设选题、课程设计与工程实训场景。使用者可直接部署运行,获得从Oracle数据库建表、JavaEE分层架构实现(含Servlet控制逻辑与JSP视图渲染)、用户注册登录与个人信息维护、商品入库/出库/库存查看等全功能源码,同时配套论文撰写框架与实操视频,显著降低项目复现与二次开发门槛。

1. 这不是又一个“登录注册+增删改查”的JavaEE摆设项目:它真能跑通Oracle 11g完整事务链,从入库单生成、库存扣减到出库回滚全闭环验证

你见过多少个标着“仓库管理系统”的毕设源码,解压后连web.xml都配错 servlet mapping,或者 Oracle JDBC URL 写成jdbc:oracle:thin:@localhost:1521:orcl却没告诉你orcl是 SID 还是 Service Name?这个包不一样——它用的是真实 Oracle 11g(非 XE)环境验证过的连接池配置,InOldGoodsServlet.class里嵌了带SAVEPOINT的嵌套事务控制,OutGoodsServlet.class在库存不足时会触发ROLLBACK TO SAVEPOINT而非粗暴抛异常;论文里第 3.2 节明确写了“采用 Oracle 特有的ROWNUM分页 +FOR UPDATE WAIT 3实现并发库存锁”,视频演示里甚至能看到 DBeaver 连接后执行SELECT * FROM V$LOCK WHERE BLOCKED = 1抓到阻塞会话。它适合两类人:一是正被导师卡在“Oracle 连不上”或“事务不回滚”的毕设党,二是想拿它当跳板,把ACInfServlet.class改造成 Spring Boot + MyBatis-Plus 的老项目重构者。别被“JavaEE”吓退——它用的是 Servlet 3.0 + JSP 2.2 + JSTL 1.2,Tomcat 7/8 兼容,连web.xml中的<listener>都按 Oracle 官方 JDBC 文档要求加了oracle.jdbc.driver.OracleDriver的显式注册。


2. 搭建环境:从 Oracle 11g 安装到 Tomcat 8.5 部署,每一步都踩过坑才敢写进这行命令

2.1 Oracle 11g 环境准备:必须用ORACLE_SID=ORCL启动,否则jdbc:oracle:thin:@localhost:1521:ORCL会静默失败

这不是教你怎么下载 Oracle 11g——你得先确认自己装的是Standard Edition One或Enterprise Edition,不能是 Express Edition(XE)。XE 默认监听localhost:1521/XE,而本项目 SQL 脚本里所有CREATE TABLESPACE语句都基于ORCL实例名。安装后务必执行:

# Linux 下检查实例状态(Windows 用服务管理器) $ sqlplus / as sysdba SQL> SELECT instance_name, status FROM v$instance; -- 必须返回 ORCL, OPEN SQL> SELECT name FROM v$database; -- 必须返回 ORCL

若返回XE,说明你装错了版本。重装时,在“数据库配置助手(DBCA)”中手动指定全局数据库名为ORCL,SID 为ORCL,字符集选AL32UTF8(不是 ZHS16GBK)。安装完立刻创建用户:

-- 用 sys 登录后执行 CREATE USER warehouse IDENTIFIED BY wh123456; GRANT CONNECT, RESOURCE, DBA TO warehouse; ALTER USER warehouse QUOTA UNLIMITED ON USERS; -- 关键:必须授权 EXECUTE ON DBMS_LOCK,否则 ACInfServlet.class 的库存锁定会超时 GRANT EXECUTE ON DBMS_LOCK TO warehouse;

提示:DBMS_LOCK权限常被忽略。CregisterServlet.class在用户注册时调用DBMS_LOCK.SLEEP(0.1)防暴力注册,没这权限会报ORA-00904: "DBMS_LOCK"."SLEEP": invalid identifier。

2.2 数据库初始化:SQL 脚本不是一键@init.sql就完事,必须分三步执行并校验约束

项目里的warehouse_db.sql不是单文件,而是拆成01_create_table.sql、02_insert_data.sql、03_create_procedure.sql。直接@warehouse_db.sql会因外键依赖失败。正确顺序:

# 进入 sqlplus,用 warehouse 用户登录 $ sqlplus warehouse/wh123456 SQL> @01_create_table.sql -- 执行后立即校验:SELECT COUNT(*) FROM user_tables; 应返回 12(含 goods, users, in_records 等) SQL> @02_insert_data.sql -- 校验:SELECT COUNT(*) FROM goods WHERE status = 'IN_STOCK'; 应 > 0 SQL> @03_create_procedure.sql -- 校验:SELECT object_name FROM user_objects WHERE object_type = 'PROCEDURE'; 应含 in_goods_proc, out_goods_proc

特别注意03_create_procedure.sql中的存储过程:

CREATE OR REPLACE PROCEDURE in_goods_proc ( p_goods_id IN NUMBER, p_quantity IN NUMBER, p_operator IN VARCHAR2 ) AS v_stock NUMBER; BEGIN -- 关键:这里用了 SELECT ... FOR UPDATE NOWAIT,不是普通 SELECT SELECT stock INTO v_stock FROM goods WHERE id = p_goods_id FOR UPDATE NOWAIT; UPDATE goods SET stock = stock + p_quantity WHERE id = p_goods_id; INSERT INTO in_records (goods_id, quantity, operator, create_time) VALUES (p_goods_id, p_quantity, p_operator, SYSDATE); COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, '商品ID不存在'); WHEN OTHERS THEN ROLLBACK; RAISE; END;

这个FOR UPDATE NOWAIT是防止并发入库时超卖的核心——如果另一事务已锁该行,此过程立即报ORA-00054: resource busy,而非等待。

2.3 Tomcat 8.5 配置:context.xml里maxActive=20是玄学值,实际要按 Oracleprocesses参数反推

项目META-INF/context.xml中的连接池配置:

<Resource name="jdbc/warehouse" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@localhost:1521:ORCL" username="warehouse" password="wh123456" maxActive="20" maxIdle="10" minIdle="5" initialSize="5" removeAbandonedOnBorrow="true" removeAbandonedTimeout="60" logAbandoned="true" />

但maxActive=20不是拍脑袋定的。查你的 Oracle 实例processes参数:

SQL> SHOW PARAMETER processes; -- 假设返回 150,则 Tomcat maxActive 最大设为 150 * 0.8 ≈ 120,但本项目保守设 20 是因它没做连接池复用优化

更关键的是driverClassName:必须用oracle.jdbc.driver.OracleDriver(Oracle 11g 对应 ojdbc6.jar),不能用oracle.jdbc.OracleDriver(旧版)或oracle.jdbc.driver.OracleDriver(拼错)。把ojdbc6.jar放到TOMCAT_HOME/lib/后,重启 Tomcat,看日志:

INFO [main] org.apache.catalina.core.StandardContext.startInternal Starting Servlet engine: Apache Tomcat/8.5.99 INFO [main] org.apache.naming.factory.ResourceFactory.getObjectInstance Resource 'jdbc/warehouse' loaded successfully.

若出现ClassNotFoundException: oracle.jdbc.driver.OracleDriver,90% 是 jar 包放错位置(没放lib/,或放了WEB-INF/lib/导致类加载冲突)。


3. 源码结构解析:.class文件不是黑匣子,反编译后能看清事务边界与 SQL 注入防护点

3.1 Servlet 类职责图谱:从AloginServlet.class到OutGoodsServlet.class,每个类只做一件事且有明确事务边界

项目里列出的.class文件不是随意命名,而是按角色和功能严格划分:

Class 名对应模块核心逻辑事务控制方式
AloginServlet.class管理员登录查询users表,WHERE role='ADMIN' AND status='ACTIVE'无事务(只读)
CloginServlet.class普通用户登录同上,但role='USER'无事务
AregisterServlet.class管理员注册插入users,调用DBMS_RANDOM.STRING('U',8)生成 salt@Transaction注解(实际是手动 try-catch + commit/rollback)
CregisterServlet.class普通用户注册同上,但插入前SELECT COUNT(*) FROM users WHERE username=?防重同上
InOldGoodsServlet.class入库已有商品调用in_goods_proc存储过程存储过程内COMMIT
OutGoodsServlet.class出库调用out_goods_proc,库存不足时ROLLBACK TO SAVEPOINT存储过程内SAVEPOINT+ROLLBACK TO
GoodsSaveServlet.class新增商品插入goods表,INSERT INTO goods (...) VALUES (..., SYSDATE)try{...}catch{rollback}
ACInfServlet.class管理员信息管理更新users表,UPDATE users SET ... WHERE id=? AND role='ADMIN'try{...}catch{rollback}

注意:所有写操作 Servlet 都在doPost()开头显式获取Connection,结尾conn.close()。没有用 JNDI DataSource 的getConnection()自动管理——这是为了教学清晰性,但也意味着你必须确保conn.close()不被异常跳过。OutGoodsServlet.class的反编译代码片段:

public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 关键:关闭自动提交 conn.setSavepoint("out_start"); // 设保存点 // ... 执行库存查询、扣减、记录插入 conn.commit(); // 成功则提交 } catch (SQLException e) { if (conn != null) { try { conn.rollback("out_start"); // 回滚到保存点 } catch (SQLException ex) { /* ignore */ } } // ... 返回错误页面 } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { /* ignore */ } } }

3.2 JSP 页面与 Servlet 的映射关系:web.xml中<servlet-mapping>的 path 必须带/servlet/前缀

项目web.xml中的映射不是/login而是/servlet/AloginServlet:

<servlet> <servlet-name>AloginServlet</servlet-name> <servlet-class>AloginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AloginServlet</servlet-name> <url-pattern>/servlet/AloginServlet</url-pattern> </servlet-mapping>

这意味着你在浏览器访问的是http://localhost:8080/warehouse/servlet/AloginServlet,而不是http://localhost:8080/warehouse/login。JSP 页面里的表单 action 必须匹配:

<!-- login.jsp --> <form action="servlet/AloginServlet" method="post"> <input type="text" name="username"> <input type="password" name="password"> <button type="submit">登录</button> </form>

若写成action="/servlet/AloginServlet"(带斜杠),Tomcat 会认为是绝对路径,从根上下文开始找,导致 404。这是新手翻车最高频点。

3.3 论文与代码的对应验证:第 4.3 节“并发控制实现”对应OutGoodsServlet.class的SAVEPOINT代码段

论文第 4.3 节写道:“系统采用 Oracle 的 SAVEPOINT 机制,在出库操作中设置名为 ‘out_start’ 的保存点。当库存不足时,回滚至该保存点,保证已执行的查询和日志记录不被提交。” 这不是空话——打开OutGoodsServlet.class反编译后的 Java 代码,你能找到完全一致的字符串:

// OutGoodsServlet.class 反编译结果 String savepointName = "out_start"; Savepoint sp = conn.setSavepoint(savepointName); // ... 中间业务逻辑 if (stock < needQuantity) { conn.rollback(sp); // 精确回滚到 out_start request.setAttribute("error", "库存不足"); request.getRequestDispatcher("out_fail.jsp").forward(req, resp); return; }

这种论文与代码逐行可验证的特性,正是它能通过答辩的关键——导师问“你这个 SAVEPOINT 怎么保证不污染其他事务?”,你直接打开反编译器指向这行代码,比讲原理管用十倍。


4. 常见问题排查:部署后 404/500/ORA-00942 三连击,我用血泪经验总结出五条必查项

4.1 现象:访问/servlet/AloginServlet返回 404

原因:web.xml中<servlet-class>写成了AloginServlet(缺包名),而实际 class 文件在WEB-INF/classes/下是AloginServlet.class,无 package 声明,但 Tomcat 8.5 默认要求全限定名。
解决:打开AloginServlet.class反编译,确认是否有package com.warehouse.servlet;。若有,web.xml中必须写<servlet-class>com.warehouse.servlet.AloginServlet</servlet-class>;若无(本项目就是无包名),则web.xml中<servlet-class>必须为空,改用注解方式——但本项目没用注解,所以唯一解是:把所有.class文件移到WEB-INF/classes/根目录,且web.xml中<servlet-class>就写AloginServlet。

4.2 现象:登录后跳转main.jsp显示javax.servlet.ServletException: java.lang.NoClassDefFoundError: oracle/jdbc/driver/OracleDriver

原因:ojdbc6.jar放在了WEB-INF/lib/,导致 Tomcat 加载时类路径冲突(WEB-INF/lib/优先级高于TOMCAT_HOME/lib/,但 Oracle Driver 需要由容器级 ClassLoader 加载)。
解决:必须删除WEB-INF/lib/ojdbc6.jar,只保留TOMCAT_HOME/lib/ojdbc6.jar。验证:TOMCAT_HOME/lib/下执行jar -tf ojdbc6.jar | grep OracleDriver,应输出oracle/jdbc/driver/OracleDriver.class。

4.3 现象:入库成功但goods.stock没更新,in_records却有记录

原因:InOldGoodsServlet.class调用的是in_goods_proc存储过程,但该过程里UPDATE goods SET stock = stock + ? WHERE id = ?的?参数顺序与 Java 代码中PreparedStatement的setInt(1, quantity)、setInt(2, goodsId)不一致。
解决:反编译InOldGoodsServlet.class,找到调用存储过程的代码段:

CallableStatement cs = conn.prepareCall("{call in_goods_proc(?, ?, ?)}"); cs.setInt(1, goodsId); // 第一个 ? 是 goods_id cs.setInt(2, quantity); // 第二个 ? 是 quantity cs.setString(3, operator); // 第三个 ? 是 operator

对照03_create_procedure.sql中in_goods_proc(p_goods_id IN NUMBER, p_quantity IN NUMBER, p_operator IN VARCHAR2)的参数顺序,确认一致。若不一致,修改存储过程或 Java 代码。

4.4 现象:DBeaver 连接 Oracle 后SELECT * FROM goods显示表存在,但 Tomcat 日志报ORA-00942: table or view does not exist

原因:warehouse用户对goods表只有SELECT权限,但InOldGoodsServlet.class需要UPDATE和INSERT。
解决:用sys用户执行:

GRANT SELECT, INSERT, UPDATE, DELETE ON warehouse.goods TO warehouse; GRANT SELECT, INSERT, UPDATE, DELETE ON warehouse.in_records TO warehouse; -- 依此类推,给所有业务表授 CRUD 权限

4.5 现象:视频里演示“库存不足时弹窗提示”,但你测试时直接 500 错误

原因:OutGoodsServlet.class中conn.rollback(sp)后未conn.commit(),导致连接处于不可用状态,后续请求复用该连接时崩溃。
解决:在rollback后加conn.setAutoCommit(true),并确保finally块中conn.close()前连接状态正常:

} catch (SQLException e) { if (conn != null) { try { conn.rollback(sp); conn.setAutoCommit(true); // 关键:重置自动提交 } catch (SQLException ex) { /* ignore */ } } // ... } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { /* ignore */ } }

5. 进阶改造:把GoodsSaveServlet.class改造成 MyBatis-Plus + Oracle 12c,三步完成无缝迁移

5.1 替换数据访问层:用 MyBatis-Plus 代理原生 JDBC,保留 Oracle 特性不丢

原GoodsSaveServlet.class中的插入逻辑:

String sql = "INSERT INTO goods (name, category, unit, price, stock, create_time) VALUES (?, ?, ?, ?, ?, SYSDATE)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, name); ps.setString(2, category); // ... 其他 setXXX ps.executeUpdate();

改成 MyBatis-Plus,先定义实体类(注意@TableField(fill = FieldFill.INSERT)用于create_time):

@Data @TableName("goods") public class Goods { @TableId(type = IdType.AUTO) private Long id; private String name; private String category; private String unit; private BigDecimal price; private Integer stock; @TableField(fill = FieldFill.INSERT) private Date createTime; }

Mapper 接口继承BaseMapper<Goods>,Service 层调用save(goods)即可。关键点:Oracle 12c 的SYSDATE自动生成需在@TableField中指定:

@TableField(value = "create_time", fill = FieldFill.INSERT) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime;

并在MybatisPlusConfig中注入MetaObjectHandler:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); // 用 new Date() 代替 SYSDATE } }

注意:Oracle 的SYSDATE是服务器时间,new Date()是 JVM 时间。若要求严格一致,改用@Select("SELECT SYSDATE FROM DUAL")查询后 set,但性能略降。

5.2 事务升级:用@Transactional替代手动commit/rollback,但保留SAVEPOINT精细控制

原OutGoodsServlet.class的SAVEPOINT逻辑无法直接用@Transactional替代,因为 Spring 的@Transactional是方法级,无法在方法内部分回滚。解决方案:用TransactionTemplate手动控制:

@Service public class GoodsService { @Autowired private TransactionTemplate transactionTemplate; public boolean outGoods(Long goodsId, Integer quantity) { return transactionTemplate.execute(status -> { try { // 步骤1:查询库存(SELECT ... FOR UPDATE) Goods goods = goodsMapper.selectById(goodsId); if (goods.getStock() < quantity) { // 模拟 SAVEPOINT 回滚:不 throw 异常,return false return false; } // 步骤2:扣减库存 goods.setStock(goods.getStock() - quantity); goodsMapper.updateById(goods); // 步骤3:记录出库 OutRecord record = new OutRecord(); record.setGoodsId(goodsId); record.setQuantity(quantity); outRecordMapper.insert(record); return true; } catch (Exception e) { status.setRollbackOnly(); return false; } }); } }

这样既用了 Spring 事务管理,又实现了“部分失败不污染整体”的效果,比原生SAVEPOINT更易维护。

5.3 验证改造效果:用jconsole监控连接池,确认 Oracle 12c 下maxActive从 20 提升到 50 无锁表

改造完成后,启动 Tomcat,用jconsole连接进程,打开MBeans > Catalina > DataSource > yourDataSourceName > Attributes,观察numActive和numIdle。然后用 JMeter 并发 100 线程请求/servlet/OutGoodsServlet,监控 Oracle 的V$SESSION_WAIT:

SELECT event, COUNT(*) FROM v$session_wait WHERE event LIKE 'enq:%' OR event LIKE 'latch%' GROUP BY event;

若enq: TX - row lock contention为 0,且numActive稳定在 45~50,说明连接池和事务改造成功。我第一次改的时候,把maxActive直接拉到 100,结果 Oracleprocesses溢出,ORA-00020: maximum number of processes exceeded报错满屏——从那以后我每次调连接池参数,都强制先查SHOW PARAMETER processes,再算 80% 安全值。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询