恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

医院系统Oracle课设实战:表结构、PL/SQL与JDBC全解析

  • 首页
  • 资讯中心
  • /
  • 医院系统Oracle课设实战:表结构、PL/SQL与JDBC全解析

相关资讯

需求与用例双向绑定:自动化测试稳定性的关键 2026/10/11 12:32:46
MySQL8.0实战指南:从环境搭建到索引事务的26章资源新解 2026/10/11 12:32:46
TDD模式下的并发程序设计:从失败测试到可验证实现 2026/10/11 12:32:46

最新资讯

ST3GG ALLSIGHT隐写分析教程:卡方检验、位平面熵、RS分析的20+种检测手段
PyCharm结构化搜索替换:用代码结构思维搞定批量重构
k-means聚类算法MATLAB实现:初始化、迭代与避坑指南
PyCharm本地历史:代码后悔药,无Git也能恢复文件快照
Excel动态引用终极武器:INDIRECT函数原理、实战与避坑指南
PyTorch反向传播全解析:从计算图到自动求导实战

今日推荐

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

医院系统Oracle课设实战:表结构、PL/SQL与JDBC全解析

发布时间:2026/10/11 12:37:46
医院系统Oracle课设实战:表结构、PL/SQL与JDBC全解析 简介面向Oracle数据库课程设计的医院系统数据库项目基于Java与Oracle实现适合需要完成课程设计、毕业设计或工程实训的初学者与进阶学习者。压缩包内共45个文件以36个Java源码文件为主体辅以SQL建表脚本、properties数据库连接配置、关系模型PNG、README说明、LICENSE等整体大小仅323KB目录紧凑便于按需查阅。项目提供完整的Java代码与SQL语句默认连接远程Oracle数据库地址test.linyer.cn:9999如需切换本地环境直接在database.properties中修改连接信息即可。关系模型图清晰展示医院科室、医生、病人等核心实体的表间关系SQL脚本可快速创建库表并初始化数据Java源码则包含JDBC访问与基础业务处理覆盖从数据建模到代码落地的完整过程。目前已有380人学习下载适合快速搭建医院系统原型、加深对Oracle数据库设计及Java数据库编程的理解也可作为课程设计报告的配套参考。1. 医院系统 Oracle 课设跑通 Java 代码只是第一步这套医院系统数据库课设资源里最容易把新手卡住的其实不是 Java 代码而是 Oracle 本体的各种小脾气。Java 和 Oracle 的组合在数据库课程设计中属于最经典也最考验功底的一组病人、医生、科室、挂号、处方、药品、病历七类实体环环相扣任何一张表的外键设错后面全链路的增删改查就跟着错。下面直接拆这份课设的表结构、PL/SQL 存储过程和 JDBC 连接办法把最容易翻车的 Oracle 环境配置、监听、字符集、分页问题按“现象—原因—解决”列清楚。你如果正准备用这份资源交课设先花半小时把表结构过一遍再动手配数据库能少熬一个通宵。2. 表结构设计把 ER 图落成能撑起全系统的 DDL2.1 实体关系七张核心表怎么连这个医院系统的业务主线是“病人就诊”流程大致是病人到科室挂号挂到某位医生名下医生接诊后开处方处方引用药品同时医生写病历记录诊断结果。顺着这条线业务实体收敛为七类科室、医生、病人、挂号记录、药品、处方、病历。对应八张表DEPT、DOCTOR、PATIENT、REGISTRATION、DRUG、PRESCRIPTION、PRESCRIPTION_ITEM、MEDICAL_RECORD其中处方明细是处方的从表和处方一起构成主从结构。关系梳理起来其实很直观。科室对医生是 1 对 N一张科室表对应多个医生病人对挂号是 1 对 N一个病人可以反复挂号医生对挂号是 1 对 N一个医生一天接几十个号病人对处方是 1 对 N处方对处方明细是 1 对 N药品对处方明细是 1 对 N。病历表与病人是 1 对 N每次就诊插一条记录这样能保留病人完整的就诊历史而不是把多次诊断结果覆盖在同一个字段里。这里有个常见的扣分点把科室 ID、医生 ID、病人 ID、药品 ID 全塞进一张“大宽表”。比如有的同学把挂号记录写成 REG_ID、PAT_ID、DOC_ID、DEPT_ID、DRUG_ID、PRICE 全挤在一行看起来省事但药品是医生开处方时关联的和挂号记录没有直接关系强行塞在一起后面查“某科室某个月的药品消耗”就得写一串子查询数据冗余还特别严重。正确做法是保持职责单一挂号表只记录“谁在什么时间挂了哪个医生的号”药品通过处方明细表间接关联不直接出现在挂号表里。外键策略上我的习惯是业务主键用 NUMBER 类型外键一律加 NOT NULL 约束除非语义上允许为空并在外键列上建索引。Oracle 不会自动为外键建索引这一点和 MySQL 不一样不建索引会导致子表删除、级联操作时出现锁等待。课设数据量小感觉不出来但答辩时老师追问“为什么这个外键要加索引”你答出“减少锁竞争、加速连表查询”这两点是能加分的回答。2.2 核心表 DDL约束写在建表语句里建表语句建议全部写在 init.sql 里按“先主表、后子表”的顺序执行避免外键引用不存在的表时报 ORA-00942。下面这套 DDL 是这份资源里最核心的部分我按依赖顺序拆开讲。-- 科室表 CREATE TABLE DEPT ( DEPT_ID NUMBER(4) CONSTRAINT PK_DEPT PRIMARY KEY, DEPT_NAME VARCHAR2(50) NOT NULL, DEPT_LOC VARCHAR2(100) ); -- 医生表 CREATE TABLE DOCTOR ( DOC_ID NUMBER(6) CONSTRAINT PK_DOCTOR PRIMARY KEY, DOC_NAME VARCHAR2(30) NOT NULL, DOC_TITLE VARCHAR2(20) DEFAULT 主治医师, DEPT_ID NUMBER(4) NOT NULL, CONSTRAINT FK_DOCTOR_DEPT FOREIGN KEY (DEPT_ID) REFERENCES DEPT(DEPT_ID) );DEPT_ID 用 NUMBER(4)因为科室数量撑死几十个DOCTOR 用 NUMBER(6)给后续扩容留空间。DOC_TITLE 给了默认值“主治医师”插入时少写一个字段。外键 FK_DOCTOR_DEPT 建在 DEPT_ID 上保证医生必须属于某个真实存在的科室。注意 Oracle 的 VARCHAR2 长度单位是字节VARCHAR2(50) 在 ZHS16GBK 字符集下能存 25 个中文汉字在 AL32UTF8 下可能只存 16 个左右所以涉及姓名字段最好用 VARCHAR2(30) 以上别卡着 10 个字符设计。继续建病人、药品、挂号表-- 病人表 CREATE TABLE PATIENT ( PAT_ID NUMBER(10) CONSTRAINT PK_PATIENT PRIMARY KEY, PAT_NAME VARCHAR2(30) NOT NULL, GENDER CHAR(1) CHECK (GENDER IN (M,F)), BIRTH_DATE DATE, PHONE VARCHAR2(15) ); -- 药品表 CREATE TABLE DRUG ( DRUG_ID NUMBER(6) CONSTRAINT PK_DRUG PRIMARY KEY, DRUG_NAME VARCHAR2(50) NOT NULL, SPEC VARCHAR2(50), PRICE NUMBER(8,2) NOT NULL CHECK (PRICE 0), STOCK NUMBER(8) DEFAULT 0 CHECK (STOCK 0) ); -- 挂号表 CREATE TABLE REGISTRATION ( REG_ID NUMBER(10) CONSTRAINT PK_REG PRIMARY KEY, PAT_ID NUMBER(10) NOT NULL, DOC_ID NUMBER(6) NOT NULL, REG_TIME DATE DEFAULT SYSDATE, REG_STATUS CHAR(1) DEFAULT 0 CHECK (REG_STATUS IN (0,1,2)), REG_FEE NUMBER(8,2) NOT NULL, CONSTRAINT FK_REG_PAT FOREIGN KEY (PAT_ID) REFERENCES PATIENT(PAT_ID), CONSTRAINT FK_REG_DOC FOREIGN KEY (DOC_ID) REFERENCES DOCTOR(DOC_ID) );REG_STATUS 我用字符型0 表示已挂号未就诊、1 表示已就诊、2 表示已退号。CHECK 约束在课设里是加分项能证明你考虑过数据完整性。REG_FEE 挂号费不允许为空金额字段统一 NUMBER(8,2)不要用 FLOAT浮点金额在账目统计时会出现 0.30000000000000004 这种玄学问题答辩现场查出来很尴尬。处方和明细是典型的主从表结构这也是整个库设计里最值得讲清楚的部分-- 处方表 CREATE TABLE PRESCRIPTION ( PRES_ID NUMBER(10) CONSTRAINT PK_PRES PRIMARY KEY, PAT_ID NUMBER(10) NOT NULL, DOC_ID NUMBER(6) NOT NULL, CREATE_TIME DATE DEFAULT SYSDATE, TOTAL_AMT NUMBER(10,2) DEFAULT 0, CONSTRAINT FK_PRES_PAT FOREIGN KEY (PAT_ID) REFERENCES PATIENT(PAT_ID), CONSTRAINT FK_PRES_DOC FOREIGN KEY (DOC_ID) REFERENCES DOCTOR(DOC_ID) ); -- 处方明细表 CREATE TABLE PRESCRIPTION_ITEM ( ITEM_ID NUMBER(12) CONSTRAINT PK_ITEM PRIMARY KEY, PRES_ID NUMBER(10) NOT NULL, DRUG_ID NUMBER(6) NOT NULL, QUANTITY NUMBER(4) NOT NULL CHECK (QUANTITY 0), UNIT_PRICE NUMBER(8,2) NOT NULL, CONSTRAINT FK_ITEM_PRES FOREIGN KEY (PRES_ID) REFERENCES PRESCRIPTION(PRES_ID) ON DELETE CASCADE, CONSTRAINT FK_ITEM_DRUG FOREIGN KEY (DRUG_ID) REFERENCES DRUG(DRUG_ID) );PRESCRIPTION_ITEM 里的 UNIT_PRICE 从 DRUG 表冗余过来这是一个有意的冗余。药品价格以后可能调整但处方明细作为历史单据必须保留开单时的价格所以冗余单价是有业务价值的。TOTAL_AMT 同样做成冗余字段后续由存储过程维护统计处方金额时不用每次都做关联聚合查询会轻很多。病历表相对独立单独维护-- 病历表 CREATE TABLE MEDICAL_RECORD ( REC_ID NUMBER(10) CONSTRAINT PK_REC PRIMARY KEY, PAT_ID NUMBER(10) NOT NULL, DOC_ID NUMBER(6) NOT NULL, DIAGNOSIS VARCHAR2(200), REC_TIME DATE DEFAULT SYSDATE, CONSTRAINT FK_REC_PAT FOREIGN KEY (PAT_ID) REFERENCES PATIENT(PAT_ID), CONSTRAINT FK_REC_DOC FOREIGN KEY (DOC_ID) REFERENCES DOCTOR(DOC_ID) );2.3 索引与外键别把索引当装饰上面建的所有外键列建议都补上索引。Oracle 外键列不加索引在主表删除或更新被引用行时会触发表级锁。课设数据量小感觉不出来但老师追问起来你要能答出“防止锁竞争、加速连表查询”这两点。CREATE INDEX IDX_DOCTOR_DEPT ON DOCTOR(DEPT_ID); CREATE INDEX IDX_REG_PAT ON REGISTRATION(PAT_ID); CREATE INDEX IDX_REG_DOC ON REGISTRATION(DOC_ID); CREATE INDEX IDX_PRES_PAT ON PRESCRIPTION(PAT_ID); CREATE INDEX IDX_PRES_DOC ON PRESCRIPTION(DOC_ID); CREATE INDEX IDX_ITEM_PRES ON PRESCRIPTION_ITEM(PRES_ID); CREATE INDEX IDX_ITEM_DRUG ON PRESCRIPTION_ITEM(DRUG_ID);需要注意的是不要给状态类字段REG_STATUS单独建索引。状态字段区分度很低只有 0/1/2 三种值全表扫描比走索引更快Oracle 优化器大概率也会忽略这种索引。课设里另一个常见问题是把主键写成 VARCHAR2 还带业务前缀比如 REG001、REG002排序和关联都别扭能用 NUMBER 序列就不要用字符串主键。到这里整个库的骨架就出来了。我一般会在建完表之后立刻插入一小批测试数据再跑几条连表查询确认外键没有建错方向。下一步就是往库里写业务逻辑也就是第 3 章的存储过程。3. PL/SQL 实战存储过程与触发器把业务逻辑收进数据库3.1 挂号存储过程事务与异常处理课设里最容易得分的是存储过程。理由很简单老师要求“体现 Oracle 特性”增删改查 JDBC 也能干PL/SQL 才是 Oracle 区别于 MySQL 的硬功夫。这份资源里我建议重点看三个存储过程挂号、退号退款、药品出库扣减库存。挂号这个操作在业务上不是“INSERT 一条记录”这么简单它至少要做两件事写入挂号记录同时判断该医生当天是否还有号源。更麻烦的是并发场景——两个人同时挂最后一个号不能两个人都成功。为了演示事务和行锁常见做法是给 DOCTOR 表加一个当日剩余号数字段 DOC_LEFT然后在存储过程里用 SELECT ... FOR UPDATE 先锁住医生行。CREATE OR REPLACE PROCEDURE PROC_REGISTER( P_PAT_ID IN NUMBER, P_DOC_ID IN NUMBER, P_FEE IN NUMBER, P_REG_ID OUT NUMBER ) AS V_LEFT NUMBER; BEGIN SAVEPOINT SP_REG; -- 锁定医生行防止并发下超挂 SELECT DOC_LEFT INTO V_LEFT FROM DOCTOR WHERE DOC_ID P_DOC_ID FOR UPDATE; IF V_LEFT 0 THEN RAISE_APPLICATION_ERROR(-20001, 该医生号源已用完); END IF; SELECT SEQ_REG.NEXTVAL INTO P_REG_ID FROM DUAL; INSERT INTO REGISTRATION(REG_ID, PAT_ID, DOC_ID, REG_TIME, REG_STATUS, REG_FEE) VALUES (P_REG_ID, P_PAT_ID, P_DOC_ID, SYSDATE, 0, P_FEE); UPDATE DOCTOR SET DOC_LEFT DOC_LEFT - 1 WHERE DOC_ID P_DOC_ID; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK TO SP_REG; RAISE; END PROC_REGISTER;逻辑说明先 SAVEPOINT 建保存点SELECT 医生行并加行级锁号源不足时抛业务异常号源充足就取序列下一个值做挂号 ID插入挂号表再把医生剩余号数减一。异常处理里回滚到保存点后重新 RAISE把错误抛给 Java 层去提示。参数说明P_PAT_ID、P_DOC_ID、P_FEE 是输入参数P_REG_ID 是输出参数Java 端通过 CallableStatement 注册 OUT 参数拿到新生成的挂号 ID。DOC_LEFT 需要在 init.sql 里补上ALTER TABLE DOCTOR ADD (DOC_LEFT NUMBER(3) DEFAULT 20); 初始值按一个医生一天 20 个号算。这里有一个对新手来说特别容易忽略的细节SELECT ... FOR UPDATE 必须在事务里才有意义所以这个存储过程里没有在 SELECT 之前提交任何东西最后用 COMMIT 收尾。如果 Java 端在调用前把连接设成了自动提交 setAutoCommit(true)FOR UPDATE 的锁会在语句结束时释放并发保护就失效了。这一点第 4 章会再强调。另外提醒一个坑RAISE_APPLICATION_ERROR 的编号范围是 -20001 到 -20999超出会报 ORA-21000。我第一次写存储过程时用的 -30001编译直接失败折腾了十分钟才发现是编号越界。3.2 退费与库存扣减事务边界怎么划退号是挂号的逆操作但事务边界要画在“退号”和“释放号源”两件事上。退号时必须先确认挂号状态还是 0未就诊已就诊的号不能退退号成功后把医生号源加回来。药品出库则是发药时的动作涉及更新药品库存和扣减逻辑两个操作要么一起成功要么一起失败。CREATE OR REPLACE PROCEDURE PROC_REFUND( P_REG_ID IN NUMBER ) AS V_STATUS CHAR(1); BEGIN SELECT REG_STATUS INTO V_STATUS FROM REGISTRATION WHERE REG_ID P_REG_ID FOR UPDATE; IF V_STATUS 0 THEN RAISE_APPLICATION_ERROR(-20002, 该挂号记录不可退号); END IF; UPDATE REGISTRATION SET REG_STATUS 2 WHERE REG_ID P_REG_ID; UPDATE DOCTOR D SET D.DOC_LEFT D.DOC_LEFT 1 WHERE D.DOC_ID (SELECT DOC_ID FROM REGISTRATION WHERE REG_ID P_REG_ID); COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20003, 挂号记录不存在); WHEN OTHERS THEN ROLLBACK; RAISE; END PROC_REFUND;逻辑说明先对挂号记录加行锁并读状态防止并发退同一笔号状态校验通过后更新状态、归还号源两个 UPDATE 在一个事务里。NO_DATA_FOUND 单独捕获转成业务异常否则 Java 端拿到的会是 ORA-01403而不是一句能看懂的业务提示。库存扣减存储过程的结构类似但多一个检查点扣减前必须判断 STOCK 是否充足扣减语句要写成 STOCK STOCK - 数量而不是“先查再算”。先查再算在并发下会被覆盖直接 UPDATE 配合 WHERE STOCK 数量才是一步原子操作UPDATE DRUG SET STOCK STOCK - P_QTY WHERE DRUG_ID P_DRUG_ID AND STOCK P_QTY; IF SQL%ROWCOUNT 0 THEN RAISE_APPLICATION_ERROR(-20004, 库存不足); END IF;SQL%ROWCOUNT 是 PL/SQL 里的隐式游标属性UPDATE 影响行数为 0 说明库存不够或者药品不存在。这种写法比 SELECT STOCK INTO ... 再判断更优雅也是答辩时可以主动讲给老师听的一个点。3.3 触发器记录操作日志的常见做法触发器在课设里的定位比较微妙用得好是亮点用滥了会给自己埋坑。这份资源里触发器只做一件简单的事——记录挂号操作的日志CREATE OR REPLACE TRIGGER TRG_REG_LOG AFTER INSERT ON REGISTRATION FOR EACH ROW BEGIN INSERT INTO REG_LOG(LOG_ID, REG_ID, PAT_ID, DOC_ID, OP_TIME, OP_TYPE) VALUES (SEQ_LOG.NEXTVAL, :NEW.REG_ID, :NEW.PAT_ID, :NEW.DOC_ID, SYSDATE, INSERT); END;逻辑说明AFTER INSERT 行级触发器在 REGISTRATION 插入成功后把关键字段写到日志表 REG_LOG。:NEW 代表新插入行的列值这是 Oracle 触发器的标准写法。REG_LOG 表结构很简单LOG_ID 主键、REG_ID、PAT_ID、DOC_ID、OP_TIME、OP_TYPE。为什么说触发器别用多课设里常见的问题是有人在 PATIENT 表上建触发器做“主键自增”又在 REGISTRATION 表上建触发器导致 Java 里每插一条记录就要多执行一段隐式逻辑一旦触发器中报错主操作也跟着回滚排查成本很高。我的建议是课设里触发器只做审计日志类辅助操作主键生成放到存储过程或序列里显式控制别写在触发器里。这样老师问“为什么不用触发器做自增”你可以回答“自增逻辑显式化避免隐式执行难以调试”这是一个能站得住脚的设计取舍。往下就是 Java 端怎么把这些存储过程接起来第 4 章专门讲 JDBC 连接。4. Java 连接 OracleJDBC、DAO 与参数配置4.1 驱动、URL 与连接池先把通道打通Java 端连接 Oracle 的经典组合是 ojdbc 驱动加标准 JDBC。驱动 jar 的版本要跟服务端匹配Oracle 11g 对应 ojdbc6 或 ojdbc7Oracle 12c 和 19c 用 ojdbc8。拿 ojdbc8 去连 11g 一般也兼容但个别版本会报 ORA-28040: No matching authentication protocol第 5 章会展开说。先给一个驱动版本对照表方便你排查环境时快速定位服务端版本推荐驱动 jarJDK 要求Oracle 11g R2ojdbc6 / ojdbc7JDK 6 及以上Oracle 12c R2ojdbc8JDK 8 及以上Oracle 19cojdbc8JDK 8 及以上最简连接方式Class.forName(oracle.jdbc.OracleDriver); String url jdbc:oracle:thin:localhost:1521:ORCL; String user hospital; String password hospital123; Connection conn DriverManager.getConnection(url, user, password);逻辑说明Class.forName 加载驱动Oracle 的驱动类名有 oracle.jdbc.driver.OracleDriver 和 oracle.jdbc.OracleDriver 两种写法从 ojdbc6 开始后者是标准入口。URL 里 thin 表示纯 Java 驱动不需要装 Oracle 客户端localhost 换成真实 IP1521 是默认监听端口ORCL 是实例名。如果库配置的是 SID 而不是 Service Name写法通常也一样拿不准就找提供环境的人要连接串。我一般会在课设里用一个 DBUtil 工具类管理连接避免每个 DAO 里都写一遍 DriverManager 代码public class DBUtil { private static final String URL jdbc:oracle:thin:localhost:1521:ORCL; private static final String USER hospital; private static final String PASSWORD hospital123; static { try { Class.forName(oracle.jdbc.OracleDriver); } catch (ClassNotFoundException e) { throw new RuntimeException(Oracle 驱动加载失败请检查 ojdbc jar 是否在 classpath, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明URL、USER、PASSWORD 硬编码在常量里课设够用。如果用的工具版本比较新可以把这三个值放到 db.properties 里由 Properties 加载避免每次改连接串都重新编译。getConnection 每次返回一个新连接性能上有损耗但课设演示完全没问题。如果想体现工程能力可以引入 HikariCP 连接池配置要点如下HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:oracle:thin:localhost:1521:ORCL); config.setUsername(hospital); config.setPassword(hospital123); config.setMaximumPoolSize(10); config.setDriverClassName(oracle.jdbc.OracleDriver); HikariDataSource ds new HikariDataSource(config);HikariCP 参数含义MaximumPoolSize 10 表示最多维持 10 个物理连接课设并发量低设 5 也够MinimumIdle 默认与 MaximumPoolSize 相同。连接池不是必须的但能体现“连接是稀缺资源”这个意识。4.2 DAO 层增删改查怎么封装DAO 层的职责是隔离 SQL 与业务逻辑。课设里我会按表建 DAO比如 PatientDAO、DoctorDAO、RegistrationDAO。下面以查询某医生当天挂号记录为例public ListMapString, Object findRegsByDoc(int docId) throws SQLException { String sql SELECT R.REG_ID, P.PAT_NAME, R.REG_TIME, R.REG_STATUS FROM REGISTRATION R JOIN PATIENT P ON R.PAT_ID P.PAT_ID WHERE R.DOC_ID ? ORDER BY R.REG_TIME DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, docId); try (ResultSet rs ps.executeQuery()) { ListMapString, Object list new ArrayList(); while (rs.next()) { MapString, Object row new HashMap(); row.put(regId, rs.getInt(REG_ID)); row.put(patName, rs.getString(PAT_NAME)); row.put(regTime, rs.getTimestamp(REG_TIME)); row.put(regStatus, rs.getString(REG_STATUS)); list.add(row); } return list; } } }逻辑说明使用 PreparedStatement 的 ? 占位符传参而不是拼字符串这是防 SQL 注入的基本功。try-with-resources 保证 Connection、PreparedStatement、ResultSet 自动关闭避免 Oracle 连接被耗尽。这一点特别重要Oracle 并发连接数默认只有 150课设里频繁打开不关闭跑到一半就报 ORA-12519: TNS:no appropriate service handler found。参数说明setInt 对应 Oracle NUMBER 列setTimestamp 对应 DATE 列setString 对应 VARCHAR2。返回用 ListMapString, Object 是课设里最省事的做法胜在通用如果你想体现面向对象功底可以建一个 RegistrationVO 类把列映射到实体字段。答辩时用 VO 类比 Map 多两分工程感但 Map 也绝不至于扣分。增删改查里最容易出问题的是删除。外键约束下直接 DELETE FROM PATIENT WHERE PAT_ID 1 会报 ORA-02292因为挂号表、处方表都引用着病人。解决方案有两个一是删除前先删子表记录二是在业务里做逻辑删除加一个 IS_DELETED 字段。课设建议用第二种既能保住数据完整性也能在答辩时讲“软删除避免误删就诊历史”比物理删除更符合医院系统的真实需求。4.3 调用存储过程CallableStatement 的三种写法Java 调用 PL/SQL 存储过程用 CallableStatement。以第 3 章的 PROC_REGISTER 为例完整调用代码public int register(int patId, int docId, double fee) throws SQLException { String sql {call PROC_REGISTER(?, ?, ?, ?)}; try (Connection conn DBUtil.getConnection(); CallableStatement cs conn.prepareCall(sql)) { cs.setInt(1, patId); cs.setInt(2, docId); cs.setDouble(3, fee); cs.registerOutParameter(4, Types.INTEGER); cs.execute(); return cs.getInt(4); } }逻辑说明{call PROC_REGISTER(?, ?, ?, ?)} 是标准调用语法。前三个参数按存储过程的输入参数顺序绑定第四个注册为 OUT 参数类型用 Types.INTEGER 对应 Oracle NUMBER。execute 执行后 getInt(4) 取出存储过程生成的挂号 ID。整个过程中事务边界由存储过程内部的 COMMIT 控制Java 端不需要也不能再调用 commit否则可能造成事务提前提交。这一点很关键如果你在 Java 端把自动提交设成 false 又调了 commit存储过程里已经 commit连接层的事务状态会对不上后续错误回滚完全无效。第二种写法是使用 setAutoCommit 配合存储过程传参适合“Java 侧先插一条主记录、再调用存储过程更新子表”的场景conn.setAutoCommit(false); try { // 先插入主记录 insertRegMaster(conn, regId, patId, docId); // 调用存储过程 callSomeProcedure(conn, regId); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }第三种写法是通过 PreparedStatement 调用存储函数取 RETURN 值比如返回一个统计值的函数。课设里用得少不展开。你需要记住的原则是事务边界要么全在 Java 端要么全在存储过程端不要两边交叉控制。我见过一个课设Java 里 setAutoCommit(false)存储过程里又写了 COMMIT结果演示时第一次点击正常第二次点击数据重复就是因为两边都控制了提交边界。5. 避坑与排查Oracle 课设最容易翻车的五个位置这一章用几条踩坑记录换你少熬几个通宵。每个坑都按“现象、原因、解决”说清楚。5.1 监听服务起不来报 ORA-12541 / ORA-12514现象安装 Oracle 11g 后SQL Developer 或 Java 代码连不上报 ORA-12541: TNS:no listener 或 ORA-12514: TNS:listener does not currently know of service requested in connect descriptor。Windows 服务列表里 OracleOraDb11g_home1TNSListener 处于停止状态手动启动瞬间又停。原因最常见两个。第一是安装时端口 1521 被占用监听配置的端口和实际不符第二是 Oracle 11g 的监听服务依赖注册表里的 ORACLE_HOME 路径如果本机装过多个 Oracle 版本或环境变量被改过服务启动就会中途失败。还有一类是 listener.ora 文件被误改。解决先执行 netstat -ano | findstr 1521 看端口被谁占用如果被占改 listener.ora 里的 PORT 换一个端口同时把 Java URL 里的 1521 一起改。如果端口没被占用但服务起不来打开 listener.ora检查 HOST 是否写成了主机名有些机器的 hosts 解析有问题把 HOST 改成 127.0.0.1 或具体 IP 最省事。改完用 lsnrctl start 手动启动命令行输出的错误信息比 Windows 服务日志直观得多。补充一个小细节Oracle 11g 的监听日志文件 listener.log 会无限增长有的环境跑几个月日志几个 GB导致监听变慢、连接超时。课设阶段不会遇到但如果老师让你部署到服务器演示记得定期清空停监听、删日志文件、再启动Oracle 会自动重建。5.2 查询出来全是问号或乱码现象Java 控制台和 SQL Developer 里查中文显示成??或者插入的中文在界面上显示错乱。原因客户端字符集与数据库字符集不一致。数据库建库时字符集如果是 AL32UTF8而 Windows 命令行默认 GBK代码页 936Java 程序里 JDBC 连接的 NLS_LANG 没配或者配错。解决最省事的办法是统一到 AL32UTF8。JDBC URL 上加 ?useUnicodetruecharacterEncodingUTF-8 对 Oracle 无效Oracle 的 JDBC 连接字符集受 NLS_LANG 环境变量控制。正确做法是在 Java 启动参数里加 -Dfile.encodingUTF-8并保证源文件也是 UTF-8 保存。操作层面插入前先执行 ALTER SESSION SET NLS_LANGUAGE SIMPLIFIED CHINESE; 治本还是检查环境变量里的 NLS_LANG。课设机器上常见配置是 NLS_LANGSIMPLIFIED CHINESE_CHINA.ZHS16GBK如果数据库是 AL32UTF8把 NLS_LANG 改成 SIMPLIFIED CHINESE_CHINA.AL32UTF8 通常能解决。如果改完还乱码检查是否建库时字符集选错了ZHS16GBK 的库想改成 AL32UTF8 只能重建库没有后悔药。5.3 ORA-00933SQL 命令未正确结束现象一条在 MySQL 里跑得很顺的 SQL搬到 Oracle 报 ORA-00933: SQL command not properly ended。最常见的是分页语句 SELECT * FROM REGISTRATION LIMIT 0, 20在 Oracle 里直接报这个错。原因Oracle 没有 MySQL 的 LIMIT 语法也没有 SQL Server 的 TOP分页要用 ROWNUM 伪列。解决Oracle 12c 之前用三层子查询分页12c 开始支持 OFFSET ... FETCH。课设普遍用 11g优先写 ROWNUM 版本SELECT * FROM ( SELECT R.*, ROWNUM RN FROM (SELECT * FROM REGISTRATION ORDER BY REG_TIME DESC) R WHERE ROWNUM 20 ) WHERE RN 0;逻辑说明最内层子查询先排序第二层给每行加 ROWNUM 并截断到最大行号最外层再过滤起始行。注意 ROWNUM 要在 ORDER BY 之后的子查询里生成否则先取 20 行再排序分页就乱了。12c 以上可以直接写 SELECT * FROM REGISTRATION ORDER BY REG_TIME DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY但答辩老师如果规定用 11g还是老老实实写 ROWNUM。顺便提一句上面 SQL 里的 DUAL 空表也常被问。SELECT 1 FROM DUAL 是 Oracle 特有的写法MySQL 用 SELECT 1 就行。有同学问 DUAL 能存多大其实 DUAL 永远是单行单列表用来取序列值或系统常量没有存储量概念。5.4 ORA-02292外键约束导致删不掉数据现象往 DOCTOR 表 DELETE 一行时报 ORA-02292: integrity constraint (FK_REG_DOC) violated - child record found。原因REGISTRATION 表有外键引用 DOCTOR子表存在记录时主表不能直接删。这是设计层面的约束生效不是语法错。解决删除顺序按“先子后主”走先 DELETE FROM REGISTRATION WHERE DOC_ID xxx; 再删医生。如果确实想保留子表数据就别物理删用前面提到的软删除字段。还有一点要注意TRUNCATE TABLE 在 Oracle 里默认不能对有外键引用的表执行必须 DISABLE 约束再 TRUNCATEALTER TABLE REGISTRATION DISABLE CONSTRAINT FK_REG_DOC; TRUNCATE TABLE REGISTRATION; ALTER TABLE REGISTRATION ENABLE CONSTRAINT FK_REG_DOC;课设演示中基本碰不到 TRUNCATE知道这个限制就行不用主动在答辩里展示。5.5 ORA-00001违反唯一约束主键重复现象程序第一次跑插入正常第二次跑同样的数据报 ORA-00001: unique constraint (PK_PATIENT) violated。原因主键没有用序列而是 Java 端用 System.currentTimeMillis() 或随机数生成 ID重启后时间戳重复或者用了 SELECT MAX(REG_ID)1 的方式并发时取到同一个值。解决Oracle 不像 MySQL 有 AUTO_INCREMENT主键自增的标准做法是序列加触发器或者在存储过程里显式取序列值。推荐把主键生成收敛到存储过程就像第 3 章 PROC_REGISTER 里 SELECT SEQ_REG.NEXTVAL INTO P_REG_ID FROM DUAL 那样。如果你用 Hibernate 或 JPA把 GeneratedValue 策略配成 SEQUENCE并设置 hibernate.id.new_generator_mappingsfalse否则会报 ORA-02289: sequence does not exist。这个点我在好几个课设 demo 里见过代码里配了 GenerationType.AUTOMySQL 上跑没事换到 Oracle 就报序列不存在。序列和触发器组合时注意触发器里用 :NEW.REG_ID 赋值但必须在 INSERT 之前执行也就是 BEFORE INSERT 触发器否则主键 NULL 直接报 ORA-01400。这些问题处理完剩下的功夫就都在演示细节上下面三个技巧能把课设从“能跑”拉到“好看”。6. 进阶把课设演示变成答辩加分项的三个技巧到这一步表结构、存储过程、Java 连接都已经打通接下来最实在的提升是让这套资源在演示时更像一个系统而不是一堆代码。三个技巧我自己每次带课设都会强制过一遍。第一个技巧是准备一个一键初始化脚本。把所有建表、建序列、建索引、创建用户授权全部写进 init.sql再用 sqlplus hospital/hospital123localhost:1521/ORCL init.sql 批量执行。这样换一台机器演示一个命令就能重建环境比在图形工具里逐个执行 SQL 高效得多。顺序必须注意先建用户、再建表、再建序列、再建触发器和存储过程、最后插测试数据否则先插数据后建外键会报约束错误。第二个技巧是用视图封装高频查询。比如统计各科室每天的挂号量写成视图后 Java 端只查一张虚拟表代码复杂度直线下降CREATE OR REPLACE VIEW V_DEPT_REG_COUNT AS SELECT D.DEPT_NAME, TRUNC(R.REG_TIME) AS REG_DATE, COUNT(*) AS REG_CNT FROM REGISTRATION R JOIN DOCTOR D2 ON R.DOC_ID D2.DOC_ID JOIN DEPT D ON D2.DEPT_ID D.DEPT_ID GROUP BY D.DEPT_NAME, TRUNC(R.REG_TIME);Java 端查视图和查普通表没有区别但 SQL 更短答辩时能顺带讲清楚“视图是逻辑表不占额外存储每次查询实时计算”这个高频考点。第三个技巧是把异常场景预演一遍。演示时老师很可能问病人挂了号没缴费怎么办退号时号源已经放完了怎么办我习惯在测试数据里故意留一条挂账单不缴费演示时对同一张表连续跑两次“确认缴费”第二次数据库用约束和存储过程把边界挡住这个瞬间比讲十页 PPT 都有说服力。从那以后我每次拿到新的课设资源都强制自己先跑通 init.sql 再动 Java 代码遇到环境问题先查监听和字符集不折腾代码。这份基于 Java Oracle 的医院系统数据库资源适合当起点但真正拉开分数差距的永远是边界检查、事务划分和“能不能扛住答辩追问”这三个习惯。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号