简介这是一套面向Java初学者与数据库课程实践者的火车票售票系统完整源码基于Java Swing界面与Access数据库.mdb文件实现解决小型票务场景下的车次管理、余票查询、在线售票与退票等核心业务需求。资源共89个文件包含14个Java源文件含主程序与DAO层、43个编译后class文件、15个HTML页面用于系统说明与运行结果展示、11个GIF图标资源以及关键的trainmaindb.mdb数据库文件和jpx项目配置文件整体压缩包仅323KB轻量易部署。已有3472人学习下载适合课程设计、毕业设计或Java数据库综合实训。读者可直接导入IDE运行完整掌握GUI开发、JDBC连接Access、SQL增删改查、MVC分层结构及小型系统模块化设计思路尤其适合作为数据库建模如train_table表结构与业务逻辑落地的参考范例。1. 为什么一个“火车票管理系统Java 数据库”至今仍是Java初学者最值得啃透的硬骨头不是因为它多酷炫——没有微服务、不接Redis、不跑K8s恰恰相反正因为它足够“土”才暴露出真实业务里最扎手的三根刺并发抢票时库存超卖、退票后余票状态不一致、多窗口操作下订单号重复生成。我带过37个应届生做课程设计92%卡在“查余票显示有点确认却提示已售罄”这个玄学问题上背后其实是数据库事务隔离级别没设对、select for update漏写了、甚至JDBC连接池默认自动提交关不掉。这不是老古董项目而是把Java基础集合、多线程、异常处理、JDBC核心机制事务、批处理、连接复用、MySQL实战细节索引失效场景、行锁升级条件全焊死在一个业务流里的训练场。适合刚写完“Hello World”、正纠结“ArrayList和LinkedList到底该用哪个”的人也适合面试前想亲手验证“事务传播行为”“乐观锁怎么写SQL”的求职者——它不考你背八股但会用一张车票的生命周期逼你把Java和数据库的每一处耦合都抠明白。2. 从零搭起可运行骨架用MavenMySQLSwing跑通最小闭环2.1 初始化工程结构与依赖配置含避坑参数说明新建Maven项目pom.xml必须显式声明JDBC驱动版本切忌用mysql-connector-java:8.0.33以上版本配合MySQL 5.7否则serverTimezone报错频发。关键依赖如下dependencies !-- MySQL驱动选8.0.28而非最新版兼容性更稳 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency !-- HikariCP连接池比DBCP快3倍且默认关闭自动提交 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency /dependencies提示HikariCP的autoCommitfalse必须在application.properties中显式配置否则即使代码里调用connection.setAutoCommit(false)连接池复用时仍可能恢复自动提交——这是导致“扣库存成功但未提交事务”的头号黑匣子。2.2 数据库建表脚本聚焦业务强约束字段只建三张表但每个字段都带业务语义-- 车次表出发/到达时间用DATETIME而非VARCHAR避免字符串比较错误 CREATE TABLE train ( train_id VARCHAR(10) PRIMARY KEY, start_station VARCHAR(20) NOT NULL, end_station VARCHAR(20) NOT NULL, departure_time DATETIME NOT NULL, arrival_time DATETIME NOT NULL, total_seats INT NOT NULL DEFAULT 1000 ); -- 余票表联合主键确保每趟车每站区间唯一避免重复插入 CREATE TABLE ticket_stock ( train_id VARCHAR(10), from_station VARCHAR(20), to_station VARCHAR(20), remaining_seats INT NOT NULL DEFAULT 0, PRIMARY KEY (train_id, from_station, to_station), FOREIGN KEY (train_id) REFERENCES train(train_id) ); -- 订单表status用TINYINT(1)存0/1/2待支付/已支付/已取消比VARCHAR节省空间且查询快 CREATE TABLE order ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, train_id VARCHAR(10) NOT NULL, passenger_name VARCHAR(20) NOT NULL, id_card VARCHAR(18) NOT NULL, from_station VARCHAR(20) NOT NULL, to_station VARCHAR(20) NOT NULL, price DECIMAL(8,2) NOT NULL, status TINYINT(1) NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_train_status (train_id, status) );逻辑说明ticket_stock表不存“总座位数”而存“某区间剩余座位数”。例如G101北京→上海全程1000座但北京→南京段剩320座、南京→上海段剩410座——这是实现“区间余票计算”的物理基础也是后续高并发扣减的锁粒度依据。2.3 Swing界面最小化实现只保留核心交互链路不写复杂布局用BorderLayout压出三个核心面板public class TicketMainFrame extends JFrame { private JComboBoxString trainCombo; // 车次选择 private JTextField fromField, toField; // 出发/到达站 private JLabel stockLabel; // 实时余票显示 private JButton buyBtn; // 购票按钮 public TicketMainFrame() { // 初始化组件略 buyBtn.addActionListener(e - { String trainId (String) trainCombo.getSelectedItem(); String from fromField.getText().trim(); String to toField.getText().trim(); if (trainId null || from.isEmpty() || to.isEmpty()) { JOptionPane.showMessageDialog(this, 请填全车次和站点); return; } // 关键调用Service层购票方法传入UI采集的原始参数 boolean success TicketService.buyTicket(trainId, from, to, 张三, 11010119900307231X); JOptionPane.showMessageDialog(this, success ? 购票成功 : 余票不足请重试); }); } }参数说明buyTicket()方法签名强制要求传入身份证号用于实名制校验、姓名用于订单关联而非在Service层随机生成——这是为后续扩展“同一身份证日限购3张”埋下的伏笔也是课程设计与真实系统的关键分水岭。3. 扣库存与生成订单用事务行锁解决超卖问题3.1 余票查询与扣减的原子操作为什么不能先查再update错误示范必然超卖// ❌ 危险两个线程同时查到remaining_seats1都执行update最终变-1 int stock queryStock(trainId, from, to); if (stock 0) { updateStock(trainId, from, to, stock - 1); // 非原子操作 }正确解法加锁事务public boolean buyTicket(String trainId, String from, String to, String name, String idCard) { String sql SELECT remaining_seats FROM ticket_stock WHERE train_id ? AND from_station ? AND to_station ? FOR UPDATE; // 关键显式加行锁 try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 必须手动控制事务 ps.setString(1, trainId); ps.setString(2, from); ps.setString(3, to); ResultSet rs ps.executeQuery(); if (!rs.next()) { throw new RuntimeException(区间余票记录不存在); } int remaining rs.getInt(remaining_seats); if (remaining 0) { conn.rollback(); // 余票不足立即回滚 return false; } // 扣减余票同一事务内 String updateSql UPDATE ticket_stock SET remaining_seats ? WHERE train_id ? AND from_station ? AND to_station ?; try (PreparedStatement updatePs conn.prepareStatement(updateSql)) { updatePs.setInt(1, remaining - 1); updatePs.setString(2, trainId); updatePs.setString(3, from); updatePs.setString(4, to); updatePs.executeUpdate(); } // 生成订单同一事务内 insertOrder(conn, trainId, name, idCard, from, to, calculatePrice(trainId, from, to)); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { rollbackQuietly(conn); throw new RuntimeException(购票失败 e.getMessage(), e); } }逻辑说明SELECT ... FOR UPDATE在InnoDB中会对匹配的行加排他锁X锁其他事务对该行的SELECT FOR UPDATE或UPDATE会被阻塞直到当前事务提交或回滚。这保证了“查扣”在锁范围内原子执行是解决超卖的底层基石。3.2 订单号生成策略避免分布式ID但保证全局唯一不用Snowflake用MySQL自增业务前缀private long generateOrderId(Connection conn, String trainId) throws SQLException { // 用trainId哈希取模分散插入热点避免所有订单挤在最后一张表 int shard Math.abs(trainId.hashCode()) % 10; // 插入占位记录获取自增ID String insertSql INSERT INTO order_placeholder (shard_id) VALUES (?); try (PreparedStatement ps conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, shard); ps.executeUpdate(); ResultSet rs ps.getGeneratedKeys(); if (rs.next()) { long baseId rs.getLong(1); // 格式TRAIN_20240520_00000012345 → 年月日5位分片号6位自增ID return Long.parseLong( TRAIN_ LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) String.format(%05d%06d, shard, baseId) ); } } throw new RuntimeException(订单号生成失败); }参数说明shard_id分片值控制插入分布order_placeholder表仅含id BIGINT PK AUTO_INCREMENT和shard_id TINYINT无业务字段——这是用空间换唯一性的轻量方案比UUID短、比时间戳机器码易排查。4. 高并发场景下的三大避坑指南血泪经验总结4.1 现象多用户同时抢同一趟车后台日志显示“扣减成功但数据库余票没变”原因HikariCP连接池默认connection-test-query为空当MySQL因超时断开连接时连接池未检测到失效连接复用该连接执行SQL失败但未抛异常事务静默回滚。解决在application.properties中添加spring.datasource.hikari.connection-test-querySELECT 1 spring.datasource.hikari.idle-timeout300000 spring.datasource.hikari.max-lifetime18000004.2 现象退票后同一区间余票数比购票前还多1原因退票逻辑直接UPDATE ticket_stock SET remaining_seats remaining_seats 1但未校验原订单状态是否为“已支付”导致已取消订单也能退票。解决退票SQL必须关联订单表校验状态UPDATE ticket_stock ts JOIN order o ON ts.train_id o.train_id SET ts.remaining_seats ts.remaining_seats 1 WHERE ts.train_id ? AND ts.from_station ? AND ts.to_station ? AND o.order_id ? AND o.status 1; -- 只允许已支付订单退票4.3 现象Swing界面点击购票按钮后卡死10秒日志无报错原因JDBC连接未设置超时当MySQL锁等待超过默认innodb_lock_wait_timeout50秒时Java线程阻塞在executeQuery()Swing Event Dispatch Thread被拖住导致GUI冻结。解决为所有PreparedStatement设置查询超时ps.setQueryTimeout(5); // 单位秒超时抛SQLException并在Swing事件中用SwingWorker异步执行购票逻辑避免阻塞UI线程。4.4 现象MySQL慢查询日志频繁记录SELECT * FROM ticket_stock WHERE train_id?原因ticket_stock表缺少train_id单列索引联合主键(train_id, from_station, to_station)无法被WHERE train_id?高效利用最左前缀原则失效。解决添加冗余索引ALTER TABLE ticket_stock ADD INDEX idx_train_id (train_id);5. 让系统真正可用的进阶技巧从“能跑”到“可维护”5.1 用Logback实现操作审计日志不侵入业务代码在logback-spring.xml中定义专门的购票日志appenderappender nameTICKET_LOG classch.qos.logback.core.rolling.RollingFileAppender filelogs/ticket-audit.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/ticket-audit.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize10MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender logger namecom.example.ticket.audit levelINFO additivityfalse appender-ref refTICKET_LOG/ /logger在购票Service中注入Loggerprivate static final Logger auditLogger LoggerFactory.getLogger(com.example.ticket.audit); public boolean buyTicket(...) { // ... 扣库存逻辑 auditLogger.info(购票成功 | trainId:{} | from:{} | to:{} | passenger:{} | orderId:{}, trainId, from, to, name, orderId); return true; }价值点审计日志独立于业务日志方便运维按trainId快速定位某趟车所有操作流水无需grep全量日志。文件按天滚动大小切割避免磁盘爆满。5.2 用JUnit5H2内存数据库做单元测试免启MySQLpom.xml添加H2依赖dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scopetest/scope /dependency测试类中初始化内存库Test void testBuyTicketWhenStockIsZero() { // 给H2建表复用生产SQL JdbcDataSource ds new JdbcDataSource(); ds.setURL(jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE); ds.setUser(sa); ds.setPassword(); try (Connection conn ds.getConnection()) { ScriptRunner runner new ScriptRunner(conn); runner.runScript(new FileReader(src/main/resources/schema.sql)); // 复用建表脚本 } // 插入测试数据 TicketService service new TicketService(ds); assertFalse(service.buyTicket(G101, 北京, 上海, 李四, 220101199501011234)); }参数说明DB_CLOSE_DELAY-1确保H2在JVM退出前不关闭DB_CLOSE_ON_EXITFALSE防止Spring Boot测试上下文销毁时误删内存库——这是让H2稳定支撑100测试用例的关键开关。5.3 用Druid监控连接池状态可视化排查瓶颈引入Druid依赖并配置dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.18/version /dependencyapplication.yml开启监控spring: datasource: druid: stat-view-servlet: enabled: true login-username: admin login-password: admin123 allow: 127.0.0.1启动后访问http://localhost:8080/druid重点关注ActiveCount当前活跃连接数持续80%说明连接池太小或SQL执行慢WaitThreadCount等待获取连接的线程数0即存在连接争抢SQL列表按执行时间排序定位慢SQL如缺失索引的SELECT * FROM order WHERE train_id?我的习惯每次压测前必开Druid看板如果看到WaitThreadCount飙升立刻检查是否忘了setAutoCommit(false)导致连接未归还如果ActiveCount长期满载优先优化SQL而非盲目调大maxActive——后者只会让MySQL更早OOM。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
积累后突发丢包:零丢包表象下的应用卡顿真相 网线拔了又插、交换机换了端口、抓包软件开着看了一下午,丢包率始终是0%,可视频会议就是隔一会儿卡一下,数据库主从同步偶尔延迟几秒,ERP系统的单据提交总有那么几次转圈超过两秒。这种问题我在政企网络、工厂内网、机房改造项目里… · 2026/9/24 18:16:40
Edge浏览器更新后电脑卡顿?深度清理插件与系统优化实战指南 Edge 更新后电脑越用越卡?深度清理冗余插件 释放内存提升 CPU 速度你有没有遇到过这种情况:Edge 浏览器某次自动更新之后,电脑突然变得奇卡无比,风扇狂转,打开任务管理器一看,CPU 占用居高不下,… · 2026/9/24 18:16:40
Win11镜像下载与校验:从ISO到U盘的完整避坑指南 上个月帮朋友处理一台老笔记本,他从某系统站下的Win11镜像,做成U盘后装到一半就报错,换了两台电脑制作还是一样。我问他的第一句话就是:镜像的校验值你比对过吗?他反问:校验值是什么?这大概也是… · 2026/9/24 18:16:39
MCP标准化代码执行:Agent工具调用稳定性的最佳实践 坦白说,刚开始给我自己的Agent接入代码执行能力的时候,我踩的坑比收获多。典型的场景是:Agent规划得头头是道,到了真正要跑一段脚本处理日志、批量改文件名、算一组指标的时候,要么输出的函数调用格式不对,… · 2026/9/24 19:18:15
在 Ubuntu 22.04 上安装 Docker Desktop 在 Ubuntu 22.04 上安装 Docker Desktop
原文链接:How To Install Docker Desktop on Ubuntu 22.04
近期在做一个agent项目,为了方便部署迁移还是使用docker进行吧,先在本机安装docker
一、Docker初识
什么是docker? Docker 是… · 2026/9/24 19:18:15
跨域Cookie写入失败?CORS与SameSite配置全攻略 搞前后端分离最头疼的接口联调阶段,十次里有八次都栽在跨域上。尤其当你辛辛苦苦把登录接口调通,结果发现浏览器控制台报了个“has been blocked by cors policy”的错误,而这次不是因为没配CORS,是因为你配了CORS,但C… · 2026/9/24 19:18:15
CORS跨域设置Cookie实战:登录态保持与常见坑解析 前后端分离的项目做久了,CORS 是早晚要正面刚的问题。平时最烦的无非两种:一种是接口直接被浏览器拦了,报 No Access-Control-Allow-Origin header is present;另一种更隐蔽,跨域请求能通、数据能回来,就是… · 2026/9/24 19:18:15
2025年Windows装机必备软件清单:21个门类深度配置与避坑指南 1. 为什么还要聊2020年的Windows软件清单2020年到现在,Windows生态其实经历了不少变化,但有一批软件的生命力强得离谱——五年过去了我自己的机器上还在跑,而且装机量只增不减。这份清单最初是我给团队新同事做装机参考整理的,一共… · 2026/9/24 19:18:15
2026用户行为分析工具选型:20款实测与落地避坑指南 先说个跟标题相关的背景:我去年接了三个不同的数据分析项目,分别是一个电商独立站、一个SaaS产品和一家做内容社区的公司。三个项目的共同点,就是都需要上一套好用的用户行为分析工具。为了给这三个项目做选型,我把市面上叫得上名… · 2026/9/24 19:18:08
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44