简介:面向Java Web入门学习者与课程设计人群,这份源码以JSP+MySQL实现个人日记本系统,涵盖用户登录验证、日记增删改查、分类管理与个人中心等完整功能链路。项目采用MVC分层思路,JSP负责视图呈现,Servlet控制请求流转,JDBC完成数据交互,并借助session维持登录状态,是理解Web请求处理与表单提交的直观案例。包体共90个文件、约256KB,以html页面、java源码、jsp动态页面为主,另含md说明文档与png/gif等界面素材,目录结构贴近真实工程,便于对照学习。当前已有223人学习浏览,配合README文档梳理环境搭建与关键代码位置,适合毕业设计或Servlet/JSP期末作业参考,可帮读者快速掌握从页面表单到数据库落盘的端到端写法。
1. 这份JSP个人日记本源码,到底能让你学会什么
如果你的搜索记录里有“jsp+mysql源码”这个组合,多半是课程设计、期末项目或自学练手找素材。这份“基于jsp+mysql的JSP个人日记本源码.zip”,本质上是一个标准的Java Web入门级项目:用JSP做页面展示、Servlet做请求处理、MySQL做数据存储,把日记本的注册、登录、写日记、修改、删除、查看列表这一套CRUD跑通。它的价值不在代码多高级,而在于——
你拿到这份源码后,能一次性看到“浏览器请求→Tomcat→Servlet→JDBC→MySQL→页面渲染”的完整链路。对于刚学完Java SE、正卡在SSM或Spring Boot门槛前的人来说,把这份小项目吃透,比直接啃框架源码更有性价比。它的坑也典型:环境变量、驱动版本、时区、中文乱码、Tomcat部署路径,每一个都是后续开发要反复遇到的。
下面按我平时接手这类源码项目的顺序,从环境搭建讲到代码改造,最后给出一组能直接用的避坑清单。
2. 跑通前的准备工作:JDK、Tomcat、MySQL的版本搭配与安装
2.1 三个环境的版本怎么选才不互相打架
这类JSP+MySQL的老项目,最怕的就是“源码是2016年的,电脑是2025年的”。先给结论:JDK用1.8,Tomcat用8.5或9.0,MySQL用5.7或8.0。JDK 11以上虽然能编译大部分JSP项目,但很多源码里用了过时的内部API,比如javax.servlet包的位置变化,会在编译期报“程序包不存在”,到时候定位问题很烧时间。Tomcat 10及以上把javax.servlet换成了jakarta.servlet,老源码直接部署会报ClassNotFoundException,这一点务必留意。
MySQL方面,5.7和8.0对这类项目都能跑。区别主要在连接驱动的写法:MySQL 5.7用的是com.mysql.jdbc.Driver,MySQL 8.0需要改成com.mysql.cj.jdbc.Driver,而且必须在连接URL里加上serverTimezone=Asia/Shanghai,否则服务端返回的时间会比你本地晚8小时或直接报CST错误。如果你的源码包里的db.properties或DBUtil.java写着老驱动名,而你本地装的是8.0,改一行就能解决。
我一般会建议新手直接装MySQL 8.0,因为现在官网能下到的安装包基本都是8.x,5.7的安装包反而不太好找了。但如果你照着网上的mysql安装教程操作时发现字符集总是设置不成功,那多半是没把my.ini里的character-set-server=utf8mb4配好,这点后面会细说。
2.2 环境变量配置与Tomcat部署的最小操作
JDK装完后,需要配JAVA_HOME和PATH。新手最容易犯的错是只配PATH不配JAVA_HOME,导致Tomcat启动脚本找不到JDK。正确做法是:
- 新建系统变量
JAVA_HOME,值填JDK安装根目录,比如C:\Program Files\Java\jdk1.8.0_202。 - 在
PATH里追加%JAVA_HOME%\bin和%JAVA_HOME%\jre\bin。 - 命令行敲
java -version验证,如果显示的版本跟你装的一致,说明成功。
Tomcat不需要安装,解压即用。但要注意:解压路径不要带中文和空格,很多人喜欢放在D:\学习资料\代码\apache-tomcat-8.5.xx,启动时脚本会卡在路径解析上。放到D:\tomcat这类纯英文路径最省心。
启动Tomcat前,先在conf/server.xml里确认端口。默认是8080,如果被其他程序占了,把<Connector port="8080"改成8081或者其它未被占用的端口。然后双击bin\startup.bat,浏览器访问http://localhost:8080看到猫的页面,说明环境通了。
如果你用的是Eclipse或IDEA,不需要手动部署到Tomcat的webapps目录。在IDEA里配置好本地Tomcat后,直接把项目以war exploded格式跑起来即可。但如果源码包里没有.idea或.classpath这些工程配置文件,你需要在IDEA里新建一个Web项目,再把源码的src目录和web目录拷进去。这一步对新手来说最容易迷茫,我建议直接用Tomcat的webapps目录部署:
copy 项目文件夹 %TOMCAT_HOME%\webapps\diary cd %TOMCAT_HOME%\bin startup.bat把整个项目文件夹复制到Tomcat的webapps下,Tomcat启动时会自动部署。访问http://localhost:8080/diary/就能看到项目首页。这种“直接拷webapps”的部署方式是老项目最省事的跑法,不用配IDE,不用管Artifact,缺点是没有编译步骤,所以你必须确认WEB-INF\classes目录下已经有编译好的.class文件——如果源码包只给了.java没给.class,就得先用javac手动编译,或者老老实实用IDE跑。
2.3 源码目录结构的三个必看位置
拿到ZIP解压后,先别急着启动,花两分钟认一下目录。以我见过的同类项目为例,常见的结构是这样的:
diary/ ├── WEB-INF/ │ ├── web.xml │ ├── classes/ │ │ └── com/ │ │ └── diary/ │ │ ├── dao/ # 数据库访问 │ │ ├── model/ # 实体类 │ │ ├── servlet/ # 控制器 │ │ └── util/ # DB连接和工具 │ └── lib/ # mysql-connector jar ├── css/ ├── js/ ├── index.jsp ├── login.jsp └── ...重点看三个地方:第一,WEB-INF/lib下有没有MySQL的JDBC驱动jar包,没有它启动后点任何功能都会报ClassNotFoundException: com.mysql.jdbc.Driver;第二,web.xml里配置的servlet映射路径,比如/login、/register、/addDiary,这些路径要和JSP表单里的action一致;第三,WEB-INF/classes下的.class文件时间戳,如果源码包只有.java文件,说明需要重新编译。
很多人在这一步卡住是因为把WEB-INF当普通目录去访问,直接在浏览器输入http://localhost:8080/diary/WEB-INF/web.xml会返回404。这是Tomcat的安全限制,WEB-INF下的文件不允许直接通过URL访问,只有Servlet内部forward或include能访问。看到这里,你就明白为什么业务代码都要放在WEB-INF外面引用。
3. 数据库设计与JDBC连接:先把数据底座立起来
3.1 建库建表:三张表的字段设计与字符集选择
个人日记本这类项目,表结构通常不复杂,但字符集和排序规则如果选错,后面全是乱码。我建议建库时用下面的DDL:
CREATE DATABASE IF NOT EXISTS diary_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE diary_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_diary ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, title VARCHAR(100) NOT NULL, content TEXT, mood VARCHAR(20), weather VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_diary_user FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;解释一下几个关键点:utf8mb4比utf8多支持4字节的Emoji字符,如果你在日记内容里存了表情符号,用utf8会报Incorrect string value错误。COLLATE utf8mb4_general_ci是大小写不敏感的排序规则,对用户名和日记标题做模糊查询时更宽容。两张表都用InnoDB,是为了让外键约束和将来可能加的事务生效。
create_time用DEFAULT CURRENT_TIMESTAMP,这样插入记录时不手动传时间也能自动填当前时间。你在源码的DiaryDao.addDiary()方法里看到的插入语句就会少一个参数。但我接手的不少源码里,表的字段名跟实体类属性名不一致,比如数据库字段叫user_id,Java属性叫userId,如果源码里用了BeanUtils拷贝属性,就会赋值不上。排查方法是看日志有没有NoSuchPropertyException,或者在Servlet里request.getParameter后打印一遍值。
不要忽略那个update_time ON UPDATE CURRENT_TIMESTAMP字段。日记编辑功能做完后,列表页往往要按“最近修改时间”排序,有这个字段就不用每次UPDATE时手动SET update_time=NOW()。
3.2 JDBC连接工具类:参数、驱动、时区的标准写法
源码里一定有一个DBUtil.java或JdbcUtils.java,核心代码长这样:
public class DBUtil { private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/diary_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逐个参数说:useSSL=false是绕过MySQL 8.0的SSL握手警告。如果你没加这个参数,控制台会刷一堆Establishing SSL connection without server's identity verification is not recommended的警告,但一般不影响运行。真正影响运行的是serverTimezone=Asia/Shanghai,MySQL 8.0要求必须显式指定时区,否则报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。
最坑的是allowPublicKeyRetrieval=true。如果你连接MySQL 8.0用的是caching_sha2_password认证插件,不打开这个参数,会报Public Key Retrieval is not allowed。这行参数一般在老教程里看不到,因为5.7默认用的mysql_native_password没有这个要求。
PASSWORD硬编码在代码里是这类课程的惯例,但你自己做项目时别学。正确的姿势是把数据库连接信息抽到db.properties里,用ResourceBundle读取,或者至少在连接时用环境变量覆盖。原因很简单:课程设计的源码会被交到老师手上,如果里面有真实密码,等于公开了你的数据库凭据。我一般会建议把密码改成弱口令(比如只用于本机开发),并加上注释提醒。
连接池方面,这个小项目用单连接的DriverManager.getConnection没问题,因为访问量低。但你得知道它的瓶颈:每次请求都创建一次数据库连接,而TCP建连和MySQL认证的开销远大于SQL执行本身。如果你之后把这个项目改造成多用户并发访问,至少换成DBCP或C3P0连接池,或者更现代的HikariCP。改造点不大,就是getConnection()内部从连接池拿连接,其余代码不用动。
3.3 数据库账号授权与建表执行
连接数据库之前,确认你的MySQL服务在运行。Windows下可以在服务管理器中找到MySQL80这样的服务,或者在命令行执行net start mysql。如果服务起不来,查看C:\ProgramData\MySQL\MySQL Server 8.0\Data下的.err文件,里面会明确写端口冲突还是数据目录权限问题。
用命令行建库建表时,我习惯把SQL写进一个init.sql文件,然后执行:
mysql -u root -p diary_db < init.sql注意diary_db必须先存在,上一节说过先CREATE DATABASE。如果你不想进命令行,也可以用Navicat或MySQL Workbench导入。
表建好后,用下面这段SQL验证数据是否正常写入:
SELECT * FROM t_diary ORDER BY update_time DESC LIMIT 5;如果查询正常,说明字符集和权限都没问题。如果哥哥字段是乱码,回my.ini把character_set_server改成utf8mb4,然后重启MySQL服务。很多mysql安装教程漏了这一步,导致数据库层面已经是乱码,Java代码再怎么写编码过滤器都没用。
4. 从登录到写日记:核心功能的最小实现与关键代码
4.1 登录与注册:密码验证只会用equals可不行
登录功能的流程是:login.jsp表单提交→LoginServlet接收参数→调用UserDao.findByUsernameAndPassword→成功则跳index.jsp,失败则回登录页并提示错误。源码里最常见的写法是这样的:
User user = userDao.findByUsername(username); if (user != null && user.getPassword().equals(password)) { // 密码错误分支缺失 session.setAttribute("loginUser", user); response.sendRedirect("index.jsp"); } else { response.sendRedirect("login.jsp?error=1"); }这段代码最大的问题是:如果findByUsername查不到用户,user为null,把user.getPassword()放前面会空指针。正确做法是用常量或Objects.equals,或者改成“先判断用户名是否存在,再比对密码”。另外,密码用明文存数据库是这类课设源码的通病,等你学完过滤器,一定能意识到要能用MD5或SHA-256加密再入库。别觉得小项目无所谓,日记本是私密性很强的数据,用户密码和日记内容都值得认真对待。
还有一处隐藏问题:登录成功用sendRedirect重定向是对的,但很多源码在重定向index.jsp之前就调用了request.getSession()把用户信息放了进去。重定向是两次请求,第一次请求的HttpServletRequest在第二次请求里已经失效,如果你把用户信息放在request里而不是session里,跳过去一看又变成未登录状态。在JSP页面里,你可以这样检查会话:
<% User loginUser = (User) session.getAttribute("loginUser"); if (loginUser == null) { response.sendRedirect("login.jsp"); return; } %>这段判断放在index.jsp的顶部,作为简单的登录拦截。真正的过滤应该用Filter,但在这个项目里先保证功能可用,再用Filter统一管理也不迟。
4.2 日记的增删改查:PreparedStatement预防SQL注入
日记添加页的表单,重点看日期和文本内容。源码里就算没有使用JSTL,也会在JSP中用<%= %>输出。这里有个安全点必须提:所有SQL操作用PreparedStatement,不要用Statement拼字符串。看一个正确写法:
public boolean addDiary(Diary diary) { String sql = "INSERT INTO t_diary (user_id, title, content, mood, weather) VALUES (?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, diary.getUserId()); ps.setString(2, diary.getTitle()); ps.setString(3, diary.getContent()); ps.setString(4, diary.getMood()); ps.setString(5, diary.getWeather()); int rows = ps.executeUpdate(); return rows > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }PreparedStatement有两点好处:参数占位符?能避免拼接SQL导致注入风险,比如用户在标题里输入'; DROP TABLE t_diary;--,如果用Statement,整条SQL就变成了两条;同时预编译机制能提高重复执行的效率。虽然这个课设项目里没人会恶意攻击你,但面试官会问,你得能答出来。
删除和更新类似,只不过更新时要防止把create_time也改了。如果表里没有update_time字段,你就得在UPDATE语句里显式设置:
UPDATE t_diary SET title=?, content=?, mood=?, weather=?, create_time=NOW() WHERE id=? AND user_id=?注意AND user_id=?这个条件。很多源码只按id删除,这意味着任何登录用户只要知道笔记ID,就能删掉别人的日记。加上user_id做二次校验是这条SQL的价值所在。同理,查询单个日记详情时也要同时带上user_id。
4.3 列表分页与日期展示:两个容易被忽略的小细节
日记列表页通常是这样的SQL:
public List<Diary> findMyDiary(int userId, int page, int pageSize) { String sql = "SELECT * FROM t_diary WHERE user_id=? ORDER BY update_time DESC LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); ... } }LIMIT的两个参数,第一个是偏移量(从第几行开始),第二个是数量。LIMIT 0, 5表示取前5条。翻页时页码从1开始,偏移量是(page-1)*pageSize,这里很容易把page直接当偏移量,导致第一页正常第二页查出空数据。遇到“下一页没数据”的问题,先看SQL里的偏移量对不对。
日期展示问题主要出现在JSP页面上。MySQL的DATETIME字段返回的是java.sql.Timestamp,你直接<% %>输出会显示成2025-06-01 14:23:45.0,末尾多了个“.0”。处理方式有两种:在Java里用SimpleDateFormat格式化,或者在JSP页面用JSTL的fmt:formatDate标签,前提是引入jstl.jar和standard.jar。很多老源码没有这两个jar,所以直接用前一种:
String formattedTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(diary.getUpdateTime());把这个formattedTime放进Diary对象或者直接set成属性,页面输出就不带多余的毫秒了。
4.4 Session与页面跳转:刷新后重复提交的坑
日记提交后,一般会response.sendRedirect("diaryList?userId=" + userId)跳回列表页。这是标准的Post/Redirect/Get模式:如果直接forward到列表页,刷新浏览器会再次提交刚才的表单,导致一条日记写了两遍。老源码里最常见的“点击一次添加了两条日记”就是没做重定向。
如果你发现源码里是request.getRequestDispatcher("list.jsp").forward(request, response),建议改成sendRedirect。改之前注意核对list.jsp是否能从request域拿到列表数据——因为重定向后request里的旧数据不会保留,你得在重定向之前把userId拼在URL参数中,列表页加载时再从数据库查。这也是为什么我前面强调列表查询方法必须接收userId参数的原因。
5. 避坑自查:JSP+MySQL项目跑不通的五个高频原因
5.1 现象:Tomcat能打开首页,点登录就报500
控制台最明显的错误是ClassNotFoundException: com.mysql.jdbc.Driver或SQLException: No suitable driver found。
原因:MySQL驱动jar不在WEB-INF/lib下,或者驱动类名和MySQL版本不匹配。MySQL 8.0的驱动类名变成了com.mysql.cj.jdbc.Driver,老项目里写的com.mysql.jdbc.Driver虽然也能用(jar内有兼容),但部分老旧jar版本不支持8.0协议。
解决:把驱动jar放到WEB-INF/lib,确认驱动类名,在DBUtil里打印一行Class.forName(DRIVER)后的日志验证加载成功。注意不要同时放两个不同版本的mysql-connector-java.jar,Tomcat的类加载顺序会乱掉,导致莫名其妙的方法签名报错。
5.2 现象:能登录,但页面所有中文都是乱码
乱码分两种:页面显示乱码和存入数据库乱码。如果数据库里的数据是中文,但页面显示??,说明数据库连接URL没写characterEncoding=utf-8,MySQL连接默认用utf8mb4之外的字符集传参。
如果数据库里直接存了乱码,说明是建库时字符集没设对,或MySQL服务端默认字符集不是utf8mb4。排查命令:
SHOW VARIABLES LIKE 'character_set%';看到character_set_server不是utf8mb4时,改my.ini后重启。JSP页面本身也要保证UTF-8编码,检查每个.jsp文件是否以UTF-8保存,且在页面顶部写全:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>如果上面这些都做了还乱码,看看Tomcat的server.xml里Connector是否加了URIEncoding="UTF-8"。处理GET请求的中文参数时,Tomcat 8以上默认就是UTF-8,但如果你用Tomcat 7,需手动配置。
5.3 现象:MySQL 8.0连接报Public Key Retrieval is not allowed
这个错非常典型,用户密码正确、服务正常,就是连不上。原因是MySQL 8.0默认用户插件是caching_sha2_password,客户端首次连接需要去服务端拿公钥,而JDBC驱动默认不自动获取。
解决:在URL末尾加allowPublicKeyRetrieval=true&useSSL=false。如果你不想改代码,也可以登录MySQL后把用户插件改回mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;两种方式都能解决,但我更推荐前者,因为改插件会影响数据库的安全策略,只为一个课设把密码插件降级不划算。
5.4 现象:点击“写日记”跳转到了404
先看浏览器地址栏URL。如果jsp页面里的form的action是<%=request.getContextPath()%>/addDiary,而web.xml里把Servlet映射到了/diary/addDiary,就会404。这类问题属于“路径对不上”。
解决:以web.xml为准,统一表单action值。JSP里尽量用${pageContext.request.contextPath}/xxx方式拼接,不要写死绝对路径。如果你项目其实已经部署在webapps/diary下,表单的action写成/diary/addDiary没问题,但若以后改了部署目录,路径就失效。这是老项目的一个通病,也是新手手上最容易混淆的概念。
5.5 现象:MySQL启动失败,报Bind on TCP/IP port: 3306
端口被其他MySQL实例或程序占用。尤其很多人电脑里装了不同版本的MySQL,之前一个实例占着3306,新装的又试图监听3306。
解决:到my.ini里把port改成3307,连接时URL里也要对应改成jdbc:mysql://localhost:3307/...。或者把旧服务停掉。Windows下用netstat -ano | findstr 3306查看占用PID和对应进程,确认不是系统关键进程后再结束。如果确认是旧MySQL,用服务管理器把旧服务停止并设为手动。
注意:排查问题不要先改代码。凡是数据库相关的错,先确认MySQL服务状态、连接参数、驱动jar这三个变量,用排除法一步步缩小范围。改代码前,先看控制台完整的异常堆栈,而不是只看最上面一行。
6. 毕业设计验收前,如何用三招把这个项目再拔高一点
如果你的目标是课程设计或毕业设计验收,只把CRUD跑通是不够的,答辩老师会用“并发”“安全”“维护性”三个维度来追问。我给你三个能快速落地、又能讲清楚原理的改造方向,工作量都不大。
第一招,给密码加上盐值哈希。不要只用MD5,至少做成MD5(用户名 + 密码)或SHA-256。在UserDao的register方法里替换明文存储,login时同样做哈希后比对。讲的时候可以提一句“防止数据库泄露时明文口令被直接使用”,这句话比写一整天分页代码更让老师买账。
第二招,给编辑和删除操作加上“请求来源校验”。做法很简单:在update.jsp和delete.jsp里用session中的userId做条件拼接SQL,这个我们前面已经做到。你再补一个小细节——列表页只显示当前登录用户的日记,查询SQL里带上user_id即可,别用WHERE 1=1从全表筛。这样从“功能正确”变成“逻辑严谨”,是评分分水岭。
第三招,把硬编码的数据库连接参数挪到db.properties文件里,然后在DBUtil中加载。这改动不到十行,但体现的是“配置文件与代码分离”的工程意识。答辩时你可以说:将来换环境不用改源码,只要修改properties文件里的数据库地址、用户名和密码。再顺手把DBUtil的getConnection方法改成从连接池拿连接,讲的时候能说出连接池比单连接的优势,就已经超出多数同学的水平了。
我自己的习惯是:拿到这类源码包,第一件事不是解压运行,而是先全局搜索String password和jdbc:mysql,把所有硬编码的敏感信息标出来。然后跑一个最小功能路径:注册→登录→写日记→改日记→删日记。这条路走通了,再去看那些花哨的功能。等你把这些东西都折腾一遍,你会发现JSP+MySQL这个组合虽然老,但它把Web开发最本质的东西——HTTP流程、会话状态、SQL边界——都教透了你。希望这篇笔记对你有帮助,祝你跑通顺利。
本文还有配套的精品资源,点击获取