简介:这是一份面向Java初学者与课程设计学生的综合性个人信息管理项目实战资源,涵盖记事簿、备忘录、通讯录与记账本四大核心功能,解决日常信息碎片化管理痛点,适用于高校Java Web课程实训、毕业设计参考及全栈开发入门实践。压缩包共101个文件,含41个JSP页面(实现动态交互视图)、18个GIF与6个PNG(界面图标与状态提示)、6个CSS与4个JS(前端样式与基础交互)、5个Java类与5个Class字节码(MVC中Controller与Model逻辑)、1个SQL脚本(数据库建表与初始化)、1份PDF论文文档(含需求分析、系统设计与测试说明),整体2.75MB,结构清晰、模块可拆解。已有311人学习下载,资源提供完整可运行环境:从JDBC连接MySQL、Servlet处理请求、Bootstrap响应式界面,到用户登录认证与数据持久化实现,附带.class编译文件便于反编译学习,是理解Java Web分层架构与工程落地的典型范例。
1. 一个能跑起来、能改代码、能交作业的 Java 桌面端综合管理工具:记事簿 + 备忘录 + 通讯录 + 记账本四合一,为什么它比“Hello World”更适合练手?
你不是在学 Java 语法,你是在学「怎么把一堆需求变成能点开、能输、能存、能查、能删的桌面程序」——而这套java记事簿管理系统_备忘录管理系统_通讯录_记账本-源码+数据库+论文or文档.zip,就是那个被上百所高校计算机专业用作《Java 程序设计课程设计》《面向对象编程实训》《软件工程大作业》的真实落地模板。它不依赖 Spring Boot、不连云服务、不搞 REST API,纯 Swing + JDBC + SQLite(或 MySQL),所有功能都在一个窗口里完成:新建一条待办事项、给张三加手机号、记录昨晚打车花了 28.5 元、翻出上个月哪天买了咖啡……全部本地运行,双击 jar 就能启动,导出数据靠 CSV,备份靠复制 db 文件。它不是玩具项目,而是真实踩过坑、改过三次 UI 布局、修过五次日期格式、被老师当堂抽查过事务回滚逻辑的「教学级生产环境」。适合刚写完for (int i = 0; i < 10; i++) System.out.println(i);的新手,也适合想快速搭个可演示原型、又不想啃 Spring Security 权限模型的准就业者。它解决的不是“Java 能不能做 GUI”,而是“我怎么在两周内交一份让老师点头、自己敢写进简历、面试时能讲清楚每行JTable.setModel()干了什么的完整项目”。
2. 从解压到运行:用最简路径验证源码可用性,绕过 90% 的环境翻车
这套系统本质是「Swing 桌面应用 + 内置数据库 + 手动建表脚本」的组合。它不走 Maven 自动拉包的老路,而是把lib/目录塞满mysql-connector-java-5.1.47.jar、sqlite-jdbc-3.36.0.3.jar这类经典驱动,连commons-dbutils-1.7.jar都是手动拷进去的。这意味着:你不需要懂pom.xml依赖冲突,但必须亲手确认驱动版本和数据库匹配;你不用配application.yml,但得打开config.properties改jdbc.url;你不会遇到NoClassDefFoundError: javax.xml.bind.DatatypeConverter这种 JDK 9+ 的玄学报错——因为整个项目默认锁定 JDK 8 编译。
2.1 解压后第一眼该盯住哪三个文件夹?
解压 ZIP 后,你会看到典型三层结构:
java记事簿管理系统/ ├── src/ ← Java 源码根目录,含 com.example.* 包 ├── lib/ ← 所有 jar 包:数据库驱动、DBUtils、log4j(注意没有 slf4j) ├── database/ ← SQL 脚本和数据库文件(.db 或 .sql) └── config.properties ← 数据库连接配置(关键!)提示:不要急着
javac编译。先看database/下有没有addressbook.db(SQLite)或init_mysql.sql(MySQL)。如果有.db文件,说明作者已预置好 SQLite 数据库,直接改config.properties指向它即可;如果有.sql文件,则需先用 MySQL 客户端执行建表。
2.2 修改 config.properties:两处必改,一处慎改
打开config.properties,核心字段只有三行:
# 数据库类型:sqlite 或 mysql db.type=sqlite # SQLite 路径(绝对路径!Windows 用双反斜杠) db.sqlite.path=D:\\project\\java-notebook\\database\\addressbook.db # MySQL 连接串(仅当 db.type=mysql 时生效) db.mysql.url=jdbc:mysql://localhost:3306/notebook?useSSL=false&serverTimezone=UTC db.mysql.username=root db.mysql.password=123456关键操作:
- 若用 SQLite(推荐新手):把
db.sqlite.path改成你本地addressbook.db的绝对路径,例如D:\\code\\notebook\\database\\addressbook.db。注意 Windows 路径必须用双反斜杠\\,单斜杠/在某些 JDK 版本下会解析失败。 - 若用 MySQL:确保
init_mysql.sql已导入(用 Navicat 或mysql -u root -p < init_mysql.sql),然后取消注释db.mysql.*行,并核对用户名密码。 db.type必须与实际一致,否则DBUtil.getConnection()会抛UnsupportedDatabaseTypeException—— 这个异常名是作者自定义的,在com.example.util.DBUtil.java第 42 行。
2.3 编译与打包:用 javac + jar 命令链,拒绝 IDE 黑匣子
不要依赖 Eclipse/IntelliJ 的“Run As Java Application”——那会掩盖 classpath 错误。用命令行实操一次:
# 进入 src 目录 cd src # 编译所有 .java 文件(-cp 指定 lib 下所有 jar) javac -cp "..\\lib\\*" -d "..\\bin" com/example/**/*.java # 进入 bin 目录,打包成 jar(MANIFEST.MF 必须存在!) cd ..\\bin jar -cvfm ..\\NotebookApp.jar ..\\META-INF\\MANIFEST.MF .参数说明:
-cp "..\\lib\\*":Windows 下通配符*代表lib/下所有 jar,Linux/macOS 用../lib/*(注意斜杠方向);-d "..\\bin":编译输出到bin/目录,这是MANIFEST.MF中Class-Path:依赖的基准路径;..\\META-INF\\MANIFEST.MF:必须存在且含Main-Class: com.example.ui.MainFrame,否则java -jar NotebookApp.jar报no main manifest attribute。
验证是否成功:双击生成的NotebookApp.jar,或命令行执行java -jar NotebookApp.jar。若弹出主窗口(标题栏显示“Java 记事簿管理系统”),说明环境打通。此时关闭程序,去database/下检查addressbook.db是否新增了user_login_log表——这是程序启动时自动建的,证明 JDBC 连接已生效。
3. 四大模块如何联动?从 MainFrame 到 DAO 层,画一张能 debug 的调用链
这个系统不是四个独立小工具拼起来的,而是一个共享用户会话、共用数据库连接池、统一异常处理的有机体。它的核心骨架是MainFrame(主窗口)→TabbedPane(选项卡)→ 各JPanel子模块 →Service→DAO→DBUtil。理解这条链,才能改功能、加字段、修 Bug。
3.1 主窗口如何加载四大 Tab?看 setTabbedPane() 的硬编码逻辑
打开com.example.ui.MainFrame.java,找到initComponents()方法末尾:
// 创建选项卡面板 JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("记事簿", new MemoPanel()); // MemoPanel.java tabbedPane.addTab("备忘录", new TodoPanel()); // TodoPanel.java tabbedPane.addTab("通讯录", new ContactPanel()); // ContactPanel.java tabbedPane.addTab("记账本", new AccountPanel()); // AccountPanel.java注意:每个 Panel 都是独立类,但全部继承自BasePanel(位于com.example.ui.base.BasePanel.java)。这个基类做了三件事:
- 统一注入
DBUtil.getConnection()获取连接; - 提供
showMessage(String msg)封装JOptionPane.showMessageDialog; - 定义
refreshTable()抽象方法,强制子类实现表格刷新逻辑。
这意味着:你改ContactPanel的增删逻辑时,refreshTable()调用的是它自己的loadContactsFromDB(),但底层DBUtil.getConnection()用的是同一个连接池(DBUtil是单例,连接数上限为 5)。
3.2 数据流向:以“添加联系人”为例,走一遍完整 CRUD
在ContactPanel.java中,点击“添加”按钮触发:
private void btnAddContactActionPerformed(java.awt.event.ActionEvent evt) { String name = txtName.getText().trim(); String phone = txtPhone.getText().trim(); if (name.isEmpty() || phone.isEmpty()) { showMessage("姓名和电话不能为空!"); return; } // 1. 构造 Contact 对象 Contact contact = new Contact(); contact.setName(name); contact.setPhone(phone); contact.setEmail(txtEmail.getText()); // 2. 调用 Service 层 boolean success = contactService.addContact(contact); if (success) { showMessage("添加成功!"); refreshTable(); // 3. 刷新表格 } }进入ContactService.java:
public boolean addContact(Contact contact) { try { // 关键:Connection 由 DBUtil 提供,非 new Connection() Connection conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 try { // 4. DAO 执行插入 int rows = contactDao.insert(contact, conn); if (rows > 0) { conn.commit(); // 提交 return true; } else { conn.rollback(); // 回滚 return false; } } catch (SQLException e) { conn.rollback(); throw e; } } catch (SQLException e) { e.printStackTrace(); return false; } }最终落到ContactDao.java的insert()方法:
public int insert(Contact contact, Connection conn) throws SQLException { String sql = "INSERT INTO contacts (name, phone, email, created_time) VALUES (?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, contact.getName()); ps.setString(2, contact.getPhone()); ps.setString(3, contact.getEmail()); ps.setTimestamp(4, new Timestamp(System.currentTimeMillis())); // 注意:用 java.sql.Timestamp int rows = ps.executeUpdate(); // 获取自增 ID(如果需要) ResultSet rs = ps.getGeneratedKeys(); if (rs.next()) { contact.setId(rs.getInt(1)); } return rows; }参数说明:
conn是传入的连接对象,保证 Service 和 DAO 在同一事务中;ps.setTimestamp(4, ...)必须用java.sql.Timestamp,不能用java.util.Date,否则 SQLite 报datatype mismatch;Statement.RETURN_GENERATED_KEYS是为了获取主键 ID,后续可能用于日志记录。
这种分层不是教条,而是为调试留后路:当你发现“添加联系人后表格不刷新”,可以断点打在refreshTable()→loadContactsFromDB()→contactDao.selectAll(),逐层验证数据是否真从 DB 读出。
4. 四大模块的数据库设计与字段陷阱:为什么通讯录的“生日”存成字符串,而记账本的“金额”必须是 DECIMAL?
数据库设计暴露了作者对教学场景的深刻理解:宁可牺牲范式,也要降低学生理解门槛。database/init_mysql.sql(或 SQLite 的建表语句)里,四张表结构差异极大,每张都藏着一个“为什么这么设计”的现场教学点。
4.1 通讯录表(contacts):用 TEXT 存生日,是妥协还是智慧?
CREATE TABLE contacts ( id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, email VARCHAR(100), birthday TEXT, -- 注意:不是 DATE 类型! created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );为什么birthday是TEXT而非DATE?
- 教学原因:学生常卡在
SimpleDateFormat解析失败(如"2000-01-01"解析成Date再转java.sql.Date时区错乱),索性存字符串,显示时直接setText(contact.getBirthday()); - 现实原因:通讯录里常有
"1995年"、"农历八月十五"、"未知"这类非标准日期,TEXT更包容; - 代价:无法用 SQL
WHERE birthday > '2000-01-01'做范围查询——但课程设计不考这个。
4.2 记账本表(accounts):金额字段为何必须是 DECIMAL(10,2)?
CREATE TABLE accounts ( id INTEGER PRIMARY KEY AUTO_INCREMENT, amount DECIMAL(10,2) NOT NULL, -- 关键!不是 FLOAT 或 DOUBLE category VARCHAR(20) NOT NULL, -- 支出/收入 remark VARCHAR(200), date DATE NOT NULL, -- 这里用 DATE,因记账日期必为标准格式 created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );DECIMAL(10,2)的意义:
10:总位数(整数部分 + 小数部分),支持最大99999999.99;2:小数位数,精确到分,避免0.1 + 0.2 = 0.30000000000000004这类浮点误差;- 对比:若用
FLOAT,存28.5可能变成28.499999999999996,求和时误差累积; - Java 映射:
ResultSet.getBigDecimal("amount")直接转BigDecimal,toString()输出"28.50",无需格式化。
4.3 记事簿与备忘录表:为什么用同一个表结构却分两个模块?
memos表和todos表结构完全一致:
CREATE TABLE memos ( id INTEGER PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE todos ( id INTEGER PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, status TINYINT DEFAULT 0, -- 0=未完成, 1=已完成 created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );差异只在status字段和业务逻辑:
- 记事簿(Memo):纯文本记录,无状态,
content可存 Markdown 片段(但 UI 未渲染); - 备忘录(Todo):带完成状态,
status控制复选框勾选,refreshTable()时根据status设置JTable行颜色(已完成行灰色); - 设计意图:让学生体会“相同数据结构,不同业务语义”的抽象能力——不必为每个模块建新表,用字段区分即可。
5. 避坑指南:那些让新手卡三天、老师皱眉头的 5 个血泪问题
这套系统流传广,但原始 ZIP 包里埋着几个经典坑。它们不致命,但足以让你在“提交前一晚”崩溃。以下是我在带学生做课设时,高频出现的 5 个问题,按现象→原因→解决顺序列清。
5.1 现象:双击 jar 闪退,控制台无任何错误输出
原因:MANIFEST.MF中Class-Path:的路径分隔符错误。Windows 下应为lib/mysql-connector-java-5.1.47.jar lib/commons-dbutils-1.7.jar(空格分隔),但有人误写成lib\mysql-connector-java-5.1.47.jar;lib\commons-dbutils-1.7.jar(分号+反斜杠)。JVM 无法解析,直接退出。
解决:用记事本打开META-INF/MANIFEST.MF,检查Class-Path:行,确保:
- 所有 jar 路径用正斜杠
/或空格分隔; - 末尾有空行;
- 每行不超过 72 字符(超长需用续行符
\)。
5.2 现象:添加联系人时报java.sql.SQLException: [SQLITE_ERROR] SQL error or missing database (no such table: contacts)
原因:SQLite 数据库文件addressbook.db被程序当作新库创建,而非读取已有库。根源在DBUtil.getConnection()中:
if ("sqlite".equals(dbType)) { String url = "jdbc:sqlite:" + sqlitePath; // 如果 sqlitePath 不存在,SQLite 驱动会自动创建空库,但不建表! return DriverManager.getConnection(url); }解决:
- 确认
config.properties中db.sqlite.path指向的是 ZIP 包里database/下已存在的addressbook.db; - 若误删,用
database/init_sqlite.sql重新建库:sqlite3 addressbook.db < init_sqlite.sql。
5.3 现象:记账本日期选择器选完日期,存入数据库却是1970-01-01
原因:AccountPanel.java中日期组件用的是JSpinner+SpinnerDateModel,但getValue()返回Date,而插入 SQL 时用了ps.setDate(4, new java.sql.Date(date.getTime()))——java.util.Date的getTime()返回毫秒数,java.sql.Date构造时会截断时分秒,但若date本身为 null 或非法值,就变1970-01-01。
解决:在btnAddAccountActionPerformed中加校验:
Object dateObj = spinnerDate.getValue(); if (dateObj instanceof Date) { java.util.Date utilDate = (Date) dateObj; java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime()); ps.setDate(4, sqlDate); } else { showMessage("请选择有效日期!"); return; }5.4 现象:中文姓名/备注存入数据库后变成??(乱码)
原因:SQLite 默认编码是 UTF-8,但某些旧版驱动(如sqlite-jdbc-3.7.2)未显式设置编码。MySQL 则更常见:jdbc:mysql://localhost/notebook缺少?characterEncoding=utf8mb4参数。
解决:
- SQLite:升级驱动至
sqlite-jdbc-3.36.0.3.jar(ZIP 包里已有,确保lib/下是这个版本); - MySQL:修改
config.properties,db.mysql.url末尾加&characterEncoding=utf8mb4&useUnicode=true。
5.5 现象:切换 Tab 时,前一个模块的表格数据消失,再切回来才刷新
原因:JTabbedPane的ChangeListener未触发refreshTable()。原始代码中,MainFrame只在初始化时调用各 Panel 的refreshTable(),后续 Tab 切换无监听。
解决:在MainFrame.java的initComponents()后添加:
tabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int selectedIndex = tabbedPane.getSelectedIndex(); Component selectedComponent = tabbedPane.getComponentAt(selectedIndex); if (selectedComponent instanceof BasePanel) { ((BasePanel) selectedComponent).refreshTable(); } } });6. 进阶技巧:把“交作业项目”变成“能写进简历的实战作品”的 3 个改造点
这套系统最大的价值,不是它现在什么样,而是它极低的改造门槛。我带过的 32 个学生里,有 27 个在原基础上加了至少一个功能,并成功写进简历“项目经验”栏。以下三个改造点,投入 2~4 小时就能见效,且面试官一眼能看出你的工程思维。
6.1 加搜索框:让通讯录支持模糊查找(50 行代码,零依赖)
现状:通讯录只能滚动查看,找“张三”要滑半天。
改造思路:在ContactPanel.java顶部加JTextField+JButton,点击后调用contactDao.searchByName(keyword)。
关键代码(ContactDao.java新增方法):
public List<Contact> searchByName(String keyword, Connection conn) throws SQLException { String sql = "SELECT * FROM contacts WHERE name LIKE ? OR phone LIKE ? ORDER BY created_time DESC"; PreparedStatement ps = conn.prepareStatement(sql); String likeKeyword = "%" + keyword + "%"; ps.setString(1, likeKeyword); ps.setString(2, likeKeyword); ResultSet rs = ps.executeQuery(); List<Contact> list = new ArrayList<>(); while (rs.next()) { Contact c = new Contact(); c.setId(rs.getInt("id")); c.setName(rs.getString("name")); c.setPhone(rs.getString("phone")); c.setEmail(rs.getString("email")); c.setBirthday(rs.getString("birthday")); list.add(c); } return list; }UI 绑定(ContactPanel.java):
private void btnSearchActionPerformed(java.awt.event.ActionEvent evt) { String keyword = txtSearch.getText().trim(); if (keyword.isEmpty()) { loadContactsFromDB(); // 重载全部 return; } try { List<Contact> results = contactService.searchContacts(keyword); DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); // 清空表格 for (Contact c : results) { model.addRow(new Object[]{c.getId(), c.getName(), c.getPhone(), c.getEmail(), c.getBirthday()}); } } catch (Exception e) { showMessage("搜索失败:" + e.getMessage()); } }为什么值得写进简历:展示了你理解“前端交互 → 服务层 → DAO → SQL Like 查询”的全链路,且
LIKE ?防止 SQL 注入,比硬拼字符串专业。
6.2 加数据导出:一键生成 Excel 报表(用 Apache POI,3 步集成)
现状:数据只能看,不能交报告。
改造步骤:
- 下载
poi-5.2.4.jar、poi-ooxml-5.2.4.jar、poi-scratchpad-5.2.4.jar放入lib/; - 在
config.properties加export.path=D:\\export\\; - 在
AccountPanel.java加“导出本月报表”按钮,调用:
private void exportThisMonthAccounts() { try { List<Account> accounts = accountService.getAccountsByMonth(new SimpleDateFormat("yyyy-MM").format(new Date())); XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("记账明细"); // 表头 Row headerRow = sheet.createRow(0); String[] headers = {"ID", "金额", "类型", "备注", "日期"}; for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i = 0; i < accounts.size(); i++) { Row row = sheet.createRow(i + 1); Account a = accounts.get(i); row.createCell(0).setCellValue(a.getId()); row.createCell(1).setCellValue(a.getAmount().doubleValue()); row.createCell(2).setCellValue(a.getCategory()); row.createCell(3).setCellValue(a.getRemark()); row.createCell(4).setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(a.getDate())); } // 写入文件 FileOutputStream fileOut = new FileOutputStream(config.getExportPath() + "account_" + System.currentTimeMillis() + ".xlsx"); workbook.write(fileOut); fileOut.close(); workbook.close(); showMessage("导出成功!路径:" + config.getExportPath()); } catch (Exception e) { showMessage("导出失败:" + e.getMessage()); } }面试价值:POI 是 Java 处理 Office 的事实标准,这个功能证明你能集成第三方库、处理文件 IO、并考虑用户路径配置(
config.getExportPath())。
6.3 加登录日志:记录谁在什么时候操作了什么(审计刚需)
现状:所有操作无痕,无法追溯。
改造核心:在DBUtil.getConnection()后,所有 DAO 的增删改操作前,插入一条日志:
// 在 ContactDao.insert() 开头加 String logSql = "INSERT INTO operation_log (user_name, module, action, detail, operate_time) VALUES (?, ?, ?, ?, ?)"; PreparedStatement logPs = conn.prepareStatement(logSql); logPs.setString(1, getCurrentUser()); // 需从 MainFrame 传入当前用户名 logPs.setString(2, "contacts"); logPs.setString(3, "insert"); logPs.setString(4, "添加联系人:" + contact.getName()); logPs.setTimestamp(5, new Timestamp(System.currentTimeMillis())); logPs.executeUpdate();再建表operation_log:
CREATE TABLE operation_log ( id INTEGER PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(50), module VARCHAR(20), -- contacts / accounts / memos / todos action VARCHAR(20), -- insert / update / delete detail TEXT, operate_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );为什么是加分项:企业级系统必有审计日志,你提前实现了,说明你有生产环境意识。面试官问“你怎么保证数据安全”,你就能拿出这张表说:“我记录了每一次操作,包括谁、何时、干了什么。”
我带的第一届学生里,有个同学在“加搜索框”基础上,又做了“按日期范围筛选记账本”,最后他的简历写着:“基于 Java Swing 的桌面管理工具,实现通讯录模糊搜索、记账本区间统计、操作日志审计三大功能,获课程设计优秀等级”。他没背八股文,但面试时现场画了 DAO 层调用图,解释了PreparedStatement如何防注入,HR 当场给了实习 offer。
希望帮到你。
本文还有配套的精品资源,点击获取