Java Swing+MySQL通讯录系统实战:从源码解析到避坑改造
简介这份资源是面向Java桌面开发初学者与课程设计者的通讯录管理系统完整项目采用Java Swing构建图形界面、MySQL存储联系人数据通过JDBC实现增删改查帮助读者理解桌面应用从界面到持久化的完整链路。压缩包共23个文件约2.16MB包含5个java源码、6个class编译文件、5个png界面截图、1个sql建库脚本、1个db数据文件以及jar依赖与工程配置覆盖源码、数据库与运行环境。项目按用户界面、Contact数据模型、业务逻辑、数据库连接、异常处理与权限控制等模块组织可直观看到登录注册、联系人添加编辑、搜索与列表展示的实现方式。目前已有3701人学习下载适合作为JavaSwingMySQL综合练习的参考案例便于快速搭建环境、对照源码理解分层设计与数据库交互细节。1. 从一份通讯录源码说起Java Swing MySQL 这套组合到底能跑出什么很多人第一次接触 Java 桌面开发都是从通讯录、图书管理、学生成绩这类小系统入手的。原因很直接业务逻辑不复杂但增删改查、界面交互、数据库连接这几块一个都不少麻雀虽小五脏俱全。这份「JavaSwingMysql 实现通讯录管理系统」的源码包走的就是这条路线——Swing 负责界面MySQL 负责存储JDBC 负责把两头接起来。它适合谁一是正在学 Java 基础、想找一个能跑起来的完整项目练手的人二是面试前需要快速回顾 Swing 事件模型和 JDBC 操作的人三是想拿一个现成的桌面端 CRUD 模板改造成自己工具的人。不适合谁如果你要做的是 Web 端或者移动端这套东西帮不上忙Swing 是纯桌面技术栈别指望它能直接搬到浏览器里跑。源码包里能看到bin、src、lib、sql、image这些目录还有addressbook.txt和.classpath、.project这类 Eclipse 工程文件。这说明它大概率是一个 Eclipse 时代的 Java 项目用lib放第三方 jar用sql放建表脚本image放界面图标。接下来我会按「先跑起来、再看结构、最后避坑」的顺序把这份资源拆开讲清楚。2. 把项目跑起来环境、建库、导工程三步走2.1 环境准备与版本选择这套项目对环境的容忍度其实挺高但有几个硬性条件绕不开。JDK 建议用 8 或 11别一上来就 JDK 17 甚至 21Swing 本身没问题但老项目里如果用了某些反射或者sun.*包高版本 JDK 会直接报模块访问错误。MySQL 用 5.7 或 8.0 都行但要注意驱动 jar 的版本必须和数据库版本匹配——这是新手翻车最多的地方。组件推荐版本说明JDK8 / 11高版本可能触发模块化限制MySQL5.7 / 8.08.0 需注意时区和驱动类名JDBC 驱动mysql-connector-java 5.1.x / 8.0.x与 MySQL 版本对应IDEEclipse / IntelliJ IDEA项目自带 Eclipse 工程文件lib目录里通常已经放了mysql-connector-java-*.jar如果没有去 MySQL 官网下载对应版本别随便找个 jar 就塞进去。驱动版本和数据库版本不匹配报的错五花八门后面避坑章节会细说。2.2 建库建表先看 sql 目录再动手项目里sql目录一般会有一个.sql文件里面是建库建表的语句。我一般不会直接无脑执行而是先打开看一眼字符集和字段类型。常见做法是手动建库指定utf8mb4避免中文姓名存进去变问号。-- 建库字符集用 utf8mb4兼容中文和特殊符号 CREATE DATABASE addressbook DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE addressbook; -- 联系人表字段按项目实际结构来这里给一个典型结构 CREATE TABLE contact ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20), email VARCHAR(100), address VARCHAR(200), remark VARCHAR(255) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 的逻辑很直白id自增主键保证每条记录唯一name设NOT NULL是因为通讯录里没名字的记录没意义其余字段允许为空。字符集统一用utf8mb4而不是utf8因为 MySQL 的utf8其实是阉割版存不了某些生僻字和 emoji。执行完建表语句后可以顺手插一条测试数据确认表结构没问题。提示如果项目自带的 sql 文件里用的是utf8建议手动改成utf8mb4再执行不然后面存中文地址容易出乱码。2.3 导入工程与修改数据库配置Eclipse 用户直接File - Import - Existing Projects into Workspace选中项目根目录即可。IDEA 用户稍微麻烦一点需要新建一个 Java 项目然后把src拷进去再把lib下的 jar 加到依赖里。导入后第一件事不是点运行而是找到数据库连接配置。配置通常写在某个工具类里类似DBUtil.java或者BaseDao.java里面会有url、username、password三个关键参数。MySQL 8.0 的 URL 和 5.7 写法不一样这是必须改对的地方。// MySQL 5.7 及以下写法 private static final String URL jdbc:mysql://localhost:3306/addressbook?useUnicodetruecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; // MySQL 8.0 写法多了时区和 SSL 参数 private static final String URL jdbc:mysql://localhost:3306/addressbook?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse;serverTimezone不设的话8.0 驱动会报时区错误useSSLfalse是关掉 SSL 连接本地开发没必要开开了反而容易因为证书问题连不上。characterEncodingutf8保证 Java 和数据库之间的字符传输不乱码。改完这三个参数基本就能连上数据库了。如果连不上先别怀疑代码用命令行或者 Navicat 确认数据库本身能登录再回头看 Java 这边。3. 拆开看结构Swing 界面层、数据模型与 JDBC 持久层怎么配合3.1 Swing 界面层JFrame、JTable 与事件监听Swing 这套东西现在看确实有点年代感但它的组件模型非常清晰适合理解「界面—事件—数据」的流转。通讯录系统的主界面一般是一个JFrame上面放JTable显示联系人列表配几个JButton做增删改查再加JTextField做搜索框。JTable是核心组件它本身不存数据数据由TableModel提供。项目里通常会自定义一个ContactTableModel继承AbstractTableModel把ListContact包装成表格能识别的行列结构。// 自定义表格模型把联系人列表映射成表格数据 public class ContactTableModel extends AbstractTableModel { private final String[] columns {ID, 姓名, 电话, 邮箱, 地址}; private ListContact contacts; public ContactTableModel(ListContact contacts) { this.contacts contacts; } Override public int getRowCount() { return contacts.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 contacts.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.getAddress(); default: return null; } } }getRowCount和getColumnCount告诉表格有多少行多少列getValueAt决定每个单元格显示什么。这样设计的好处是数据和界面解耦——数据库查出来的ListContact一变调用fireTableDataChanged()就能刷新表格不用手动去改每个单元格。事件监听部分按钮点击后触发ActionListener在监听器里调用业务方法再把最新数据重新灌进TableModel。3.2 数据模型与业务逻辑Contact 类与 DAO 模式Contact类是整个系统的数据载体字段和数据库表一一对应。这个类没什么玄学就是标准的 JavaBean私有字段、公有 getter/setter、一个无参构造、一个全参构造。别小看它字段类型选错会导致后面一堆麻烦——比如id用int还是Integerphone用String还是long。手机号必须用String因为前导零和长度问题用数字类型存迟早出事。业务逻辑层通常用 DAO 模式一个ContactDao类封装所有数据库操作。这样界面层只管调方法不直接写 SQL职责清晰。// 查询所有联系人返回 ListContact public ListContact findAll() { ListContact list new ArrayList(); String sql SELECT * FROM contact ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { 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.setAddress(rs.getString(address)); list.add(c); } } catch (SQLException e) { e.printStackTrace(); } return list; }用PreparedStatement而不是Statement是为了防 SQL 注入同时预编译对重复执行更友好。try-with-resources语法保证连接、语句、结果集自动关闭不用手动写finally块。ORDER BY id DESC让最新添加的联系人排在最前面符合使用习惯。增删改操作结构类似只是把executeQuery换成executeUpdate返回受影响行数来判断是否成功。3.3 数据库连接与异常处理JDBC 工具类的写法数据库连接如果每次操作都DriverManager.getConnection频繁创建销毁连接开销很大。常见做法是写一个DBUtil工具类把驱动加载和连接获取封装起来。简单项目里不一定上连接池但至少要把配置抽出来别把账号密码散落在各个 DAO 里。public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/addressbook?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); // 加载驱动只需一次 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }Class.forName在静态块里执行类加载时只跑一次。MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver5.7 是com.mysql.jdbc.Driver写错了直接报ClassNotFoundException。异常处理方面DAO 层捕获SQLException后要么往上抛要么记日志别吞掉不处理——界面层需要知道操作是否成功才能弹提示框告诉用户。4. 避坑与排查连接失败、乱码、驱动不匹配这些血泪经验4.1 数据库连不上先看报错关键词现象启动程序后弹窗提示连接失败控制台报Communications link failure或Access denied for user。原因前者通常是 MySQL 服务没启动或者端口不对后者是账号密码错误或者该用户没有远程/本地访问权限。解决先确认 MySQL 服务在运行命令行执行mysql -u root -p能登录。如果命令行能登、Java 登不上检查 URL 里的端口是不是 3306账号密码有没有多余空格。8.0 还要确认serverTimezone参数加了没有。4.2 中文乱码三个地方都要统一现象界面上显示的联系人姓名是问号或者方块数据库里查出来却是正常的。原因字符集不统一。数据库、表、连接 URL、Java 文件编码任何一处不一致都会乱码。解决数据库和表用utf8mb4JDBC URL 加characterEncodingutf8IDE 里把项目文件编码设成 UTF-8。三处对齐后基本不会再乱。如果还有问题检查addressbook.txt那个文件是不是 GBK 编码读的时候要指定编码。4.3 驱动版本不匹配ClassNotFound 和时区错误现象报ClassNotFoundException: com.mysql.jdbc.Driver或者The server time zone value is unrecognized。原因前者是驱动 jar 没加到 classpath或者 8.0 驱动用了旧类名后者是 8.0 驱动没设时区。解决确认lib下的 jar 已经加到项目依赖里。8.0 驱动类名改成com.mysql.cj.jdbc.DriverURL 加serverTimezoneAsia/Shanghai。如果用的是 Maven检查pom.xml里mysql-connector-java的版本和数据库版本是否对应。4.4 界面卡死Swing 线程模型别搞错现象点击查询按钮后界面无响应要等好几秒才恢复。原因数据库操作写在了事件分发线程EDT里Swing 的界面刷新和事件处理都在这个线程一旦阻塞整个界面就卡住。解决耗时的数据库操作放到SwingWorker的doInBackground里执行结果通过done方法回到 EDT 更新界面。小数据量下不觉得数据一多或者网络慢这个问题就会暴露。4.5 打包后连不上jar 里的驱动和外部配置现象IDE 里跑得好好的打成 jar 包双击运行就报连接失败。原因打包时没把 MySQL 驱动打进去或者配置文件路径用的是相对路径jar 运行时工作目录变了。解决用 Maven 的shade插件或者手动把驱动 jar 放进MANIFEST.MF的Class-Path里。配置文件如果放在 jar 外部用绝对路径或者基于user.dir拼接别写死相对路径。5. 进阶改造从能跑到好用几个我常做的调整项目能跑起来只是第一步真正拿去用或者拿去面试展示还得做几处改造。第一个是搜索功能原版可能只支持按姓名精确匹配我一般会改成模糊查询用LIKE %keyword%同时支持姓名和电话两个字段。// 模糊搜索姓名或电话包含关键词 public ListContact search(String keyword) { ListContact list new ArrayList(); String sql SELECT * FROM contact WHERE name LIKE ? OR phone LIKE ? ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String pattern % keyword %; ps.setString(1, pattern); ps.setString(2, pattern); ResultSet rs ps.executeQuery(); 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.setAddress(rs.getString(address)); list.add(c); } } catch (SQLException e) { e.printStackTrace(); } return list; }LIKE的%放在关键词两边表示前后都可以有任意字符。两个?分别对应姓名和电话setString按顺序赋值。这样用户输入「张」能搜到所有姓张的输入「138」能搜到电话里含 138 的。第二个改造是输入校验。原版可能直接拿文本框内容就往数据库塞我一般会在保存前检查姓名非空、电话格式基本合法。不用写太复杂的正则至少把空字符串和纯空格拦掉。// 保存前的简单校验 if (name null || name.trim().isEmpty()) { JOptionPane.showMessageDialog(this, 姓名不能为空); return; } if (phone ! null !phone.matches(\\d{7,15})) { JOptionPane.showMessageDialog(this, 电话格式不正确); return; }trim()去掉首尾空格再判断防止用户输入一串空格绕过校验。电话用\d{7,15}做基本长度和数字校验不追求完美匹配所有号段但能拦住大部分误输入。第三个改造是给表格加排序。JTable自带setAutoCreateRowSorter(true)一行代码就能让点击表头排序生效。但要注意如果TableModel里的数据是List排序后行号和数据索引会对不上取值时要用convertRowIndexToModel转换。// 开启表格自动排序 table.setAutoCreateRowSorter(true); // 获取选中行时把视图行号转成模型行号 int viewRow table.getSelectedRow(); int modelRow table.convertRowIndexToModel(viewRow); Contact selected contacts.get(modelRow);这个转换是很多人忽略的坑。不转换的话排序后点第一行拿到的可能是原始列表里的第三条数据删错人就是这么来的。从那以后我每次用JTable加排序都强制走一遍convertRowIndexToModel再也没出过删错记录的事。最后一个建议是加一个简单的日志输出不用上 Log4j 那么重用java.util.logging或者直接System.out.println把关键 SQL 和执行结果打出来。调试阶段能省很多猜的时间上线前再关掉或者改成写文件。希望这些调整能帮到你让这份源码不只是「能跑」而是真正能拿去改、拿去用。本文还有配套的精品资源点击获取