Java个人日记本系统毕设:数据库、连接池与Servlet全解析
发布时间:2026/9/28 1:49:45来源:尧图网络
简介一套基于 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同时连接串里必须加serverTimezoneAsia/Shanghai否则时间字段会报错。这一条就能劝退一批照抄 8.x 驱动配置的人。2.2 从DAO到Service到Servlet源码分层与各层职责拿到源码后不要直接导入 IDE 开跑先看包结构。典型的分层是com.xxx.diary下分entity、dao、service、servlet、util五层。entity 放日记和用户的实体类字段与数据库表一一对应dao 层写 JDBC 操作比如UserDao里findByUsername、insertUserDiaryDao里insertDiary、updateDiary、deleteDiary、findByUserIdservice 层把 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-nameDiaryServlet/servlet-name servlet-classcom.diary.servlet.DiaryServlet/servlet-class /servlet servlet-mapping servlet-nameDiaryServlet/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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两张表都用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?useUnicodetruecharacterEncodingutf8useSSLfalse); 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(); } }这段配置里最容易被忽略的是连接串上的characterEncodingutf8。数据库是 utf8mb4连接串是 utf8这两者并不冲突因为 utf8mb4 兼容 utf8只要连接串和页面编码保持 utf8中文就不会乱码。useSSLfalse是必须加的MySQL 5.7 默认开了 SSL 校验不加的话日志里会刷 Warning。serverTimezone在 5.1.49 驱动下可以不加但如果你是 8.x 驱动就必须加。maxWaitMillis设置为 3000 意味着连接池耗尽时线程最多等 3 秒超时抛异常而不是无限阻塞。这个参数在答辩时可以向老师解释为“防止死等导致的请求堆积”。maxTotal20是连接池上限不是越大越好连接数超过数据库实例的处理能力反而会拖慢响应。对毕业设计来说 20 足够聊到生产环境可以补一句“生产上会根据并发压测结果调这个值”。3.3 时间与字符集的两个先手时间字段的坑比字符集更隐蔽。很多人的思路是 Java 里用new java.util.Date()传给数据库存进去的是2019-08-21 15:23:45.0页面显示时多出.0虽然不影响功能但答辩时有老师说“这数据格式不对”你还得费口舌解释。兜底的做法是实体类里的Date字段配合 Jackson 的JsonFormat注解统一输出格式数据库端用DATETIMEJava 端用java.util.Date中间不做任何字符串转换让 JDBC 驱动自己处理。字符集要检查三个位置数据库字符集、连接串编码、JSP 页面编码。任何一个不一致都会乱码。最稳定的组合是数据库utf8mb4、连接串characterEncodingutf8、JSP 页面pageEncodingUTF-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 注入的重灾区。最容易犯的错是把用户输入直接拼接进 SQLString sql SELECT * FROM diary WHERE title LIKE % keyword %;用户输入 OR 11 --就能把整个表的日记全查出来输入; DROP TABLE diary; --就更危险了。要彻底避开只能靠 PreparedStatement 的预编译。参数占位符和用户输入分离用户输入永远只当数据、不当 SQL 执行public ListDiary searchDiary(int userId, String keyword) { String sql SELECT * FROM diary WHERE user_id ? AND title LIKE ? ORDER BY create_time DESC; ListDiary 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并重新部署别只动文件系统不重新构建。第二个高频故障是登录后列表页中文乱码。现象页面标题正常但用户名和日记内容全是???。原因数据库连接串缺characterEncodingutf8或者 JSP 页面漏了pageEncodingUTF-8。解决连接串补参数所有 JSP 头部统一写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %。排查方式是查数据库里存进去的就是???还是只有页面显示乱码——把System.out.println打出来的中文和数据库里的比对就能确定乱码发生在哪一层。第三个高频故障是Communications link failure。现象系统跑一会儿后突然报这个错重启 Tomcat 又好了。原因MySQL 默认 8 小时空闲断开连接连接池里的连接已经被数据库踢掉。解决连接串加autoReconnecttrue但这不是根治方案更好的做法是在连接池配置里加testOnBorrowtrue和validationQuerySELECT 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是否含charsetUTF-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表列清楚字段名、类型、约束、外键关系都写上老师问哪张表你都能快速找到准确答案。第二张底牌是连接池参数表initialSize5、maxTotal20、maxWaitMillis3000这三个参数的值和理由背熟顺便能说一句“这些值适合毕设规模生产环境需要压测后调整”。第三张底牌是功能边界说明哪些功能做了、哪些没做、为什么没做。诚实说“没有做找回密码功能因为这是毕设范围之外的需求”比含糊其辞体面得多。5.3 一个值得加的进阶技巧写操作留痕如果想让系统多一个亮点就给你的日记写操作加一个记录日志的模块。删除日记时把操作时间、操作人、日记标题写进一张diary_log表。这个技巧代码量不大一张新表加两个 DAO 方法就能实现但在答辩时它是一个完整的“审计追踪”案例。我在给毕业设计做代码审查时见过几十份写操作不留痕的作业能主动想到这个层面的很少。小伙子们记住技术的价值从来不在于堆新框架而在于想清楚你要解决的问题。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网