☰
JavaWeb日记系统实战:Servlet+JSP+MySQL实现登录注册与增删改查
2026/10/5 3:12:26 网站建设 项目流程

1. 项目全貌梳理与核心需求拆解

做JavaWeb课程设计、毕业设计,或者只是想把Servlet、JSP这套老技术栈彻底吃透的人,应该都绕不开一个经典命题:写一个带用户登录、增删改查的小系统。今天我聊的这个项目——基于JavaWeb和MySQL的JSP+Servlet日记系统,看起来名字又长又直白,其实就是把Java、JSP、Bootstrap、Servlet、MySQL这一整套技术串起来,做成一个能注册、能登录、能写日记、能管理日记的完整Web应用。

先说这个系统到底能干什么。用户打开浏览器访问项目地址,首先看到的是登录页面和注册入口,注册完账号之后进入系统主页,主页展示了当前用户写过的所有日记列表,支持按时间倒序排列,每一篇日记可以查看详情、编辑内容、删除记录,同时支持分页浏览。整个系统没有复杂的业务逻辑,但却把JavaWeb开发最核心的套路全部覆盖了:前端页面用JSP+Bootstrap来渲染,浏览器发请求,Servlet接收请求、调用DAO层操作MySQL数据库,再把结果塞回JSP页面展示。这就是最标准的Model1和Model2混合模式,理解了这个项目的请求流转,后面学Spring MVC、Spring Boot都会顺畅很多。

这个项目适合谁呢?我觉得主要三类人。第一类是正在学JavaWeb的学生,课堂上听了Servlet生命周期、JSP内置对象这些概念,但缺乏一个完整的项目把这些抽象概念落到实际代码里;第二类是准备做课程设计或毕业设计的同学,需要一套结构清晰、注释规范、答辩能讲明白的参考代码;第三类是刚入职或者想回顾JavaWeb基础的后端开发,用这个项目把JDBC、分页查询、Session管理等基本功重新捡起来。

我当年第一次完整跑通这个项目的时候,最大的感受就是:"原来一个请求从浏览器到数据库再回到浏览器的完整链路是这样的。" 这种认知上的打通,比背十遍面试题都管用。这篇内容我会把这个日记系统的架构设计、数据库建表、核心代码逻辑、运行配置、常见报错一次性讲清楚,同时把我实际运行项目时踩过的坑也一并整理出来,希望能帮你省下几个晚上的调试时间。

2. 技术选型背后的小算盘:为什么是JSP+Servlet+Bootstrap

很多初学者拿到项目后第一反应是"这套技术是不是太老了",甚至有人会问"为什么不直接上Spring Boot"。这里我得替这套技术栈说句公道话:作为教学项目和基础训练,JSP+Servlet恰恰是不可跳过的一环。

2.1 Servlet和JSP的分工逻辑

先理清Servlet和JSP到底是什么关系。Servlet是一个运行在服务端的Java类,专门用来接收HTTP请求、处理业务逻辑、返回响应;JSP本质上也是一个Servlet,只是在写法上更偏向于页面展示,可以让Java代码和HTML混在一起写,服务端会把JSP编译成Servlet再执行。

在这个日记系统里,分工是这样的:所有请求地址比如/login、/register、/diary/list都由Servlet接收,Servlet里写判断逻辑、调用数据库方法、处理Session;而JSP页面只负责展示数据,通过${}EL表达式或者<c:forEach>JSTL标签把后台传过来的数据循环渲染成HTML表格和卡片。简单说,Servlet是"后台大脑",JSP是"前台门面"。

这种分工方式的好处是职责边界清晰,开发者能直观感受到每一层做了什么。很多新手在接触Spring Boot时容易一头雾水,因为框架帮我们把请求分发、参数封装、响应转换全自动化了,出问题反而不知道去哪排查。而JSP+Servlet所有步骤都摆在明面上,请求怎么进的Servlet、参数怎么从request里取值、数据怎么塞进session,一眼就能看懂。

