简介本资源是一套基于Java Swing与MySQL开发的桌面端通讯录管理系统完整工程面向Java初学者及GUI应用开发学习者解决联系人信息增删改查、本地持久化存储与界面交互实践等核心问题。压缩包共23个文件含5个核心Java源码如Contact类、主界面AddressSwing等、6个编译后class文件、1个MySQL数据库文件.db及1个建表SQL脚本配合5张界面图标PNG和项目配置文件.classpath、.project等完整呈现从编码、编译、数据库初始化到运行的全流程结构。资源大小2.16MB目录层次清晰src与bin分离sql与image独立归档便于理解MVC分层逻辑与工程组织规范。已有3700人学习下载提供可直接导入IDE运行的可执行jar、带异常处理与JDBC连接封装的业务代码、以及含登录/搜索/编辑功能的完整Swing界面实现是掌握Java桌面应用开发技术栈的典型入门级实战范例。1. JavaSwingMySQL通讯录系统不是Demo是能直接双击运行、改完数据库就能上线的桌面应用你可能见过太多“Java通讯录”课程设计——界面简陋、数据硬编码、删联系人弹窗后程序直接崩。但这个AddressSwing项目不是教学玩具它带完整.sql脚本一键建库、lib/下封装了mysql-connector-java-8.0.33.jar非过时5.x、src/里ContactDAO类用预编译语句防SQL注入、bin/目录下已编译好可执行class文件——你解压后不用配环境只要本地装了JRE 8 和 MySQL 5.7/8.0改两行配置就能跑通增删查改全流程。它解决的是真实场景下的三个痛点新手写Swing界面总卡在事件监听器嵌套、JDBC连接池缺失导致并发查询卡死、MySQL中文乱码和时区错配引发数据错位。适合刚学完Java基础想落地第一个GUI项目的人也适合需要快速交付内部工具的运维或测试同事——我去年给客户现场部署时从下载到录入200条联系人只用了47分钟。2. 从零启动解压即用的三步验证法含MySQL初始化实操这个项目最反直觉的地方在于它不依赖IDE也不要求你手动编译。所有可执行文件已预编译进bin/源码只是给你留的修改入口。但前提是MySQL服务必须就绪——很多翻车都卡在这一步。下面按真实部署顺序走2.1 创建数据库并导入初始表结构先确认MySQL服务正在运行Windows用服务管理器查MySQL80Linux执行sudo systemctl status mysqld。然后用命令行登录mysql -u root -p输入密码后执行建库语句注意字符集必须为utf8mb4CREATE DATABASE addressbook CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE addressbook;接着退出MySQL用source命令导入项目自带的SQL脚本路径需替换为你解压后的实际路径mysql -u root -p addressbook /path/to/your/通讯录管理系统/sql/addressbook.sql提示addressbook.sql文件里已包含CREATE TABLE contacts (...) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键字段如name VARCHAR(50) NOT NULL、phone VARCHAR(20)都加了索引。别手敲建表语句——表结构里created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP这个默认值是防止你后续插入空时间戳报错的救命设置。2.2 修改数据库连接配置重点看端口和时区项目里数据库连接参数藏在src/com/swing/db/DBUtil.java中。打开这个文件找到这行private static final String URL jdbc:mysql://localhost:3306/addressbook?useSSLfalseserverTimezoneGMT%2B8;这里有两个玄学坑必须改如果你的MySQL端口不是默认3306比如用Docker映射到3307必须同步改这里的端口号serverTimezoneGMT%2B8是为了解决中国用户常见的「存进去的时间比实际晚8小时」问题。如果你用的是MySQL 5.7以下版本得换成serverTimezoneAsia/Shanghai8.0版本强烈建议用GMT%2B8否则JDBC驱动会抛The server time zone value XXX is unrecognized异常。2.3 启动主程序绕过IDE直接运行不要用Eclipse或IntelliJ打开项目——你会被.project和.classpath里的旧路径搞晕。正确做法是进入解压目录找到bin/文件夹在该目录下打开终端Windows用CMDMac/Linux用Terminal执行启动命令java -cp .:../lib/mysql-connector-java-8.0.33.jar com.swing.ui.MainFrame注意-cp参数里.表示当前目录即bin/../lib/...jar是相对路径引用驱动包。Windows用户要把冒号:换成分号;即java -cp .;../lib/mysql-connector-java-8.0.33.jar ...。如果提示Could not find or load main class说明你没在bin/目录下执行或者com.swing.ui.MainFrame类名拼错了检查大小写Java对大小写敏感。3. 核心模块拆解Swing界面逻辑与JDBC操作如何严丝合缝这个系统的健壮性藏在三个关键类的协作里MainFrame主窗口、ContactTableModel表格数据模型、ContactDAO数据库访问对象。它们不是简单调用关系而是用MVC模式做了职责隔离——这也是它比90%课程设计强的根本原因。3.1 MainFrameSwing事件驱动的中枢神经MainFrame.java继承自JFrame但它没用ActionListener写一堆匿名内部类。而是把所有按钮点击事件绑定到独立的方法上比如添加联系人按钮addBtn.addActionListener(e - showAddDialog());而showAddDialog()方法里它创建了一个JDialog模态对话框并把Contact对象传给AddContactDialog构造函数。这种写法的好处是界面逻辑和业务逻辑完全解耦。你改添加逻辑时不用碰主窗口的布局代码换皮肤时只改UIManager.setLookAndFeel(...)一行就行。关键细节MainFrame初始化时调用了loadContactsFromDB()这个方法不是直接查数据库而是通过ContactDAO.findAll()获取ListContact再塞进ContactTableModel。这意味着你后续要加搜索功能只需改ContactDAO.findByKeyword()界面层完全不用动。3.2 ContactTableModel让JTable真正“活”起来的数据容器Swing里JTable默认用DefaultTableModel但那是阉割版——不支持排序、不支持实时刷新。这个项目自己写了ContactTableModel继承AbstractTableModelpublic class ContactTableModel extends AbstractTableModel { private ListContact contacts new ArrayList(); Override public int getRowCount() { return contacts.size(); } Override public Object getValueAt(int rowIndex, int columnIndex) { Contact c contacts.get(rowIndex); switch (columnIndex) { case 0: return c.getId(); case 1: return c.getName(); case 2: return c.getPhone(); case 3: return c.getEmail(); default: return ; } } }重点在fireTableDataChanged()的调用时机每次ContactDAO执行完insert()或delete()都会触发ContactTableModel的refreshData()方法里面调用fireTableDataChanged()。这不是简单的重绘而是通知JTable整个数据源已变更连排序状态都会重置——避免出现“删了第3行结果第5行消失”的诡异现象。3.3 ContactDAOJDBC操作的防御式封装ContactDAO.java是整个系统的数据命脉。它没用任何ORM框架但用预编译语句PreparedStatement堵死了SQL注入漏洞public void insert(Contact contact) throws SQLException { String sql INSERT INTO contacts (name, phone, email, address, created_at) VALUES (?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setString(1, contact.getName()); pstmt.setString(2, contact.getPhone()); pstmt.setString(3, contact.getEmail()); pstmt.setString(4, contact.getAddress()); pstmt.setTimestamp(5, new Timestamp(System.currentTimeMillis())); pstmt.executeUpdate(); // 获取自增主键 try (ResultSet rs pstmt.getGeneratedKeys()) { if (rs.next()) { contact.setId(rs.getLong(1)); } } } }参数说明Statement.RETURN_GENERATED_KEYS是为了拿到MySQL自增的id避免后续更新时找不到主键setTimestamp(5, ...)用System.currentTimeMillis()而不是new Date()因为后者在某些JDK版本里会丢失毫秒精度try-with-resources确保Connection和PreparedStatement必然关闭——这是血泪经验我曾在线上环境因连接未关闭3小时后MySQL报Too many connections。4. 避坑指南五个让新手当场崩溃的真实问题及根治方案这个项目看似简单但我在带实习生时发现92%的人会在前30分钟内卡在以下问题。这些不是“可能遇到”而是“必然踩中”的坑附带精准定位和一招毙命的解法4.1 现象启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因JDBC驱动类名在MySQL 8.0已从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver但项目里DBUtil.java的Class.forName(com.mysql.jdbc.Driver)没更新。解决打开src/com/swing/db/DBUtil.java把第18行改为Class.forName(com.mysql.cj.jdbc.Driver);。同时确认lib/下的jar包确实是8.x版本文件名含8.0.33不是mysql-connector-java-5.1.47.jar这种老古董。4.2 现象中文姓名存进数据库变成??但用Navicat查显示正常原因MySQL服务端字符集是utf8mb4但JDBC连接URL里没加characterEncodingutf8mb4参数。解决修改DBUtil.java的URL字符串在末尾追加characterEncodingutf8mb4完整URL应为jdbc:mysql://localhost:3306/addressbook?useSSLfalseserverTimezoneGMT%2B8characterEncodingutf8mb44.3 现象点击“添加联系人”按钮无反应控制台没报错原因Swing事件线程Event Dispatch Thread被阻塞。常见于在ActionListener里直接调用耗时的ContactDAO.insert()而没用SwingWorker异步处理。解决在MainFrame.java的showAddDialog()方法里把contactDAO.insert(contact)包进SwingWorkernew SwingWorkerVoid, Void() { Override protected Void doInBackground() throws Exception { contactDAO.insert(contact); return null; } Override protected void done() { model.refreshData(); // 刷新表格 JOptionPane.showMessageDialog(MainFrame.this, 添加成功); } }.execute();4.4 现象删除联系人后表格里还显示那条数据刷新页面才消失原因ContactDAO.delete()执行成功但ContactTableModel的contacts列表没同步删除fireTableDataChanged()没触发。解决检查ContactDAO.delete(long id)方法末尾是否调用了model.removeContactById(id)项目里应该有这个方法。如果没有就在delete()方法最后加一行ContactTableModel.getInstance().removeContactById(id);——注意ContactTableModel必须是单例模式否则getInstance()会返回新实例。4.5 现象MySQL 8.0连接时报Public Key Retrieval is not allowed原因MySQL 8.0默认开启caching_sha2_password认证插件而老版JDBC驱动不支持。解决在MySQL命令行执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;然后重启MySQL服务。这是最稳妥的方案比在URL里加allowPublicKeyRetrievaltrue更安全后者有中间人攻击风险。5. 进阶实战给通讯录加搜索、导出Excel、批量导入三大刚需功能光能增删查改只是入门。真实办公场景里老板第一句话往往是“能不能按部门筛选”、“导出成Excel发邮件”、“我们有2000条老数据怎么一次导入”。下面这三个功能我直接给你可粘贴的代码和配置全部基于现有架构无缝扩展。5.1 实现模糊搜索在ContactDAO里加findByKeyword方法在ContactDAO.java里新增方法注意SQL注入防护public ListContact findByKeyword(String keyword) throws SQLException { String sql SELECT * FROM contacts WHERE name LIKE ? OR phone LIKE ? OR email LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { String likeKeyword % keyword %; pstmt.setString(1, likeKeyword); pstmt.setString(2, likeKeyword); pstmt.setString(3, likeKeyword); try (ResultSet rs pstmt.executeQuery()) { ListContact list new ArrayList(); while (rs.next()) { 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.setAddress(rs.getString(address)); c.setCreatedAt(rs.getTimestamp(created_at)); list.add(c); } return list; } } }然后在MainFrame.java的搜索按钮事件里调用searchBtn.addActionListener(e - { String keyword searchField.getText().trim(); if (!keyword.isEmpty()) { try { ListContact results contactDAO.findByKeyword(keyword); model.setContacts(results); // setContacts方法需在ContactTableModel里实现 } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 搜索失败 ex.getMessage()); } } });ContactTableModel里要加setContacts(ListContact contacts)方法并调用fireTableDataChanged()。这样搜索结果会立刻刷新表格不用重启程序。5.2 导出Excel用Apache POI 5.2.4轻量级不依赖Office先下载poi-5.2.4.jar和poi-ooxml-5.2.4.jar放进lib/目录。然后在ContactDAO.java里加静态导出方法public static void exportToExcel(ListContact contacts, String filePath) throws IOException { try (XSSFWorkbook workbook new XSSFWorkbook(); FileOutputStream fileOut new FileOutputStream(filePath)) { XSSFSheet sheet workbook.createSheet(通讯录); String[] headers {ID, 姓名, 电话, 邮箱, 地址, 创建时间}; XSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum 1; for (Contact c : contacts) { XSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(c.getId()); row.createCell(1).setCellValue(c.getName()); row.createCell(2).setCellValue(c.getPhone()); row.createCell(3).setCellValue(c.getEmail()); row.createCell(4).setCellValue(c.getAddress()); row.createCell(5).setCellValue(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(c.getCreatedAt())); } workbook.write(fileOut); } }在MainFrame.java里绑定导出按钮exportBtn.addActionListener(e - { try { ListContact all contactDAO.findAll(); JFileChooser fc new JFileChooser(); fc.setDialogTitle(选择导出路径); fc.setSelectedFile(new File(addressbook_export.xlsx)); if (fc.showSaveDialog(this) JFileChooser.APPROVE_OPTION) { String path fc.getSelectedFile().getAbsolutePath(); if (!path.endsWith(.xlsx)) path .xlsx; ContactDAO.exportToExcel(all, path); JOptionPane.showMessageDialog(this, 导出成功 path); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, 导出失败 ex.getMessage()); } });5.3 批量导入CSV用OpenCSV解析防内存溢出lib/里加opencsv-5.7.1.jar。在ContactDAO.java加导入方法关键分批提交每100条commit一次public void importFromCSV(String csvPath) throws SQLException, IOException { try (CSVReader reader new CSVReader(new FileReader(csvPath))) { ListString[] records reader.readAll(); Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 String sql INSERT INTO contacts (name, phone, email, address, created_at) VALUES (?, ?, ?, ?, ?); try (PreparedStatement pstmt conn.prepareStatement(sql)) { int count 0; for (String[] record : records) { if (record.length 4) continue; // 跳过格式错误行 pstmt.setString(1, record[0]); // name pstmt.setString(2, record[1]); // phone pstmt.setString(3, record[2]); // email pstmt.setString(4, record[3]); // address pstmt.setTimestamp(5, new Timestamp(System.currentTimeMillis())); pstmt.addBatch(); count; if (count % 100 0) { // 每100条提交一次 pstmt.executeBatch(); conn.commit(); } } pstmt.executeBatch(); conn.commit(); } } }注意CSV文件格式必须是UTF-8无BOM首行不能是标题程序不跳过首行。如果数据量超10万行建议把count % 100改成count % 500减少事务开销。从那以后我每次接到“加个导出功能”的需求都强制走一遍POI的XSSFWorkbook创建流程——哪怕只是导出3列数据也先写好模板再填值。因为曾经有次线上导出用HSSFWorkbook对应.xls处理超过65536行直接OOM挂掉重启后才发现该用XSSFWorkbook。希望帮到你。本文还有配套的精品资源点击获取