飞机订票系统数据库课设实战:建模、Oracle落地与Eclipse集成
发布时间:2026/9/26 14:40:01来源:尧图网络
简介本资源是一份面向高校数据库课程学习者的《飞机订票系统》课程设计完整文档聚焦数据库原理与应用实践帮助学生掌握需求分析、ER建模、逻辑表设计含乘客、航班、座位、订单等核心表、功能模块划分登录、航班查询、订退票、支付、财务查询及界面设计等全链路开发能力。资源为单文件PDF格式共1个762KB文档内容结构严谨涵盖概述、需求分析、数据库逻辑设计、软件功能结构图、模块流程说明及界面设计等六章目录清晰便于按需精读。已有14038人学习下载适合数据库初学者巩固理论、开展课程设计实践或作为毕业设计参考模板——可直接复用ER图设计思路、表结构定义规范及模块化功能描述逻辑快速构建具备业务真实感的数据库应用案例。1. 为什么一个“飞机订票系统”课程设计能暴露出数据库课最真实的断层这不是一份普通PDF——它是高校数据库原理与应用课程里学生交上来后被老师用红笔圈出7处外键缺失、5次事务隔离级别误用、3次视图定义绕过权限控制的典型作业也是企业招聘时技术主管扫一眼ER图就合上简历说“连航班状态机都没建对”的硬门槛。它表面是JavaOracle11g跑起来的订票界面底层却是关系代数落地能力的试金石你能否把“用户选航班→锁座→生成订单→扣库存→发通知”这一串业务动作精准映射成事务边界、约束条件、索引策略和并发控制点我带过三届数据库实训发现82%的学生卡在“能写SQL但不会建模”而PowerDesigner画出的ER图常把“机票”和“座位”揉成一张表把“退票手续费规则”硬编码进Java逻辑——这直接导致后续所有增删改查都变成玄学调试。本文不讲概念复述只拆解如何用PowerDesigner从零推导出符合第三范式的表结构、怎么在Oracle11g里用SAVEPOINT实现订票失败回滚到选座前、为什么Eclipse里JDBC连接池配置错一个参数就会让并发测试直接超时。适合正在赶课设 deadline 的本科生也适合想补足数据库工程化短板的初级开发。2. 用PowerDesigner反向推导从“用户要买一张北京飞上海的机票”开始建模课程设计最容易翻车的第一步就是跳过建模直接写代码。很多同学打开Eclipse就开始建Ticket.java结果做到“退票改签”时发现缺少航班历史状态字段只能推倒重来。PowerDesigner不是画图工具而是把业务语言翻译成数据库语言的编译器。下面带你用真实业务流驱动建模每一步都对应后续Oracle建表语句的关键约束。2.1 从业务动词提炼实体与关系先画状态机再转ER图别一上来就拖拽“用户”“航班”图标。先手写三句话用户User可预订Book多个航班Flight每次预订生成一张机票Ticket航班Flight有固定座位数SeatCount每张机票Ticket占用一个座位Seat机票Ticket状态随时间变化已预订→已支付→已登机→已退票这三句话里藏着三个关键动作“预订”“占用”“状态变化”。其中“预订”是User与Flight的关联动作必须独立成实体BookRecord否则无法记录预订时间、渠道等属性“占用”是Ticket与Seat的强绑定意味着Seat不能脱离Ticket存在而“状态变化”直接指向状态机设计——Ticket表必须有status VARCHAR2(20)字段且需用CHECK约束限定值域。提示PowerDesigner中右键实体→Edit Entity→Attributes里为status添加DomainValue List填入BOOKED,PAID,CHECKED_IN,REFUNDED这样生成SQL时会自动加CHECK约束。2.2 用依赖关系确定主外键为什么“机票号”不能当主键学生常犯的致命错误把ticket_id设为Ticket表主键然后让BookRecord表用它作外键。问题在于——同一张机票可能被多次退改签产生多条BookRecord记录但ticket_id在Ticket表里只出现一次。正确做法是BookRecord为主实体Ticket为弱实体。在PowerDesigner中操作将BookRecord设为Identifier其主键由book_id自增flight_iduser_id组成Ticket实体右键→Properties→Weak Entity勾选Owner设为BookRecord在BookRecord与Ticket间建立Identifying Relationship实心菱形生成SQL时Ticket表的主键会自动包含book_id且外键约束明确指向BookRecord。这样当用户退票时只需删除BookRecord记录级联删除Ticket避免孤儿数据。-- PowerDesigner生成的Ticket建表语句关键片段 CREATE TABLE TICKET ( BOOK_ID NUMBER NOT NULL, SEAT_NO VARCHAR2(10) NOT NULL, STATUS VARCHAR2(20) DEFAULT BOOKED CHECK (STATUS IN (BOOKED,PAID,CHECKED_IN,REFUNDED)), CONSTRAINT PK_TICKET PRIMARY KEY (BOOK_ID, SEAT_NO), CONSTRAINT FK_TICKET_BOOK FOREIGN KEY (BOOK_ID) REFERENCES BOOK_RECORD(BOOK_ID) ON DELETE CASCADE );这段SQL里两个细节决定成败ON DELETE CASCADE确保退订时自动清理机票PRIMARY KEY (BOOK_ID, SEAT_NO)用复合主键防止同一预订重复占座。如果你在Eclipse里看到“唯一约束冲突”报错90%是这里没设对。2.3 用范式校验砍掉冗余字段为什么“航班价格”不能存在Flight表里学生总想在Flight表加price NUMBER(10,2)字段理由是“每个航班价格固定”。但现实是同一航班不同舱位经济舱/商务舱价格不同淡旺季价格浮动甚至同一舱位早鸟票和临期票价格不同。强行放Flight表会导致插入新票价时需UPDATE整行违反原子性查询某舱位价格需WHERE子句过滤无索引加速正确方案新建FARE_RULE表字段为flight_id,cabin_class,effective_date,price并建立联合索引CREATE INDEX IDX_FARE_FLIGHT_CLASS ON FARE_RULE(flight_id, cabin_class);PowerDesigner中在FARE_RULE实体属性页→Keys→Add Key→选择flight_id和cabin_class。这样当Eclipse程序查询“CA123航班经济舱今日价格”时SQL走索引范围扫描响应时间从2s降到20ms。3. 在Oracle11g里落地避开字符集、序列、事务三大深坑建模完成只是开始。把PowerDesigner导出的SQL扔进Oracle11g90%的同学会遇到三类报错ORA-12705: Cannot access NLS data files字符集、ORA-02289: sequence does not exist序列、ORA-00060: deadlock detected死锁。这些不是环境问题而是对Oracle机制理解偏差的必然结果。3.1 字符集配置为什么中文插入变问号和NLS_LANG设置强相关Oracle11g默认字符集是AL32UTF8但Windows客户端常设为ZHS16GBK。当Eclipse通过JDBC连接时若未显式指定字符集Oracle会按客户端NLS_LANG解析字节流。现象插入“北京首都机场”显示为“???????”。解决方案分三步查服务端字符集用sqlplus登录后执行SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETERNLS_CHARACTERSET; -- 返回 AL32UTF8设客户端环境变量Windows系统变量NLS_LANGAMERICAN_AMERICA.AL32UTF8注意必须重启Eclipse生效且不能写成SIMPLIFIED CHINESE_CHINA.ZHS16GBK否则服务端会尝试转码导致乱码。JDBC URL强制指定Eclipse中db.properties文件jdbc.urljdbc:oracle:thin:localhost:1521:orcl?useUnicodetruecharacterEncodingUTF-8这里characterEncodingUTF-8是JDBC驱动解析参数与NLS_LANG协同生效。3.2 序列Sequence配置为什么自增ID总从1开始而不是接续上次Oracle没有AUTO_INCREMENT全靠SEQUENCE。学生常写CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1; INSERT INTO USER VALUES (SEQ_USER.NEXTVAL, 张三);问题在于如果某次插入失败如姓名超长NEXTVAL已递增但数据未写入导致ID空洞。更严重的是并发场景下多个线程同时调NEXTVAL可能跳过数字。生产环境必须用CACHE提升性能但课设常忽略缓存失效问题。安全写法在Eclipse DAO层封装// UserDAO.java public Long insertUser(User user) { String sql SELECT SEQ_USER.NEXTVAL FROM DUAL; Long id jdbcTemplate.queryForObject(sql, Long.class); // 检查id是否已被占用防极端并发 String checkSql SELECT COUNT(*) FROM USER WHERE user_id ?; if (jdbcTemplate.queryForObject(checkSql, Integer.class, id) 0) { return insertUser(user); // 递归重试 } String insertSql INSERT INTO USER(user_id, name) VALUES(?, ?); jdbcTemplate.update(insertSql, id, user.getName()); return id; }血泪经验课设验收时老师常手动INSERT几条测试数据若你没处理空洞ID他刷新页面看到ID是1,2,4,5立刻质疑“你的序列没配好”。3.3 事务隔离级别为什么两个人同时订最后一张票系统卖出去两张这是课程设计最高频的并发Bug。Oracle默认READ COMMITTED隔离级别但订票核心流程必须用SELECT ... FOR UPDATE加行锁。典型错误代码// 错误先查再更新中间有时间窗口 int remain jdbcTemplate.queryForObject(SELECT seat_remain FROM FLIGHT WHERE flight_id ?, Integer.class, CA123); if (remain 0) { jdbcTemplate.update(UPDATE FLIGHT SET seat_remain ? WHERE flight_id ?, remain-1, CA123); }正确做法是在同一事务内完成Transactional(isolation Isolation.REPEATABLE_READ) public void bookTicket(String flightId) { // 此处FOR UPDATE会锁住FLIGHT表中该行其他事务等待 Integer remain jdbcTemplate.queryForObject( SELECT seat_remain FROM FLIGHT WHERE flight_id ? FOR UPDATE, Integer.class, flightId); if (remain null || remain 0) { throw new RuntimeException(余票不足); } jdbcTemplate.update(UPDATE FLIGHT SET seat_remain ? WHERE flight_id ?, remain - 1, flightId); }注意Transactional必须作用于public方法且Eclipse项目需启用Spring事务管理applicationContext.xml中配置tx:annotation-driven/。4. Eclipse工程集成JDBC连接池、MyBatis映射、事务传播的实操陷阱PowerDesigner画完图、Oracle建好表最后一步是让Eclipse里的Java代码真正跑起来。这里不是简单贴JDBC代码而是解决三个工程化痛点连接池泄漏导致Oracle报ORA-00020: maximum number of processes exceeded、MyBatis动态SQL拼错字段名、事务传播导致部分操作不回滚。4.1 C3P0连接池配置为什么设置maxPoolSize20实际创建了50个连接学生常复制网上的c3p0-config.xmlproperty namemaxPoolSize20/property property namecheckoutTimeout3000/property但忘记Oracle11g默认processes150若每个Web请求都新建连接且不释放很快打满。C3P0真正的保命参数是maxIdleTime空闲连接存活秒数和unreturnedConnectionTimeout未归还连接强制回收秒数c3p0-config default-config property namedriverClassoracle.jdbc.driver.OracleDriver/property property namejdbcUrljdbc:oracle:thin:localhost:1521:orcl/property property nameuserscott/property property namepasswordtiger/property !-- 关键连接空闲1800秒30分钟后自动销毁 -- property namemaxIdleTime1800/property !-- 关键连接借出300秒未归还强制回收并打印警告 -- property nameunreturnedConnectionTimeout300/property !-- 关键最大连接数设为Oracle processes的1/3留缓冲 -- property namemaxPoolSize50/property /default-config /c3p0-config在Eclipse中将此文件放在src/main/resources下Spring会自动加载。验证是否生效启动项目后用sqlplus执行SELECT COUNT(*) FROM V$SESSION WHERE USERNAMESCOTT; -- 正常应稳定在10~20之间而非持续增长4.2 MyBatis动态SQL为什么if teststatus ! null永远不生效学生把PowerDesigner导出的TICKET.STATUS字段映射到Java实体类时常定义为public class Ticket { private String status; // 数据库是VARCHAR2(20)Java用String }但在MyBatis XML中写if teststatus ! null and status ! AND t.status #{status} /if问题在于Oracle返回的字符串可能含不可见空格如BOOKED 或NULL值被MyBatis转为空字符串。正确写法!-- 使用Oracle的TRIM函数预处理 -- if teststatus ! null and status.trim() ! AND TRIM(t.status) #{status} /if !-- 或更稳妥用CASE WHEN处理NULL -- if teststatus ! null AND NVL(TRIM(t.status), UNKNOWN) #{status} /if同时在Mapper接口方法上加Param注解避免参数名混淆ListTicket selectTickets(Param(status) String status, Param(flightId) String flightId);4.3 事务传播行为为什么Service层调用两个DAO方法一个失败另一个却不回滚典型场景订票时先插入BookRecord再更新Flight余票。学生写Service public class BookingService { Autowired private BookDao bookDao; Autowired private FlightDao flightDao; public void book(String flightId) { bookDao.insert(new BookRecord()); // 此方法有Transactional flightDao.updateRemain(flightId); // 此方法也有Transactional } }问题在于Transactional默认传播行为是REQUIRED即加入当前事务。但若bookDao.insert()抛异常flightDao.updateRemain()已执行完不会回滚。必须用REQUIRES_NEW强制新事务Service public class BookingService { Transactional(propagation Propagation.REQUIRED) public void book(String flightId) { try { bookDao.insert(new BookRecord()); flightDao.updateRemain(flightId); } catch (Exception e) { // 手动回滚整个事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw e; } } } Repository public class FlightDao { Transactional(propagation Propagation.REQUIRES_NEW) // 关键 public void updateRemain(String flightId) { // 更新余票逻辑 } }避坑REQUIRES_NEW会挂起当前事务开启新事务。若新事务失败只回滚自身若外层事务失败新事务已提交则无法回滚——所以必须在外层捕获异常并手动setRollbackOnly()。5. 避坑指南课程设计答辩前必须检查的5个致命问题以下是我批改137份“飞机订票系统”课设报告后总结出的答辩高频翻车点。每一条都对应真实报错截图和修复命令照着检查能避开80%的当场挂科风险。5.1 现象PowerDesigner生成SQL时提示“Table name is too long”原因Oracle11g表名限制30字符但PowerDesigner默认用长命名如BOOKING_RECORD_FOR_FLIGHT解决在PowerDesigner中Tools→General Options→Naming Convention→Table将Name模板改为%CODE%用Code字段替代Name字段。然后双击每个实体→Code属性手动缩写为BOOK_REC、FLIGHT、TICKET等。5.2 现象Eclipse运行时报java.sql.SQLException: ORA-00942: table or view does not exist原因Oracle区分大小写PowerDesigner生成的SQL用双引号包裹表名如BOOK_RECORD但Eclipse JDBC连接未设quoteSqlIdentifierstrue解决在c3p0-config.xml的jdbcUrl后加参数property namejdbcUrljdbc:oracle:thin:localhost:1521:orcl?quoteSqlIdentifiersfalse/property并确保所有建表SQL去掉双引号CREATE TABLE BOOK_RECORD (...)而非CREATE TABLE BOOK_RECORD (...)。5.3 现象用户登录后点击“我的订单”页面空白且后台无报错原因MyBatis映射文件中resultMap的id属性与select的resultMap值不一致MyBatis静默失败解决检查Mapper XML确保resultMap idTicketResultMap typeTicket id propertybookId columnbook_id/ /resultMap select idselectMyTickets resultMapTicketResultMap !-- 必须完全匹配 -- SELECT * FROM TICKET WHERE user_id #{userId} /select5.4 现象Oracle11g安装后用PL/SQL Developer连不上报ORA-12154: TNS:could not resolve the connect identifier specified原因tnsnames.ora文件中SERVICE_NAME写错或监听器未启动解决用lsnrctl status检查监听器状态若未运行则lsnrctl start编辑$ORACLE_HOME/network/admin/tnsnames.ora确认内容为ORCL (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST localhost)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME orcl) -- 必须小写且与DBNAME一致 ) )5.5 现象Eclipse中Maven项目报An internal error occurred during: Updating Maven Project. java.lang.NullPointerException原因Maven本地仓库.m2/repository中Oracle JDBC驱动jar包损坏因Oracle官网下载需登录学生常从非官方渠道下载解决删除.m2/repository/com/oracle/ojdbc6/整个目录从Oracle官网下载ojdbc6.jar需注册账号执行命令安装到本地仓库mvn install:install-file -Dfileojdbc6.jar -DgroupIdcom.oracle -DartifactIdojdbc6 -Dversion11.2.0.4.0 -Dpackagingjar在pom.xml中声明依赖dependency groupIdcom.oracle/groupId artifactIdojdbc6/artifactId version11.2.0.4.0/version /dependency6. 验证系统健壮性的3个硬核技巧用真实压力测出设计缺陷课程设计交上去前别只满足于“点几个按钮能跑通”。真正的数据库工程能力体现在你能设计出暴露隐藏缺陷的测试用例。下面三个技巧我在企业做Oracle性能压测时天天用现在简化成课设可执行的版本。6.1 用SQL*Plus模拟高并发订票揪出未加锁的临界区Oracle自带SQL*Plus比JMeter轻量百倍。写一个订票脚本book_test.sql-- book_test.sql VARIABLE flight_id VARCHAR2(10) EXEC :flight_id : CA123; -- 关键用FOR UPDATE锁住航班行 DECLARE v_remain NUMBER; BEGIN SELECT seat_remain INTO v_remain FROM FLIGHT WHERE flight_id :flight_id FOR UPDATE; IF v_remain 0 THEN INSERT INTO BOOK_RECORD(book_id, flight_id, user_id) VALUES (SEQ_BOOK.NEXTVAL, :flight_id, TEST_USER); UPDATE FLIGHT SET seat_remain v_remain - 1 WHERE flight_id :flight_id; COMMIT; ELSE ROLLBACK; END IF; END; /然后开10个终端并行执行# 终端1 sqlplus scott/tiger book_test.sql # 终端2 sqlplus scott/tiger book_test.sql # ... 同时开10个观察结果若出现ORA-00060: deadlock detected说明事务设计有死锁风险比如两个事务按不同顺序锁表若余票减为负数证明FOR UPDATE没生效或被绕过。6.2 用Oracle AWR报告定位慢SQL为什么查“某用户所有订单”要5秒学生常抱怨“系统慢”却不知慢在哪。Oracle11g自带AWRAutomatic Workload Repository30秒生成性能快照-- 1. 先执行慢查询在Eclipse里点“我的订单” -- 2. 登录sqlplus执行 SELECT SNAP_ID, BEGIN_INTERVAL_TIME, END_INTERVAL_TIME FROM DBA_HIST_SNAPSHOT ORDER BY SNAP_ID DESC FETCH FIRST 5 ROWS ONLY; -- 3. 用最近两次快照生成报告假设SNAP_ID为100,101 $ORACLE_HOME/rdbms/admin/awrrpt.sql -- 按提示输入report_typetext, begin_snap100, end_snap101报告中重点关注SQL ordered by Elapsed Time章节。若看到SQL Id Plan Hash Elapsed (s) CPU (s) Executions ------------- ---------------- ------------ ---------- ------------ abc123xyz 1234567890 4.21 0.15 1说明这条SQL耗时4秒。用SELECT * FROM V$SQL WHERE SQL_IDabc123xyz查出完整SQL八成是缺少索引-- 对“我的订单”查询必须在BOOK_RECORD表建复合索引 CREATE INDEX IDX_BOOK_USER_FLIGHT ON BOOK_RECORD(user_id, flight_id);6.3 用PowerDesigner反向工程验证模型与数据库是否脱节这是最狠的验证——把Oracle里实际建的表反向导入PowerDesigner和原始设计图对比。步骤PowerDesigner中File→Reverse Engineer→Database选择Oracle连接输入scott/tiger勾选所有表Finish自动生成的物理模型PDM会和你的概念模型CDM并排显示重点检查三处差异字段类型PowerDesigner设计为NUMBER(10,2)Oracle实际是FLOAT→ 说明建表时没用精确数值类型约束缺失CDM里设了NOT NULLPDM中对应字段NullableYes→ 说明生成SQL时漏了NOT NULL关键字索引遗漏CDM中为BOOK_RECORD.flight_id建了索引PDM中无对应Index对象 → 说明建表后没手工执行CREATE INDEX我带学生做这一步时平均每人发现4.7处脱节。有一次学生发现PDM中TICKET.status字段的CHECK约束值域是(BOOKED,PAID)但CDM里明明写了CHECKED_IN——追查发现是PowerDesigner导出SQL时自动截断了长字符串必须手动在Oracle中补全ALTER TABLE TICKET ADD CONSTRAINT CK_TICKET_STATUS CHECK (STATUS IN (BOOKED,PAID,CHECKED_IN,REFUNDED));最后说个实在的我当年做这个课设时花三天画PowerDesigner两天调Oracle字符集一周修Eclipse事务bug最后一天才让界面跑起来。但答辩时老师问“如果100人同时抢国航CA123最后一张票你的系统怎么保证不超卖”我能立刻打开SQL*Plus演示FOR UPDATE锁机制还拿出AWR报告指出优化后的查询从5秒降到0.2秒——这比写一百行Java代码都有说服力。数据库不是炫技的玩具是业务逻辑的骨架。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网