2.2 Bootstrap在前端扮演的角色

再说Bootstrap。这个项目的前端没有用Vue、React这些框架,而是选择了Bootstrap 3或4版本配合JSP使用。原因有两点:第一,JSP是服务端渲染,配合Bootstrap这种“拿过来直接用”的CSS组件库非常顺手,不需要构建工具、不需要npm安装依赖,把bootstrap.min.css和bootstrap.min.js放进webapp目录下的static文件夹,页面里通过<link>标签引入就能生效;第二,Bootstrap自带的栅格系统、表单样式、按钮、分页组件、模态框,可以让一个没有任何前端功底的Java开发者在半小时内拼出一个像模像样的管理界面,尤其是日记列表的分页条,直接用Bootstrap的pagination类名就能实现,省去大量手写CSS的时间。

2.3 MySQL为什么是省心之选

数据库选用MySQL,这个决策应该没什么争议。MySQL在JavaWeb学习阶段的地位基本是统治级的,一方面是免费开源,另一方面是生态环境太好了,网上随便一搜就是海量的建表语句、报错解决方案。而且MySQL 5.7和8.0配合JDBC驱动连接Java程序,在配置文件里写好driverClassName、url、username、password四个参数就能跑起来,对新手极其友好。

不过这里也有一点需要提前说清楚的取舍:如果用最新的MySQL 8.0.x,JDBC驱动类名要写com.mysql.cj.jdbc.Driver,URL里还需要加上serverTimezone=Asia/Shanghai这个时区参数,否则会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized的这个经典错误。我在后面"踩坑实录"那一节会详细展开这个问题。如果用的是MySQL 5.7.x,驱动类名写com.mysql.jdbc.Driver即可,但也别高兴太早——5.7版本在某些字符集配置上同样有坑。整体来看,只要选一个版本然后把它彻底搞定,MySQL就是整个项目里最省心的环节。

2.4 为什么不直接用Spring Boot

我没有一上来就推荐Spring Boot,因为如果你是第一次接触JavaWeb,直接跳到Spring Boot反而容易留下知识盲区。Servlet规范、Filter过滤器、Session管理、请求转发与重定向、JDBC连接管理,这些底层原理在Spring Boot里都被封装成了几个注解和工具类,短期来看写代码很爽,长期来看一旦遇到和高并发、调优、框架源码相关的面试题,就会暴露出基础不稳的问题。我个人的建议是:先用这个JSP+Servlet项目把地基打牢,再顺着学Maven、MyBatis、Spring、Spring Boot,届时你会发现框架只是一个"更高效率的封装",底层思路你全都见过。

3. 数据库设计实战:从用户表到日记表

数据库设计是整个系统的地基。我在设计这个日记系统时,没有一上来就写代码,而是先在纸上把业务涉及的数据实体和它们之间的关系理清楚。这个系统的核心实体很简单:用户(user)和日记(diary),一个用户对应多篇日记,所以是典型的一对多关系。

3.1 用户表结构分析

用户表用于存储系统的登录账号信息,字段设计不需要花哨,但每个字段的类型、长度、约束都得有意义。以下是我在实际项目中应用的建表语句:

CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户ID主键', `username` VARCHAR(50) NOT NULL COMMENT '用户名,登录账号', `password` VARCHAR(100) NOT NULL COMMENT '密码,建议MD5加密存储', `nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称,用于页面展示', `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱,备用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个细节值得单独说明。第一,主键用INT AUTO_INCREMENT自增整数,在数据量不大的学习项目里完全够用,没必要学互联网大厂去搞雪花算法,那是分布式场景才需要考虑的问题。第二,username字段建了唯一索引UNIQUE KEY,防止用户重复注册。如果你不提前建这个约束,就只能在注册的Servlet代码里先做一次”用户名是否存在”的查询,多写代码不说,还有并发注册导致脏数据的风险。第三,密码字段长度我留了100,即使你后续不用MD5而改用SHA-256或者加盐加密,存储空间也足够,不用反反复复改表结构。

3.2 日记表结构分析

日记表是这个系统的核心业务表。每个字段的设计都对应一个真实的功能需求。我的建表语句如下:

CREATE TABLE `diary` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '日记ID', `user_id` INT NOT NULL COMMENT '所属用户ID,关联user表', `title` VARCHAR(100) NOT NULL COMMENT '日记标题', `content` TEXT NOT NULL COMMENT '日记正文,TEXT类型支持长文本', `weather` VARCHAR(20) DEFAULT NULL COMMENT '当天天气', `mood` VARCHAR(20) DEFAULT NULL COMMENT '当天心情', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), CONSTRAINT `fk_diary_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

