
简介这份资源是面向Java桌面应用开发初学者与课程设计者的通讯录管理系统完整项目源码基于Java Swing构建图形界面、MySQL负责数据存储通过JDBC实现联系人信息的增删改查。项目涵盖用户界面、数据模型、业务逻辑、数据库连接、异常处理与权限控制等模块适合用来理解桌面端三层结构如何落地也可作为毕业设计或实训作业的参考模板。压缩包共23个文件约2.16MB包含5个java源文件、6个class编译文件、5张png界面素材以及sql建库脚本、db数据文件、jar依赖与工程配置文件源码与可运行产物齐备便于直接导入IDE调试。目前已有3701人学习下载读者可借此掌握Swing组件布局、JDBC连接池式操作与SQL语句编写并参考其中的异常捕获与数据校验思路快速搭建自己的通讯录或类似信息管理工具。1. JavaSwingMysql 通讯录管理系统从建表到打包一个能交作业也能上生产的完整路径很多人第一次接触 JavaSwingMysql 通讯录管理系统是在课程设计或者毕业设计里。需求听起来简单增删改查联系人。但真正动手就会发现坑不在业务逻辑而在环境、驱动、事务和界面线程上。我带过几届学生的课设也帮朋友改过几个上线用的内部通讯录工具最常见的翻车现场是MySQL 8 装好了JDBC 驱动却还是 5.x 的 jarSwing 表格删了一行数据库里没删模糊查询一敲中文就乱码。这套系统麻雀虽小五脏俱全它把 Java 桌面开发、JDBC 编程、SQL 事务、MVC 分层全串了一遍。如果你正在找 javaweb项目完整案例mysql 的轻量替代或者想用一个能跑通的小项目把 java基础 和 mysql事务处理 串起来这篇就是按我实际搭环境的顺序写的每一步都有命令和参数说明新手能跟熟手能直接拿去改。2. 环境与建表把 MySQL 8 和 JDBC 驱动先对齐2.1 为什么通讯录系统首选 MySQL 8 而不是 5.7选版本这件事很多人是看 mysql安装教程 随手装一个。我的建议是直接上 MySQL 8.0原因有三个。第一8.0 默认字符集是 utf8mb4通讯录里存中文姓名、备注、甚至 emoji 都不会出问题5.7 默认 latin1建库时不显式指定后面就是一堆乱码血泪经验。第二8.0 的窗口函数和 CTE 在后续做分组统计比如按公司分组统计联系人时更顺手。第三新项目没有理由迁就老版本除非你的服务器上已经跑着 5.7 且不能动。安装方式上Windows 用 mysql下载官网 的 MSI 安装包最省事Linux 上 rpm安装mysql 或者用包管理器都行。装完第一件事是验证字符集-- 查看服务端字符集必须是 utf8mb4 SHOW VARIABLES LIKE character_set_server; SHOW VARIABLES LIKE collation_server;如果 character_set_server 不是 utf8mb4去 my.cnf 或 my.ini 的 [mysqld] 段加两行character-set-serverutf8mb4 和 collation-serverutf8mb4_unicode_ci然后重启服务。这一步不做后面 Swing 界面里显示问号你会以为是 Java 的问题其实是数据库的锅。2.2 通讯录表结构字段类型和索引怎么定通讯录的核心表不复杂但字段类型选错后面排序和查询都会难受。下面是我一般会用的建表语句CREATE DATABASE IF NOT EXISTS address_book DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE address_book; CREATE TABLE contact ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, name VARCHAR(50) NOT NULL COMMENT 姓名, phone VARCHAR(20) NOT NULL COMMENT 手机号, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, company VARCHAR(100) DEFAULT NULL COMMENT 公司, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个参数说明。phone 用 VARCHAR 而不是 BIGINT因为手机号可能带 86 前缀或者分机号而且你不会拿它做算术。uk_phone 唯一索引防止重复录入这是通讯录最实用的约束。idx_name 普通索引是为了姓名模糊查询注意 LIKE %张% 这种前置通配符用不上索引但 LIKE 张% 能命中所以搜索框设计时值得引导用户。created_at 和 updated_at 交给数据库自动维护比在 Java 里手动 set 时间靠谱少一处出错点。提示如果你的 MySQL 是 5.7DATETIME 的 ON UPDATE CURRENT_TIMESTAMP 支持没问题但默认值 CURRENT_TIMESTAMP 在早期 5.6 上有坑5.7 之后都正常。2.3 JDBC 驱动版本对齐mysql-connector-j 的坑这是新手翻车率最高的一步。MySQL 8 必须配 mysql-connector-j 8.x用 5.1.x 的驱动连 8.0 会报 Unknown system variable query_cache_size 或者 SSL 相关错误。反过来8.x 驱动连 5.7 一般兼容但连接串参数写法有变化。Maven 项目直接引dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version /dependency注意 groupId 从老的 mysql 改成了 com.mysqlartifactId 也从 mysql-connector-java 改成了 mysql-connector-j这是 8.0.31 之后的变化很多老教程还写着旧坐标复制过去会拉不到包。连接串我固定用这个模板private static final String URL jdbc:mysql://localhost:3306/address_book ?useUnicodetrue characterEncodingutf8 useSSLfalse allowPublicKeyRetrievaltrue serverTimezoneAsia/Shanghai;useSSLfalse 是本地开发省去证书配置生产环境该开还是要开。allowPublicKeyRetrievaltrue 解决 8.0 默认 caching_sha2_password 认证方式下的公钥检索问题不加这个经常报 Public Key Retrieval is not allowed。serverTimezone 必须显式指定否则 8.x 驱动可能按 UTC 解析你存进去的时间会差 8 小时这个玄学问题排查起来很费时间。3. Swing 界面与 JDBC 分层把表格和数据库接起来3.1 三层结构为什么通讯录也要分 DAO 和 Service有人觉得通讯录就一张表直接在一个 JFrame 里写 SQL 完事。能跑但改起来痛苦。我一般分三层entity 放 Contact 实体dao 放 ContactDao 负责 SQLservice 放 ContactService 负责事务和校验ui 放 MainFrame 只处理界面事件。这样做的直接好处是Swing 的事件线程EDT里不写 SQL避免界面卡死单元测试可以只测 DAO不用启动界面。Contact 实体就是标准 JavaBean字段和表一一对应注意用 java数据类型 里合适的包装类型id 用 Long 而不是 long因为新增时 id 为 null 表示未入库。public class Contact { private Long id; private String name; private String phone; private String email; private String company; private String remark; // getter/setter 省略 }3.2 ContactDao增删改查的 SQL 与 PreparedStatementDAO 层全部用 PreparedStatement不要拼字符串这是防 SQL 注入的基本功也是 java开发工程师面试题 里高频考点。下面给出核心方法public class ContactDao { // 新增返回自增主键 public long insert(Contact c) throws SQLException { String sql INSERT INTO contact(name, phone, email, company, remark) VALUES(?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, c.getName()); ps.setString(2, c.getPhone()); ps.setString(3, c.getEmail()); ps.setString(4, c.getCompany()); ps.setString(5, c.getRemark()); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { return rs.getLong(1); } } } return -1; } // 按姓名模糊查询注意参数里带 % 由 Java 拼不写进 SQL public ListContact searchByName(String keyword) throws SQLException { String sql SELECT * FROM contact WHERE name LIKE ? ORDER BY name; ListContact list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } return list; } private Contact mapRow(ResultSet rs) throws SQLException { Contact c new Contact(); c.setId(rs.getLong(id)); c.setName(rs.getString(name)); c.setPhone(rs.getString(phone)); c.setEmail(rs.getString(email)); c.setCompany(rs.getString(company)); c.setRemark(rs.getString(remark)); return c; } }逻辑说明try-with-resources 保证 Connection、PreparedStatement、ResultSet 自动关闭这是 JDBC 最容易漏的地方漏了连接池很快耗尽。RETURN_GENERATED_KEYS 让新增后能拿到数据库生成的 id界面刷新时不用重新查一遍。模糊查询的 % 在 Java 侧拼接SQL 里只留一个 ?这样既安全又清晰。参数说明setString 的索引从 1 开始不是 0这个错新手常犯。searchByName 里 ORDER BY name 让结果稳定避免每次查询顺序乱跳。3.3 Swing 表格模型DefaultTableModel 还是自定义 TableModelJTable 的数据来源是 TableModel。小项目直接用 DefaultTableModel 最快但它把所有数据存成 Vector类型不安全。我一般自定义一个 ContactTableModel 继承 AbstractTableModel内部持有 List 这样增删改后只要 fireTableDataChanged()界面自动刷新。public class ContactTableModel extends AbstractTableModel { private final String[] columns {ID, 姓名, 手机号, 邮箱, 公司, 备注}; private ListContact data new ArrayList(); public void setData(ListContact list) { this.data list; fireTableDataChanged(); // 通知 JTable 重绘 } public Contact getContactAt(int row) { return data.get(row); } Override public int getRowCount() { return data.size(); } Override public int getColumnCount() { return columns.length; } Override public String getColumnName(int col) { return columns[col]; } Override public Object getValueAt(int row, int col) { Contact c data.get(row); switch (col) { case 0: return c.getId(); case 1: return c.getName(); case 2: return c.getPhone(); case 3: return c.getEmail(); case 4: return c.getCompany(); case 5: return c.getRemark(); default: return null; } } }关键点是 fireTableDataChanged()它触发 JTable 重新读取所有行。如果你只改了一行用 fireTableRowsUpdated(row, row) 性能更好。注意所有对 model 的修改都必须在 EDT 上做Swing 不是线程安全的后台线程查完数据库后要用 SwingUtilities.invokeLater 切回 EDT 再更新 model否则界面偶发错乱这种 bug 极难复现。3.4 事件绑定新增、删除、修改按钮怎么接界面上的按钮监听器里只做三件事收集输入、调 service、刷新表格。不要在监听器里写 SQL。删除按钮的典型写法deleteBtn.addActionListener(e - { int row table.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选中一行); return; } Contact c tableModel.getContactAt(row); int confirm JOptionPane.showConfirmDialog(this, 确定删除 c.getName() 吗, 确认, JOptionPane.YES_NO_OPTION); if (confirm ! JOptionPane.YES_OPTION) return; try { contactService.deleteById(c.getId()); reloadTable(); // 重新查询并 setData } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 删除失败 ex.getMessage()); } });逻辑说明getSelectedRow 返回视图行号如果表格启用了 RowSorter排序视图行号和 model 行号不一致必须用 table.convertRowIndexToModel(row) 转换否则排序后删错行这是 Swing 表格的经典坑。确认对话框是后悔药删数据前问一句能省很多事。4. 避坑与排查通讯录系统最常见的 5 个翻车现场4.1 中文乱码从数据库到界面全链路排查现象界面输入中文姓名保存后表格显示问号或者数据库里查出来是乱码。原因乱码可能出现在三个环节——数据库字符集、连接串编码、Swing 组件字体。任何一环不是 UTF-8 都会出问题。解决按顺序查。第一步 SHOW VARIABLES LIKE character_set% 确认服务端是 utf8mb4。第二步连接串带 useUnicodetruecharacterEncodingutf8。第三步确认建表和建库都指定了 utf8mb4。三步都对了还乱码检查 Swing 的 JTextField 是否用了不支持中文的字体换成 Microsoft YaHei 或 SimSun。4.2 驱动报错 Public Key Retrieval is not allowed现象连接 MySQL 8 时抛异常 Public Key Retrieval is not allowed。原因MySQL 8 默认认证插件是 caching_sha2_passwordJDBC 首次连接需要检索公钥但驱动默认禁止。解决连接串加 allowPublicKeyRetrievaltrue。生产环境更稳妥的做法是把用户认证插件改成 mysql_native_password或者配置 SSL 证书但本地开发加参数最快。4.3 时间差 8 小时serverTimezone 没设现象新增联系人后created_at 比实际时间早或晚 8 小时。原因JDBC 8.x 驱动默认按 UTC 解析 DATETIME而 MySQL 服务端可能是本地时区。解决连接串显式加 serverTimezoneAsia/Shanghai。如果服务端时区也乱用 SET time_zone 08:00 在会话里统一。4.4 表格排序后删错行现象点击表头排序后选中某行删除删掉的却是另一行。原因JTable 的 RowSorter 让视图行号和 model 行号分离getSelectedRow 返回的是视图行号。解决删除和修改前统一用 int modelRow table.convertRowIndexToModel(table.getSelectedRow()) 转换再用 modelRow 取数据。4.5 连接未关闭导致连接数耗尽现象程序运行一段时间后报 Too many connections。原因DAO 里 Connection 没关或者异常路径下没关。解决全部用 try-with-resources不要手动 close。如果用了连接池HikariCP 等确保每次用完归还。本地小项目可以先用 DriverManager但养成资源自动关闭的习惯。5. 进阶技巧用事务保证批量导入的一致性通讯录系统做到后面一定会遇到批量导入 CSV 或者从 Excel 粘贴一批联系人的需求。这时候单条 insert 循环会有两个问题慢以及中途失败导致部分数据入库。正确做法是用事务包住整批操作配合 addBatch 提升性能。public int batchInsert(ListContact list) throws SQLException { String sql INSERT INTO contact(name, phone, email, company, remark) VALUES(?,?,?,?,?); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 try (PreparedStatement ps conn.prepareStatement(sql)) { for (Contact c : list) { ps.setString(1, c.getName()); ps.setString(2, c.getPhone()); ps.setString(3, c.getEmail()); ps.setString(4, c.getCompany()); ps.setString(5, c.getRemark()); ps.addBatch(); } ps.executeBatch(); conn.commit(); // 全部成功才提交 return list.size(); } catch (SQLException e) { conn.rollback(); // 任何一条失败整批回滚 throw e; } } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }逻辑说明setAutoCommit(false) 关闭自动提交executeBatch 把多条 insert 打包发送减少网络往返。commit 之前任何异常都走 rollback保证要么全进要么全不进。finally 里恢复 autoCommit 再关闭连接如果用了连接池归还前恢复状态是必须的否则下一个借用者会继承错误的事务状态。参数说明batch 大小建议 500 到 1000 条一批太大内存压力大太小提升不明显。如果导入数据里有重复手机号唯一索引会抛异常整批回滚这时候可以在 Java 侧先去重或者用 INSERT ... ON DUPLICATE KEY UPDATE 改成更新逻辑。验证方法导入后查 SELECT COUNT(*) 对比源数据条数再随机抽几条核对字段。我一般还会故意在中间插一条重复手机号确认整批回滚生效这个测试能验证事务真的在起作用。说个我自己的习惯每次改完 DAO 层先写一个 main 方法直接调不启动界面确认 SQL 和事务没问题再接 UI。Swing 的调试成本比纯 Java 高把数据层和界面层分开验证能省一半时间。这套通讯录系统看着简单但把字符集、驱动版本、事务、EDT 这几个点踩明白后面做任何 Java 桌面加数据库的项目都是同一套路。希望帮到你。本文还有配套的精品资源点击获取