
简介一套基于Java Swing、MySQL与JDBC的完整人事管理系统项目主要面向正在进行数据库课程设计或练习Java桌面GUI开发的在校学生能够帮助解决课程设计中常见的数据连接、界面布局与基本增删改查等需求。压缩包共24个文件包含11个Java源文件、12个PNG图片和1个SQL脚本整体大小仅有143KB其中Java代码负责实现登录、员工管理等功能PNG图片用作界面展示素材SQL脚本提供建库建表及初始数据方便快速搭建运行环境。项目自带可见的软件操作界面读者拿到后可以导入工程运行对照源码理解Swing组件如何与JDBC协同操作MySQL数据表。由于不同MySQL版本可能引发一些基础性报错资源描述明确提示可自行解决这也有助于锻炼环境配置与错误排查能力。目前该资源已有3878人学习下载适合作为课程设计参考或入门级人才管理练手项目。1. javaswingmysqljdbc做人事管理系统课程设计里这套组合到底图什么如果你正在翻数据库课程设计题目大概率会看到「人事管理系统」这个经典选项而配套的常见技术栈就是javaswingmysqljdbc。很多人在Web框架遍地走的时代会疑惑为什么还要用Swing做桌面端答案是这套组合能用最短路径把数据库的增删改查、事务、多表关联全部落到可点击的界面里不需要装Tomcat不需要写前后端接口一个main方法就能跑起来。对于要交课程设计、要答辩演示、要解释清楚我确实操作了数据库的场景它比SSH或SpringBoot更容易当面讲明白。本文就按我从建表到连库、从Swing事件到踩坑排错的实际顺序把整个方案拆给你看。2. 人事管理系统的数据库设计三张表的关系、字段选型与建表SQL2.1 人事管理系统的核心表部门、员工、薪资为什么不能合成一张表人事管理系统最基本的诉求是维护员工信息、所属部门和薪资记录。如果偷懒只建一张员工表把部门名称和薪资字段直接塞进去看似省事但会出现数据冗余一个部门有50个员工部门名称就被重复存了50次部门改名时要更新50条记录。这是典型的违反第二范式。常见做法是拆成部门表、员工表、薪资表用外键把关系串起来。设计表结构前先明确需求。员工信息至少要包含工号、姓名、性别、出生日期、入职日期、部门ID部门表包含部门ID、部门名称、部门描述薪资表包含薪资ID、员工ID、基本工资、岗位工资、发放月份。薪资表单独拆出来是为了能记录员工每个月的薪资变化而不只是存一个当前值。如果你后面要扩展按月份查询工资条这种设计直接能用。字段类型的选择也有讲究。工号不要用int因为工号可能带前导零比如00123int会把它变成123。建议用varchar出生日期和入职日期用date性别可以用char(1)或tinyint但课程设计里为了演示直观用varchar(4)存男/女也行只是要注意数据约束。工资字段用decimal(10,2)而不是floatfloat在比较和求和时会有精度误差答辩时如果被问为什么薪资表用decimal这是一个加分回答。2.2 建表SQL主键自增、外键约束与字符集一次性配好下面是这套系统最常用的建表脚本我习惯在Navicat或者MySQL命令行里先执行确认无误后再在JDBC代码里使用。注意统一使用utf8mb4字符集否则后面中文乱码会折磨你半宿。CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hrms; CREATE TABLE dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 部门ID, dept_name VARCHAR(50) NOT NULL UNIQUE COMMENT 部门名称, dept_desc VARCHAR(200) COMMENT 部门描述 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT部门表; CREATE TABLE emp ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 员工ID, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, emp_name VARCHAR(30) NOT NULL COMMENT 姓名, gender VARCHAR(4) COMMENT 性别, birth_date DATE COMMENT 出生日期, hire_date DATE COMMENT 入职日期, dept_id INT COMMENT 部门ID, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工表; CREATE TABLE salary ( salary_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 薪资ID, emp_id INT NOT NULL COMMENT 员工ID, base_salary DECIMAL(10,2) COMMENT 基本工资, bonus DECIMAL(10,2) COMMENT 奖金, pay_month VARCHAR(7) COMMENT 发放月份如2024-05, CONSTRAINT fk_salary_emp FOREIGN KEY (emp_id) REFERENCES emp(emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT薪资表;这段SQL里dept表的dept_id是主键自增emp表通过dept_id关联部门salary表通过emp_id关联员工。外键约束保证了不会有员工挂在不存在部门下的脏数据。ENGINEInnoDB是必须的因为InnoDB支持事务和外键MyISAM虽然也能建外键语法但不生效。COMMENT写在每个字段后面在Navicat里看得清楚答辩时截图也好看。建完表后先插入几行测试数据比如三个部门、五名员工、五条薪资记录。这样后面调试JDBC时一查就有结果不用现造数据。插入时注意先插部门表再插员工表否则外键会报错。这个顺序本身就是对外键约束最直接的理解。2.3 数据库修改结构课程设计里常见的ALTER操作写代码过程中经常要改表结构。比如你发现gender字段长度不够或者想给emp表加一个联系电话字段。很多同学直接删表重建但这样原来插入的测试数据全没了。正确做法是用ALTER TABLE来修改结构这也是热词里高频出现的mysql数据库修改结构的实际使用场景。-- 给员工表增加联系电话字段 ALTER TABLE emp ADD COLUMN phone VARCHAR(20) AFTER hire_date; -- 修改字段类型把gender改为char(2) ALTER TABLE emp MODIFY COLUMN gender VARCHAR(2); -- 给薪资表增加唯一约束避免同一个人同一月份被插入两次 ALTER TABLE salary ADD UNIQUE KEY uk_emp_month (emp_id, pay_month);修改结构后要检查对应的Java实体类字段是否同步。如果你在Swing界面里绑定的是emp表的phone列而Java类里没有这个属性运行时就会反射报错。建议每改一次表结构立即同步修改三个地方实体类、DAO的ResultSet取值、界面JTable的列模型。这是很多人翻车的地方。3. JDBC连接mysql与参数设置从驱动加载到结果集释放的完整模板3.1 JDBC连接的固定流程Class.forName、Connection、Statement、ResultSet无论你的界面做得多花哨数据层的核心就是JDBC。JDBC连接mysql的标准流程六步走加载驱动、获取连接、创建Statement、执行SQL、处理ResultSet、释放资源。我用一个工具类封装这几个步骤避免每个DAO方法里重复写连接代码。import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这个模板里的参数一个一个说。URL中useUnicodetrue和characterEncodingutf8是保证中文能正确读写useSSLfalse是因为本地开发MySQL通常没配SSL证书不关掉会报安全连接警告serverTimezoneAsia/Shanghai是MySQL 8.0以上版本必写的不写会报时区错误。USER和PASSWORD按你自己的MySQL账号改。Class.forName加载驱动这行在MySQL 8.0之前是老写法com.mysql.jdbc.Driver8.0之后必须换成com.mysql.cj.jdbc.Driver。如果你用的MySQL 5.7写老驱动也没问题但如果你安装的是mysql 8.0.x最新版却抄了网上老代码就会在这个地方报ClassNotFoundException。驱动jar包要放到项目的lib目录并添加到Build Path这一步很多第一次用IDEA的人会漏掉。3.2 PreparedStatement代替Statement防SQL注入也防SQL拼接崩溃执行SQL有两个选择Statement和PreparedStatement。课程设计里有人图省事写Statement把用户输入直接拼进SQL字符串。比如查询员工姓名String sql SELECT * FROM emp WHERE emp_name name ;如果name里包含英文单引号SQL直接报错如果name是; DROP TABLE emp;--这种内容数据库可能就被删了。虽然课程设计是自己本地玩但答辩老师看到这种代码大概率要扣印象分。正确做法是从头到尾使用PreparedStatement把参数用问号占位符留出来参数单独传。public ListEmp findByName(String name) { ListEmp list new ArrayList(); String sql SELECT e.*, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id WHERE e.emp_name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % name %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Emp emp new Emp(); emp.setEmpId(rs.getInt(emp_id)); emp.setEmpNo(rs.getString(emp_no)); emp.setEmpName(rs.getString(emp_name)); emp.setGender(rs.getString(gender)); emp.setDeptName(rs.getString(dept_name)); list.add(emp); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这里用了try-with-resources语法Java 7之后的写法连接、PreparedStatement、ResultSet都会自动关闭不用再手动写close。LIKE查询时把%写在参数值两端而不是拼在SQL字符串里能避免很多边界麻烦。LEFT JOIN保证即使员工没有分配部门也能查出员工记录只是部门名为null。注意setString的参数位置从1开始和SQL里问号出现的顺序一致。如果SQL里有多个问号就要多次setXxx。ps.executeQuery()返回结果集处理完后不需要手动关ResultSet吗try-with-resources里的rs变量也在括号中声明会自动关。但如果你在外面单独声明了Connection和Statement就还是需要手动关顺序是先关ResultSet再关Statement最后关Connection。3.3 JDBC事务当删除员工并清理其薪资记录必须同时成功人事管理系统里有一个典型场景删除员工时不仅要删除emp表记录还要删除salary表里该员工的薪资记录。如果先删薪资成功删员工失败数据库里就会留下没有主记录的薪资反过来也一样。这时需要把两个删除放进一个事务里要么都成功要么都回滚。这就是热词里jdbc transaction的落地场景。MySQL的InnoDB支持事务但JDBC默认的autoCommit是true也就是每条SQL执行完自动提交。要手动管理事务先把autoCommit设为false执行完所有SQL后commit任何一步出错就rollback。注意: Connection对象必须在整个事务范围内复用不能用一次查询就关掉。public boolean deleteEmpAndSalary(int empId) { Connection conn null; PreparedStatement psEmp null; PreparedStatement psSal null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String sqlSal DELETE FROM salary WHERE emp_id ?; psSal conn.prepareStatement(sqlSal); psSal.setInt(1, empId); psSal.executeUpdate(); String sqlEmp DELETE FROM emp WHERE emp_id ?; psEmp conn.prepareStatement(sqlEmp); psEmp.setInt(1, empId); psEmp.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (psSal ! null) psSal.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (psEmp ! null) psEmp.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }这里有一个容易被忽略的点手动事务后finally里关闭Connection前要把autoCommit恢复为true因为连接如果被连接池复用这个状态会污染下一条业务。虽然这个示例里没有用连接池但养成好习惯没有坏处。rollback时要注意如果conn本身为null或者已经抛异常断开rollback也会抛SQLException所以包了一层嵌套catch。3.4 要不要用数据库连接池课程设计规模的现实答案很多教程一上来就推C3P0、Druid连接池理由是频繁开关Connection性能差。这话没错但对课程设计来说单机演示、几十条数据、几个按钮点击Connection开关的损耗基本感觉不到。如果你硬要在Swing里集成Druid反而会引入配置文件和jar包依赖答辩时还要解释为什么用连接池、连接池参数怎么设复杂度陡增。我一般建议课程设计阶段就用DriverManager把代码写干净、事务处理好一样能拿高分。如果你的题目要求系统需支持多用户并发访问那至少说明软著或课程设计等级更高这时再引入连接池。常见做法是把DBUtil中的getConnection换成从DruidDataSource获取核心代码改动很小。如果真要换连接池最简化配置是这样import com.alibaba.druid.pool.DruidDataSource; public class DBUtilWithPool { private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setUrl(URL); dataSource.setUsername(USER); dataSource.setPassword(PASSWORD); dataSource.setInitialSize(2); dataSource.setMaxActive(5); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }initialSize是启动时创建几个连接maxActive是最大连接数。课程设计场景这两个值够了不要盲目设成100会浪费本地数据库资源而且Swing是单线程界面并发量非常低。4. 把增删改查装进swing界面JTable、PreparedStatement与事件回调的配合4.1 JTable显示数据库结果TableModel与ResultSet如何映射Swing里表格组件是JTable但JTable不能直接接收ResultSet必须通过TableModel中转。常见做法是写一个自定义TableModel把数据库列名和行数据存进内存刷新表格时重新查数据库并更新模型。下面这段代码是员工列表展示的核心逻辑。import javax.swing.table.AbstractTableModel; import java.util.List; public class EmpTableModel extends AbstractTableModel { private String[] columnNames {工号, 姓名, 性别, 部门}; private ListEmp dataList; public EmpTableModel(ListEmp list) { this.dataList list; } Override public int getRowCount() { return dataList null ? 0 : dataList.size(); } Override public int getColumnCount() { return columnNames.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { Emp emp dataList.get(rowIndex); switch (columnIndex) { case 0: return emp.getEmpNo(); case 1: return emp.getEmpName(); case 2: return emp.getGender(); case 3: return emp.getDeptName(); default: return null; } } Override public String getColumnName(int column) { return columnNames[column]; } }这段代码里数据源是List 而不是直接用ResultSet。这么做的原因是ResultSet依赖打开的数据库连接如果在界面渲染时底层连接被关闭表格就会报错。先把ResultSet遍历转换成Java对象列表再交给TableModel是更稳妥的做法。getValueAt方法里根据columnIndex逐个返回字段columnCount和columnNames数组长度保持同步。注意Emp这个实体类的deptName字段来自多表查询的别名不是emp表原生字段。所以在DAO里查询时要写SELECT e.emp_no, e.emp_name, e.gender, d.dept_name让数据库把关联好的部门名查出来而不是在Java代码里二次查询。4.2 刷新表格的三种方式重新查询、model更新、fireTableDataChanged在Swing界面里点击按钮新增或删除员工后表格必须刷新才能看到最新数据。很多人直接把JTable的实例重新setModel这种做法有效但不优雅而且如果界面上有排序器或列宽设置会被重置。更标准的方式是保留同一个TableModel替换里面的dataList然后调用fireTableDataChanged通知表格重绘。public class EmpListFrame extends JFrame { private JTable table; private EmpTableModel model; public void refreshTable() { ListEmp list empDao.findAll(); model.setDataList(list); model.fireTableDataChanged(); } }这里的setDataList方法需要在EmpTableModel里手动加比重新new一个model省内存也保留列宽。刷新操作要放在查询按钮事件或增删改成功的回调里。有一种翻车现场是数据库里数据明明是新的但表格一直显示旧数据原因就是没调用fireTableDataChanged只改了model里的listSwing不知道要重绘。如果把model整个替换成新实例要注意JTable的getModel方法返回的是Object类型后续鼠标监听获取选中行时需要强制转换。所以保留同一个model引用会让后续编码更顺畅。4.3 新增员工表单从JDialog取值到PreparedStatement参数绑定新增员工需要弹出一个表单对话框包含工号、姓名、性别、部门下拉框。部门下拉框的数据也要从数据库dept表读取。这里最容易犯的错是界面表单校验做了但DAOInsert时字段顺序和数据库表结构不一致导致数据错列。我习惯在DAO构造SQL时使用显式列名不写INSERT INTO emp VALUES(...)这种裸写法。public boolean insertEmp(Emp emp) { String sql INSERT INTO emp (emp_no, emp_name, gender, birth_date, hire_date, dept_id) VALUES (?,?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, emp.getEmpNo()); ps.setString(2, emp.getEmpName()); ps.setString(3, emp.getGender()); ps.setDate(4, Date.valueOf(emp.getBirthDate())); ps.setDate(5, Date.valueOf(emp.getHireDate())); ps.setInt(6, emp.getDeptId()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }mysql的DATE类型与Java的LocalDate转换使用Date.valueOf()它期望的字符串格式是yyyy-MM-dd这就是Swing界面里的日期文本框必须做格式校验的原因。如果用户输入2024/05/01valueOf方法直接抛IllegalArgumentException。常见的补救办法是在表单里用JTextField 正则校验或者直接用第三方日期选择器组件。课程设计里手动校验就够了用SimpleDateFormat parse后format成标准字符串再交给setString。gender字段这里用String绑定如果数据库里存的是男、女没问题。如果你建表时用的是tinyint(1)那setString会报类型转换错误必须setInt。建表阶段就统一好改起来成本最低。4.4 删除与修改选中行后回填主键到底传哪个删除员工之前需要获取用户在JTable中选中的行。JTable的鼠标/键盘选中事件返回的是视图行号如果表格有排序或过滤视图行号跟你remember的model行号可能不一致。这里我采取最简单且不容易出错的方案不做排序直接通过table.getSelectedRow()获取行索引再从TableModel的数据列表里取对应主键。int selectedRow table.getSelectedRow(); if (selectedRow 0) { JOptionPane.showMessageDialog(this, 请先选择要删除的员工); return; } Emp emp model.getDataList().get(selectedRow); int empId emp.getEmpId(); boolean ok empDao.deleteEmpAndSalary(empId); if (ok) { refreshTable(); }修改操作和新增类似差别在于先要把选中行的数据回填到编辑对话框中。回填时注意JTextField.setText的顺序以及下拉框要按部门ID匹配选中项而不是按部门名称。因为数据库中dept_id才是唯一标识如果你两个部门同名虽然建表时有UNIQUE约束但你不一定加了按名称匹配会选错部门。修改的SQL要使用UPDATE emp SET ... WHERE emp_id?绝不能WHERE emp_no?虽然工号也唯一但工号可能允许用户修改一旦修改后主键标识变化关联的薪资表外键并不会跟着改会造成悬空引用。所以写修改逻辑时一定要用不可变的主键emp_id作为WHERE条件。这是个容易让系统产生脏数据的隐蔽坑。4.5 Swing事件回调与数据库操作的顺序画界面是画界面查库是查库Swing是事件驱动模型按钮点击后进入actionPerformed方法。很多人把所有逻辑堆进这个方法里连接数据库、执行SQL、刷新表格。功能能跑但代码一团乱也不符合分层思想。我习惯至少分三层界面层JFrame/JDialog、数据访问层DAO、工具层DBUtil。按钮事件里建立一个独立的线程去执行DAO操作再把结果通过SwingUtilities.invokeLater回调到界面线程。为什么强调线程分离因为数据库查询可能耗时几十到几百毫秒如果在Swing的EDTEvent Dispatch Thread里执行鼠标会变成等待状态窗口拖动会卡用户以为程序死了。完整方案是使用SwingWorker。下面给出一个查询按钮的标准写法。btnSearch.addActionListener(e - { String keyword txtKeyword.getText().trim(); new SwingWorkerListEmp, Void() { Override protected ListEmp doInBackground() { return empDao.findByName(keyword); } Override protected void done() { try { ListEmp list get(); model.setDataList(list); model.fireTableDataChanged(); } catch (Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(EmpListFrame.this, 查询失败); } } }.execute(); });doInBackground方法在后台线程执行不会卡界面done方法在EDT线程自动执行安全地更新UI。这里get()会从后台线程取出结果。注意查询不到数据时不要弹出警告直接显示空表格就好空表格也是一种正常反馈。很多教程里界面一卡就先想到加线程。但课程设计的数据量小直接在主线程执行大多数时候也没事我教你用SwingWorker是给你一个好习惯而不是必须。5. javaswingjdbcmysql避坑驱动找不到、中文乱码、界面卡死的排查顺序5.1 现象ClassNotFoundException: com.mysql.cj.jdbc.Driver这是我在这类题目里见过的最频繁报错。出现这个异常时后边还会跟着Caused by: java.lang.ClassNotFoundException提示第一反应别去翻驱动类名拼写先检查驱动jar包有没有真的进入运行时classpath。很多人IDEA项目里有个lib文件夹jar也放进去了但没右键Add as Library导致编译能过、一运行就找不到类。解决办法是在项目结构中确认lib下的mysql-connector-java.jar已经出现在Libraries列表里。另一个原因是MySQL版本和驱动版本跨度大。比如你安装的是mysql 8.0.44但手里jar还是5.1.49Class.forName就应该写成com.mysql.jdbc.Driver而不是新的带cj的类名。如果你不确定自己的MySQL版本在命令行输入mysql --version看一下再决定写哪一行。现在大多数课程设计电脑装的是8.0.x直接使用com.mysql.cj.jdbc.Driver即可。5.2 现象数据库中文乱码不管是插入还是显示都是问号中文乱码有三个层面的原因每层都要匹配才对MySQL表字符集、JDBC连接参数、Java程序编译编码。先说表字符集用第2章的建表SQL已经指定utf8mb4如果你是用默认设置的MySQL默认可能还是latin1插入中文就会变问号。检查表的方法SHOW CREATE TABLE emp看DEFAULT CHARSET一栏。然后是JDBC连接URL必须带characterEncodingutf8我给的DBUtil模板里已经写了。如果你用的是MySQL 8.0还可以加一行connectionCollationutf8mb4_general_ci让排序规则也统一。最隐蔽的是Java源文件编码。IDEA里如果没有把File Encoding设为UTF-8源文件里的中文字符串会被编译成GBK字节运行时显示乱码。这个问题通过刷新表格显示的乱码往往看不出是数据库问题还是界面问题。一个排查技巧是直接用命令行mysql执行INSERT中文再SELECT如果不乱说明MySQL没问题或不乱就继续检查JDBC和Java工程编码。5.3 现象Swing窗口一打开就白屏CPU飚高点任何按钮都没反应典型的Swing事件线程阻塞。如果你在JFrame的构造方法里直接调用了一个很慢的数据库查询整个EDT线程会被卡住界面画不出来看起来就像假死。黑匣子一样的现象让新手以为是MySQL没启动。实际上只要把数据库操作放进SwingWorker窗口就能正常显示数据到了再刷新。还有一个容易忽略的坑JTable的TableModel如果初始化时dataList为nullgetRowCount返回0没问题但如果你在某些系统LF下调用model.getValueAt(0,0)而dataList是null会抛NullPointerException卡住界面。所以要么在构造EmpTableModel时就传入一个空的ArrayList要么在getValueAt开头判空避免list为null时的空指针。这类无提示的界面卡死最难排查优先怀疑回调方法里是否访问了空集合。5.4 现象DELETE或UPDATE语句明明执行了但事务没有生效如果你执行两条SQL时没有设置autoCommit(false)那么第一条DELETE就算成功第二条失败第一条也已经写入数据库因为在自动提交模式下每条SQL都是独立事务。事务不生效使得删除员工及其薪资变成了可能只删一半。还有一种情况是你设置了conn.setAutoCommit(false)但在catch里没有调rollback程序抛异常后直接跳出了方法数据库连接还保持着未提交事务状态其他查询连接看不到新增数据。这时在finally里加rollback兜底前提是conn不为null且事务未提交。此外InnoDB是事务引擎如果你建表时用了MyISAM就算你写了commit和rollback也不会生效因为MyISAM根本支持不了事务。排查顺序是SHOW CREATE TABLE确认引擎再看代码里是否设置了autoCommit。答辦时老师如果问你的删除操作怎么保证原子性这个问题就值得说清楚。5.5 现象mysql安装完成后root密码不对连接报Access denied经常有同学明明刚才还能在命令行里登录mysql换到Java程序里用同一个账号密码却报Access denied。原因是MySQL 8.0默认的密码加密规则是caching_sha2_password而老版本驱动比如5.1.x只支持mysql_native_password。解决方案有两个要么换新版驱动jar包要么把root用户的加密规则改回来。改规则的方法比较简单在命令行MySQL执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;如果你用的是mysql 5.7.44这类版本本身是mysql_native_password一般不会遇到这个问题。所以遇到Access denied先别怀疑密码打错先看MySQL安装教程里用的版本和当前版本的认证插件差异。另外DBUtil里的连接URL如果写的是localhost而MySQL只允许root从localhost登录那没问题如果用了127.0.0.1有可能触发不同的host匹配规则建议URL和MySQL user的host字段保持一致。6. 给人事系统加登录日志用JDBC写审计记录并验证数据一致性课程设计功能做到增删改查后很多人会卡在怎么加点别人没有的功能。加登录日志是个性价比极高的选择表结构简单、代码量少、答辩时还能展示我考虑到系统安全审计。核心思路是新建一张login_log表记录登录时间、用户账号、登录结果成功/失败。登录时不管成功失败都写一条日志。CREATE TABLE login_log ( log_id INT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(30) NOT NULL, login_time DATETIME NOT NULL, success TINYINT(1) DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;然后写一个LoginLogDAO插入一条记录只需要一个方法public boolean insertLog(String userName, boolean success) { String sql INSERT INTO login_log (user_name, login_time, success) VALUES (?, NOW(), ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, userName); ps.setBoolean(2, success); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }登录界面验证密码时无论验证通过还是失败都调用insertLog。这个功能的验证方法不只是看日志表多了一行还可以把登录日志表做成另一个Swing界面用JTable展示最近登入记录。这样就从增删改查升级到了多表操作主从界面联动。一个我踩过的教训是写login_time用NOW()而不是Java端传new Timestamp因为MySQL服务器时间和本机时间可能不同步用数据库时间能让日志记录更权威。希望这篇从建表到JDBC再到Swing的步骤能帮你避开我当年走过的弯路让课程设计少熬夜多些底气去讲清楚每一个按钮背后发生了什么。本文还有配套的精品资源点击获取