user_id字段设置了外键fk_diary_user,指向用户表的主键,同时设置了级联删除ON DELETE CASCADE,意思是当用户账号被删除时,这个用户的所有日记一并删除,避免产生孤儿数据。虽然在学习项目中删除用户的功能可能根本不会写,但这个设计习惯值得保留,面试官问起外键约束时你能讲得明明白白。

weather和mood这两个字段是我在基础需求上做的合理扩展。写日记的时候顺手选一下今天天气怎么样、心情怎么样,会让这个系统更有仪式感,同时也给以后扩展“按心情筛选日记”功能留了口子。这两个字段用VARCHAR(20)就够,存“晴”“多云”“开心”“emo”这种短词,没必要用枚举类型,因为需求随时可能调整,枚举改起来太麻烦。

update_time字段的ON UPDATE CURRENT_TIMESTAMP是MySQL 5.6之后支持的写法,每次更新记录时自动刷新修改时间,省去在Java代码里手动维护这个字段。这也是我在实际项目里特意保留的一个小亮点,答辩的时候可以让老师知道你不只是照着模板敲代码,是真的理解每个字段的用意。

3.3 表关系与数据操作复盘

两张表通过user_id建立起一对多关系后,CRUD操作就有了明确的逻辑:

  • 注册功能:向user表INSERT一条记录。
  • 登录功能:根据用户名查询user表,比对密码。
  • 新增日记:向diary表INSERT,其中user_id取当前登录用户的ID。
  • 日记列表:SELECT * FROM diary WHERE user_id = 当前用户ID ORDER BY create_time DESC LIMIT ?,?,注意加WHERE条件实现“仅看自己的日记”这个需求,不然后果就是所有用户都能看到彼此的内容,这是新手最容易忽略的越权漏洞。
  • 删除日记:DELETE FROM diary WHERE id = ? AND user_id = ?,删除条件里带上user_id,既能防止用户删除别人的日记,又是一种简易的数据权限校验。

关于JDBC连接数据库的操作,我建议把所有重复性的代码封装到一个DBUtil工具类里,核心思路是:在静态代码块里加载数据库驱动,提供一个getConnection()方法返回连接对象,再提供close()方法关闭资源。很多初学者每写一个DAO方法就重新写一遍Class.forName、DriverManager.getConnection,代码重复得惨不忍睹,封装工具类之后,每个DAO方法只关注自己的SQL逻辑,这不仅是代码整洁的问题,更是一种工程意识的体现。

4. 核心功能实现与关键代码解析

数据库设计好了,接下来就是把这个系统的核心功能一个个实现出来。我按照实际开发的顺序来拆解:环境准备、项目骨架搭建、注册登录模块、日记管理模块、分页查询优化。

4.1 环境准备与项目骨架搭建

我开发这个项目时使用的环境组合是:JDK 1.8、IntelliJ IDEA 2020.3、Tomcat 8.5、MySQL 5.7,Maven做依赖管理。这套组合主打一个“稳定兼容”,JDK1.8是JavaWeb项目的常青树,Tomcat 8.5支持Servlet 3.1规范,和这个项目使用的Servlet API版本完美匹配。

