简介:一套基于Java Swing与MySQL的员工工资管理系统,面向Java初学者与课程设计人群,主要覆盖员工信息管理、部门信息管理和工资管理三大核心模块,并实现匿名登录查询工资功能,可直接用于毕业设计或课设参考。压缩包共包含133个文件,以16个Java源文件与109个class编译文件为主,另附SQL初始化脚本、项目配置文件和可运行jar包,整体仅2.1MB,轻量简洁,便于导入IDE运行与二次开发。从代码结构可看出功能覆盖员工、部门与工资模块的增删改查、设置、查询和统计等完整流程,方便按图索骥定位相关实现。系统经多次测试运行稳定,下载后即可体验管理员与匿名用户双角色完整流程,尤其适合希望快速掌握Swing界面编程与MySQL数据库连接操作的读者。目前已有3961人浏览学习,属于同类课设资源中热度较高的选择。
1. 员工工资管理系统还在用 Java Swing 做?这恰恰是它最合适的时候
标题里的技术栈拆开看其实很朴素:Java 负责业务逻辑,Swing 负责窗口与表格展示,MySQL 负责数据长期保存,三者串起来就是一个员工工资管理系统的完整闭环。很多团队一听到 Swing 就默认这是毕业设计,但在一台没有外网、没有专职 IT 的办公室里,一个双击就能打开的桌面工资管理工具,比 Web 后台更省事:不用部署中间件,不用维护前端依赖,数据直接落在内网 MySQL 实例上。这个标题能解决的核心问题是工资怎么录、怎么算、怎么查、怎么防重复,适合想从零复现完整案例的开发者,也适合拿来做企业内的小工具自用。
2. 选型与架构:Swing 桌面端为什么能扛起工资管理,三层职责怎么切
2.1 为什么桌面管理系统不选 Web,Swing 反而被召回
这个方案最容易收到的问题就是:现在谁还写 Swing?真正做过一次内网办公工具的人会反过来问:为什么一定要上 Web?工资管理的数据量不大,用户无非是财务和行政,总共不到十个账号。Web 方案要装 Tomcat 或 Spring Boot 内嵌容器,还要考虑端口占用、浏览器兼容、前端构建链路,维护成本全部压在一个人身上;Swing 双击即可运行,升级时换一个 jar 或 exe 就行,几乎不需要中间层。
这两年一些老团队把 Swing 重新召回桌面办公场景,原因非常现实:终端电脑配置参差、系统权限受限、又不能把公司数据放到公网服务上。桌面端对网络连接依赖极小,MySQL 连不上时能弹出明确的错误对话框,而不是像网页一样直接白屏或 500。这不是技术落后,是场景匹配。Swing 也有代价,界面观感确实停留在早期桌面风格,所以使用时要克制:不做花哨样式,重点保证稳定、可操作、可打印。
2.2 三层职责划分:UI 只画界面,DAO 只碰 SQL
这个项目最容易翻车的写法,是把 JDBC 代码直接写进按钮监听器里。用户第一次点击能跑,第二次换个查询条件就要改界面代码。我一般会把工程拆成四层,结构如下:
com.example.salary ├── Main.java // 程序入口:负责拉起登录窗口 ├── ui // Swing 窗口、面板、表格模型 ├── service // 登录校验、工资计算、事务编排 ├── dao // JDBC 数据访问,对 service 暴露方法 └── entity // User、Employee、SalaryRecord 实体Main 只做一件事:启动登录界面。ui 包里的类不出现任何 SQL 字符串和 Connection 对象,只负责收集用户输入、调用 service、刷新界面。service 层处理业务规则,比如实发工资计算、月份重复校验;dao 层是唯一允许碰 JDBC 的地方,方法命名尽量直白,比如 findEmployeeByEmpNo、insertSalaryRecord。这样拆的直接好处是排错成本低:某个 DAO 查询写错了,写个 main 方法就能单独验证,不需要把整个界面跑起来点一遍按钮。
2.3 开发环境约定:安装 MySQL 后先确认四件事
开发环境不复杂,JDK 8 以上、MySQL 5.7 或 8.0、一个数据库连接驱动 jar、一个 Java IDE。很多人照 MySQL 安装配置教程装完之后,急着建表,结果连接时报一堆时区、字符集问题。我一般装完先确认四件事:MySQL 服务已启动且允许本机连接;JAVA_HOME 和 PATH 环境变量正确,命令行能敲出 java -version;驱动 jar 已放进项目的 lib 目录并加入了编译 classpath;创建一个独立库,默认字符集设为 utf8mb4。这四项里任何一项没对齐,后续所有排查都会往错误方向跑。
如果不用 Maven 而是手动编译运行,classpath 是最容易踩的一个点。常见做法是用通配符把 lib 目录下所有 jar 一次性加入:
javac -encoding UTF-8 -cp "lib/*:src" src/com/example/salary/Main.java -d out java -cp "lib/*:out" com.example.salary.Main第一行把源码编到 out 目录,第二行运行时把 lib 下的驱动和 out 目录放进 classpath。Windows 下路径分隔符是分号,Linux 和 macOS 是冒号,这行命令直接决定你能不能跑起来。用 IDE 的同学也要注意,jar 拷进项目目录不等于加载,必须 Add as Library 之后才参与编译和运行。
2.4 驱动依赖:Maven 坐标与手动 lib 的取舍
依赖管理上两种路线都行。用 Maven 就引 mysql-connector-j,版本和你本地 MySQL 主版本对应即可;不用 Maven,就从驱动官网下载 jar 放进 lib 目录。这里有一个踩出来的教训:classpath 里绝对不能同时放两个大版本不同的驱动 jar。旧版本驱动的类名是 com.mysql.jdbc.Driver,新版本是 com.mysql.cj.jdbc.Driver,两者同时存在时注册顺序不受控,会出现“有时连得上,有时报驱动类找不到”的玄学问题。统一成一个版本,是所有驱动类报错的第一条排查规则。
3. 数据表与 JDBC 连接:decimal 精度、事务边界与三个必调参数
3.1 三张表的设计:金额一律 decimal,防重复靠唯一索引
工资管理系统不是复杂业务,但数据库设计有几个硬要求:金额必须精确、同一个人同一个月不能发两次、员工离职后档案不能被工资记录引用报错。下面这套建表 SQL 是常见做法,我一般会在项目开始就把这三张表先落地:
CREATE DATABASE employee_salary DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(16) NOT NULL DEFAULT 'ADMIN', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; CREATE TABLE t_employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '员工工号', name VARCHAR(32) NOT NULL, department VARCHAR(32) NOT NULL, position_title VARCHAR(32) NOT NULL COMMENT '岗位名称', base_salary DECIMAL(10,2) NOT NULL COMMENT '基础工资', entry_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT '1 在职,0 离职' ) ENGINE=InnoDB; CREATE TABLE t_salary_record ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, salary_month CHAR(7) NOT NULL COMMENT '格式 2025-06', base_salary DECIMAL(10,2) NOT NULL, bonus DECIMAL(10,2) NOT NULL DEFAULT 0.00, overtime DECIMAL(10,2) NOT NULL DEFAULT 0.00, deduction DECIMAL(10,2) NOT NULL DEFAULT 0.00, actual_salary DECIMAL(10,2) NOT NULL COMMENT '实发工资', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_salary_month (emp_id, salary_month), CONSTRAINT fk_salary_emp FOREIGN KEY (emp_id) REFERENCES t_employee(id) ) ENGINE=InnoDB;工资字段全部用 DECIMAL(10,2) 而不是 DOUBLE,是因为浮点类型在累计加减时会产生精度漂移,发工资这种对账敏感的数据经不起一分的误差。t_salary_record 表把基础工资、奖金、加班费、扣款、实发工资都冗余在一行里,查询时直接读取实发,不用每次临时计算。最关键的约束是唯一索引 uk_salary_month,它从数据库层面拦截“同一员工同一个月重复发薪”。外键则保证工资记录不会指向一个不存在的员工。这个冗余和约束的组合,是后面所有业务代码的安全底网。
3.2 必调连接参数:characterEncoding、serverTimezone、useSSL 各管什么
JDBC 连接这一步,代码本身很短,坑全在连接串的参数里。我一般会在一个单独的类里维护 getConnection 方法,方便统一改配置:
public class DbUtil { private static final String URL = "jdbc:mysql://localhost:3306/employee_salary" + "?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai" + "&useSSL=false&allowPublicKeyRetrieval=true"; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, "root", "你的密码"); } }每个参数都有明确职责,缺一个就碰到一类典型问题。把它们整理成一张参数表,排查时对着看:
| 参数 | 作用 | 不带它时看到的现象 |
|---|---|---|
| useUnicode=true&characterEncoding=utf8 | 中文按 UTF-8 传输 | 表里中文问号,界面中文乱码 |
| serverTimezone=Asia/Shanghai | 指定时区 | 报 CST 或 UTC 相关时区异常 |
| useSSL=false | 关闭 SSL 握手 | 内网连接变慢,控制台刷 SSL 警告 |
| allowPublicKeyRetrieval=true | 允许获取公钥 | 报 Public Key Retrieval is not allowed |
前两个参数解决的是字符集和时区一致性问题,这也是 MySQL 安装配置教程里最容易被忽略的两项。useSSL 在内网场景通常关掉,因为工资管理系统走局域网,加密意义不大,反而白白增加握手开销。allowPublicKeyRetrieval 是 MySQL 8.0 默认认证插件 caching_sha2_password 带来的必配项,很多旧项目升级到 8.0 之后连不上,就是缺这个参数。
3.3 PreparedStatement 与 ORDER BY:SQL 写常量,值走占位符
连接串只是第一关,真正每天要写的是查询。查员工列表、按月份查工资记录,我全部用 PreparedStatement,不用字符串拼接:
public List<String> findDailyIds() { String sql = "SELECT e.emp_no, e.name, s.salary_month, s.actual_salary " + "FROM t_salary_record s " + "JOIN t_employee e ON s.emp_id = e.id " + "WHERE s.salary_month = ? " + "ORDER BY e.emp_no"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, month); try (ResultSet rs = ps.executeQuery()) { // 逐行封装成 SalaryRecord 对象并返回 } } catch (SQLException e) { throw new RuntimeException("查询工资记录失败", e); } }这里有三点要刻意控制。第一,排序尽量放在 SQL 层用 ORDER BY 做,让 MySQL 处理排序,而不是把数据全部取到内存再手动排。第二,ORDER BY 后面的字段不能通过 ? 占位符传入,如果列表需要支持用户选择排序字段,就用 if 分支检查字段名后拼接白名单。第三,ResultSet 遍历完就关闭,连接、语句、结果集全部放进 try-with-resources,防止连接泄漏把 MySQL 的连接数耗尽。
3.4 发薪事务:多条记录要么全部生效,要么全部回滚
工资发放往往不是发一个人,而是选中一个部门批量发。批量插入时最怕的情况是:前二十条插进去了,第二十一条因为重复键失败,界面报错退出,数据库里却残留了一半数据。所以批量发薪必须放进一个事务里,以下是核心控制逻辑:
public void batchPay(List<SalaryRecord> records) { String sql = "INSERT INTO t_salary_record " + "(emp_id, salary_month, base_salary, bonus, overtime, deduction, actual_salary) " + "VALUES (?, ?, ?, ?, ?, ?, ?)"; try (Connection conn = DbUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement(sql)) { for (SalaryRecord r : records) { ps.setLong(1, r.getEmpId()); ps.setString(2, r.getSalaryMonth()); ps.setBigDecimal(3, r.getBaseSalary()); ps.setBigDecimal(4, r.getBonus()); ps.setBigDecimal(5, r.getOvertime()); ps.setBigDecimal(6, r.getDeduction()); ps.setBigDecimal(7, r.getActualSalary()); ps.addBatch(); } ps.executeBatch(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException("批量发薪失败,已全部回滚", e); } finally { conn.setAutoCommit(true); } } catch (SQLException e) { throw new RuntimeException("数据库连接失败", e); } }setAutoCommit(false) 是把多条 INSERT 变成一个工作单元;executeBatch 统一执行;commit 之前任何一条语句抛异常,都会走进 catch 里的 rollback。事务边界一定要涵盖整个循环,而不是每插一条就自动提交一次。另外,唯一索引是防重复的最后一道闸,两个窗口同时给同一个人发同一个月工资时,MySQL 的 InnoDB 引擎在唯一索引等值插入时会加行锁,后到的操作要么等待,要么直接报重复键被回滚。MySQL 锁的分类在这里不需要深挖,你只需要知道应用层校验和数据库唯一索引是两道互补的防线。
4. Swing 界面与业务联动:登录鉴权、JTable 刷新与工资录入校验
4.1 登录窗口:界面不写 SQL,Service 返回结果是唯一依据
登录界面的逻辑很简单:输入用户名密码,点击登录,成功跳主界面,失败弹提示。代码实现的重点不在界面布局,而在事件监听器里绝对不能写 SQL。我一般把登录动作写成这样:
btnLogin.addActionListener(e -> { String username = txtUser.getText().trim(); String password = new String(txtPwd.getPassword()); User user = authService.login(username, password); if (user != null) { new MainFrame(user).setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, "账号或密码错误"); } });JPasswordField 的 getPassword() 返回 char[],不要用 toString 转成 String 留在堆里,这是一种习惯性安全写法。authService.login 内部会调用 DAO 查询 t_user 表,比对通过后返回包含角色的 User 对象;返回 null 就表示账号或密码不匹配。这样做的价值在于,界面只依赖 service 的返回值,密码怎么加密、SQL 怎么写、角色怎么解析,全部和 UI 解耦。演示项目里密码可以明文存储,真实上线时建议至少加盐哈希,这个改动只影响 service 和 DAO,不会动界面代码。
4.2 JTable 刷新三部曲:清空模型、填充数据、重置排序
主界面里最常用的组件是 JTable,员工列表、工资记录都靠它展示。新手最容易犯的错是查询完直接 table.setModel,导致表头重建、列宽重置、排序器丢失。我一般会固定用一套刷新写法:
public void refreshEmployeeTable(List<Employee> list) { DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); // 第一步:清空旧数据 for (Employee e : list) { // 第二步:逐行填充 model.addRow(new Object[]{ e.getEmpNo(), e.getName(), e.getDepartment(), e.getPositionTitle(), e.getBaseSalary().setScale(2), e.getStatus() }); } table.setModel(model); // 第三步:重新套回表格 table.repaint(); }setRowCount(0) 这步很关键,不加的话每次查询都会把新数据追加在旧数据后面,界面越看越乱。第三步重新 setModel 之后,之前设置的 TableRowSorter、列宽、渲染器会被重置,所以要么在刷新后重新配置,要么只通过更新模型数据而不是换模型来刷新。几十行数据时可以用 TableRowSorter 实现点击列头排序,但数据量上千之后,排序决定权应该交回 SQL 的 ORDER BY,UI 层排序只是视图动作,不会改数据源顺序。
4.3 工资录入与实发计算:BigDecimal 统一算,四舍五入到分
工资录入表单通常有四个金额输入:基础工资、奖金、加班费、扣款。实发工资不要在界面上直接相加减,而是由 service 层统一计算。这里唯一正确的方式是 BigDecimal:
public BigDecimal calcActualSalary(String baseStr, String bonusStr, String overtimeStr, String deductionStr) { BigDecimal base = new BigDecimal(baseStr.trim()); BigDecimal bonus = new BigDecimal(bonusStr.trim()); BigDecimal overtime = new BigDecimal(overtimeStr.trim()); BigDecimal deduction = new BigDecimal(deductionStr.trim()); BigDecimal actual = base.add(bonus).add(overtime).subtract(deduction); return actual.setScale(2, RoundingMode.HALF_UP); }从表单拿到的是字符串,用 new BigDecimal(String) 构造,不允许用 new BigDecimal(double),那是精度灾难。计算完用 setScale(2, RoundingMode.HALF_UP) 四舍五入到分,符合财务习惯。录入校验也要在 service 层完成:四个金额都不能为负,salary_month 用正则校验必须形如 2025-06,员工状态必须是在职。这些校验看起来琐碎,但少了任何一条,脏数据都会顺着 DAO 落进数据库,等对账的时候才暴露。
4.4 超过 300ms 的查询交给 SwingWorker,别阻塞事件线程
登录查询很快,可以放在按钮事件里直接执行。但工资记录按月汇总、员工报表这类查询,可能涉及多表连接,处理时间明显变长。Swing 是单线程模型,所有界面重绘都依赖事件分发线程 EDT,在这个线程里执行 JDBC 查询,窗口会直接卡死。常见做法是用 SwingWorker 把耗时操作挪到后台:
SwingWorker<List<SalaryRecord>, Void> worker = new SwingWorker<>() { @Override protected List<SalaryRecord> doInBackground() { return salaryService.queryByMonth(month); } @Override protected void done() { try { refreshTable(get()); } catch (ExecutionException e) { JOptionPane.showMessageDialog(frame, "查询失败:" + e.getCause().getMessage()); } } }; worker.execute();doInBackground 在后台线程执行,不会阻塞 EDT;done 方法会自动回到 EDT,所以在这里可以安全地更新 JTable。后台线程里绝对不能直接调用 model.addRow 或 setText,那会触发跨线程访问 UI 的异常。判断标准很简单:数据访问耗时超过 300 毫秒,就值得扔进 SwingWorker。等真正遇到界面未响应时再改,排查成本比提前封装高得多。
5. 避坑记录:驱动加载、中文乱码与 Swing 线程阻塞的五处翻车点
5.1 驱动类找不到:Class.forName 和 jar 没有真正进 classpath
现象:程序启动后几秒,控制台抛出 java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,登录窗口还没弹出来就退出了。这个错误在项目初期出现频率最高,也最容易被误导。
原因主要有两个:第一,MySQL 8.x 驱动类的完整类名是 com.mysql.cj.jdbc.Driver,而 5.1 时代是 com.mysql.jdbc.Driver,写旧类名在 8.x 驱动下必然找不到;第二,IDE 里能看到 import 语句不报错,但运行时 classpath 里根本没有 jar,说明驱动只是放在项目目录里,没有被标记为 Library。解决方法是:只保留一个版本的驱动 jar,确认它参与了编译和运行;手动运行时检查 java -cp 有没有把 lib 目录带上。如果 classpath 同时存在两个大版本驱动,注册顺序不可控,会出现“时好时坏”的随机失败。
5.2 中文乱码:文件编码、连接串、表字符集三处必须一致
现象:界面表格里中文全是问号,或者从 MySQL 命令行查出来的中文变成乱码,但英文数字正常。这几乎是所有 Java 桌面项目第一次连接数据库都会撞上的问题。
原因是字符集没有对齐,三处只要有一处不一致就乱码:IDE 保存文件的编码、JDBC 连接串的 characterEncoding、数据库表的默认字符集。解决时按顺序检查这三样:IDE 统一 UTF-8;连接串加 useUnicode=true&characterEncoding=utf8;建表指定 utf8mb4。还有一个隐藏坑:手动 java 命令跑在 Windows 控制台时,如果控制台代码页是 936,日志里的中文会乱码,但界面的中文正常,这是控制台显示问题,不是数据库问题,别把它带进代码排查里。
5.3 界面卡成白屏:在 EDT 里做了 JDBC 耗时操作
现象:点击“查询全部工资记录”按钮后,窗口变成未响应,鼠标拖动也没反应,过几秒才恢复,数据量大的时候干脆假死。
原因是 Swing 的事件监听器默认运行在 EDT 上,JDBC 查询是网络加磁盘操作,耗时远高于界面渲染需求。EDT 被查询阻塞后,窗口拖拽、最小化、重绘这些事件全都排队等待,界面表现就是卡死。解决方法是按第 4.4 节的做法,把耗时查询封装进 SwingWorker,在 doInBackground 里访问数据库,在 done 里更新表格。记住 Swing 的单线程模型是铁律,后台线程直接操作组件不仅会卡死,还会抛出 InterruptedException 或线程竞争导致数据错乱。
5.4 MySQL 8 连接报错:认证插件、公钥获取与时区缺一不可
现象:本地装的是 MySQL 8.0,用老驱动连接报 Unable to load authentication plugin caching_sha2_password,或者报 Public Key Retrieval is not allowed,还有可能报服务器时区无法识别。
原因是 MySQL 8.0 把默认认证插件换成了 caching_sha2_password,老版本驱动不认识这个插件;allowPublicKeyRetrieval 未开启时,驱动又不允许从服务端获取公钥做加密通信。解决方法是把驱动升级到 8.x,连接串加上 allowPublicKeyRetrieval=true。顺带检查 serverTimezone,Java 8 以上版本必须显式指定时区,否则驱动读取系统默认时区会得到无法识别的值。我一般统一写成 serverTimezone=Asia/Shanghai,不要依赖操作系统时区,这样换一台机器部署也不会因为时区不同而翻车。
5.5 SQL 拼接与空指针:一个引号毁掉查询缓存
现象:文本框输入1' or '1'='1后,查询结果把整个表的数据都带出来;或者录入工资时奖金不填,保存就报空指针。
这就是 SQL 注入的典型表现,字符串拼接把用户输入的内容直接变成了 SQL 语义的一部分,原来的条件被 or 改写,等于没过滤。解决方法是 100% 使用 PreparedStatement,所有值都走 ? 占位符,哪怕输入内容全是数字也不能拼字符串。涉及 IN 列表时,不要试图传一个字符串进去,而是循环生成对应数量的占位符。空指针的坑通常藏在金额字段:表单里奖金留空,代码用 getText() 拿到空字符串,直接 new BigDecimal("") 必然抛异常。在 service 层做统一的空值判断,例如 StringUtils.isBlank 时按 0.00 处理,再进入计算,能省掉后续大量排查时间。
6. 进阶:月度工资报表导出与最小闭环验证
工资管理系统能正常增删改查之后,接下来最重要的能力是把月度工资导成文件。最常见的落地做法是导出 CSV,让 Excel 能直接打开。导出时有一个细节:中文要正常显示,文件必须带 UTF-8 BOM 头,否则 Excel 打开 UTF-8 编码的 CSV 会出现中文乱码。核心逻辑如下:
public void exportMonthly(String month) throws IOException { List<SalaryRecord> records = salaryService.queryByMonth(month); StringBuilder sb = new StringBuilder("\uFEFF"); // BOM,让 Excel 识别 UTF-8 sb.append("工号,姓名,月份,基础工资,奖金,加班费,扣款,实发工资\n"); for (SalaryRecord r : records) { sb.append(r.getEmpNo()).append(',') .append(r.getName()).append(',') .append(r.getSalaryMonth()).append(',') .append(r.getBaseSalary()).append(',') .append(r.getBonus()).append(',') .append(r.getOvertime()).append(',') .append(r.getDeduction()).append(',') .append(r.getActualSalary()).append('\n'); } Files.write(Paths.get("salary_" + month + ".csv"), sb.toString().getBytes(StandardCharsets.UTF_8)); }BigDecimal 直接 toString 输出,不会丢精度,也不会有科学计数法隐患。导出之前先跑一遍完整闭环验证:建库建表并插入一个 admin 账号;新增一条在职员工档案;录入一个月的工资;执行月度查询;导出 CSV 并用 Excel 打开核对实发工资。整个链路走通,这个系统才算真正交付。上线前还要做一步备份,mysqldump 命令顺手就能完成:mysqldump -u root -p employee_salary > backup.sql,恢复时用 mysql -u root -p employee_salary < backup.sql。我自己做这类小系统有个习惯:每次改完连接参数先写一条 select 1 的最短查询验证,再开始跑业务,这能省下大量“怎么连不上”的血泪经验。希望帮到你。
本文还有配套的精品资源,点击获取