尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JavaWeb实战:从零搭建小型音乐网站,掌握Servlet/JSP/MySQL全栈开发

JavaWeb实战:从零搭建小型音乐网站,掌握Servlet/JSP/MySQL全栈开发 简介JavaWeb开发是构建动态网站的核心技术栈其底层原理基于HTTP协议与Servlet规范。通过经典的MVC架构模式开发者能够清晰地分离业务逻辑、数据与视图实现高效、可维护的Web应用。在工程实践中数据库设计与会话管理是保障系统稳定与安全的关键环节例如使用BCrypt加密存储用户密码以及通过过滤器实现统一的访问控制。这些技术广泛应用于内容管理、电商后台及各类信息展示平台。本文聚焦于一个贴近实战的小型音乐网站项目详细解析如何运用Servlet、JSP、JDBC与MySQL实现用户认证、音乐文件上传、在线播放及歌单管理等核心功能为初学者提供一个从理论到实践的完整学习路径。1. 项目概述与核心价值最近几年我身边不少刚学完JavaWeb基础的朋友总想找个能串起所有知识点的项目练手。课本上的“学生管理系统”做腻了想搞点有意思的但又怕太复杂hold不住。这时候一个功能完整、技术栈经典、又贴近生活的小型音乐网站就成了一个绝佳的选择。它不像电商、社交平台那样庞大但麻雀虽小五脏俱全从前端页面展示、用户交互到后端业务逻辑、数据库设计再到文件上传、播放控制等核心功能一个不落。通过亲手搭建这样一个网站你能把Servlet、JSP、JDBC、MVC这些听起来抽象的概念变成一行行看得见、能运行的代码真正理解一个Web应用是如何从零开始“跑”起来的。这个基于JavaWeb的小型音乐网站核心目标就是模拟一个音乐流媒体的基础功能。用户能够浏览音乐库、搜索歌曲、在线播放、管理个人歌单管理员则负责音乐和歌手信息的增删改查。它解决的核心问题是为JavaWeb学习者提供一个从理论到实践、从单一技术点到完整项目集成的桥梁。项目本身技术选型经典且实用后端用Servlet处理请求JSP渲染页面JDBC操作MySQL数据库前端用HTML、CSS、JavaScript和一点Bootstrap让界面看得过去核心的音频播放则交给HTML5的audio标签。整个项目在IDEA里开发用Maven管理依赖最后打包成WAR部署到Tomcat。无论你是想巩固所学、丰富简历项目经验还是单纯对如何做一个网站感到好奇跟着这个思路走一遍收获都会远超你的预期。2. 项目整体架构与技术选型解析2.1 为什么选择经典JavaWeb技术栈很多新手会纠结现在Spring Boot这么火为什么还要从Servlet/JSP开始我的体会是用最基础的技术实现完整功能是对底层原理最好的理解方式。Spring Boot确实能极大提升开发效率但它也封装了大量细节。直接从框架入手就像开车只会用自动挡一旦遇到复杂路况或者车辆故障你可能连引擎盖都不会开。而这个音乐网站项目采用ServletJSPJDBC的经典组合能让你清晰地看到一个HTTP请求的完整生命周期从浏览器发出请求到Tomcat容器接收再到匹配对应的Servlet最后生成响应返回。MVC模式的原始实现虽然没用Spring MVC但我们可以手动划分层次Servlet作ControllerJSP作ViewJavaBean和DAO作Model理解数据是如何在层间流动的。数据库连接与操作的本质亲手编写JDBC代码配置连接池处理SQL异常你会对后续MyBatis或JPA这样的ORM框架为何存在、解决了什么问题有更深的体会。技术选型清单如下后端核心Servlet 3.0、JSP 2.0。这是JavaEE Web开发的标准所有应用服务器都支持。数据库MySQL 5.7。关系型数据库免费、流行、资料多完全满足音乐数据存储需求。前端技术HTML5、CSS3、JavaScript (ES5)、Bootstrap 4/5。Bootstrap能快速搭建响应式布局让我们把精力更多放在后端逻辑上。开发工具IntelliJ IDEA社区版即可、Maven 3.6用于依赖管理和项目构建、Tomcat 9.0Servlet容器。音频播放HTML5audio控件。原生支持兼容性好无需引入额外插件。2.2 数据库设计如何规划音乐网站的数据结构数据库设计是项目的基石设计得好后续开发事半功倍。我们需要分析核心实体用户、音乐、歌手、歌单。它们之间的关系如下用户和歌单是一对多关系一个用户可以创建多个歌单。歌单和音乐是多对多关系一个歌单包含多首音乐一首音乐可以被多个歌单收录。这需要一个中间表playlist_music来维护。歌手和音乐是一对多关系一个歌手可以有多首音乐一首音乐通常对应一位歌手这里简化处理。基于此我们设计五张核心表-- 用户表 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL UNIQUE COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码需加密存储, email varchar(100) DEFAULT NULL, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 歌手表 CREATE TABLE singer ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 歌手名, gender tinyint(1) DEFAULT NULL COMMENT 性别0-女1-男2-组合, pic varchar(500) DEFAULT NULL COMMENT 歌手图片URL, introduction text COMMENT 简介, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 音乐表核心表 CREATE TABLE music ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(200) NOT NULL COMMENT 歌曲名, singer_id int(11) NOT NULL COMMENT 歌手ID, album varchar(200) DEFAULT NULL COMMENT 专辑名, duration int(11) DEFAULT NULL COMMENT 时长秒, file_url varchar(500) NOT NULL COMMENT 音乐文件存储路径相对或绝对, lyric_url varchar(500) DEFAULT NULL COMMENT 歌词文件路径, play_count int(11) DEFAULT 0 COMMENT 播放次数, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_singer_id (singer_id), CONSTRAINT fk_music_singer FOREIGN KEY (singer_id) REFERENCES singer (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 歌单表 CREATE TABLE playlist ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 歌单标题, cover_url varchar(500) DEFAULT NULL COMMENT 封面图URL, description text COMMENT 描述, user_id int(11) NOT NULL COMMENT 创建用户ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), CONSTRAINT fk_playlist_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 歌单-音乐关联表解决多对多关系 CREATE TABLE playlist_music ( playlist_id int(11) NOT NULL, music_id int(11) NOT NULL, add_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (playlist_id,music_id), KEY idx_music_id (music_id), CONSTRAINT fk_pm_playlist FOREIGN KEY (playlist_id) REFERENCES playlist (id) ON DELETE CASCADE, CONSTRAINT fk_pm_music FOREIGN KEY (music_id) REFERENCES music (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意密码字段务必使用varchar(255)为后续使用BCrypt等强哈希算法留足空间。所有时间字段建议统一使用datetime并设置默认值CURRENT_TIMESTAMP。外键约束FOREIGN KEY能保证数据完整性但在极高并发场景下可能影响性能学习阶段建议保留。2.3 项目目录结构与MVC分层在IDEA中创建一个Maven Web项目后清晰的目录结构是良好代码组织的开始。我推荐以下结构src/main/java ├── com.yourdomain.music │ ├── bean/ // 实体类与数据库表对应如User, Music, Singer │ ├── dao/ // 数据访问层接口如UserDao, MusicDao │ ├── dao.impl/ // DAO接口实现类使用JDBC │ ├── service/ // 业务逻辑层接口 │ ├── service.impl/ // Service接口实现类 │ ├── servlet/ // Servlet控制器处理HTTP请求 │ └── util/ // 工具类如数据库连接池工具、文件上传工具 src/main/resources ├── db.properties // 数据库连接配置 └── ... // 其他配置文件 src/main/webapp ├── WEB-INF │ ├── web.xml // 部署描述符 │ └── lib/ // 依赖的JAR包Maven管理则通常不需要 ├── css/ // 样式文件 ├── js/ // JavaScript文件 ├── images/ // 图片资源 ├── uploads/ // 上传的音乐、图片文件存储目录 ├── index.jsp // 首页 ├── login.jsp // 登录页 ├── music/ // 音乐相关JSP页面 ├── user/ // 用户相关JSP页面 └── admin/ // 后台管理页面分层解析Bean层就是普通的Java类包含私有属性和getter/setter方法用于在层间传递数据。DAO层负责所有数据库操作。每个DAO类对应一个实体封装了增删改查CRUD的SQL语句。这里会大量用到PreparedStatement来防止SQL注入。Service层处理业务逻辑。例如“用户收藏音乐”这个操作Service层会先检查用户和音乐是否存在然后调用DAO层插入关联记录可能还会更新音乐的收藏数。它是Servlet和DAO之间的桥梁。Servlet层作为控制器Controller。它接收JSP页面发来的请求如/addMusic调用相应的Service方法处理业务然后将结果数据设置到请求request.setAttribute或会话session.setAttribute中最后转发forward或重定向redirect到JSP页面展示结果。JSP页面作为视图View。主要职责是展示数据应尽量减少Java代码块% ... %多用JSTL标签和EL表达式${}来呈现Servlet传过来的数据。3. 核心功能模块实现详解3.1 用户认证与会话管理这是网站安全的第一道门槛。绝对不能明文存储密码我们采用业界通用的BCrypt算法进行哈希加密。1. 密码加密工具类 首先在pom.xml中添加BCrypt的依赖如org.mindrot:jbcrypt:0.4。然后创建一个PasswordUtil工具类。// src/main/java/com/yourdomain/music/util/PasswordUtil.java import org.mindrot.jbcrypt.BCrypt; public class PasswordUtil { // 生成加密后的密码 public static String hashPassword(String plainPassword) { return BCrypt.hashpw(plainPassword, BCrypt.gensalt()); } // 验证密码 public static boolean checkPassword(String plainPassword, String hashedPassword) { return BCrypt.checkpw(plainPassword, hashedPassword); } }2. 登录Servlet实现// src/main/java/com/yourdomain/music/servlet/LoginServlet.java WebServlet(/login) // 使用注解配置Servlet映射替代web.xml public class LoginServlet extends HttpServlet { private UserService userService new UserServiceImpl(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); User user userService.findUserByUsername(username); if (user ! null PasswordUtil.checkPassword(password, user.getPassword())) { // 登录成功将用户信息存入Session HttpSession session request.getSession(); session.setAttribute(user, user); // 重定向到首页 response.sendRedirect(request.getContextPath() /index.jsp); } else { // 登录失败设置错误信息并返回登录页 request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }3. 登录状态检查与过滤器 为了防止用户未登录就访问个人中心等页面我们需要一个过滤器Filter。// src/main/java/com/yourdomain/music/filter/AuthFilter.java WebFilter(/*) // 过滤所有请求 public class AuthFilter implements Filter { 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(); String contextPath request.getContextPath(); // 排除登录、注册、静态资源等不需要过滤的路径 if (uri.endsWith(login.jsp) || uri.endsWith(login) || uri.endsWith(register.jsp) || uri.endsWith(register) || uri.contains(/css/) || uri.contains(/js/) || uri.contains(/images/) || uri.contains(/uploads/)) { chain.doFilter(request, response); return; } HttpSession session request.getSession(false); // 检查session中是否存在用户信息 if (session null || session.getAttribute(user) null) { // 未登录重定向到登录页 response.sendRedirect(contextPath /login.jsp); } else { // 已登录放行 chain.doFilter(request, response); } } }实操心得过滤器的排除路径配置非常重要且容易出错。特别是静态资源路径/css/,/js/如果没排除会导致CSS和JS文件被拦截页面样式和功能全部失效。建议使用uri.contains()进行模糊匹配更稳妥。3.2 音乐文件上传、存储与播放这是项目的核心功能。难点在于文件上传的处理和网络路径的映射。1. 文件上传Servlet 我们使用Apache Commons FileUpload库来处理multipart/form-data类型的表单。先在pom.xml中引入依赖。// src/main/java/com/yourdomain/music/servlet/admin/MusicUploadServlet.java WebServlet(/admin/music/upload) MultipartConfig(maxFileSize 1024 * 1024 * 50) // 限制单个文件最大50MB public class MusicUploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取普通表单字段 String musicName request.getParameter(name); String singerId request.getParameter(singerId); // 2. 获取上传的音乐文件 Part musicFilePart request.getPart(musicFile); String fileName getSubmittedFileName(musicFilePart); // 需要自己写方法提取文件名 // 3. 生成唯一文件名防止覆盖 String fileExtension fileName.substring(fileName.lastIndexOf(.)); String savedFileName System.currentTimeMillis() _ UUID.randomUUID() fileExtension; // 4. 确定存储目录绝对路径 String uploadDir getServletContext().getRealPath(/uploads/music); File saveDir new File(uploadDir); if (!saveDir.exists()) saveDir.mkdirs(); // 5. 保存文件 String filePath uploadDir File.separator savedFileName; musicFilePart.write(filePath); // 6. 将文件信息如相对路径 /uploads/music/xxx.mp3存入数据库 Music music new Music(); music.setName(musicName); music.setSingerId(Integer.parseInt(singerId)); music.setFileUrl(/uploads/music/ savedFileName); // 存储相对路径便于前端访问 // ... 调用Service保存music对象到数据库 // 7. 返回结果 response.sendRedirect(request.getContextPath() /admin/music/list.jsp); } // 辅助方法从Part中获取原始文件名 private String getSubmittedFileName(Part part) { for (String cd : part.getHeader(content-disposition).split(;)) { if (cd.trim().startsWith(filename)) { String fileName cd.substring(cd.indexOf() 1).trim().replace(\, ); return fileName.substring(fileName.lastIndexOf(/) 1).substring(fileName.lastIndexOf(\\) 1); } } return null; } }2. 前端播放器实现 在音乐列表页或播放页使用HTML5的audio标签。!-- music/player.jsp -- audio idaudioPlayer controls preloadmetadata stylewidth:100%; source src${music.fileUrl} typeaudio/mpeg 您的浏览器不支持 audio 元素。 /audio div button onclickdocument.getElementById(audioPlayer).play()播放/button button onclickdocument.getElementById(audioPlayer).pause()暂停/button input typerange idvolumeSlider min0 max1 step0.1 value1 onchangechangeVolume(this.value) /div script function changeVolume(value) { document.getElementById(audioPlayer).volume value; } // 可以添加更多控制逻辑如播放列表、上一首/下一首 /script注意事项audio标签的src属性需要的是音乐文件的可访问URL。我们存储在数据库的是相对路径如/uploads/music/xxx.mp3Tomcat会将其映射到服务器上的真实文件。确保uploads目录在Web应用的根目录下且Tomcat有读取权限。3.3 歌单管理与音乐收藏这个功能涉及多对多关系的操作是练习复杂业务逻辑的好例子。1. 创建歌单Service逻辑// src/main/java/com/yourdomain/music/service/impl/PlaylistServiceImpl.java public class PlaylistServiceImpl implements PlaylistService { private PlaylistDao playlistDao new PlaylistDaoImpl(); private PlaylistMusicDao playlistMusicDao new PlaylistMusicDaoImpl(); Override public boolean createPlaylist(Playlist playlist) { // 简单校验 if (playlist.getTitle() null || playlist.getTitle().trim().isEmpty()) { return false; } return playlistDao.insert(playlist) 0; } Override public boolean addMusicToPlaylist(int playlistId, int musicId) { // 检查是否已存在关联避免重复添加 if (playlistMusicDao.exists(playlistId, musicId)) { return false; } return playlistMusicDao.insert(playlistId, musicId) 0; } Override public ListMusic getMusicsByPlaylistId(int playlistId) { // 通过中间表查询歌单下的所有音乐 return playlistMusicDao.findMusicsByPlaylistId(playlistId); } }2. 前端AJAX交互实现收藏功能 为了提升用户体验收藏音乐通常使用AJAX无刷新完成。// 在音乐列表页每个音乐条目旁有一个“收藏”按钮 function addToPlaylist(musicId) { // 假设用户已经选择了某个歌单或者弹出歌单选择框 let playlistId document.getElementById(selectedPlaylistId).value; if (!playlistId) { alert(请先选择或创建歌单); return; } // 发起AJAX请求 fetch(/music/addToPlaylist, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded, }, body: playlistId${playlistId}musicId${musicId} }) .then(response response.json()) .then(data { if (data.success) { alert(收藏成功); } else { alert(收藏失败 data.message); } }) .catch(error console.error(Error:, error)); }对应的Servlet需要处理这个POST请求调用PlaylistService.addMusicToPlaylist方法并返回JSON格式的结果。4. 前端页面交互与体验优化4.1 使用Bootstrap快速搭建响应式界面对于后端开发者来说自己写CSS调样式是个痛苦的过程。Bootstrap能极大解放生产力。在webapp目录下引入Bootstrap的CSS和JS文件可以通过CDN或下载到本地。一个典型的导航栏和音乐卡片布局示例!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title小型音乐网站/title !-- Bootstrap 5 CSS -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css relstylesheet !-- 自定义CSS -- link href${pageContext.request.contextPath}/css/style.css relstylesheet /head body !-- 导航栏 -- nav classnavbar navbar-expand-lg navbar-dark bg-primary div classcontainer-fluid a classnavbar-brand href${pageContext.request.contextPath}/index.jspMusicWeb/a !-- ... 导航项 ... -- c:if test${not empty sessionScope.user} span classnavbar-text欢迎${sessionScope.user.username}/span /c:if /div /nav !-- 内容区 -- div classcontainer mt-4 div classrow idmusicList !-- 使用JSTL循环遍历音乐列表 -- c:forEach varmusic items${musicList} div classcol-md-3 col-sm-6 mb-4 div classcard h-100 img src${music.singer.pic} classcard-img-top alt${music.singer.name} onerrorthis.srcdefault.jpg div classcard-body h5 classcard-title text-truncate${music.name}/h5 p classcard-text歌手${music.singer.name}/p audio controls classw-100 source src${music.fileUrl} typeaudio/mpeg /audio button classbtn btn-outline-primary btn-sm mt-2 onclickaddToPlaylist(${music.id})收藏/button /div /div /div /c:forEach /div /div !-- Bootstrap JS Bundle -- script srchttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/js/bootstrap.bundle.min.js/script script src${pageContext.request.contextPath}/js/main.js/script /body /html提示记得在JSP页面顶部引入JSTL核心标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。${pageContext.request.contextPath}用于获取当前应用的上下文路径确保资源引用正确。4.2 实现音乐搜索与分页功能当音乐数量增多时搜索和分页是必备功能。1. 后端搜索与分页Servlet// src/main/java/com/yourdomain/music/servlet/MusicListServlet.java WebServlet(/music/list) public class MusicListServlet extends HttpServlet { private MusicService musicService new MusicServiceImpl(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取请求参数 String keyword request.getParameter(keyword); // 搜索关键词 String pageStr request.getParameter(page); // 当前页码 String sizeStr request.getParameter(size); // 每页条数 int currentPage (pageStr null || pageStr.isEmpty()) ? 1 : Integer.parseInt(pageStr); int pageSize (sizeStr null || sizeStr.isEmpty()) ? 10 : Integer.parseInt(sizeStr); // 2. 调用Service查询数据 PageBeanMusic pageBean musicService.searchMusicByPage(keyword, currentPage, pageSize); // 3. 将数据存入request转发给JSP request.setAttribute(pageBean, pageBean); request.setAttribute(keyword, keyword); request.getRequestDispatcher(/music/list.jsp).forward(request, response); } }这里用到了一个自定义的PageBean类用来封装分页数据当前页数据列表、总记录数、总页数、当前页等。2. 前端分页条组件 在list.jsp中根据PageBean对象生成分页导航。!-- 分页导航 -- nav aria-labelPage navigation ul classpagination justify-content-center c:if test${pageBean.currentPage 1} li classpage-item a classpage-link href?keyword${param.keyword}page${pageBean.currentPage-1}size${pageBean.pageSize}上一页/a /li /c:if c:forEach begin1 end${pageBean.totalPage} vari li classpage-item ${i pageBean.currentPage ? active : } a classpage-link href?keyword${param.keyword}page${i}size${pageBean.pageSize}${i}/a /li /c:forEach c:if test${pageBean.currentPage pageBean.totalPage} li classpage-item a classpage-link href?keyword${param.keyword}page${pageBean.currentPage1}size${pageBean.pageSize}下一页/a /li /c:if /ul /nav5. 项目部署、优化与常见问题排查5.1 使用Maven打包与Tomcat部署开发完成后需要将项目部署到服务器或本地Tomcat运行。1. Maven打包 在IDEA中右侧Maven工具栏找到你的项目展开Lifecycle双击package。Maven会执行编译、测试并在target目录下生成一个项目名.war文件例如music-website.war。2. 部署到Tomcat本地Tomcat将生成的.war文件复制到Tomcat安装目录的webapps文件夹下。启动Tomcat运行bin/startup.bat或startup.shTomcat会自动解压war包并部署应用。访问http://localhost:8080/music-website即可。IDEA内嵌Tomcat在IDEA中配置运行配置Run/Debug Configuration选择本地Tomcat指定部署的Artifact为war exploded开发时常用支持热更新或war包。3. 数据库连接池配置优化 在开发中我们可能直接在DAO层每次操作都创建连接这非常低效。生产环境必须使用连接池如HikariCP或Druid。以HikariCP为例在pom.xml中添加依赖然后创建一个DataSourceUtil工具类来获取连接。// src/main/java/com/yourdomain/music/util/DataSourceUtil.java public class DataSourceUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai); config.setUsername(root); config.setPassword(yourpassword); config.setMaximumPoolSize(20); // 连接池最大连接数 config.setMinimumIdle(5); // 最小空闲连接 config.setIdleTimeout(600000); // 空闲连接超时时间毫秒 config.setConnectionTimeout(30000); // 获取连接超时时间 dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void closeConnection(Connection conn) { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }然后在所有DAO实现类中使用DataSourceUtil.getConnection()替代DriverManager.getConnection()。5.2 常见问题与排查技巧实录在实际开发中你几乎一定会遇到下面这些问题。我把我的踩坑记录分享给你希望能帮你节省时间。问题1页面显示乱码中文变成问号现象从数据库读出的中文或提交表单后的中文在页面上显示为???。排查步骤数据库层面确保MySQL数据库、表、字段的字符集为utf8mb4支持emoji。连接URL加上参数?useUnicodetruecharacterEncodingutf8。Java代码层面在Servlet中在request.getParameter()之前设置请求编码request.setCharacterEncoding(UTF-8)在响应输出前设置response.setContentType(text/html;charsetUTF-8)。JSP页面层面在JSP页面顶部添加% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8%。根治方案创建一个编码过滤器CharacterEncodingFilter对所有请求和响应统一设置UTF-8编码。问题2静态资源CSS, JS, 图片404 Not Found现象页面能打开但没有样式浏览器控制台报错找不到CSS/JS文件。原因路径错误。在JSP/HTML中引用资源时路径是相对于当前请求URL的容易出错。解决方案始终使用绝对路径。在JSP中使用${pageContext.request.contextPath}来获取应用根路径。!-- 正确 -- link href${pageContext.request.contextPath}/css/bootstrap.min.css relstylesheet !-- 错误如果当前页面不在根目录就会404 -- link href/css/bootstrap.min.css relstylesheet问题3文件上传后无法访问或播放现象音乐上传成功数据库路径也存了但前端audio标签无法加载。排查检查文件是否真的保存到了uploads/music目录下。检查保存到数据库的路径。应该是相对于Web应用根目录的路径如/uploads/music/xxx.mp3。前端src直接使用这个值即可。检查Tomcat是否对uploads目录有读取权限。直接在浏览器地址栏输入http://localhost:8080/your-app/uploads/music/xxx.mp3看是否能直接下载或播放。如果不能说明文件服务有问题。问题4部署到服务器后上传的文件重启后消失原因如果你把文件上传到IDEA项目内部的webapp/uploads目录当IDEA重新构建或Tomcat重新部署时这个目录可能会被清理或覆盖。解决方案将上传目录设置为服务器操作系统上的一个绝对路径且该路径在Tomcat工作目录之外。例如在Servlet中// 在web.xml中配置一个初始化参数指定上传根目录 String uploadRoot getServletContext().getInitParameter(uploadRoot); // 或者使用系统环境变量 String uploadRoot System.getProperty(user.home) /music_website_uploads;同时需要配置一个静态资源映射在Tomcat的server.xml或应用内配置让这个外部目录也能通过URL访问。问题5数据库连接数耗尽现象运行一段时间后网站无法访问日志中抛出SQLException: Too many connections。原因没有正确关闭数据库连接Connection、语句Statement或结果集ResultSet导致连接没有释放回连接池。解决方案务必在finally块中关闭资源或者使用Java 7的try-with-resources语法。// 传统写法 Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DataSourceUtil.getConnection(); ps conn.prepareStatement(sql); // ... 设置参数执行查询 rs ps.executeQuery(); // ... 处理结果 } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭顺序ResultSet - Statement - Connection try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } // 这里close()实际是将连接还回连接池 } // 使用try-with-resources推荐 try (Connection conn DataSourceUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { // ... 设置参数 try (ResultSet rs ps.executeQuery()) { // ... 处理结果 } } catch (SQLException e) { e.printStackTrace(); }这个小型音乐网站项目虽然功能上比不上成熟的商业产品但它完整地走通了一个Web应用从设计、开发到部署的全流程。我个人的体会是做项目最大的收获不是把功能堆砌出来而是在解决一个个具体问题的过程中把书本上零散的知识点串联成网。比如为了解决文件上传你不得不去研究Servlet 3.0的MultipartConfig和Part接口为了优化性能你引入了数据库连接池为了更好的用户体验你学习了前端AJAX和Bootstrap。每一个坑踩过去你的技术理解就深一层。当你看到自己写的网站能播放音乐、能管理歌单时那种成就感是单纯看教程无法比拟的。如果你在实现过程中卡住了别急着百度先自己思考一下问题的可能原因理清数据流和逻辑很多时候答案就在调试的日志里。这个项目做完你完全可以以此为蓝本去尝试添加更多功能比如音乐推荐算法、评论系统、第三方登录让它真正成为你简历上的一个亮点。本文还有配套的精品资源点击获取
返回列表