在IDEA里创建项目的步骤是这样的:新建Maven项目,勾选Web Application模板,填好GroupId和ArtifactId,然后手动补充webapp/WEB-INF/web.xml。在pom.xml中添加以下关键依赖:

<dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL标签库 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> </dependencies>

这里的provided作用域值得留意:Servlet和JSP API由Tomcat容器提供,打包时不需要打进去,如果误用了compile默认作用域,会导致后面把项目部署到Tomcat时出现类冲突。依赖版本我用的驱动是8.0.26,虽然数据库是5.7,但MySQL 8.0驱动向下兼容5.x版本的数据库,这样可以少装一个驱动文件。实际连接URL这样写:

String url = "jdbc:mysql://localhost:3306/diary_system?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8";

useSSL=false因为我是在本地开发,没必要开启SSL加密;serverTimezone=Asia/Shanghai解决了时区报错;characterEncoding=utf8保证中文数据不乱码。这三件套相当于Java连接MySQL的“保命符”,缺一个都可能触发莫名其妙的报错。

4.2 注册登录模块:Session管理是核心

注册登录是几乎所有Web系统的入口功能,这个模块做扎实了,后面所有功能都会顺理成章。

注册的流程是这样的:用户填写用户名、密码、确认密码,点击提交后表单数据POST到RegisterServlet。Servlet先做基础校验——用户名不能为空、密码长度不能小于6位、两次密码必须一致——然后调用UserDao.findByUsername(username)检查用户名是否已被占用,如果没有则调用UserDao.register(user)插入数据库,最后重定向到login.jsp页面让用户登录。注意,我在这里用的是response.sendRedirect()而不是request.getRequestDispatcher().forward(),因为注册成功后如果直接转发到登录页,用户刷新浏览器会导致表单重复提交,重定向可以避免这个问题。

登录模块有一个细节值得多说几句:小程序项目里通常用Session保存用户状态。用户输入正确的用户名密码后,Servlet里执行这段代码:

User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("currentUser", user); session.setMaxInactiveInterval(30 * 60); // 设置会话超时时间为30分钟 response.sendRedirect(request.getContextPath() + "/diary/list"); } else { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); }

session.setAttribute("currentUser", user)把整个User对象放进会话,之后日记相关的Servlet里只需要通过(User) session.getAttribute("currentUser")就能拿到当前登录用户的信息,这是最简洁的做法。设置会话超时时间setMaxInactiveInterval(30 * 60)是为了防止用户长时间挂着不操作导致Session一直被占用。

登录成功后的页面跳转推荐用重定向,因为登录成功后地址栏应该重置到主页地址,让用户产生清晰的页面变化感知;登录失败则用请求转发,把错误提示errorMsg带回登录页回显。整个模块需要配套一个LogoutServlet,调用session.invalidate()销毁会话,再把用户带回登录页。

4.3 日记管理模块:CRUD循环

日记模块是整个系统的重头戏,包含列表展示、新增日记、编辑日记、删除日记四个操作,与数据库的增删改查一一对应。

列表展示是这样实现的:DiaryServlet接收前端的/diary/list请求,从Session中取出当前用户,调用DiaryDao.findByUserId(userId, offset, pageSize)查询数据库,得到List<Diary>集合后,通过request.setAttribute("diaryList", list)放到request域中,然后请求转发到diaryList.jsp。JSP页面里用JSTL标签循环渲染数据:

<c:forEach items="${diaryList}" var="diary"> <div class="card mb-3"> <div class="card-body"> <h5 class="card-title">${diary.title}</h5> <p class="card-text">${diary.content}</p> <span class="badge bg-info">天气:${diary.weather}</span> <span class="badge bg-success">心情:${diary.mood}</span> <small class="text-muted">创建时间:${diary.createTime}</small> <a href="${pageContext.request.contextPath}/diary/edit?id=${diary.id}">编辑</a> <a href="${pageContext.request.contextPath}/diary/delete?id=${diary.id}" onclick="return confirm('确定要删除这篇日记吗?')">删除</a> </div> </div> </c:forEach>

