JSP+Servlet+MySQL音乐管理系统实战 简介这是一套基于Java Web技术栈开发的在线音乐信息管理系统面向Java初学者与Web开发入门者提供前后端分离式功能实践案例。系统涵盖用户端歌曲检索、在线试听、下载及播放记录管理以及管理员端的歌曲增删改查、分类维护与文件上传等完整业务闭环适用于课程设计、毕业设计或技术练手场景。资源包共110个文件含29个JSP页面实现前后端交互逻辑、16个JAR依赖库、11个Java核心类如SongAction、ManagerDAO、ConnDB等、11个编译后Class文件及2个MP3示例音频辅以CSS、JavaScript、SQL建表脚本等整体压缩包大小为14.32MB。目前已有447人学习下载读者可直接部署运行快速掌握JSPServletMySQL典型三层架构实现方式并通过源码理解分页处理MyPagination.class、数据库连接池、Ajax异步请求及LRC歌词解析等实用细节。1. 为什么还在用 JSP 做音乐管理系统——一个被低估的“老派但能跑通”的全栈落地路径你点开这个标题大概率不是来怀旧的。你可能是刚接了个校内实训任务、毕业设计选题卡在“Java Web”方向或是被甲方一句“用 Java 做个后台管理”钉在原地翻遍 GitHub 却发现 Spring Boot 项目动辄 200 依赖、Vue 前端要配 webpack、Docker 还没装好就卡在 node-sass 编译失败……而真正能当天搭起数据库、写完登录页、上传一首 MP3 并列表展示的方案反而是这套看起来“过时”的组合JSP Servlet MySQL 原生 JavaScript。它不炫技但每一步都踩在教科书级的 Java Web 基础上——Servlet 处理请求流转、JSP 渲染动态页面、MySQL 存歌名/歌手/时长/封面路径、JavaScript 控制播放器 DOM 和表单校验。这不是复古是删掉所有中间层后的最小可行闭环你能亲手看到 request 怎么进、response 怎么出、SQL 怎么拼、DOM 怎么改。适合 Java 初学者建立完整链路认知也适合需要快速交付、无运维压力、纯内网部署的中小型音乐资源归档场景比如学院资料室、社团曲库、教师课件音频库。它不解决高并发、不兼容 PWA、不做微服务拆分——但它能让你在 4 小时内把一首《卡农》从本地上传、存进数据库、在网页列表里点击播放且所有代码都在一个 Tomcat 目录里可查、可调、可 debug。2. 搭建环境用最朴素的方式让 Tomcat 吃上 MySQL 驱动和 JSP 页面JSP 不是独立运行的它依赖 Servlet 容器Tomcat解析为 Java 类再编译执行而数据库操作靠 JDBC 驱动桥接。这两步看似基础却是新手最容易卡死的“第一道墙”。别急着下载最新版 Tomcat 或 MySQL——版本兼容性比新潮更重要。我实测过Tomcat 9.0.x MySQL 5.7.x mysql-connector-java 5.1.47.jar 是目前最稳的三角组合8.0 驱动对老 JSP 项目常报java.sql.SQLException: Unknown system variable query_cache_size。下面步骤严格按顺序走跳过任何一步都可能引发后续玄学报错。2.1 安装与验证 Tomcat确认 JSP 编译器已就位Tomcat 不是“安装”是解压即用。重点在于确认其内置的 Jasper JSP 引擎是否激活# 下载 apache-tomcat-9.0.83.tar.gzLinux或 zipWindows解压到无中文路径目录 # 启动前检查 JAVA_HOME 是否指向 JDK 8非 JREJDK 8u202 是最佳实践 echo $JAVA_HOME # 应输出 /usr/lib/jvm/java-8-openjdk-amd64 或类似路径 # 启动 cd apache-tomcat-9.0.83/bin ./startup.sh # Windows 用 startup.bat # 访问 http://localhost:8080 —— 若看到 Apache Tomcat 首页说明容器启动成功 # 关键验证创建 test.jsp 放入 webapps/ROOT/ # 内容% new java.util.Date() % # 访问 http://localhost:8080/test.jsp —— 若显示当前时间证明 JSP 编译器工作正常提示若页面空白或 404请检查webapps/ROOT/目录权限Linux 需 chmod -R 755、确认conf/server.xml中Connector port8080未被注释、且logs/catalina.out末尾无SEVERE: Error starting static Resources报错。2.2 配置 MySQL 连接池用 JNDI 而非硬编码驱动硬写Class.forName(com.mysql.jdbc.Driver)在 JSP 里是教学演示生产级必须用 JNDI 数据源——它由 Tomcat 管理连接生命周期避免每次请求都新建/销毁连接。配置分三步缺一不可放驱动包将mysql-connector-java-5.1.47.jar复制到apache-tomcat-9.0.83/lib/不是WEB-INF/lib/这是全局驱动位置声明数据源编辑conf/context.xml在Context标签内插入Resource namejdbc/musicDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/music_db?useUnicodetrueamp;characterEncodingUTF-8amp;serverTimezoneGMT%2B8 usernameroot passwordyour_password maxActive20 maxIdle10 minIdle5 initialSize5 removeAbandonedOnBorrowtrue removeAbandonedTimeout60 logAbandonedtrue/引用数据源在你的 Web 应用META-INF/context.xml若无则新建中添加?xml version1.0 encodingUTF-8? Context ResourceLink namejdbc/musicDB globaljdbc/musicDB typejavax.sql.DataSource/ /Context参数说明url中serverTimezoneGMT%2B8解决 MySQL 8.0 时区报错maxActive设为 20 是针对单机开发环境的合理上限removeAbandonedOnBorrowtrue是防连接泄漏的后悔药——当连接被借出超 60 秒未归还自动回收。2.3 创建数据库与表结构聚焦音乐核心字段拒绝过度设计音乐管理系统不需要用户等级、积分、评论等扩展字段。先建库再建表字段命名直白可读-- 创建数据库UTF8MB4 支持 emoji 封面名如 古典精选 CREATE DATABASE music_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE music_db; -- 创建音乐表id 自增主键cover_path 存相对路径便于 JSP 用 img src${music.coverPath} 直接渲染 CREATE TABLE music_info ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 歌曲标题, artist VARCHAR(50) NOT NULL COMMENT 歌手, album VARCHAR(100) DEFAULT COMMENT 专辑, duration TIME NOT NULL COMMENT 时长格式 HH:MM:SS, cover_path VARCHAR(200) DEFAULT COMMENT 封面图片路径如 upload/cover/1.jpg, audio_path VARCHAR(200) NOT NULL COMMENT 音频文件路径如 upload/audio/1.mp3, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, status TINYINT DEFAULT 1 COMMENT 状态1-启用0-下架 ); -- 插入测试数据确保 INSERT 语句末尾有分号 INSERT INTO music_info (title, artist, album, duration, cover_path, audio_path) VALUES (卡农, 乔治·温斯顿, 钢琴独奏集, 00:05:12, upload/cover/canon.jpg, upload/audio/canon.mp3);注意audio_path和cover_path存的是相对路径相对于 Web 应用根目录不是绝对磁盘路径。这样 JSP 页面才能用audio src${music.audioPath}正常加载——Tomcat 会自动映射upload/目录到webapps/ROOT/upload/。3. 前台页面用原生 JavaScript 控制播放器而非引入框架JSP 的优势在于服务端动态生成 HTML前端只需轻量 JS 交互。这里不碰 jQuery 或 Vue用纯 JS 实现三个刚需列表渲染、点击播放、进度条拖拽。关键点在于JSP 生成初始数据JS 只负责 DOM 操作和事件绑定绝不发起 AJAX 请求简化架构降低调试复杂度。3.1 JSP 动态生成音乐列表用 JSTL 替代 scriptlet避免在 JSP 里写% while(rs.next()) { %这类易出错的 Java 脚本改用 JSTL 标签库需提前引入!-- 在 JSP 顶部声明 -- % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !-- 在 body 中渲染列表 -- div classmusic-list c:forEach items${musicList} varmusic div classmusic-item>!-- 在页面底部或单独 JS 文件中 -- script // 全局音频对象避免重复创建 let currentAudio null; function playMusic(id) { // 1. 先停止当前播放 if (currentAudio !currentAudio.paused) { currentAudio.pause(); } // 2. 获取对应音频路径JSP 已将路径注入页面此处用>.music-item .cover { width: 120px; height: 120px; object-fit: cover; /* 关键保持宽高比居中裁剪 */ border-radius: 4px; } /* 兼容老浏览器IE 不支持 object-fit的 fallback */ .music-item .cover { width: 120px; height: 120px; background-size: cover; background-position: center center; }避坑点object-fit: cover是标准方案但 IE11 不支持。若需兼容必须用div stylebackground-image: url(${music.coverPath});替代img并设置background-size: cover。JSP 中写法div classcover stylebackground-image: url(${music.coverPath});/div4. 后台逻辑Servlet 处理增删改查用 DAO 分离数据访问JSP 是视图Servlet 是控制器DAO 是数据访问层。三层分离不是教条而是为了让修改 SQL 时不用动 HTML让调整页面样式时不用碰 Java 逻辑。以下以“上传新音乐”为例展示完整链路。4.1 文件上传 Servlet用 Apache Commons FileUpload 解析 multipartJSP 表单必须设enctypemultipart/form-dataServlet 用ServletFileUpload解析// UploadMusicServlet.java WebServlet(/uploadMusic) public class UploadMusicServlet extends HttpServlet { private static final long serialVersionUID 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 检查是否为 multipart 请求 boolean isMultipart ServletFileUpload.isMultipartContent(request); if (!isMultipart) { request.setAttribute(error, 请使用 multipart/form-data 提交); request.getRequestDispatcher(upload.jsp).forward(request, response); return; } // 2. 配置文件上传处理器 DiskFileItemFactory factory new DiskFileItemFactory(); ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(10 * 1024 * 1024); // 10MB 限制 upload.setSizeMax(20 * 1024 * 1024); // 总请求大小限制 try { ListFileItem items upload.parseRequest(request); MapString, String params new HashMap(); FileItem audioItem null; FileItem coverItem null; // 3. 解析表单字段和文件 for (FileItem item : items) { if (item.isFormField()) { params.put(item.getFieldName(), item.getString(UTF-8)); } else { String fieldName item.getFieldName(); if (audioFile.equals(fieldName)) { audioItem item; } else if (coverFile.equals(fieldName)) { coverItem item; } } } // 4. 保存文件到服务器关键路径必须相对于 webapps/ROOT String uploadDir getServletContext().getRealPath(/upload/); File uploadPath new File(uploadDir); if (!uploadPath.exists()) uploadPath.mkdirs(); String audioFileName System.currentTimeMillis() _ audioItem.getName(); String coverFileName System.currentTimeMillis() _ coverItem.getName(); audioItem.write(new File(uploadPath, audio/ audioFileName)); coverItem.write(new File(uploadPath, cover/ coverFileName)); // 5. 构建 MusicBean 并调用 DAO 插入数据库 MusicBean music new MusicBean(); music.setTitle(params.get(title)); music.setArtist(params.get(artist)); music.setAlbum(params.get(album)); music.setDuration(params.get(duration)); // 前端传入 HH:MM:SS 格式 music.setCoverPath(upload/cover/ coverFileName); music.setAudioPath(upload/audio/ audioFileName); MusicDAO dao new MusicDAO(); boolean success dao.insertMusic(music); request.setAttribute(message, success ? 上传成功 : 上传失败请重试); } catch (Exception e) { e.printStackTrace(); request.setAttribute(error, 文件处理异常 e.getMessage()); } request.getRequestDispatcher(upload.jsp).forward(request, response); } }逻辑说明getServletContext().getRealPath(/upload/)返回 Tomcat 中webapps/ROOT/upload/的绝对磁盘路径确保文件存到正确位置audioFileName加时间戳前缀防止重名MusicBean是 POJO字段与数据库表一一对应MusicDAO.insertMusic()封装了 JDBC 插入逻辑与 Servlet 解耦。4.2 DAO 层用 PreparedStatement 防 SQL 注入用 JNDI 获取连接DAO 不直接 new Connection而是从 JNDI 查找数据源// MusicDAO.java public class MusicDAO { private DataSource dataSource; public MusicDAO() { try { Context initCtx new InitialContext(); Context envCtx (Context) initCtx.lookup(java:comp/env); dataSource (DataSource) envCtx.lookup(jdbc/musicDB); } catch (NamingException e) { throw new RuntimeException(JNDI 数据源查找失败, e); } } public boolean insertMusic(MusicBean music) { String sql INSERT INTO music_info (title, artist, album, duration, cover_path, audio_path) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn dataSource.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, music.getTitle()); pstmt.setString(2, music.getArtist()); pstmt.setString(3, music.getAlbum()); pstmt.setString(4, music.getDuration()); // 直接存字符串MySQL TIME 类型自动转换 pstmt.setString(5, music.getCoverPath()); pstmt.setString(6, music.getAudioPath()); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } }参数说明PreparedStatement的?占位符强制类型安全杜绝 OR 11类注入dataSource.getConnection()从连接池获取连接用完自动归还executeUpdate()返回影响行数0即表示插入成功。5. 避坑JSPMySQL 开发中最容易翻车的 4 个血泪现场这些坑我每个都踩过且都是在凌晨两点部署失败时才意识到——不是代码写错了是环境或配置的隐性约束在作祟。列在这里省得你重蹈覆辙。5.1 现象JSP 页面中文显示为乱码但数据库里存的是正确的原因Tomcat 默认用 ISO-8859-1 解析 GET 请求参数而 JSP 页面% page contentTypetext/html;charsetUTF-8%只控制响应编码不解决请求参数解码。解决在conf/web.xml的servlet配置中为defaultservlet 添加init-paramservlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namefileEncoding/param-name param-valueUTF-8/param-value /init-param /servlet同时在所有 Servlet 的doGet方法开头加request.setCharacterEncoding(UTF-8);5.2 现象MySQL 连接池报错Cannot create PoolableConnectionFactory日志显示Access denied for user rootlocalhost原因MySQL 5.7 默认 root 用户只允许localhost连接而 Tomcat 的 JNDI 数据源连接时 host 是127.0.0.1非localhost导致权限不匹配。解决登录 MySQL执行CREATE USER root127.0.0.1 IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON *.* TO root127.0.0.1; FLUSH PRIVILEGES;或更简单UPDATE mysql.user SET Host% WHERE Userroot AND Hostlocalhost; FLUSH PRIVILEGES;仅限内网环境5.3 现象上传的 MP3 文件在 JSP 页面audio标签中无法播放控制台报404 Not Found原因文件确实存到了webapps/ROOT/upload/audio/xxx.mp3但 Tomcat 默认禁止直接访问upload/目录下的文件安全策略返回 404。解决在conf/web.xml的servlet-mapping后添加静态资源映射servlet-mapping servlet-namedefault/servlet-name url-pattern/upload/*/url-pattern /servlet-mapping这告诉 Tomcat/upload/路径下的所有请求都交给 default servlet 处理即静态文件服务。5.4 现象JSP 页面加载后自动刷新一次jsp页面让加载完后刷新一次造成表单重复提交原因表单提交后Servlet 处理完重定向到列表页response.sendRedirect(list.jsp)但开发者误在list.jsp顶部写了meta http-equivrefresh content0或 JavaScriptlocation.reload()。解决彻底删除所有自动刷新代码采用 PRG 模式Post-Redirect-Get表单提交用 POSTServlet 处理完用sendRedirect跳转浏览器地址栏变为 GET 请求避免刷新重复提交。检查list.jsp源码确认无meta刷新标签和window.location.reload()调用。6. 进阶技巧用 JavaScript 动态控制视频旋转与播放器 UI让音乐系统多一层交互感虽然标题是“音乐管理系统”但实际业务中常需嵌入教学视频、MV 或课程讲解片段。JSP 可以轻松混排音视频而 JavaScript 能赋予它们更精细的控制能力——比如旋转视频画面适配横竖屏、动态切换播放器皮肤、甚至用 Canvas 绘制频谱可视化。这里不讲大而全的框架只给一个能立刻抄作业的、真实可用的视频旋转技巧它来自热搜词javascript:v document.queryselector(video);v.style.rotate -90deg;v.s的启发但做了工程化加固。6.1 用 CSS Transform 旋转 video 元素同时保持 controls 可用原生video的controls属性在旋转后会错位。解决方案用transform: rotate()旋转 video 容器而非 video 本身并用transform-origin锚定旋转中心!-- JSP 中嵌入视频 -- div classvideo-container stylewidth: 400px; height: 300px; video idlessonVideo width400 height300 controls source srcupload/video/intro.mp4 typevideo/mp4 /video /div script // 旋转按钮 document.getElementById(rotateBtn).addEventListener(click, function() { const container document.querySelector(.video-container); const currentRotate parseInt(container.style.transform.replace(/[^-\d]/g, ) || 0); const newRotate (currentRotate 90) % 360; // 关键旋转容器而非 video 元素 container.style.transform rotate(${newRotate}deg); container.style.transformOrigin center center; // 同步调整容器宽高避免旋转后溢出 if (newRotate 90 || newRotate 270) { container.style.width 300px; container.style.height 400px; } else { container.style.width 400px; container.style.height 300px; } }); /script为什么有效transform作用于容器video 元素内部 controls 仍按原始坐标渲染transform-origin: center center确保绕中心旋转不偏移宽高交换适配旋转后的布局。这比直接video.style.rotate更可靠因为后者会让 controls 按旋转后坐标计算导致点击失效。6.2 用 JavaScript 判断数据类型精准处理不同格式的音乐元信息音乐文件上传后常需解析 ID3 标签如 MP3 的标题、艺术家或视频的时长。但浏览器无法直接读取二进制元数据所以通常依赖后端解析。不过前端可做轻量级校验——比如判断上传文件是否为合法音频格式避免用户误传 PDF// 在上传表单的 onchange 事件中 document.getElementById(audioFile).addEventListener(change, function(e) { const file e.target.files[0]; if (!file) return; // 检查文件类型MimeType const validTypes [audio/mpeg, audio/wav, audio/ogg, audio/mp4]; if (!validTypes.includes(file.type)) { alert(请选择 MP3、WAV、OGG 或 MP4 格式的音频文件); e.target.value ; // 清空 input避免无效提交 return; } // 检查文件大小前端二次校验 const maxSize 50 * 1024 * 1024; // 50MB if (file.size maxSize) { alert(文件大小不能超过 ${maxSize / (1024*1024)} MB); e.target.value ; return; } // 显示文件信息提升用户体验 document.getElementById(fileName).textContent file.name; document.getElementById(fileSize).textContent (file.size / (1024*1024)).toFixed(2) MB; });参数说明file.type由浏览器根据文件头和扩展名推测虽不绝对可靠但能拦截 90% 的明显错误file.size是精确字节数前端校验可减少无效请求toFixed(2)保留两位小数符合热搜词javascript保留两位小数的需求。6.3 用 JavaScript 函数封装播放器状态管理避免全局变量污染前面示例用了currentAudio全局变量实际项目中应封装为模块// player.js const MusicPlayer (function() { let instance null; let audio null; function init() { if (!audio) { audio new Audio(); audio.addEventListener(timeupdate, updateProgress); audio.addEventListener(ended, onEnded); } return { play: function(src) { if (audio.src ! src) { audio.src src; audio.load(); // 确保元数据加载 } audio.play(); }, pause: function() { audio.pause(); }, seek: function(time) { audio.currentTime time; }, getDuration: function() { return audio.duration; }, getCurrentTime: function() { return audio.currentTime; } }; } return { getInstance: function() { if (!instance) { instance init(); } return instance; } }; })(); // 在页面中使用 document.getElementById(playBtn).addEventListener(click, function() { const player MusicPlayer.getInstance(); player.play(/upload/audio/canon.mp3); });为什么值得做立即执行函数表达式IIFE创建私有作用域audio变量无法被外部篡改getInstance()保证单例避免多个 audio 实例冲突方法名语义清晰play/pause/seek比直接操作 DOM 更易维护。这是我带实习生时强制推行的规范——代码可读性比少写两行更重要。最后说句实在的这套 JSP 方案不会让你在简历上写“精通 Spring Cloud”但它能让你在 3 天内交付一个可演示、可修改、可解释每一行代码作用的完整系统。当别人还在配 Maven 依赖冲突时你已经把《卡农》播起来了。技术选型没有高低只有“此刻能否解决问题”。希望帮到你。本文还有配套的精品资源点击获取