简介:一套基于 Java 的个人日记本系统毕业设计完整项目,面向计算机相关专业学生及需要参考完整 Web 开发流程的开发者,可解决毕业设计选题、编码实现与本地部署等常见问题。资源共 4 个文件,总大小 53MB:源码压缩包内含项目工程,采用分层结构组织业务逻辑与界面;数据库脚本提供初始化表结构与示例数据;两个 mp4 视频分别演示项目运行效果与部署步骤,覆盖环境配置、工程导入、数据库连接和系统访问。三类文件分工明确,便于按需查阅。目前已有 1556 人学习,适合作为课设或毕设的对照蓝本。通过阅读源码可了解用户认证、日记增删改查、会话管理等 Java Web 开发关键点,数据库脚本可直接还原数据表结构,部署视频则便于快速排查环境问题;三者配合可帮助学习者串起需求分析、系统设计、编码实现与发布运维的完整链路。
1. 个人日记本系统与Java毕设:这套源码真正在训练你的三件事
如果你拿到手的毕业设计是“基于Java的个人日记本系统”,先别急着把它当成一个低难度的“增删改查”项目。一个日记本系统的完整链路,恰好覆盖了Java方向毕业设计最容易被答辩老师追问的三块硬骨头:数据库表设计是否合理、连接池与事务是否真的理解、部署环境是否亲手踩过坑。和那些动辄堆几十张表的“管理系统”不同,日记本系统表少、逻辑集中,反而更适合用来把 Java Web 的基础讲透。这套源码的价值不在功能多炫,而在你能不能在答辩现场把“为什么这样设计”说清楚。适合目标明确的人群:基础一般但想稳过答辩的本科生,以及想用最短时间把 Servlet + JSP + MySQL 串起来的自学者。
2. 技术选型与源码结构:Java方向毕设先定这三件事
2.1 JDK、Servlet容器与数据库版本怎么配
个人日记本系统这类毕设,技术栈基本是固定的:Java + Servlet + JSP + MySQL。相比 Spring Boot 全家桶,这套传统栈更能体现你对 Java Web 基础的理解,而且答辩时老师问“Servlet 生命周期”“Session 原理”,你完全接得住。版本上建议用 JDK 1.8 + Tomcat 8.x + MySQL 5.7,这是大多数毕设源码的默认组合。JDK 8 和 Tomcat 8 兼容性最稳,MySQL 5.7 的 sql_mode 默认值不会像 8.0 那样严格拒绝 group by 的非聚合列,省去很多报错。
环境配置的常见顺序是:先装 JDK 并配好 JAVA_HOME,再装 Tomcat 并确认 CATALINA_HOME,最后装 MySQL 并设置 root 密码。很多人第一步就翻车,卡在java -version能出来但 Tomcat 启动不了,原因是系统里残留了高版本 JDK 的环境变量。我的习惯是安装完 JDK 后,把 PATH 里多余的 Java 路径全部删掉,只保留%JAVA_HOME%\bin。Tomcat 启动后如果端口被占用,改conf/server.xml里的 Connector port 属性,常见值是 8080,改成 8081 或 9090 都可以。
数据库连接建议用 JDBC 驱动 5.1.49,这个版本对 MySQL 5.7 支持完整,驱动包放到项目的WEB-INF/lib目录下。注意驱动类名是com.mysql.jdbc.Driver,如果你的 pom 里引入的是 8.x 驱动,驱动类名要改成com.mysql.cj.jdbc.Driver,同时连接串里必须加serverTimezone=Asia/Shanghai,否则时间字段会报错。这一条就能劝退一批照抄 8.x 驱动配置的人。
2.2 从DAO到Service到Servlet:源码分层与各层职责
拿到源码后不要直接导入 IDE 开跑,先看包结构。典型的分层是com.xxx.diary下分entity、dao、service、servlet、util五层。entity 放日记和用户的实体类,字段与数据库表一一对应;dao 层写 JDBC 操作,比如UserDao里findByUsername、insertUser,DiaryDao里insertDiary、updateDiary、deleteDiary、findByUserId;service 层把 DAO 操作组合成业务动作,比如注册时先查重再插入;servlet 层接收请求、调用 service、返回 JSP 页面。
public int updateDiary(Diary diary) { String sql = "UPDATE diary SET title = ?, content = ?, mood = ?, update_time = NOW() WHERE id = ? AND user_id = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, diary.getTitle()); ps.setString(2, diary.getContent()); ps.setString(3, diary.getMood()); ps.setInt(4, diary.getId()); ps.setInt(5, diary.getUserId()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }这段代码是日记编辑功能里最常见的一个 DAO 方法。注意update_time = NOW()写在 SQL 里而不是在 Java 里new Date()传入,好处是时间以数据库服务器为准,避免本机时间和服务器时间不一致导致记录混乱。WHERE id = ? AND user_id = ?是个容易被忽略的安全细节,它保证用户只能改自己的日记,哪怕请求里伪造了别人的日记 id 也改不动,这在答辩时可以直接讲成“数据隔离”。
PreparedStatement的四个setXxx方法按 SQL 中?出现的顺序赋值,从 1 开始计数。粗心的同学经常在标题和内容上传反,造成日记内容写到标题列里。赋值完成后executeUpdate()返回受影响行数,返回 0 说明没有匹配的记录,servlet 层拿到这个结果就可以给用户弹“修改失败”。
2.3 为什么不用Spring Boot?毕业设计的“够用”与“加分”
很多人纠结:既然 Spring Boot 开发效率高,为什么毕设还要用 Servlet?关键在于你的答辩场景。Spring Boot 把 Tomcat 内嵌、自动配置、依赖管理全封装掉了,你写完全程可能都说不清请求是怎么到达 Controller 的,老师一问“Tomcat 在哪配的”就露馅。而传统 Servlet 项目,你亲手配置了 web.xml、编写了 Servlet 映射、管理了数据库连接,这些恰恰是 Java Web 的基础知识点。
<servlet> <servlet-name>DiaryServlet</servlet-name> <servlet-class>com.diary.servlet.DiaryServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DiaryServlet</servlet-name> <url-pattern>/diary</url-pattern> </servlet-mapping>这段 web.xml 配置告诉你两件事:servlet-name可以自由取名但必须唯一,servlet-class必须写类的全限定名,url-pattern决定前端访问路径。很多同学在此翻车——改了 Servlet 类名但忘了改 web.xml,结果启动不报错、访问就 404。合理边界是:如果你的毕设题目明确要求“基于 Spring Boot”,那用 Boot 没错;如果题目只写了“基于 Java”,传统 Servlet 方案更容易自圆其说。
从“加分”角度看,你可以在传统方案上做三个小升级:引入 DBCP 或 C3P0 连接池、使用 Filter 做编码和登录拦截、用 Jackson 把日记数据输出成 JSON 接口。这三个点都能在答辩时展示你对“工程化”的理解,而且它们不需要替换掉 Servlet 这一层,是低成本高回报的改造方向。注意不要为了追求大而全,硬塞 MyBatis 或 Redis,毕设的核心是讲清楚,不是炫技。
3. 数据库设计与连接池:日记系统的数据地基
3.1 核心表设计与SQL
个人日记本系统的数据库通常只有两张表:用户表和日记表。用户表字段一般有id、username、password、nickname、create_time;日记表字段有id、user_id、title、content、mood、weather、create_time、update_time。两张表通过user_id外键关联,这个设计是数据表设计的核心,也是答辩时老师必问的一个点——为什么日记表要有user_id字段?答案是可以查询某用户的所有日记,也方便做权限控制,保证每条日记都有归属。
CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(64) NOT NULL, `nickname` VARCHAR(50) DEFAULT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `diary` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `title` VARCHAR(100) NOT NULL, `content` TEXT, `mood` VARCHAR(20) DEFAULT NULL, `weather` VARCHAR(20) DEFAULT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;两张表都用utf8mb4字符集,而不是utf8。很多人觉得“我存中文没问题啊”,但utf8在 MySQL 中最多支持 3 字节,而 emoji 表情是 4 字节,用户在日记标题里加个表情就会报Incorrect string value错误。utf8mb4 是 utf8 的超集,存中文和 emoji 都没问题。建表脚本里我加了KEY idx_user_id,这个普通索引的作用是加速按用户查日记的操作——表数据量小的时候感觉不出来,但你可以在答辩时说“这是为数据量增长预留的优化手段”。
时间字段的DEFAULT CURRENT_TIMESTAMP很关键,它让插入记录时自动带上当前时间,少写一行 Java 代码。注意diary表的update_time用了ON UPDATE CURRENT_TIMESTAMP,这样你在 Java 里更新日记内容时,时间戳会自动刷新,不需要你手动setUpdateTime。提交数据库脚本时,建议把DROP TABLE IF EXISTS和CREATE TABLE写在一起,方便重复执行。
3.2 数据库连接池参数设置
连接池是毕设答辩的高频考点。我不建议新手在 DAO 里每次DriverManager.getConnection()现连现关,那样连接开销大,而且你没法回答“高并发下连接不够怎么办”。常见的做法是引入commons-dbcp2或c3p0,配一个连接池工具类。以 DBCP2 为例,核心配置项有initialSize、maxTotal、maxIdle、minIdle、maxWaitMillis五个。
public class DBUtil { private static final BasicDataSource dataSource = new BasicDataSource(); static { dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/diary_db?useUnicode=true&characterEncoding=utf8&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxIdle(10); dataSource.setMinIdle(5); dataSource.setMaxWaitMillis(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这段配置里最容易被忽略的是连接串上的characterEncoding=utf8。数据库是 utf8mb4,连接串是 utf8,这两者并不冲突,因为 utf8mb4 兼容 utf8,只要连接串和页面编码保持 utf8,中文就不会乱码。useSSL=false是必须加的,MySQL 5.7 默认开了 SSL 校验,不加的话日志里会刷 Warning。serverTimezone在 5.1.49 驱动下可以不加,但如果你是 8.x 驱动就必须加。
maxWaitMillis设置为 3000 意味着连接池耗尽时,线程最多等 3 秒,超时抛异常而不是无限阻塞。这个参数在答辩时可以向老师解释为“防止死等导致的请求堆积”。maxTotal=20是连接池上限,不是越大越好,连接数超过数据库实例的处理能力反而会拖慢响应。对毕业设计来说 20 足够,聊到生产环境可以补一句“生产上会根据并发压测结果调这个值”。
3.3 时间与字符集的两个先手
时间字段的坑比字符集更隐蔽。很多人的思路是 Java 里用new java.util.Date()传给数据库,存进去的是2019-08-21 15:23:45.0,页面显示时多出.0,虽然不影响功能,但答辩时有老师说“这数据格式不对”,你还得费口舌解释。兜底的做法是:实体类里的Date字段配合 Jackson 的@JsonFormat注解,统一输出格式;数据库端用DATETIME,Java 端用java.util.Date,中间不做任何字符串转换,让 JDBC 驱动自己处理。
字符集要检查三个位置:数据库字符集、连接串编码、JSP 页面编码。任何一个不一致都会乱码。最稳定的组合是:数据库utf8mb4、连接串characterEncoding=utf8、JSP 页面pageEncoding="UTF-8"。同时要在 web.xml 里加一个编码过滤器,否则表单提交的中文在 Servlet 里request.getParameter()取出来是乱码。这个 Filter 在网络上有一百种写法,我建议直接用最简单的:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); }然后把过滤器在 web.xml 里映射到/*,放在所有 Servlet 映射之前。写request.setCharacterEncoding必须在第一次读取参数之前,否则取出来的已经是乱码,再设置就晚了。放在过滤器里天然满足这个时序要求,这也是为什么建议你现在就养成“表单提交一律走过滤器”的习惯,它可以省掉每个 Servlet 里重复写编码设置的麻烦。
4. 登录、日记与关键字检索:核心功能落地与高频故障排查
4.1 注册登录的密码处理与Session状态
注册登录是日记系统的门面。密码存数据库前必须处理,明文存储是答辩大忌。毕设级别的合理方案是对密码做 MD5 加盐,盐值可以取用户名。加密逻辑放在 service 层的一个工具类里,不要在 Servlet 里散落加密代码。常见写法如下:
public static String md5WithSalt(String password, String salt) { String base = password + salt; StringBuilder sb = new StringBuilder(); try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] bytes = md.digest(base.getBytes("UTF-8")); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) sb.append('0'); sb.append(hex); } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); }这段代码做了两件事:把密码和用户名拼接后做 MD5 摘要,再把字节数组转成十六进制字符串。0xff & b是为了处理字节符号位,否则Integer.toHexString会输出ffffffff这种 8 位十六进制,长度不一致。注册时调用md5WithSalt(password, username)存入数据库,登录时把用户输入的密码按同样规则加密,再和库里比对。如果两个密文相同,登录成功。
登录成功后,把用户信息放进 Session 而不是只放个 userId。常见写法是session.setAttribute("loginUser", user),这样 JSP 页面可以直接用session.getAttribute("loginUser").nickname显示昵称。同时做一层登录拦截:在过滤器里判断session.getAttribute("loginUser")是否为空,为空就重定向到登录页。这里有个特别容易踩的坑——Session 在 Tomcat 默认 30 分钟过期,用户写日记写到一半去吃了顿饭,回来提交就跳回登录页,写的全丢了。缓解的办法是前端定时发送心跳请求保持 Session 活跃,或者表单提交失败时保留用户输入内容让他能复制回来。
4.2 日记增删改查与服务层事务
日记的增删改查是核心中的核心。增加日记时,Servlet 取出 title、content、mood、weather 四个参数,从 Session 里拿 userId,组装成 Diary 对象传给 service 层,service 再调用 DAO 插入。删除和修改都要带上userId条件,保证用户只能操作自己的数据。这里不细讲每个方法,把最容易翻车的删除操作拿出来看:
public boolean deleteDiary(int diaryId, int userId) { String sql = "DELETE FROM diary WHERE id = ? AND user_id = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, diaryId); ps.setInt(2, userId); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }看到那条WHERE id = ? AND user_id = ?了吗?很多同学写成只按 id 删,结果一个用户拿别人的日记 id 拼个请求就能删掉别人的数据。答辩老师就爱问这个:你怎么防止越权访问?你答“我所有对日记的写操作都带 userId 条件”,这比把“权限”两个字挂在嘴上有说服力得多。service 层的方法只有一行逻辑也能暴露事务问题——真实场景里一次写操作可能涉及多张表,比如写日记的同时更新用户表里的日记统计数据,只有一个 DAO 操作撑不起事务。
事务怎么在纯 Servlet + JDBC 里做?关键在于不能让 DAO 自己拿连接,因为事务要求多个操作用同一连接。常见方案是在 service 层手动获取连接、开启事务、把连接传给 DAO,最后提交或回滚。更好的做法是写成DBUtil.getConnection()后用conn.setAutoCommit(false)包住两个 DAO 调用,DAO 需要重载一个接收 Connection 的版本。毕设系统大部分业务单表操作居多,事务这块你可以放一个“多表操作场景”的演练代码在答辩时展示,比如“记录操作日志 + 删除日记”用事务包起来。
4.3 用一个模糊搜索看SQL注入与预编译
日记列表页通常会提供一个按标题关键字搜索的功能。这个功能看着简单,却是 SQL 注入的重灾区。最容易犯的错是把用户输入直接拼接进 SQL:
String sql = "SELECT * FROM diary WHERE title LIKE '%" + keyword + "%'";用户输入' OR 1=1 --就能把整个表的日记全查出来,输入'; DROP TABLE diary; --就更危险了。要彻底避开,只能靠 PreparedStatement 的预编译。参数占位符和用户输入分离,用户输入永远只当数据、不当 SQL 执行:
public List<Diary> searchDiary(int userId, String keyword) { String sql = "SELECT * FROM diary WHERE user_id = ? AND title LIKE ? ORDER BY create_time DESC"; List<Diary> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setString(2, "%" + keyword + "%"); ResultSet rs = ps.executeQuery(); // 遍历 ResultSet 封装成 Diary 对象并放入 list } catch (SQLException e) { e.printStackTrace(); } return list; }注意%和keyword是拼在一起作为一个参数传给setString的,不是拼进 SQL 字符串,所以用户输入里的特殊字符只会被当成字面量,不会被解析成 SQL 语法。这个道理能讲清楚,答辩老师基本不会再为难你。顺带说一句:ORDER BY create_time DESC用来让新写的日记排前面,是列表页的常见需求,很多人写日记系统时漏掉排序,结果日记顺序乱跳。
4.4 五个高频故障的排查记录
第一个高频故障是 Tomcat 启动报ClassNotFoundException: com.mysql.jdbc.Driver。现象:控制台打印驱动类找不到。原因:JDBC 驱动 jar 没放进WEB-INF/lib,或放在了lib但 IDE 没有重新部署。解决:确认mysql-connector-java-5.1.49.jar在WEB-INF/lib下,在 IDEA 的 Project Structure 里把 jar 标记为Compile并重新部署,别只动文件系统不重新构建。
第二个高频故障是登录后列表页中文乱码。现象:页面标题正常但用户名和日记内容全是???。原因:数据库连接串缺characterEncoding=utf8,或者 JSP 页面漏了pageEncoding="UTF-8"。解决:连接串补参数,所有 JSP 头部统一写<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>。排查方式是查数据库里存进去的就是???还是只有页面显示乱码——把System.out.println打出来的中文和数据库里的比对,就能确定乱码发生在哪一层。
第三个高频故障是Communications link failure。现象:系统跑一会儿后突然报这个错,重启 Tomcat 又好了。原因:MySQL 默认 8 小时空闲断开连接,连接池里的连接已经被数据库踢掉。解决:连接串加autoReconnect=true,但这不是根治方案;更好的做法是在连接池配置里加testOnBorrow=true和validationQuery=SELECT 1,每次从池中取连接前先验证有效性,无效就丢弃重新建连。
第四个高频故障是多个请求都能改同一篇日记,造成相互覆盖。现象:两个窗口同时编辑同一篇日记,后保存的覆盖先保存的。原因:没有做并发版本控制。毕设层面不要求做乐观锁,但你可以加一个version字段,更新时SET version = version + 1 WHERE version = ?,更新行数为 0 就提示用户“内容已被修改”。这个点讲出来,答辩档次直接往上走。
第五个高频故障是404 Not Found访问不到 Servlet。现象:Tomcat 启动正常,但访问/diary/login报 404。原因:很可能是 Servlet 映射的 URL 和表单提交的 action 不一致,或者类名改了但 web.xml 里还是旧的servlet-class。解决:打开浏览器开发者工具的 Network 面板,看请求的实际 URL,再对照 web.xml 的url-pattern,两者必须完全一致。这条经验在面试时也通用——前后端路径对不上,排查顺序永远是先看请求地址再看后端映射。
5. 部署验证与答辩:三个能直接照做的收尾技巧
5.1 部署验证的“三步自查法”
拿到项目后不要直接点运行,按三步走能帮你提前排除九成问题。第一步查字符集:打开浏览器按 F12,看页面响应头的Content-Type是否含charset=UTF-8,再进数据库执行SHOW VARIABLES LIKE 'character_set_server'确认服务端字符集。第二步查数据源:直接SELECT * FROM diary看看能不能查到记录,如果刚部署完查不到,先确认数据库脚本真的执行成功了——用 Navicat 或命令行执行完后一定要commit,很多初学着在命令行跑完脚本忘了提交。第三步查日志:Tomcat 的logs/catalina.out里如果只报 Warning 不报 Error,基本可以上线;报 Error 就先看报错行号,八成问题能定位到 4.4 节那五个坑里。
5.2 答辩时你手里该有的三张“底牌”
第一张底牌是表结构设计图:用表格把user表和diary表列清楚,字段名、类型、约束、外键关系都写上,老师问哪张表你都能快速找到准确答案。第二张底牌是连接池参数表:initialSize=5、maxTotal=20、maxWaitMillis=3000这三个参数的值和理由背熟,顺便能说一句“这些值适合毕设规模,生产环境需要压测后调整”。第三张底牌是功能边界说明:哪些功能做了、哪些没做、为什么没做。诚实说“没有做找回密码功能,因为这是毕设范围之外的需求”比含糊其辞体面得多。
5.3 一个值得加的进阶技巧:写操作留痕
如果想让系统多一个亮点,就给你的日记写操作加一个记录日志的模块。删除日记时,把操作时间、操作人、日记标题写进一张diary_log表。这个技巧代码量不大,一张新表加两个 DAO 方法就能实现,但在答辩时它是一个完整的“审计追踪”案例。我在给毕业设计做代码审查时,见过几十份写操作不留痕的作业,能主动想到这个层面的很少。小伙子们记住:技术的价值从来不在于堆新框架,而在于想清楚你要解决的问题。希望帮到你。
本文还有配套的精品资源,点击获取