注意${pageContext.request.contextPath}这个EL表达式的用法,它动态获取项目上下文路径,这样无论是本地部署还是服务器部署都能正确拼出URL,避免硬编码路径导致项目名改动后全部链接失效的问题。删除链接上加了onclick确认弹窗,也给操作加了一道保险。

新增日记和编辑日记可以共用一个JSP页面。diaryEdit.jsp页面里先判断有没有diary对象,如果有则表单回填数据,没有则为空表单。提交时统一交给DiarySaveServlet处理,在Servlet里再做一次判断:如果请求参数里有id且这个id对应的日记属于当前登录用户,就执行UPDATE操作;否则执行INSERT操作。这种“一个Servlet处理保存”的设计比拆成addServlet和updateServlet更简洁,代码量能少三分之一。

这里要特别强调一个越权防护细节:编辑和删除操作时必须携带user_id条件。比如做删除功能,SQL不能只写DELETE FROM diary WHERE id=?,而应该写成DELETE FROM diary WHERE id=? AND user_id=?,user_id从当前Session获取。不这么做的话,用户只要猜到或者拿到别人的日记ID,就能把别人的日记删掉,这在安全上是非常低级的漏洞,但也是新手项目里出现频率最高的漏洞之一。

4.4 分页查询:避免一次加载全部数据

日记系统的数据量在现阶段不至于爆炸,但分页功能是JavaWeb面试必问的知识点,也几乎是所有后台管理系统的标配。用LIMIT关键字实现物理分页其实非常直观,核心逻辑只有三步:

  1. 计算出总记录数:SELECT COUNT(*) FROM diary WHERE user_id=?。
  2. 根据每页显示条数计算总页数:totalPages = (totalCount + pageSize - 1) / pageSize,这里用(总数+页大小-1)/页大小这个公式做向上取整,避免多算一页。
  3. 查询当前页数据:SELECT * FROM diary WHERE user_id=? ORDER BY create_time DESC LIMIT ?,?,两个问号分别是offset(偏移量)和pageSize(页大小),offset = (currentPage - 1) * pageSize。

JSP页面的分页条用Bootstrap的pagination组件实现:

<nav aria-label="Page navigation"> <ul class="pagination"> <li class="page-item <c:if test="${currentPage == 1}">disabled</c:if>"> <a class="page-link" href="?page=${currentPage - 1}">上一页</a> </li> <c:forEach begin="1" end="${totalPages}" var="i"> <li class="page-item <c:if test="${i == currentPage}">active</c:if>"> <a class="page-link" href="?page=${i}">${i}</a> </li> </c:forEach> <li class="page-item <c:if test="${currentPage == totalPages}">disabled</c:if>"> <a class="page-link" href="?page=${currentPage + 1}">下一页</a> </li> </ul> </nav>

页码遍历用JSTL的<c:forEach>从1循环到总页数,当前页高亮用active类控制,上一页在首页时禁用,下一页在末页时禁用。这套写法是可复用的标准模板,以后做任何系统的分页都可以直接抄。

5. 从配置到上线:IDEA运行JavaWeb项目的完整流程

项目代码写完了,最终得跑起来才算完成。很多初学者面临的最大障碍不是写代码,而是不知道如何在IDEA里把一个JavaWeb项目配上Tomcat并运行起来。我在这里完整复盘一遍流程,每个步骤都是踩过坑之后总结出来的标准操作。

5.1 本地开发环境配置

首先是要保证三个基础软件装好:JDK 1.8、MySQL 5.7或8.0、IDEA。JDK安装后要在系统环境变量里配置JAVA_HOME指向JDK安装目录,然后在Path中添加%JAVA_HOME%\bin,之后在命令行敲java -version验证是否成功。这一步如果没配好,后面IDEA里编译项目都会报找不到JDK。

