
简介这是一套基于Java Web主流技术栈开发的电影在线观看系统完整源码面向Java初学者与SSM框架学习者帮助掌握企业级Web应用从后端架构到前端交互的全流程实现。资源包含322个文件涵盖35个核心Java业务类、29个JSP页面、31个XML配置与映射文件、30个JS脚本及15个CSS样式文件辅以Layui前端组件、MySQL建表SQL及Maven项目配置结构清晰、模块完整。压缩包仅4.32MB轻量易导入适配Eclipse/IDEA等主流开发环境。已有551人学习下载可直接运行体验用户登录、影片浏览、播放记录、后台管理等典型功能并深入理解Controller-Service-Mapper分层设计、Spring事务管理、JSP动态页面渲染及Layui响应式布局实践。1. 为什么还在用 JavaSSMJSP 做电影在线观看系统不是过时而是稳得有道理你可能刚刷完 Spring Boot 自动装配的源码转头就看到毕设群里有人发“JavaSSMJSP 电影系统源码”心里一愣这组合不是十年前的老古董吗但现实是——高校实训平台、中小型企业内部管理系统、教育类项目交付现场SSMJSP 仍是高频落地组合。它不追求云原生弹性伸缩但能用一套清晰分层Controller→Service→Dao、低学习门槛JSP 天然嵌入 HTML、零前端构建LayUI 提供即用组件的方式在两周内跑通「用户登录→影片列表→详情页→播放页→评论提交」全链路。尤其当团队里有熟悉 JSP 标签库的讲师、MySQL 表结构需快速映射到 JSP 表单、部署环境限定为 Tomcat 8.5JDK 8 时这套技术栈反而比强行套 Spring Boot Thymeleaf Vue 更少踩坑。本文不讲“为什么不用新框架”只聚焦如何让这个组合真正跑起来、查得清、改得动、扩得稳。2. 搭建 SSM 基础骨架从 Maven 依赖到 Spring 容器初始化2.1 Maven 依赖配置精准控制版本冲突边界SSM 项目最常卡在依赖版本不兼容上。Spring 4.3.x 与 MyBatis 3.4.x 可以共存但若引入spring-webmvc5.0JSP EL 表达式解析会报javax.el.ExpressionFactory找不到类。因此pom.xml必须显式锁定核心版本properties spring.version4.3.29.RELEASE/spring.version mybatis.version3.4.6/mybatis.version mysql.connector.version5.1.47/mysql.connector.version jstl.version1.2/jstl.version /properties dependencies !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis 整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL 驱动注意5.1.x 系列兼容 JDK 88.x 需要 mysql-connector-java 8.0 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.connector.version}/version /dependency !-- JSTL 支持 JSP 标签 -- dependency groupIdjstl/groupId artifactIdjstl/artifactId version${jstl.version}/version /dependency /dependencies提示mysql-connector-java必须选 5.1.47 而非 8.0.x否则Class.forName(com.mysql.jdbc.Driver)在 JSP 中会抛ClassNotFoundException。这是 JSP 页面直连数据库时的经典陷阱不是驱动没加载而是类名变了8.0 改为com.mysql.cj.jdbc.Driver而老式 JSP 代码未同步更新。2.2 Spring 配置文件分层applicationContext.xml 与 spring-mvc.xml 的职责切割SSM 项目必须拆成两个 XML 配置文件否则事务失效、Bean 注入失败。applicationContext.xml管理 Service 和 Dao 层 Beanspring-mvc.xml专管 Controller 和视图解析器!-- applicationContext.xml -- context:component-scan base-packagecom.movie.service,com.movie.dao/ bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.movie.dao/ /bean!-- spring-mvc.xml -- context:component-scan base-packagecom.movie.controller use-default-filtersfalse context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean注意InternalResourceViewResolver的prefix必须指向/WEB-INF/jsp/这是 Tomcat 安全机制要求——JSP 文件不能放在 WebRoot 下被直接 URL 访问必须藏在WEB-INF内部。若写成/jsp/浏览器访问http://localhost:8080/movie/list.jsp会 404但 Controller 返回list却能正常渲染因为 DispatcherServlet 会自动拼接前缀后缀。2.3 web.xml 初始化顺序Listener → Filter → Servlet 的执行链Tomcat 启动时web.xml的加载顺序决定容器能否识别 Spring 上下文。错误顺序会导致ApplicationContext为空!-- web.xml -- listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param !-- 编码过滤器必须在 DispatcherServlet 之前 -- filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping关键点ContextLoaderListener加载的是根上下文Service/DaoDispatcherServlet加载的是子上下文Controller。子上下文能访问父上下文的 Bean但反之不行。若把ContextLoaderListener放在DispatcherServlet后面Controller 里的AutowiredService 就会为 null。3. LayUI 与 JSP 深度集成实现电影列表页的响应式渲染3.1 LayUI 表格组件绑定 JSP 数据避免 JSON 序列化陷阱LayUI 表格默认期望 JSON 数组但 JSP 直接输出 List 对象会触发toString()导致[Movie1a2b3c]这种乱码。正确做法是在 Controller 中将 List 转为 JSON 字符串再由 JSP 输出到 JavaScript 变量// MovieController.java RequestMapping(/list) public String list(Model model) { ListMovie movies movieService.findAll(); // 使用 Jackson 转 JSON需引入 jackson-databind ObjectMapper mapper new ObjectMapper(); try { String json mapper.writeValueAsString(movies); model.addAttribute(movieJson, json); // 传给 JSP } catch (JsonProcessingException e) { e.printStackTrace(); } return movie/list; }!-- /WEB-INF/jsp/movie/list.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % html head link relstylesheet href${pageContext.request.contextPath}/static/layui/css/layui.css /head body div classlayui-container table classlayui-table lay-data{id:movieTable} lay-filtermovieFilter thead tr th lay-data{field:id, width:80}ID/th th lay-data{field:title, width:200}片名/th th lay-data{field:director, width:120}导演/th th lay-data{field:year, width:100}年份/th th lay-data{field:score, width:100}评分/th /tr /thead /table /div script src${pageContext.request.contextPath}/static/layui/layui.js/script script layui.use([table], function(){ var table layui.table; // 注意此处 movieJson 是 JSP 输出的字符串需 JSON.parse var data JSON.parse(${movieJson}); table.render({ elem: #movieTable, data: data, cols: [[ {field:id, title:ID}, {field:title, title:片名}, {field:director, title:导演}, {field:year, title:年份}, {field:score, title:评分} ]] }); }); /script /body /html逻辑说明JSON.parse(${movieJson})中的${movieJson}是 JSP 表达式会在服务端渲染成纯 JSON 字符串如[{\id\:1,\title\:\阿凡达\}]再由浏览器 JS 解析。若直接写data: ${movieJson}JSP 会尝试把字符串当 JS 对象字面量解析缺少引号导致语法错误。3.2 LayUI 表单提交与 JSP 参数接收处理中文乱码与空值校验用户在 LayUI 弹窗中填写电影信息表单提交到 Controller需确保中文不乱码、空字段不插入 NULL!-- /WEB-INF/jsp/movie/add.jsp -- form classlayui-form action${pageContext.request.contextPath}/movie/save methodpost div classlayui-form-item label classlayui-form-label片名/label div classlayui-input-block input typetext nametitle required lay-verifyrequired placeholder请输入片名 classlayui-input /div /div div classlayui-form-item label classlayui-form-label导演/label div classlayui-input-block input typetext namedirector required lay-verifyrequired placeholder请输入导演 classlayui-input /div /div div classlayui-form-item div classlayui-input-block button classlayui-btn lay-submit lay-filteraddForm立即提交/button /div /div /form// MovieController.java RequestMapping(value /save, method RequestMethod.POST) public String save(Movie movie, Model model) { // JSP 表单提交时若 director 为空movie.setDirector(null) → 插入数据库为 NULL // 但业务要求空导演显示为未知需手动处理 if (movie.getDirector() null || movie.getDirector().trim().isEmpty()) { movie.setDirector(未知); } if (movie.getYear() null) { movie.setYear(2024); // 默认年份 } movieService.save(movie); return redirect:/movie/list; }参数说明Movie movie由 Spring MVC 的ModelAttributeMethodProcessor自动绑定其原理是调用request.getParameter(title)获取值再通过反射调用movie.setTitle()。若 JSP 表单字段名与 Movie 实体类属性名一致区分大小写即可完成绑定。但空字符串不会自动转为null而是传入空字符串因此isEmpty()判断比 null更安全。3.3 LayUI 分页与 MySQL LIMIT 语句联动手写分页 SQL 而非依赖 PageHelperPageHelper 在 SSM 中易与事务管理器冲突且 JSP 页面需手动计算总页数。更可控的做法是 Controller 计算 offsetMapper 写原生 SQL// MovieController.java RequestMapping(/listPage) public String listPage(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int limit, Model model) { int offset (page - 1) * limit; ListMovie movies movieService.findByPage(offset, limit); int total movieService.countAll(); // 单独查总数 model.addAttribute(movies, movies); model.addAttribute(total, total); model.addAttribute(page, page); model.addAttribute(limit, limit); return movie/list_page; }!-- MovieMapper.xml -- select idfindByPage resultTypeMovie SELECT id, title, director, year, score FROM movie ORDER BY id DESC LIMIT #{offset}, #{limit} /select select idcountAll resultTypeint SELECT COUNT(*) FROM movie /select!-- /WEB-INF/jsp/movie/list_page.jsp -- div idpage classlayui-box/div script layui.use([laypage], function(){ var laypage layui.laypage; laypage.render({ elem: page, count: ${total}, // JSP 输出总数 limit: ${limit}, curr: ${page}, theme: #1E9FFF, jump: function(obj, first){ if(!first){ window.location.href ${pageContext.request.contextPath}/movie/listPage?page obj.curr limit obj.limit; } } }); }); /script关键细节jump回调中!first判断排除首次渲染避免页面加载时重复跳转。obj.curr是 LayUI 计算出的当前页码obj.limit是每页条数拼接 URL 触发 Controller 重新查询形成无刷新分页效果。4. MySQL 数据库设计电影系统核心表结构与索引优化策略4.1 三张主表的字段设计与外键约束电影在线观看系统至少需movie影片主表、user用户表、comment评论表三张表。设计时需兼顾查询效率与数据一致性表名字段名类型是否允许 NULL说明movieidBIGINT PK AINO主键自增titleVARCHAR(200)NO片名加索引directorVARCHAR(100)YES导演可为空yearINTNO年份范围 1900-2030scoreDECIMAL(2,1)YES评分0.0~10.0cover_urlVARCHAR(500)YES封面图地址useridBIGINT PK AINO主键usernameVARCHAR(50) UNINO用户名唯一passwordVARCHAR(100)NOBCrypt 加密密码emailVARCHAR(100)YES邮箱用于找回密码commentidBIGINT PK AINO主键movie_idBIGINTNO外键关联 movie.iduser_idBIGINTNO外键关联 user.idcontentTEXTNO评论内容create_timeDATETIMENO创建时间默认 CURRENT_TIMESTAMP-- 创建 movie 表 CREATE TABLE movie ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 片名, director varchar(100) DEFAULT NULL COMMENT 导演, year int(4) NOT NULL COMMENT 年份, score decimal(2,1) DEFAULT NULL COMMENT 评分, cover_url varchar(500) DEFAULT NULL COMMENT 封面图地址, PRIMARY KEY (id), KEY idx_title (title) USING BTREE, KEY idx_year_score (year,score) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT影片主表; -- 创建 comment 表含外键 CREATE TABLE comment ( id bigint(20) NOT NULL AUTO_INCREMENT, movie_id bigint(20) NOT NULL, user_id bigint(20) NOT NULL, content text NOT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY fk_movie_id (movie_id), KEY fk_user_id (user_id), CONSTRAINT fk_movie_id FOREIGN KEY (movie_id) REFERENCES movie (id) ON DELETE CASCADE, CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表;索引说明idx_title加速按片名模糊搜索WHERE title LIKE %复仇%idx_year_score是联合索引支持按年份排序取高分影片ORDER BY year DESC, score DESC LIMIT 10避免 filesort。4.2 防止脏读的事务配置在 Service 层标注 Transactional用户对同一影片多次点击“收藏”按钮若不加事务控制可能插入多条重复记录。Transactional必须加在 Service 方法上而非 ControllerService public class MovieService { Autowired private MovieMapper movieMapper; Autowired private FavoriteMapper favoriteMapper; Transactional(rollbackFor Exception.class) public void toggleFavorite(Long userId, Long movieId) { // 先查是否已收藏 Favorite existing favoriteMapper.findByUserAndMovie(userId, movieId); if (existing ! null) { // 已存在则删除 favoriteMapper.delete(existing.getId()); } else { // 不存在则新增 Favorite fav new Favorite(); fav.setUserId(userId); fav.setMovieId(movieId); fav.setCreateTime(new Date()); favoriteMapper.insert(fav); } } }!-- applicationContext.xml 中启用事务注解 -- tx:annotation-driven transaction-managertransactionManager/ bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean注意Transactional默认只对RuntimeException回滚。若业务异常继承Exception如BusinessException必须显式声明rollbackFor BusinessException.class否则事务不回滚导致数据不一致。4.3 MySQL 字符集与连接配置解决 JSP 中文显示乱码即使数据库建表用utf8mb4若 JDBC 连接未指定字符集JSP 页面仍会显示??。spring-datasource.xml中的url必须带参数bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/movie_db?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneGMT%2B8amp;allowPublicKeyRetrievaltrueamp;useSSLfalse/ property nameusername valueroot/ property namepassword value123456/ /bean参数说明useUnicodetrue启用 Unicode 编码支持characterEncodingutf8客户端连接使用 UTF-8注意MySQL 5.5 实际应为utf8mb4但 JDBC 驱动兼容utf8参数serverTimezoneGMT%2B8解决The server time zone value XXX is unrecognized错误allowPublicKeyRetrievaltrueMySQL 8.0 必需否则连接拒绝5. Intellij IDEA 中 JSP 开发调试技巧解决 color scheme 缺失与热部署失效问题5.1 恢复 JSP 语法高亮手动配置 Editor Color SchemeIntellij IDEA 2022 默认关闭 JSP 支持导致.jsp文件无颜色标记、EL 表达式不识别。需手动启用并配置File → Settings → Languages Frameworks → Java EE → JSP✅ 勾选Enable JSP supportFile → Settings → Editor → Color Scheme → General→ 展开Code → Injected language fragment→ 右侧勾选Show preview in tooltipFile → Settings → Editor → Color Scheme → HTML→ 找到JSP Scripting和JSP Tag→ 分别设置字体颜色如JSP Scripting设为蓝色#0000FFJSP Tag设为绿色#008000验证方法新建test.jsp输入% new java.util.Date() %若日期部分变蓝说明 JSP 脚本高亮生效输入c:forEach若标签变绿说明 JSTL 标签识别成功。5.2 Tomcat 热部署失效的三大修复路径修改 JSP 后刷新页面无变化常见于以下场景场景原因修复命令/操作JSP 修改不生效IDEA 默认禁用 JSP 自动编译Settings → Build → Compiler → Build project automatically✅ 勾选Help → Find Action → Registry → compiler.automake.allow.when.app.running✅ 勾选Java 类修改不生效Tomcat 未开启热替换Run → Edit Configurations → Server → On ‘Update’ action → Update classes and resourcesOn frame deactivation → Update resources静态资源CSS/JS不更新浏览器强缓存在web.xml中添加过滤器xmlbrfilterbr filter-nameNoCacheFilter/filter-namebr filter-classcom.movie.filter.NoCacheFilter/filter-classbr/filterbrfilter-mappingbr filter-nameNoCacheFilter/filter-namebr url-pattern*.css/url-patternbr/filter-mappingbr并在NoCacheFilter.java中设置response.setHeader(Cache-Control, no-cache, no-store, must-revalidate)5.3 查询 MySQL 数据库连接状态定位连接池耗尽问题当系统运行一段时间后出现Connection refused或Too many connections需检查 MySQL 当前连接数# 登录 MySQL mysql -u root -p # 查看当前所有连接 SHOW PROCESSLIST; # 查看最大连接数限制 SHOW VARIABLES LIKE max_connections; # 查看当前活跃连接数 SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE COMMAND ! Sleep; # 杀死长时间 Sleep 连接ID 为 1234 KILL 1234;优化建议在dataSource配置中添加连接池参数以 Druid 为例bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namemaxActive value20/ !-- 最大活跃连接数 -- property nameminIdle value5/ !-- 最小空闲连接数 -- property nametimeBetweenEvictionRunsMillis value60000/ !-- 检测间隔 -- property nameminEvictableIdleTimeMillis value300000/ !-- 最小空闲时间 -- /bean若maxActive设为 20但SHOW PROCESSLIST显示 30 连接说明连接未正确归还池中需检查 DAO 层是否漏写conn.close()或 MyBatis 是否配置了closeConnectiontrue。本文还有配套的精品资源点击获取