ARTICLE DETAIL

资讯详情

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

JavaWeb影院订票系统实战:Servlet+JSP+MySQL手把手教学

JavaWeb影院订票系统实战:Servlet+JSP+MySQL手把手教学 简介这是一套开箱即用的Java Web影院订票系统源码面向Java初学者、高校课程设计学生及中小型团队开发者解决从零搭建完整Web应用的学习与开发效率问题。资源包含773个文件涵盖107个Java后端逻辑类、60个Vue前端组件、156个JS交互脚本、49个CSS样式文件及32个HTML页面模板辅以SQL建表语句、Shiro权限配置、Spring Boot启动脚本如install.bat、run.bat等工程化支持文件压缩包仅19.52MB轻量易部署。已有46人学习下载适合毕业设计快速落地、教学案例演示或MVP产品原型验证。读者可直接导入IDE运行获得含用户管理、影片排期、在线选座、订单支付等核心功能的全栈实现并通过清晰分层的代码结构Controller/Service/Mapper/Vue组件深入理解Spring BootMyBatisVue技术栈的协同机制与企业级开发规范。1. 为什么一个“JavaWeb影院订票系统”源码能帮你把Servlet、JSP、MySQL和MVC真正焊进肌肉记忆里你不是缺Java语法书而是缺一个能跑起来、能改得动、能查得出错在哪的完整Web项目。这个“Java系统源码基于JavaWeb的影院订票系统”不是玩具Demo——它有真实业务闭环用户注册登录 → 查看影厅排片 → 选座下单 → 订单状态流转 → 后台管理影片/场次/座位。它用最朴素的Servlet JSP JDBC MySQL组合不绕开Filter拦截、Session管理、事务控制、SQL注入防护这些一线高频踩坑点。我带新人时总说Spring Boot是高速公路但JavaWeb是修路的夯土机——没亲手夯过永远不知道地基哪层松、哪层裂。这套源码就是那台夯土机没有自动装配、没有注解魔法、所有request.getParameter()、out.print()、conn.prepareStatement()都赤裸裸摆在你眼前。它适合两类人刚学完Java基础、正卡在“写了HelloWorld却不会连数据库”的新手或是准备Java开发岗面试、需要快速复现一个可讲清技术链路的实战案例的求职者。别被“老技术”劝退——2024年校招笔试题里“手写Servlet处理表单提交并防SQL注入”仍是高频题。2. 从零部署用Tomcat 9 JDK 8 搭建可运行环境避坑版2.1 环境版本选择为什么坚持用JDK 8 Tomcat 9而不是最新版很多教程一上来就推Tomcat 10结果你照着跑JSP编译报错、Servlet类找不到——因为Tomcat 10默认使用Jakarta EE 9命名空间jakarta.servlet.*而本项目源码里全是javax.servlet.*。这不是代码问题是规范迁移的断层。血泪经验只要源码没声明支持Jakarta EE一律锁死JDK 8 Tomcat 9.0.x。JDK 8是JavaWeb生态事实上的“稳定锚点”Spring Framework 5.3.x、MyBatis 3.4.x、Hibernate 5.4.x全兼容且企业老旧系统维护仍以它为主流。下载地址直接搜“Apache Tomcat 9.0.83 archive”2023年最后一个稳定归档版JDK选“Java SE Development Kit 8u391”。安装后验证# 终端执行 java -version # 输出应为java version 1.8.0_391 catalina.sh version # Linux/macOS # 或 catalina.bat version Windows # 输出应含 Server version: Apache Tomcat/9.0.83提示Windows用户务必关闭Windows Defender实时保护否则Tomcat启动时可能因扫描war包导致超时失败JDK环境变量JAVA_HOME必须指向JDK根目录非jre且PATH中%JAVA_HOME%\bin需在%SystemRoot%\system32之前。2.2 部署WAR包三步完成项目上线含web.xml配置解析项目源码通常提供target/cinema.war或dist/cinema.war。不要直接双击运行正确流程停止Tomcat./bin/shutdown.shLinux/macOS或双击bin/shutdown.batWindows清空旧部署删除webapps/cinema文件夹及webapps/cinema.war如有复制新WAR包将cinema.war放入webapps/目录启动Tomcat./bin/startup.shTomcat会自动解压WAR包并部署。关键验证点访问http://localhost:8080/cinema/login.jsp若看到登录页即成功。此时打开webapps/cinema/WEB-INF/web.xml重点看三处!-- web.xml 核心片段 -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.cinema.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern !-- 注意JSP走静态路径Servlet走此映射 -- /servlet-mapping filter filter-nameEncodingFilter/filter-name filter-classcom.cinema.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern !-- 全局字符编码过滤器防中文乱码 -- /filter-mapping参数说明url-pattern决定请求路由逻辑——.jsp后缀由Tomcat内置JspServlet处理/login这类无后缀路径由自定义Servlet接管/*表示Filter拦截所有请求。若页面提交表单后中文变问号一定是EncodingFilter未生效或request.setCharacterEncoding(UTF-8)漏写。2.3 数据库初始化MySQL 5.7建库脚本执行与连接池配置项目通常附带sql/cinema.sql。用MySQL命令行执行-- 创建数据库注意字符集 CREATE DATABASE cinema CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE cinema; -- 执行建表语句此处省略具体DDL实际需粘贴sql文件内容 -- 插入初始数据如管理员账号 INSERT INTO user (username, password, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, admin); -- MD5(123456)提示密码字段存的是MD5哈希值非明文e10adc3949ba59abbe56e057f20f883e对应123456。若需重置密码用在线MD5生成器转换后更新数据库。连接配置在WEB-INF/classes/db.properties中drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/cinema?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8 usernameroot password123456 initialSize5 maxActive20关键参数说明serverTimezoneGMT%2B8解决MySQL 5.7时区报错常见错误The server time zone value ... is unrecognizeduseUnicodetruecharacterEncodingUTF-8强制UTF-8编码避免中文存取乱码initialSize/maxActiveDBCP连接池参数小项目设5/20足够若并发测试时报Cannot create PoolableConnectionFactory先检查MySQL是否允许远程连接GRANT ALL ON cinema.* TO root% IDENTIFIED BY 123456; FLUSH PRIVILEGES;3. 核心业务拆解从选座到支付的四层代码链路含关键代码块3.1 用户登录Session认证与密码安全校验逻辑登录入口是login.jsp表单提交至/loginServlet映射。LoginServlet.java核心逻辑// com.cinema.servlet.LoginServlet protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 1. 参数校验防空指针 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { request.setAttribute(msg, 用户名或密码不能为空); request.getRequestDispatcher(login.jsp).forward(request, response); return; } // 2. 密码MD5加密前端不传明文 String md5Password DigestUtils.md5Hex(password); // Apache Commons Codec // 3. 查询数据库JDBC模板 User user userDao.findUserByUsernameAndPassword(username, md5Password); if (user ! null) { // 4. 写入Session非Cookie HttpSession session request.getSession(); session.setAttribute(user, user); // 存用户对象供后续页面取用 session.setMaxInactiveInterval(1800); // 30分钟无操作自动失效 // 5. 重定向到首页避免重复提交 response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } }逻辑说明DigestUtils.md5Hex()来自commons-codec-1.11.jar比手写MessageDigest简洁若项目无此jar需手动添加到WEB-INF/lib/session.setAttribute()是服务端状态保持的核心request.getSession()若无Session则新建有则复用response.sendRedirect()是重定向URL变化request.getRequestDispatcher().forward()是转发URL不变登录成功必须重定向防止F5刷新重复提交3.2 影片排片查询三层架构中的DAO层SQL编写技巧前端通过/showList请求获取今日排片ShowServlet.java调用ShowService再调ShowDao。关键在ShowDaoImpl.java的SQL// com.cinema.dao.impl.ShowDaoImpl public ListShow findShowsByDate(String date) { String sql SELECT s.id, s.movie_id, m.name as movie_name, s.time, s.hall_id, h.name as hall_name, s.price, s.status, s.seats_total, s.seats_sold FROM show s JOIN movie m ON s.movie_id m.id JOIN hall h ON s.hall_id h.id WHERE DATE(s.time) ? AND s.status 1 // status1表示已发布 ORDER BY s.time; return queryRunner.query(sql, new BeanListHandler(Show.class), date); }参数说明DATE(s.time) ?MySQL函数提取日期部分适配datetime类型字段如2024-05-20 14:30:00→2024-05-20queryRunner是Apache Commons DbUtils的QueryRunner实例自动处理PreparedStatement参数绑定与结果集映射BeanListHandler将结果集自动转为ListShow要求Show类字段名与SQL别名一致如m.name as movie_name→Show.movieName注意若出现Column movie_name not found in ResultSet检查Show类是否有private String movieName;及对应getter/setter且字段名严格匹配别名大小写敏感。3.3 座位选择前端JS动态渲染与后端库存校验的双重保障选座页selectSeat.jsp用AJAX加载座位图核心是seat.js// seat.js 片段 function loadSeats(showId) { $.get(/seat?showId showId, function(data) { let html ; data.forEach(function(seat) { let cls seat.status 0 ? seat-available : seat-occupied; // 0可选1已售 html div classseat ${cls}>// 关键库存校验必须在事务中完成 Connection conn null; try { conn JdbcUtils.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查场次剩余座位数SELECT ... FOR UPDATE String checkSql SELECT seats_sold, seats_total FROM show WHERE id ? FOR UPDATE; Show show queryRunner.query(conn, checkSql, new BeanHandler(Show.class), showId); if (show.getSeatsSold() selectedSeats.size() show.getSeatsTotal()) { throw new RuntimeException(座位不足请重新选择); } // 2. 更新座位状态UPDATE seat SET status1 WHERE id IN (...) String updateSql UPDATE seat SET status1 WHERE id IN ( placeholders ); queryRunner.update(conn, updateSql, params); // 3. 更新场次已售数量 String updateShowSql UPDATE show SET seats_sold seats_sold ? WHERE id ?; queryRunner.update(conn, updateShowSql, selectedSeats.size(), showId); conn.commit(); // 提交事务 } catch (Exception e) { if (conn ! null) conn.rollback(); // 回滚 request.setAttribute(msg, 下单失败 e.getMessage()); }避坑点FOR UPDATE是MySQL行锁防止并发下单超卖若用SELECT ... WHERE status0再UPDATE高并发下必然超卖。4. 避坑指南部署与调试中90%开发者踩过的5个深坑4.1 现象登录后跳转到空白页浏览器地址栏显示/index.jsp但页面无内容原因index.jsp中% page contentTypetext/html;charsetUTF-8 %缺失或web.xml中jsp-config未设置page-encodingUTF-8/page-encoding导致JSP编译时中文字符解析失败整个页面输出为空。解决在所有JSP顶部添加% page contentTypetext/html;charsetUTF-8 %并在web.xml中补充jsp-config jsp-property-group url-pattern*.jsp/url-pattern page-encodingUTF-8/page-encoding /jsp-property-group /jsp-config4.2 现象MySQL报错Communications link failure但本地Navicat能连原因Tomcat运行在Linux服务器时localhost解析为IPv6地址::1而MySQL默认只监听IPv4的127.0.0.1。解决修改MySQL配置my.cnf添加bind-address 0.0.0.0或127.0.0.1重启MySQL或在JDBC URL中强制指定IPv4jdbc:mysql://127.0.0.1:3306/cinema?...4.3 现象选座时部分座位显示灰色不可选但数据库status0原因seat表中status字段为TINYINT(1)Java中ResultSet.getInt(status)返回0/1但JSP中EL表达式${seat.status 0}将字符串0与整数0比较结果恒为false。解决统一用Integer.valueOf(seat.getStatus()).equals(0)或在DAO层将status转为Booleanseat.setStatus(rs.getInt(status) 0 ? true : false);4.4 现象IDEA中运行报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL Connector/J 8.0驱动类名已改为com.mysql.cj.jdbc.Driver而项目代码仍用旧类名。解决两种方案任选其一方案A降级驱动下载mysql-connector-java-5.1.47.jar兼容com.mysql.jdbc.Driver方案B升级代码在db.properties中改drivercom.mysql.cj.jdbc.Driver并在JDBC URL末尾加allowPublicKeyRetrievaltrueuseSSLfalse4.5 现象后台管理页新增影片后前台排片列表不显示新影片原因MovieDaoImpl.java中插入影片的SQL未指定id字段设为AUTO_INCREMENT但INSERT INTO movie VALUES (?,?,?,?)用了全字段插入导致id被赋值为0违反主键约束插入失败但未抛异常。解决显式列出字段名排除idString sql INSERT INTO movie (name, director, actor, duration) VALUES (?,?,?,?); queryRunner.update(conn, sql, name, director, actor, duration);5. 进阶改造三个低成本高回报的实战升级点附可抄代码5.1 升级点一用Filter实现统一登录拦截替代每个Servlet里写if判断当前登录校验散落在LoginServlet、OrderServlet等各处易遗漏。新建LoginCheckFilter.java// com.cinema.filter.LoginCheckFilter public class LoginCheckFilter implements Filter { private static final String[] EXCLUDE_PATHS {/login.jsp, /login, /register.jsp, /register, /css/, /js/, /images/}; Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 白名单路径放行 for (String path : EXCLUDE_PATHS) { if (uri.contains(path)) { chain.doFilter(req, resp); return; } } // 检查Session HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }在web.xml中注册filter filter-nameLoginCheckFilter/filter-name filter-classcom.cinema.filter.LoginCheckFilter/filter-class /filter filter-mapping filter-nameLoginCheckFilter/filter-name url-pattern/*/url-pattern /filter-mapping效果所有非白名单路径自动校验登录态/admin/movieList.jsp无需再写if(sessionnull) redirect。5.2 升级点二为订单表添加乐观锁字段解决并发更新冲突当前订单状态更新如“已支付”→“已出票”直接UPDATE order SET status2 WHERE id?高并发下可能覆盖彼此。在order表中新增version INT DEFAULT 0字段并修改更新逻辑// OrderDaoImpl.java public int updateStatus(Long orderId, String newStatus, int oldVersion) { String sql UPDATE order SET status?, versionversion1 WHERE id? AND version?; return queryRunner.update(conn, sql, newStatus, orderId, oldVersion); }调用时// OrderServlet.java int updated orderDao.updateStatus(orderId, 2, order.getVersion()); if (updated 0) { throw new RuntimeException(订单已被其他操作更新请刷新重试); }价值不用悲观锁SELECT FOR UPDATE阻塞性能用版本号实现轻量级并发控制。5.3 升级点三用Log4j2替换System.out.println实现日志分级与文件输出项目中大量System.out.println(DEBUG: xxx)无法关闭且不记录时间线程。引入log4j2.xml!-- src/main/resources/log4j2.xml -- Configuration statusWARN Appenders File nameFileAppender fileNamelogs/cinema.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Root levelinfo AppenderRef refFileAppender/ /Root /Loggers /Configuration在Servlet中使用import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class LoginServlet extends HttpServlet { private static final Logger logger LogManager.getLogger(LoginServlet.class); protected void doPost(...) { logger.info(用户 {} 尝试登录, username); // 自动输出到logs/cinema.log if (user null) { logger.warn(登录失败用户名 {} 不存在或密码错误, username); } } }参数说明levelinfo表示只记录INFO及以上日志DEBUG被屏蔽%d{HH:mm:ss.SSS}输出毫秒级时间戳[%t]显示线程名便于排查并发问题。我带过的实习生第一个月都在改bug第二个月开始主动加Filter、加日志、加事务——不是因为他们更聪明而是他们愿意把一个老项目从头到尾敲一遍、改一遍、debug一遍。这套影院系统就像一辆手动挡老吉普离合、油门、档位全得自己踩、自己挂、自己听声辨速。现在你手里有了钥匙也知道了哪个档位容易熄火、哪段坡道要补油。希望帮到你。本文还有配套的精品资源点击获取
返回列表