MySQL的安装我强烈建议用安装包方式而不是解压版,虽然解压版更干净,但需要手动初始化数据目录、配置my.ini、注册Windows服务,每步都有出错概率。安装版一路Next也能自定义数据目录和端口,省心很多。安装完成后,使用MySQL自带命令行或者Navicat/SQLyog这类可视化工具,执行前面提到的建表语句,把数据库创建出来。注意MySQL 8.0默认的认证插件是caching_sha2_password,老版本的JDBC驱动连接会报错,所以要么升级驱动到8.0版本,要么创建用户时指定mysql_native_password认证,这也是一个高频坑。

5.2 用IDEA配置Tomcat并启动项目

在IDEA中打开项目后,点击顶部工具栏的“Add Configuration”,选择“Tomcat Server”,注意要选Local版本。在Application server下拉框里点击Configure,找到你本地安装的Tomcat目录,比如D:\apache-tomcat-8.5.82,IDEA会自动识别版本号。然后切到Deployment页签,点击+号把当前项目以Artifact方式添加进去,Application context建议填/,这样项目访问地址就是http://localhost:8080/,不用再带一长串项目名。

这里有一个新手常犯的错误:只配置了Tomcat但忘了把项目部署进去,导致点运行按钮后浏览器访问404。每次修改代码后,建议手动点击右上角的“Rerun”重启Tomcat。有些朋友希望代码改完自动热部署,可以在Tomcat配置的On frame deactivation选择Update classes and resources,但JSP改动能自动生效,Java类改动经常还是要重启容器。还有一个第一节课就会遇到的坑:IDEA默认Tomcat会占用1099端口的JMX配置端口,如果本机这个端口被占用会导致启动失败,报错信息里会明确提示,把JMX port改成一个别的端口号比如1098即可。

5.3 常见启动故障快速定位

项目跑不起来,先按这个顺序排查:第一,看IDEA控制台的Tomcat Catalina Log和Localhost Log标签页,里面有完整的异常堆栈信息,这是定位问题的第一手资料;第二,检查项目的web.xml文件是否存在且配置正确,Servlet的servlet-mapping路径有没有写错;第三,确认MySQL服务已经启动——Windows下可以在“服务”窗口找到MySQL服务双击启动,或者命令行执行net start mysql;第四,检查数据库连接配置是否和本地数据库一致,尤其是密码必须和安装MySQL时设置的一致。

我遇到过一个非常无语的情况:数据库密码正确、驱动正确、URL正确,但是连接还是报Access denied for user 'root'@'localhost',最后发现是MySQL服务端启用了skip_grant_tables模式,密码验证被彻底跳过,导致所有密码校验异常。这类问题排查起来耗时,但只要你掌握"先看日志、再查配置、最后测联通"的思路,总能一步步接近真相。

6. 高频踩坑与排查手册:我在这套系统上跌过的跟头

每个做JavaWeb项目的人都会遇到一些让人崩溃的报错。我把自己在开发和运行这套日记系统过程中遇到的高频问题整理成了一份排查手册,每一个都给出了具体的解决方案和产生原因,希望对你有实际帮助。

6.1 MySQL 5.7升级到8.0后的连接问题

项目开发后期,我把本地数据库从MySQL 5.7升级到了8.0.26版本,一启动项目就报错:Loading class 'com.mysql.jdbc.Driver'. This is deprecated. The new driver class is 'com.mysql.cj.jdbc.Driver'。这不是致命错误,但每次启动都会出现。解决方案是把驱动类名从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver。

紧接着还遇到了第二个问题:java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone。这是因为MySQL 8.0服务端的时区信息无法被JDBC识别,需要在连接URL上增加serverTimezone=Asia/Shanghai参数。这两个问题几乎每个用MySQL 8.0跑JavaWeb项目的人都会遇到,记下这个配置组合基本可以一次解决。

6.2 中文参数乱码的三种解法

做日记系统时中文乱码问题几乎必然出现:从前端表单提交的中文数据,存到数据库再查出来就变成了“???”,或者在页面上显示成乱码。这个问题有三个层面的解决方案,需要同时做对才能彻底解决。

