ARTICLE DETAIL

资讯详情

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

Java+Servlet+JSP+MySQL新闻发布系统实战:从建表到部署避坑

Java+Servlet+JSP+MySQL新闻发布系统实战:从建表到部署避坑 简介这是一套面向高校计算机相关专业学生的Java Web新闻发布系统完整源码采用ServletJSPMySQL技术栈实现适合作为期末大作业、课程设计或Java Web入门练手项目。项目难度适中源码经过本地编译验证可正常运行并附有文档说明评审得分达到98分内容经助教老师审定能够满足学习与提交作业的双重需求。压缩包共1647个文件约24.45MB其中包含54个Java源文件、92个HTML页面、90个CSS样式表、364个JavaScript脚本以及439张PNG图片和260个GIF素材另有SQL建库脚本、Maven构建配置与前端依赖库覆盖新闻发布系统的前后端完整结构。目前已有145人学习下载。读者可获得一套可直接运行的新闻发布系统参考实现涵盖新闻增删改查、分类管理、用户权限等典型模块配合文档说明可快速理解Servlet与JSP的协作流程也便于在此基础上二次开发或撰写课程设计报告。1. 从零搭一套 JavaServletJSPMySQL 新闻发布系统这套老技术栈为什么还值得动手如果你正在搜「JavaServletJspMysql实现Web新闻发布系统源码文档说明」大概率是三种人之一课程设计要交差的学生、想补一遍 Web 底层原理的转行者、或者面试前想把 Servlet 生命周期、JSP 九大内置对象、MySQL 事务这几块重新捋顺的 Java 工程师。这套技术栈看着「老」但它恰恰是理解现代框架的照妖镜——Spring MVC 的 DispatcherServlet 本质就是一个 ServletMyBatis 的 ResultSet 映射本质就是 JDBC 封装你把这套手写一遍再回头看 Spring Boot很多「玄学」会瞬间落地。这篇笔记不讲空泛概念而是按「能跑起来 → 能改起来 → 能排错」的路径把新闻发布系统的表结构、Servlet 分层、JSP 页面渲染、MySQL 连接与事务、以及部署踩坑一条条拆开。适合有 Java 基础、会写public static void main、但没完整做过一个 Web 项目的人也适合已经用过框架、想回头补底层的人。全程用 JDK 8 Tomcat 8.5 MySQL 5.7/8.0 这套最稳的组合不追新版本因为老项目最怕版本对不上。2. 新闻发布系统的表结构与分层设计先想清楚数据怎么流2.1 四张核心表怎么设计才够用新闻发布系统听起来功能多拆开其实就四件事用户登录、新闻增删改查、栏目分类、评论。对应四张表就够不要一上来搞十几张表把自己绕晕。-- 用户表区分管理员和普通用户 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 存MD5别存明文, role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 栏目表新闻分类 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 栏目名如时政/体育 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 新闻表核心表 CREATE TABLE news ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, category_id INT, author_id INT, publish_time DATETIME DEFAULT CURRENT_TIMESTAMP, view_count INT DEFAULT 0, KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 评论表 CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, news_id INT NOT NULL, user_id INT, content VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明news表用category_id和author_id做外键关联但故意不建物理外键约束只建索引。原因很实际——新闻删除时如果物理外键级联容易误删评论用应用层控制更灵活。view_count放新闻表而不是单独统计表是因为这个量级几千条新闻根本不需要拆表过早优化是负担。参数说明字符集统一utf8mb4别用utf8否则用户评论里带 emoji 会直接报错插入失败这是血泪经验。password字段给 64 位是为了存 MD5 后的 32 位十六进制串留余量实际用MD5或SHA-256都行但绝对不要存明文。2.2 DAO-Service-Servlet 三层怎么切很多人写 Servlet 项目把所有 JDBC 代码塞进 Servlet 的doGet里结果一个方法两百行改一个字段要翻半天。正确切法是三层DAO 层只负责和数据库打交道一个方法对应一条 SQL返回ListNews或单个对象。Service 层负责业务逻辑比如「发布新闻时先校验标题非空再插入再返回主键」。Servlet 层只负责收参数、调 Service、转发 JSP不写 SQL。// NewsDao.java —— 只做数据访问 public class NewsDao { public ListNews findAll() throws SQLException { ListNews list new ArrayList(); String sql SELECT n.*, c.name AS categoryName FROM news n LEFT JOIN category c ON n.category_id c.id ORDER BY n.publish_time DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { News n new News(); n.setId(rs.getInt(id)); n.setTitle(rs.getString(title)); n.setCategoryName(rs.getString(categoryName)); list.add(n); } } return list; } }逻辑说明用LEFT JOIN而不是INNER JOIN是为了防止某条新闻的category_id为空时整条新闻查不出来。try-with-resources自动关闭连接避免忘记close()导致连接池耗尽——这是新手最常见的翻车点。参数说明DBUtil.getConnection()内部用DriverManager还是连接池取决于项目规模。课程设计用DriverManager够了但如果要模拟并发建议上 Druid 或 HikariCP否则每个请求新建连接几十个并发就卡死。3. Servlet 与 JSP 的配合请求怎么进来页面怎么出去3.1 一个新闻列表请求的完整链路从浏览器输入news?actionlist到页面渲染出新闻中间经过这些环节浏览器发 GET 请求到/news。Tomcat 根据web.xml或WebServlet注解找到NewsServlet。Servlet 的doGet被调用读取action参数。调NewsService.findAll()内部调NewsDao.findAll()。把结果ListNews存进request.setAttribute(newsList, list)。request.getRequestDispatcher(/news_list.jsp).forward(request, response)。JSP 用 JSTL 遍历newsList渲染 HTML。WebServlet(/news) public class NewsServlet extends HttpServlet { private NewsService newsService new NewsService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action) || action null) { ListNews list newsService.findAll(); req.setAttribute(newsList, list); req.getRequestDispatcher(/news_list.jsp).forward(req, resp); } else if (detail.equals(action)) { int id Integer.parseInt(req.getParameter(id)); News news newsService.findById(id); req.setAttribute(news, news); req.getRequestDispatcher(/news_detail.jsp).forward(req, resp); } } }逻辑说明用action参数做路由分发是 Servlet 时代最经典的做法等价于现在 Spring MVC 的RequestMapping。forward是服务器内部跳转浏览器地址栏不变如果换成sendRedirect地址栏会变且request里的属性会丢失——这是新手最容易搞混的两个方法。参数说明Integer.parseInt如果参数不是数字会抛NumberFormatException生产环境必须 try-catch 并返回 400 页面否则用户手改 URL 就能让服务器报 500。3.2 JSP 页面怎么写才不变成「Java 代码堆」JSP 的本质是「带 Java 代码的 HTML」但如果你在 JSP 里写% Class.forName(com.mysql.jdbc.Driver); %那就彻底跑偏了。JSP 只应该做两件事取数据、渲染。% page contentTypetext/html;charsetUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body h2新闻列表/h2 c:forEach items${newsList} varn div classnews-item a hrefnews?actiondetailid${n.id}${n.title}/a span${n.categoryName} | ${n.publishTime}/span /div /c:forEach /body /html逻辑说明用 JSTL 的c:forEach替代% for(...) %页面干净且不易出错。${n.title}是 EL 表达式等价于n.getTitle()但更简洁。注意 JSTL 需要引入jstl.jar和standard.jarTomcat 8.5 以上还要注意版本匹配否则会报NoClassDefFoundError。参数说明contentTypetext/html;charsetUTF-8必须写在 page 指令里否则中文会乱码。如果只在 HTML 的meta里写 charsetServlet 输出流用的还是默认编码照样乱码——这是 JSP 中文乱码的根因。4. MySQL 连接与事务新闻发布系统里最容易出问题的地方4.1 JDBC 连接配置与常见连接失败public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/news_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动未找到, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明Class.forName加载驱动MySQL 8.0 用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver写错直接ClassNotFoundException。URL 里useSSLfalse是必须的否则 MySQL 8.0 会警告 SSL 连接问题虽然不影响功能但日志很烦。参数说明serverTimezoneAsia/Shanghai不加的话插入的时间会比实际少 8 小时这是 MySQL 8.0 时区问题的经典坑。characterEncodingutf8配合数据库的utf8mb4保证中文和 emoji 都能存。4.2 发布新闻时的事务控制发布新闻如果涉及「插入新闻 更新栏目计数」两个操作必须用事务包起来否则插入了新闻但计数没更新数据就不一致。public boolean publishNews(News news) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 String sql1 INSERT INTO news(title, content, category_id, author_id) VALUES(?,?,?,?); try (PreparedStatement ps conn.prepareStatement(sql1)) { ps.setString(1, news.getTitle()); ps.setString(2, news.getContent()); ps.setInt(3, news.getCategoryId()); ps.setInt(4, news.getAuthorId()); ps.executeUpdate(); } String sql2 UPDATE category SET news_count news_count 1 WHERE id ?; try (PreparedStatement ps conn.prepareStatement(sql2)) { ps.setInt(1, news.getCategoryId()); ps.executeUpdate(); } conn.commit(); // 两个都成功才提交 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }逻辑说明setAutoCommit(false)是事务的起点commit()是终点中间任何一步抛异常都走rollback()。注意finally里要把autoCommit恢复成true再关闭连接否则连接放回池里还是事务状态下一个请求会莫名其妙锁表。参数说明PreparedStatement用?占位符不仅防 SQL 注入还能让 MySQL 缓存执行计划比字符串拼接快。setInt、setString的类型要和数据库字段对应类型不匹配会报Data truncation错误。5. 部署与联调避坑那些让项目跑不起来的细节5.1 常见问题排查现象一启动 Tomcat 报ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因MySQL 驱动 jar 没放进WEB-INF/lib或者只放在了项目的 Build Path 里。 解决把mysql-connector-java-8.0.x.jar复制到web/WEB-INF/lib目录下重新部署。Eclipse 里 Build Path 的库不会自动打包进 war。现象二页面中文全是问号???。原因JSP 页面编码、Servlet 响应编码、数据库连接编码三者不一致。 解决三处都要设。JSP 的pageEncodingUTF-8Servlet 的resp.setContentType(text/html;charsetUTF-8)JDBC URL 的characterEncodingutf8。缺一个就乱码。现象三java.sql.SQLException: The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因MySQL 8.0 的时区没配系统默认时区名带中文。 解决JDBC URL 加serverTimezoneAsia/Shanghai或者进 MySQL 执行SET GLOBAL time_zone 8:00。现象四新闻列表页刷新后数据不更新。原因浏览器缓存了 JSP 生成的 HTML或者 Tomcat 没重新编译 JSP。 解决开发阶段在 JSP 里加% response.setHeader(Cache-Control,no-cache); %或者直接清浏览器缓存。Tomcat 的work目录也可以删掉强制重编译。现象五javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core。原因JSTL 标签库的 jar 没引入或者版本和 Tomcat 不匹配。 解决Tomcat 8.5 用 JSTL 1.2jstl-1.2.jar一个文件就够Tomcat 9 以上要注意javax和jakarta包名变化别混用。5.2 部署检查清单检查项正确做法常见错误JDK 版本与 Tomcat 匹配如 JDK 8 Tomcat 8.5JDK 17 配 Tomcat 8 直接起不来驱动 jar 位置WEB-INF/lib只放 Build Path部署后丢失数据库连接先本地mysql -u root -p能连上直接跑项目连不上再回头查端口占用改 Tomcat 的server.xml端口8080 被占启动报Address already in use项目访问路径http://localhost:8080/项目名/news?actionlist漏了项目名4046. 进阶技巧把新闻发布系统改造成能写进简历的项目如果你只是把源码跑起来交作业那这套系统价值有限。真正能让你在面试里讲出东西的是加几个「有技术含量」的改造点。第一把view_count的更新改成异步。每次打开新闻详情都UPDATE news SET view_count view_count 1高并发下这行 SQL 会成为行锁热点。改造思路用ConcurrentHashMap在内存里累加定时比如每 30 秒批量刷回数据库。这样既减少了数据库压力又能在面试里聊「读写分离」「缓存」的雏形。第二给新闻列表加分页。不要用LIMIT offset, size做深分页offset到十万条时性能断崖式下跌。正确做法是用「游标分页」WHERE id last_id ORDER BY id DESC LIMIT 10每次把上一页最后一条的 id 传给下一页。这个细节能直接对应面试里的「MySQL 深分页优化」。第三把密码存储从 MD5 换成 BCrypt。MD5 已经被证明不安全加盐也挡不住彩虹表。引入jBCrypt库BCrypt.hashpw(password, BCrypt.gensalt())一行搞定。面试官问「你怎么存密码」答 BCrypt 比答 MD5 高一个档次。第四用 Filter 统一处理编码和登录校验。不要在每个 Servlet 里写req.setCharacterEncoding(UTF-8)写一个EncodingFilter和AuthFilter在web.xml里配置拦截路径。这是 Servlet 规范里最实用的设计模式也是 Spring Security 的思想源头。WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); // 放行交给下一个 Filter 或 Servlet } }逻辑说明chain.doFilter是过滤器链的核心不调用它请求就被截断了。WebFilter(/*)表示拦截所有请求包括静态资源所以实际项目里通常会排除.css、.js路径。参数说明setCharacterEncoding只对 POST 请求体有效GET 请求的参数编码由 Tomcat 的server.xml里Connector的URIEncoding决定Tomcat 8 以上默认就是 UTF-8不用额外配。我自己带过几个做课程设计的人最常见的后悔药就是「一开始没分层写到后面想重构已经来不及」。所以哪怕是个小系统也先把 DAO、Service、Servlet 三层目录建好后面加功能才不会乱。这套技术栈虽然老但把上面这些点吃透再去看 Spring Boot 的自动配置你会发现它帮你省掉的正是这些重复劳动——而知道省掉了什么才是真正理解框架的开始。希望帮到你。本文还有配套的精品资源点击获取
返回列表