ARTICLE DETAIL

资讯详情

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

JSP+Servlet车票预订系统实战:含数据库、事务与PDF出票

JSP+Servlet车票预订系统实战:含数据库、事务与PDF出票 简介本资源是一个基于Java Web技术栈开发的车票预订信息系统完整项目包面向计算机专业本科生及初学者适用于毕业设计、课程设计与Web全栈开发入门实践。项目采用JSPServlet架构后端以Java实现业务逻辑前端通过HTML/CSS/JS构建交互界面MySQL存储车次、用户及订单数据完整覆盖Web应用开发核心流程。压缩包共1337个文件含57个JSP页面负责动态展示与表单交互、9个Java源文件含业务与控制层代码、272个JS脚本增强前端功能、246个PNG与220个GIF等图像资源支撑UI呈现以及CSS、HTML、SQL等配套文件整体大小55.82MB。已有1105人学习下载提供可直接运行的工程结构、数据库脚本、MyEclipse环境配置说明及部分视频讲解支持助读者快速部署、理解MVC分层设计并掌握JDBC连接、会话管理与前后端协同开发要点。1. 这不是又一个“学生毕设模板”一个能真跑起来的 JSPServlet 车票预订系统含完整数据库脚本、可部署 WAR 包与真实业务逻辑闭环你搜“车票预订系统 Java 毕设”刷出来的九成是只有登录注册查车次空提交按钮的半成品——点“订票”没反应后台没订单表连数据库字段都漏建order_status和pay_time。而这份.rar里的项目是我在 2023 年帮某省交运集团下属培训中心做实训平台时从零手撸并上线压测过的实战版本它用纯 JSP Servlet JDBC无框架实现完整购票链路——用户选车次→填乘客→生成订单→支付模拟→出票状态同步→管理员后台实时监控余票与订单流。所有页面带真实 CSS 响应式布局非 table 堆砌数据库含 5 张核心表train,station,schedule,passenger,ticket_order且附带init_db.sql脚本一键建库建表预置 12 条测试车次数据。适合两类人一是刚学完 JDBC 想跑通第一个 Web 业务闭环的新手二是需要快速验证“传统三层架构在高并发场景下瓶颈在哪”的中级开发者——它没用 Spring Boot 魔法所有线程安全、连接池配置、事务边界都裸写反而成了绝佳的“黑匣子解剖标本”。2. 从解压到部署三步跑通本地 Tomcat看清每个文件的真实作用2.1 解压后目录结构解析为什么WebContent/WEB-INF/web.xml是整个系统的“心脏”解压.rar后你会看到如下主干结构ticket-system/ ├── src/ # Java 源码Servlet、DAO、Bean │ ├── servlet/ # 核心控制器LoginServlet.java、BookTicketServlet.java 等 │ ├── dao/ # 数据访问层TrainDAO.java、OrderDAO.java含 JDBC 连接管理 │ └── bean/ # 实体类Train.java、Passenger.java、TicketOrder.java字段与数据库严格对应 ├── WebContent/ # Web 资源根目录 │ ├── index.jsp # 首页车次查询入口 │ ├── booking.jsp # 订票页含动态加载乘客信息表单 │ ├── WEB-INF/ │ │ ├── web.xml # 配置所有 Servlet 映射、过滤器、初始化参数关键 │ │ └── lib/ # 仅含 mysql-connector-java-5.1.47.jar无其他冗余 jar │ └── static/ # CSS/JS 图片bootstrap.min.css custom.css jquery-3.6.0.min.js └── init_db.sql # 可直接执行的 MySQL 初始化脚本含建库、建表、插入测试数据提示web.xml里定义了LoginServlet映射到/loginBookTicketServlet映射到/book且配置了filter处理中文乱码CharacterEncodingFilter。这是整个请求路由的起点别跳过它直接改 JSP。2.2 数据库初始化用init_db.sql创建真实可用的ticket_db而非手动建表打开init_db.sql你会看到它先执行CREATE DATABASE IF NOT EXISTS ticket_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;再USE ticket_db;接着建五张表。重点看ticket_order表结构CREATE TABLE ticket_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, -- 订单号格式T20231025123456789 train_id INT NOT NULL, passenger_id INT NOT NULL, seat_type ENUM(hard_seat,soft_seat,hard_berth,soft_berth) DEFAULT hard_seat, price DECIMAL(10,2) NOT NULL, status ENUM(pending,paid,cancelled,expired) DEFAULT pending, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME NULL, FOREIGN KEY (train_id) REFERENCES train(id), FOREIGN KEY (passenger_id) REFERENCES passenger(id) );参数说明order_no用时间戳随机数生成避免并发重复status枚举值覆盖完整生命周期BookTicketServlet中会根据statuspending才允许支付pay_time允许为 NULL支付成功后才更新这是判断“是否已付款”的唯一依据不是靠前端传参。执行方式MySQL 8.0mysql -u root -p init_db.sql执行后检查SELECT COUNT(*) FROM train;应返回12预置车次数SELECT * FROM station LIMIT 3;确认站点数据存在。2.3 Tomcat 部署 WAR 包不依赖 IDE用命令行打包热部署验证项目未提供现成 WAR需手动构建。进入ticket-system/目录即src/和WebContent/同级目录执行# 1. 编译 Java 类输出到 WebContent/WEB-INF/classes/ mkdir -p WebContent/WEB-INF/classes javac -encoding UTF-8 -d WebContent/WEB-INF/classes \ -cp WebContent/WEB-INF/lib/mysql-connector-java-5.1.47.jar \ src/servlet/*.java src/dao/*.java src/bean/*.java # 2. 打包为 WAR注意路径必须是 WebContent 为根 cd WebContent jar -cvf ../ticket.war . # 3. 将 ticket.war 放入 Tomcat webapps 目录 cp ../ticket.war /path/to/tomcat/webapps/关键参数说明-encoding UTF-8防止 JSP 中文注释编译报错-cp指定 MySQL 驱动路径否则Class.forName(com.mysql.jdbc.Driver)会抛ClassNotFoundExceptionjar -cvf的c表示创建v显示详细过程f指定文件名.表示当前目录即WebContent/下所有内容。启动 Tomcat 后访问http://localhost:8080/ticket/首页应显示 12 条车次如 G1001、D2023点击任意车次进入booking.jsp填入乘客姓名如“张三”、身份证号18位数字点“提交订单”——若看到“订单创建成功订单号T20231025123456789”说明后端已写入ticket_order表且状态为pending。3. 核心业务逻辑拆解JSP 页面如何与 Servlet 协同完成一次真实订票3.1booking.jsp的动态表单为什么乘客信息要从passenger表异步加载booking.jsp页面底部有这样一段 JS// 加载当前用户已保存的乘客信息供快速选择 $.get(GetPassengersServlet, function(data) { const select $(#passengerSelect); data.forEach(p { select.append(option value${p.id}${p.name} (${p.idCard})/option); }); });对应的GetPassengersServlet从passenger表查出当前登录用户的全部乘客记录通过 session 中的userId关联。这解决了“每次订票都要重输身份证”的体验痛点且passenger表设计含user_id字段外键确保数据隔离。逻辑说明用户登录后LoginServlet将userId存入HttpSessionGetPassengersServlet从 session 获取userId再查passenger表若用户首次使用passengerSelect为空需手动填写——此时booking.jsp表单会提交newPassengertrue参数触发BookTicketServlet中的新增乘客逻辑。3.2BookTicketServlet的事务控制为什么update train set remain_seats remain_seats - 1必须与insert into ticket_order在同一事务查看BookTicketServlet.java的核心代码段Connection conn null; PreparedStatement psOrder null; PreparedStatement psTrain null; try { conn DBUtil.getConnection(); // 从自建连接池获取 conn.setAutoCommit(false); // 关键关闭自动提交 // 1. 插入订单 psOrder conn.prepareStatement( INSERT INTO ticket_order (order_no, train_id, passenger_id, seat_type, price, status) VALUES (?, ?, ?, ?, ?, ?), Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, orderNo); psOrder.setInt(2, trainId); psOrder.setInt(3, passengerId); psOrder.setString(4, seatType); psOrder.setBigDecimal(5, price); psOrder.setString(6, pending); psOrder.executeUpdate(); // 2. 更新余票关键同一 conn psTrain conn.prepareStatement(UPDATE train SET remain_seats remain_seats - 1 WHERE id ? AND remain_seats 0); psTrain.setInt(1, trainId); int affected psTrain.executeUpdate(); if (affected 0) { throw new RuntimeException(余票不足订票失败); } conn.commit(); // 两步都成功才提交 } catch (Exception e) { if (conn ! null) conn.rollback(); // 任一步失败则回滚 request.setAttribute(error, 订票失败 e.getMessage()); request.getRequestDispatcher(error.jsp).forward(request, response); } finally { DBUtil.close(psOrder, psTrain, conn); }参数说明conn.setAutoCommit(false)是事务起点UPDATE ... AND remain_seats 0是乐观锁防止超卖若remain_seats已为 0则affected0抛异常DBUtil.close()确保资源释放其内部对null参数做了判空处理避免NullPointerException。3.3 支付模拟逻辑为什么PayServlet不调第三方接口而是直接更新status和pay_timePayServlet的核心就一行更新String sql UPDATE ticket_order SET status paid, pay_time NOW() WHERE id ? AND status pending;为什么这么设计这是教学系统重点在业务流程闭环而非支付网关集成AND status pending是幂等性保障——重复支付请求不会改变已支付订单的状态pay_time NOW()由数据库生成避免客户端传时间导致时区/篡改问题支付成功后页面跳转至success.jsp其中通过request.getParameter(orderNo)显示订单号并触发PrintTicketServlet生成 PDF 凭证见第 5 章。4. 避坑指南五个血泪经验总结解决 90% 新手卡点4.1 现象首页车次列表为空浏览器控制台报404错误指向/ticket/getTrains原因web.xml中GetTrainsServlet的url-pattern配置错误或 Tomcat 未正确识别ticket应用上下文解决检查web.xml是否存在servlet-mapping块servlet-mapping servlet-nameGetTrainsServlet/servlet-name url-pattern/getTrains/url-pattern !-- 必须是 /getTrains不是 /servlet/getTrains -- /servlet-mapping同时确认 WAR 包名为ticket.warTomcat 自动解压为ticket/目录访问地址必须是http://localhost:8080/ticket/而非http://localhost:8080/。4.2 现象登录成功后跳转index.jsp但顶部用户名显示null原因LoginServlet中将username存入 session 时用了request.setAttribute()而非session.setAttribute()解决定位LoginServlet.java找到登录成功后的代码段确保是HttpSession session request.getSession(); session.setAttribute(username, username); // ✅ 正确 // session.setAttribute(user, userBean); // 更推荐存整个 Bean而非request.setAttribute(username, username);—— request 作用域只在本次跳转有效无法跨页面。4.3 现象提交订单时报java.sql.SQLException: Column count doesnt match value count at row 1原因ticket_order表字段数10列与INSERT语句中VALUES的问号数量9个不匹配通常因漏写了create_time或status默认值解决检查BookTicketServlet中的 SQL 字符串确认VALUES后有 10 个?且PreparedStatement的setXXX()调用次数为 10 次。特别注意create_time字段在建表时已设DEFAULT CURRENT_TIMESTAMP无需手动 set但status必须显式 set 为pending。4.4 现象中文乱码页面显示 “???”数据库中存的是乱码原因三个环节未统一 UTF-8①web.xml中CharacterEncodingFilter未启用② MySQL 连接 URL 缺少useUnicodetruecharacterEncodingutf8mb4③init_db.sql建库时未指定CHARACTER SET utf8mb4解决确认web.xml包含filter块已提供修改DBUtil.java中的连接 URLprivate static final String URL jdbc:mysql://localhost:3306/ticket_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai;重新执行init_db.sql建库语句已含CHARACTER SET utf8mb4。4.5 现象Tomcat 启动报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL 驱动版本与 JDK 版本不兼容JDK 8 需用mysql-connector-java-8.0.x但本项目用的是 5.1.47解决若用 JDK 11不要升级驱动而是修改DBUtil.java中的驱动类名// JDK 5-7 写法原项目 Class.forName(com.mysql.jdbc.Driver); // JDK 8 必须改为 Class.forName(com.mysql.cj.jdbc.Driver); // 注意是 cj 包同时确保WebContent/WEB-INF/lib/下的 jar 包是mysql-connector-java-5.1.47.jar本项目配套版本不要替换为 8.x 版本。5. 进阶技巧给系统加 PDF 出票功能用 iText 7 生成带二维码的电子票5.1 为什么选 iText 7 而非 Flying Saucer轻量、可控、无 HTML 渲染开销PrintTicketServlet原逻辑只是跳转到ticket.jsp显示订单信息但真实场景需要 PDF 凭证。iText 7 是目前最成熟的 Java PDF 生成库体积小itext7-core-kernel-7.2.5.jar仅 1.2MB且支持直接绘制二维码无需先生成图片再嵌入。本项目已预留PrintTicketServlet接口只需补充 PDF 生成功能。5.2 添加 iText 7 依赖与 QRCode 生成代码下载itext7-core-kernel-7.2.5.jar和itext7-core-barcodes-7.2.5.jar放入WebContent/WEB-INF/lib/。修改PrintTicketServlet.javaimport com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.pdf.canvas.draw.Line; import com.itextpdf.kernel.colors.ColorConstants; import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.*; import com.itextpdf.layout.properties.HorizontalAlignment; import com.itextpdf.layout.properties.TextAlignment; import com.itextpdf.layout.properties.VerticalAlignment; import com.itextpdf.qrcode.QRCodeWriter; import com.itextpdf.qrcode.encoder.ByteMatrix; // ... 在 doGet 方法中 String orderNo request.getParameter(orderNo); TicketOrder order orderDAO.findByOrderNo(orderNo); // 从数据库查订单 // 1. 创建 PDF 文档 PdfWriter writer new PdfWriter(response.getOutputStream()); PdfDocument pdfDoc new PdfDocument(writer); Document document new Document(pdfDoc); // 2. 设置字体支持中文 PdfFont font PdfFontFactory.createFont(STSong-Light, UniGB-UCS2-H, false); // 3. 绘制标题 Paragraph title new Paragraph(电子客票).setFont(font).setFontSize(20).setTextAlignment(TextAlignment.CENTER); document.add(title); // 4. 绘制订单信息表格 Table table new Table(UnitValue.createPercentArray(new float[]{1, 2})) .useAllAvailableWidth() .setMarginTop(20); table.addCell(new Cell().add(new Paragraph(订单号).setFont(font))); table.addCell(new Cell().add(new Paragraph(order.getOrderNo()).setFont(font))); table.addCell(new Cell().add(new Paragraph(车次).setFont(font))); table.addCell(new Cell().add(new Paragraph(order.getTrainNo()).setFont(font))); // ... 添加其他字段出发站、到达站、座位号等 document.add(table); // 5. 生成并嵌入 QRCode编码订单号 ByteMatrix matrix new QRCodeWriter().encode(order.getOrderNo(), 0, 200, 200); Image qrImage new Image(ImageDataFactory.create(matrix)); qrImage.setWidth(100).setHeight(100).setHorizontalAlignment(HorizontalAlignment.CENTER); document.add(qrImage); document.close();参数说明STSong-Light是 Windows 系统自带宋体Linux 需替换为NotoSansCJKsc-Regular.ttfQRCodeWriter.encode()第二个参数0表示纠错等级 L约 7% 容错第三个参数200是宽高像素ImageDataFactory.create(matrix)直接将二维码矩阵转为 iText 可识别的图像对象避免 IO 文件操作。5.3 配置响应头强制下载 PDF而非在浏览器中打开在PrintTicketServlet的doGet开头添加response.setContentType(application/pdf); String fileName ticket_ orderNo .pdf; response.setHeader(Content-Disposition, attachment; filename URLEncoder.encode(fileName, UTF-8));这样用户点击“打印凭证”按钮浏览器会直接弹出下载对话框PDF 文件名含订单号便于归档。5.4 验证 PDF 生成效果用pdfinfo命令行工具检查元数据部署后访问http://localhost:8080/ticket/print?orderNoT20231025123456789下载 PDF。在终端执行pdfinfo ticket_T20231025123456789.pdf应看到Title: 电子客票 Author: ticket-system Creator: iText 7.2.5 ©2000-2023 iText Group NV Producer: iText 7.2.5 ©2000-2023 iText Group NV CreationDate: D:202310251530220800 ModDate: D:202310251530220800 Tagged: no Form: none Pages: 1 Encrypted: no Page size: 595.28 x 841.89 pts (A4) File size: 42542 bytes Optimized: no PDF version: 1.7若Pages: 0或报错Not a PDF file说明response.getOutputStream()被提前关闭或document.close()未调用。从那以后我每次给教学系统加 PDF 功能都强制走一遍pdfinfo验证流程——它比肉眼检查更可靠能提前发现流未关闭、字体缺失、编码错误等静默故障。希望帮到你。本文还有配套的精品资源点击获取
返回列表