第一层是页面层面:JSP页面顶部必须声明<%@ page contentType="text/html;charset=UTF-8" language="java" %>,<meta charset="UTF-8">也要写上。第二层是Servlet层面:在doPost方法中,如果使用POST提交,需要在读取参数之前调用request.setCharacterEncoding("UTF-8");如果使用GET提交,还需要在Tomcat的server.xml中给Connector增加URIEncoding="UTF-8"属性,或者在代码里对参数做new String(param.getBytes("ISO-8859-1"), "UTF-8")转换。第三层是数据库层面:建表时指定DEFAULT CHARSET=utf8mb4,同时确保数据库连接URL带上characterEncoding=utf8。三层都做对了,中文才能一路通行无阻。

6.3 部署到服务器后配置修改的一个细节

本地跑通之后,把项目打成WAR包放到远程服务器上的Tomcat部署,会发现数据库连不上了。这多半是因为本地配置写死了localhost:3306,到了服务器上应该改成服务器的数据库地址。我建议把这个配置统一提取到一个db.properties资源文件里,通过Properties类加载读取,这样以后不管是本地开发还是服务器部署,只需要改一个文件,不涉及到代码重新编译。这也是从“学生项目”向“工程化项目”迈进的第一步。

6.4 类找不到与被占用的端口

如果启动时看到ClassNotFoundException: com.mysql.jdbc.Driver,要么是pom.xml中没有引入MySQL驱动依赖,要么是驱动没有随项目打进WEB-INF/lib目录。在IDEA里检查Project Structure中的Artifacts选项卡,把MySQL驱动的lib目录加入输出包即可。另一个经典问题是端口占用:Port 8080 was already in use。解决方法是查出占用端口的进程并结束它,Windows用netstat -ano | findstr 8080查看PID,然后在任务管理器里结束对应进程;或者在Tomcat配置中把HTTP端口改成一个不常用的比如8081。这两个问题都不难,但是它们是新手最经常卡住不动的地方。

7. 项目复盘与后续扩展:这套代码还能带给你什么

整个日记系统从头到尾跑通之后,我强烈建议你不要只停留在“能运行”这个阶段,而是顺着现有代码继续做扩展,因为这套结构其实是很多更复杂系统的缩小版,它身上承载的概念可以延伸到各种业务系统。

一个很好的扩展方向是给日记增加标签系统,在现有diary表的基础上增加一张tag表,再增加一张diary_tag关联表来维护多对多关系。这个改动会迫使你去思考“中间表”的设计理念,也会让你的项目在答辩时更有说头。第二个方向是给系统加入简单的数据统计,比如在首页用ECharts图表展示用户每月写了多少篇日记、心情占比分布,虽然ECharts在JSP中直接引入是旧时代的做法,但理解这种前后端协作方式仍然有价值。第三个方向是安全加固,密码加密从MD5换成加盐的SHA-256,增加防SQL注入的预编译占位符(其实现在用的PreparedStatement基本已经防住),给Session增加简单的验证码校验逻辑。

就我个人经验而言,做完这个项目后再去学SSM或者Spring Boot,你会发现那些框架里的@RequestMapping、@ResponseBody、MyBatis的Mapper接口,本质上都是对Servlet和JDBC的封装。你已经见过“裸实现”的样子,再看“封装实现”,理解速度会有质的提升。这个日记系统虽然小,但它是连接Java基础知识和主流框架的一座稳定的桥,值得你为它花上几个周末。

如果在运行过程中遇到这里没有提到的问题,建议你先按“浏览器URL对不对 → Tomcat日志报什么 → 数据库连接正常不 → SQL语句有没有执行成功”这条链路逐层排查。JavaWeb排错说白了就是一条请求从浏览器出发最终到达数据库,每一个环节都可能出问题,你把它当成一条流水线来看待,定位问题就会有理有据、不慌不忙。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询