SSM+JSP+Layui电影系统实战:从搭建到优化 📅 发布时间:2026/9/10 2:44:26 👁 浏览次数: 简介这是一套基于SSM框架开发的电影在线观看系统实战项目面向Java初学者与Web开发入门者帮助掌握企业级Java Web应用的完整开发流程。项目采用JSP前端页面、SpringSpringMVCMyBatis后端架构集成Layui UI组件与MySQL数据库覆盖用户管理、影片展示、播放记录、历史浏览等核心功能模块适合课程设计、毕业设计或技术栈整合练习。资源包共322个文件包含35个Java业务类、31个XML配置与映射文件、29个JSP页面、30个JS交互脚本、23个JPG与15个PNG素材图以及SQL建表语句、Maven依赖配置等关键文件整体压缩后仅4.32MB轻量易部署。已有551人学习下载提供可直接运行的完整工程结构、清晰分层的Controller/Service/Mapper代码逻辑及典型业务实现如VideoController、UserServiceImple、History数据操作便于理解MVC职责划分与前后端协同机制。1. 为什么一个“电影在线观看系统”要用 SSM JSP Layui它不是过时了吗很多人看到“Java SSM JSP Layui”第一反应是这组合是不是该进博物馆了尤其在 Spring Boot 满天飞、Vue/React 单页应用成标配的今天还用 JSP 渲染页面、Layui 做前端、XML 配置 Spring MVC听起来像在修一台还在跑 Windows XP 的服务器。但现实恰恰相反——大量高校课程设计、企业内部管理后台、中小型政务或文化类平台仍在稳定使用这套技术栈。它不是“过时”而是“精准匹配”开发周期短、部署成本低、运维门槛小、教师能讲透、学生能跑通、甲方能验收。比如这个“电影在线观看系统”核心诉求从来不是高并发流媒体分发而是用户能注册登录、按分类/关键词查片、点开详情页看海报简介播放地址、管理员能增删影片和分类、所有操作有完整 CRUD 和权限隔离。SSM 提供清晰的分层控制Spring 管 Bean、SpringMVC 接请求、MyBatis 操作 MySQLJSP 在服务端完成模板渲染避免前后端分离带来的跨域调试和接口联调压力Layui 则用极简 JS/CSS 快速搭出可用的表格、弹窗、表单和视频播放容器。对 Java 初学者而言这套组合就像一辆带离合器的手动挡教练车——不炫技但每一步都踩得到底。2. 搭建 SSM 基础骨架从 Maven 依赖到 MyBatis 映射文件生成2.1 Maven 依赖配置选对版本是避坑第一步Maven 是整个项目的构建中枢依赖版本冲突是新手最常卡住的环节。本项目需严格匹配 JDK 8JSP 兼容性要求、Spring 4.3.x与 SSM 组合最稳、MyBatis 3.4.x、MySQL Connector/J 5.1.47非 8.x避免serverTimezone报错。pom.xml中关键依赖如下properties spring.version4.3.30.RELEASE/spring.version mybatis.version3.4.6/mybatis.version mysql-connector-java.version5.1.47/mysql-connector-java.version junit.version4.12/junit.version /properties dependencies !-- Spring Core -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency !-- MyBatis-Spring 整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql-connector-java.version}/version /dependency !-- JSP Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependencies提示javax.servlet-api的scope必须设为provided否则 Tomcat 启动时会因类重复加载报java.lang.LinkageErrorMySQL 驱动若用 8.x 版本连接 URL 必须加?serverTimezoneGMT%2B8useSSLfalse而本项目用 5.1.47 可省去此参数降低初学者配置复杂度。2.2 MyBatis Generator 自动生成实体与 Mapper拒绝手写样板代码电影系统涉及movie影片、category分类、user用户、comment评论等多张表手动编写 POJO、Mapper 接口、XML 映射文件极易出错且耗时。MyBatis GeneratorMBG是标准解法。在pom.xml中添加插件配置plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.0/version configuration configurationFilesrc/main/resources/generatorConfig.xml/configurationFile verbosetrue/verbose overwritetrue/overwrite /configuration /plugingeneratorConfig.xml中指定数据库连接与表规则jdbcConnection driverClasscom.mysql.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/movie_db?characterEncodingutf8 userIdroot password123456/ table tableNamemovie domainObjectNameMovie enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ table tableNamecategory domainObjectNameCategory /执行mvn mybatis-generator:generate后自动生成Movie.java、MovieMapper.java、MovieMapper.xml。其中 XML 文件里resultMap已按字段名自动映射无需手动写idBaseResultMap冗余定义select标签中 SQL 语句已含WHERE条件占位符后续只需在 Service 层传入MovieExample对象即可动态拼接查询条件。2.3 Spring 与 MyBatis 整合SqlSessionFactoryBean 是关键枢纽SSM 整合的核心在于让 Spring 容器接管 MyBatis 的SqlSessionFactory。在spring-dao.xml中配置!-- 数据源配置 -- bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/movie_db?characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /bean !-- SqlSessionFactoryBean -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean !-- Mapper 扫描器 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.movie.dao/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean注意MapperScannerConfigurer的basePackage必须与 DAO 接口所在包路径完全一致如com.movie.dao.MovieMapper否则 Spring 启动时报No qualifying bean of type MovieMappersqlSessionFactoryBeanName用字符串引用而非ref这是 Spring 早期版本的兼容写法避免FactoryBean初始化顺序问题。3. JSP 页面与 Layui 前端协同实现视频播放与响应式布局3.1 JSP 中嵌入 MP4 视频播放原生video标签就够用JSP 不是只能做静态展示它能直接读取后端传来的影片路径并渲染 HTML5video标签。在movie-detail.jsp中% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title${movie.title} - 电影详情/title link relstylesheet href${pageContext.request.contextPath}/layui/css/layui.css /head body div classlayui-container h1${movie.title}/h1 div classlayui-row div classlayui-col-md8 !-- 视频播放区域 -- video width100% height400 controls autoplay source src${pageContext.request.contextPath}${movie.videoPath} typevideo/mp4 您的浏览器不支持 video 标签。 /video /div div classlayui-col-md4 h3影片信息/h3 pstrong导演/strong${movie.director}/p pstrong主演/strong${movie.actors}/p pstrong时长/strong${movie.duration} 分钟/p /div /div /div script src${pageContext.request.contextPath}/layui/layui.js/script /body /html逻辑说明movie.videoPath是数据库中存储的相对路径如/videos/inception.mp4JSP 通过${pageContext.request.contextPath}拼接成完整 URL确保资源可被 Web 容器Tomcat正确解析controls属性启用播放控件autoplay在部分浏览器中可能被策略阻止实际部署时建议移除。3.2 Layui 表格渲染电影列表用>div classlayui-card div classlayui-card-header影片列表/div div classlayui-card-body table idmovieTable lay-filtermovieFilter/table /div /div script layui.use([table, form], function(){ var table layui.table; table.render({ elem: #movieTable, height: 500, url: ${pageContext.request.contextPath}/movie/list, // 后端返回 JSON 数组 page: true, cols: [[ {field:id, title:ID, width:80, sort: true}, {field:title, title:片名, minWidth:150}, {field:categoryName, title:分类, width:120}, {field:director, title:导演, width:120}, {field:duration, title:时长(分钟), width:100}, {fixed: right, title:操作, toolbar: #movieBar, width:150} ]] }); // 行工具栏事件 table.on(tool(movieFilter), function(obj){ var data obj.data; if(obj.event detail){ location.href ${pageContext.request.contextPath}/movie/detail?id data.id; } }); }); /script !-- 行工具栏模板 -- script typetext/html idmovieBar a classlayui-btn layui-btn-xs lay-eventdetail查看/a /script参数说明url指向 Controller 的RequestMapping(/movie/list)方法该方法返回ListMovieSpringMVC 自动序列化为 JSONtoolbar指向页面底部的script typetext/html模板实现“查看”按钮field名必须与 Java Bean 的 getter 方法名一致如getCategoryName()→categoryName否则列为空。3.3 Layui 日期控件限制最大日期为当天解决laydate.render()的常见误配电影系统中“上映时间”字段需限制用户不能选择未来日期。Layui 的laydate模块提供max参数但其值必须是时间戳格式而非字符串。在movie-add.jsp的日期输入框中div classlayui-form-item label classlayui-form-label上映时间/label div classlayui-input-block input typetext namereleaseDate idreleaseDate required lay-verifyrequired placeholderyyyy-MM-dd autocompleteoff classlayui-input /div /div script layui.use(laydate, function(){ var laydate layui.laydate; // 获取当前时间戳毫秒 var today new Date().getTime(); // 转换为 laydate 要求的 yyyy-MM-dd 字符串格式 var now new Date(today); var maxDate now.getFullYear() - (now.getMonth() 1).toString().padStart(2, 0) - now.getDate().toString().padStart(2, 0); laydate.render({ elem: #releaseDate, format: yyyy-MM-dd, max: maxDate, // 关键必须是字符串不是时间戳 trigger: click }); }); /script注意max参数若传入today毫秒数laydate 会解析失败导致控件无法打开必须转换为yyyy-MM-dd格式字符串。padStart(2, 0)保证月份和日期为两位数避免2024-1-5这类非法格式。4. SSM 层间协作Controller 接口设计与 Service 事务边界4.1 Controller 层用ResponseBody返回 JSON而非跳转 JSP虽然整体是 JSP 渲染但部分交互如搜索、分页、点赞需异步返回数据。Controller 方法应明确区分返回视图用String返回数据用ResponseBody。例如影片搜索接口Controller RequestMapping(/movie) public class MovieController { Autowired private MovieService movieService; // 返回 JSP 页面 RequestMapping(value /list, method RequestMethod.GET) public String listPage(Model model) { ListMovie movies movieService.selectAll(); model.addAttribute(movies, movies); return movie-list; // 对应 /WEB-INF/jsp/movie-list.jsp } // 返回 JSON 数据供 Layui table 使用 RequestMapping(value /list, method RequestMethod.POST) ResponseBody public MapString, Object listForTable( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer limit, RequestParam(required false) String keyword) { PageHelper.startPage(page, limit); ListMovie movies movieService.selectByKeyword(keyword); PageInfoMovie pageInfo new PageInfo(movies); MapString, Object result new HashMap(); result.put(code, 0); result.put(msg, ); result.put(count, (int) pageInfo.getTotal()); result.put(data, pageInfo.getList()); return result; } }逻辑说明ResponseBody注解使 SpringMVC 调用MappingJackson2HttpMessageConverter将 Map 序列化为 JSONPageHelper.startPage()是 MyBatis 分页插件它在 SQL 执行前拦截并注入LIMIT子句PageInfo包含总记录数、当前页数据等count字段对应 Layuitable.render()的count属性用于计算总页数。4.2 Service 层Transactional的粒度控制与异常处理电影系统的业务逻辑如“用户收藏影片”需保证user_fav表插入与movie表fav_count字段更新的原子性。Service 方法必须用Transactional声明事务且粒度要细Service public class MovieService { Autowired private MovieMapper movieMapper; Autowired private UserFavMapper userFavMapper; Transactional(rollbackFor Exception.class) public void addFavorite(Integer userId, Integer movieId) throws Exception { // 1. 检查是否已收藏 UserFavExample example new UserFavExample(); example.createCriteria().andUserIdEqualTo(userId).andMovieIdEqualTo(movieId); long count userFavMapper.countByExample(example); if (count 0) { throw new Exception(已收藏过该影片); } // 2. 插入收藏记录 UserFav fav new UserFav(); fav.setUserId(userId); fav.setMovieId(movieId); fav.setCreateTime(new Date()); userFavMapper.insert(fav); // 3. 更新影片收藏数 Movie movie new Movie(); movie.setId(movieId); movie.setFavCount(movieMapper.selectByPrimaryKey(movieId).getFavCount() 1); movieMapper.updateByPrimaryKeySelective(movie); } }参数说明rollbackFor Exception.class确保所有异常都触发回滚默认只回滚RuntimeExceptionupdateByPrimaryKeySelective只更新非 null 字段避免覆盖title、director等其他属性countByExample是 MBG 生成的统计方法比手写SELECT COUNT(*)更安全。4.3 MyBatis 动态 SQL 实现模糊搜索if与concat的组合用法影片搜索需支持按片名、导演、主演多字段模糊匹配。在MovieMapper.xml中select idselectByKeyword parameterTypestring resultTypeMovie SELECT m.*, c.name as categoryName FROM movie m LEFT JOIN category c ON m.category_id c.id WHERE 11 if testkeyword ! null and keyword ! AND ( m.title LIKE CONCAT(%, #{keyword}, %) OR m.director LIKE CONCAT(%, #{keyword}, %) OR m.actors LIKE CONCAT(%, #{keyword}, %) ) /if ORDER BY m.id DESC /select注意CONCAT(%, #{keyword}, %)是 MySQL 函数避免在 Java 层拼接%导致 SQL 注入if标签确保keyword为空时不执行WHERE条件防止WHERE 11 AND ()语法错误resultTypeMovie指定结果映射到Movie类categoryName字段通过别名自动注入到Movie.categoryName属性。5. MySQL 数据库设计与性能优化索引、字符集与外键约束5.1 电影系统核心表结构movie与category的关联设计MySQL 表设计直接影响查询效率与数据一致性。movie表主键为idcategory_id作为外键关联category表且必须建立索引CREATE TABLE category ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 分类名称, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT影片分类表; CREATE TABLE movie ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 片名, category_id int(11) NOT NULL COMMENT 分类ID, director varchar(100) DEFAULT NULL COMMENT 导演, actors varchar(500) DEFAULT NULL COMMENT 主演, duration int(11) DEFAULT NULL COMMENT 时长(分钟), video_path varchar(200) NOT NULL COMMENT 视频文件路径, fav_count int(11) DEFAULT 0 COMMENT 收藏数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_category_id (category_id) USING BTREE, -- 关键为外键建索引 CONSTRAINT fk_movie_category FOREIGN KEY (category_id) REFERENCES category (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT影片主表;提示KEY idx_category_id是必须的否则JOIN category查询时会全表扫描CHARSETutf8mb4支持 emoji 和生僻字避免utf8在 MySQL 5.7 中实际只支持 3 字节 UTF-8 的陷阱FOREIGN KEY约束保证movie.category_id值必须存在于category.id中删除分类前需先清空关联影片。5.2 查询优化实战用EXPLAIN分析慢查询并添加复合索引当用户按“分类关键词”搜索时原始 SQL 可能变慢。例如SELECT * FROM movie m LEFT JOIN category c ON m.category_id c.id WHERE c.name 科幻 AND m.title LIKE %盗梦%;执行EXPLAIN查看执行计划EXPLAIN SELECT * FROM movie m LEFT JOIN category c ON m.category_id c.id WHERE c.name 科幻 AND m.title LIKE %盗梦%;若type为ALL全表扫描说明未命中索引。优化方案是创建复合索引-- 在 movie 表上创建 (category_id, title) 复合索引 ALTER TABLE movie ADD INDEX idx_cat_title (category_id, title); -- 在 category 表上为 name 字段单独建索引 ALTER TABLE category ADD INDEX idx_name (name);逻辑说明idx_cat_title让WHERE category_id ? AND title LIKE ?能走索引范围扫描idx_name加速c.name 科幻的等值查询复合索引遵循“最左前缀原则”category_id必须在title左侧否则title单独查询无法使用该索引。5.3 MySQL 安装与字符集配置my.cnf中的关键参数本地开发环境若未正确配置 MySQL 字符集会导致中文存入乱码、JSP 页面显示方块。在my.cnfWindows 下为my.ini中必须设置[client] default-character-set utf8mb4 [mysql] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci init_connectSET NAMES utf8mb4 skip-character-set-client-handshake true重启 MySQL 后执行SHOW VARIABLES LIKE character_set_%;确认所有值均为utf8mb4。此时CREATE DATABASE movie_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;创建的数据库才能完整支持中文、emoji 及四字节 UTF-8 字符。注意init_connect参数确保每个新连接自动执行SET NAMES utf8mb4避免在 JDBC URL 中重复加?characterEncodingutf8mb4skip-character-set-client-handshake强制服务端忽略客户端声明的字符集统一以utf8mb4处理这是解决乱码的终极保险。6. JSP 修改不生效与 Maven 编译问题定位与修复的三步法6.1 JSP 修改后页面无变化检查 Tomcat 的 work 目录与 JSP 编译缓存JSP 本质是 ServletTomcat 会将其编译为.class文件存于work/Catalina/localhost/[app-name]/目录下。修改 JSP 后未生效90% 情况是旧编译文件未被清除。解决步骤停止 Tomcat进入work目录删除整个Catalina文件夹清理 Maven 项目右键项目 →Maven→Update Project勾选Force Update of Snapshots/Releases重启 Tomcat观察控制台是否输出JspServlet重新编译日志如Compiling JSP: /movie-list.jsp。若仍无效检查web.xml中是否禁用了 JSP 自动重编译servlet servlet-namejsp/servlet-name servlet-classorg.apache.jasper.servlet.JspServlet/servlet-class init-param param-namedevelopment/param-name param-valuetrue/param-value !-- 必须为 true -- /init-param /servlet提示developmenttrue是开发模式开关设为false时 JSP 修改后需重启 Tomcat 才生效Eclipse 中若勾选了Project→Build Automatically则保存 JSP 时会自动触发编译无需手动刷新。6.2 Maven 依赖下载失败配置阿里云镜像仓库加速国内访问 Maven Central 仓库极慢常导致Could not transfer artifact错误。在~/.m2/settings.xml中配置阿里云镜像mirrors mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors同时在pom.xml中声明仓库非必需但更显式repositories repository idaliyun/id urlhttps://maven.aliyun.com/repository/public/url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /repository /repositories执行mvn clean compile -U-U强制更新快照观察日志中Downloading from aliyun是否出现确认依赖正从阿里云下载。6.3 运行时ClassNotFoundException排查WEB-INF/lib与 Maven 依赖冲突启动时报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet说明 Spring MVC 的 jar 未加载。原因通常是pom.xml中spring-webmvc依赖 scope 误设为test或providedEclipse 中Deployment Assembly未包含 Maven 依赖。验证方法展开项目Deployment Assembly右键项目 →Properties→Deployment Assembly确认Maven Dependencies已添加且Deploy Path为WEB-INF/lib。若缺失点击Add→Java Build Path Entries→Maven Dependencies→Finish。关键区别provided仅表示运行时由容器Tomcat提供适用于servlet-api而spring-webmvc必须打包进WEB-INF/lib否则 Tomcat 找不到DispatcherServlet类。此错误在 SSM 项目中高频出现本质是 Maven 作用域理解偏差。Layui 的laydate控件在点击后日历面板位置偏移可通过 CSS 覆盖.layui-laydate的top和left属性强制重定位或在laydate.render()中设置position: static并配合trigger元素的offset参数微调。本文还有配套的精品资源点击获取