ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+MySQL学生信息管理系统:JDBC连接与事务处理实战

Java+MySQL学生信息管理系统:JDBC连接与事务处理实战 简介一份基于Java与MySQL的学生信息管理系统讲解文档面向Java初学者、在校生以及需要完成课程设计或毕业设计的人群。文档围绕学生信息管理系统的完整实现展开重点讲解基于Java Swing的窗口界面设计、JDBC连接MySQL执行增删改查操作、管理员登录校验等核心环节并给出了admin表与student表的建库建表语句以及学生信息字段学号、姓名、性别、院系、籍贯、学分、邮箱、电话的数据库设计思路。同时文档还展示了model-dao-view的代码分层结构包含Admin和Student实体类的属性与访问方法以及部分核心代码片段能够帮助读者理解业务操作与数据访问之间的调用流程。资源共1个文件为PDF格式大小135KB属于精简型讲解资料读者可按文档指引搭配GitHub中的完整工程进行实战练习。目前已有5010人学习下载尤其适合作为Java课程设计、MySQL数据库应用的入门参考具有较高的实践借鉴价值。1. 学生信息管理系统一道课设题也是 JDBC 最实用的练兵场第一次接触「JavaMySQL实现学生信息管理系统」这个标题的人多半是带着同一个诉求来的期末课程设计要交东西学了 Java 基础却不知道怎么连上数据库或者代码写完了但一跑就报错。这个项目听起来普通却是把 Java 语法、JDBC 连接、SQL 设计和事务意识串起来的最短路径。它能解决的不只是「交一份作业」而是让你真正理解一次请求从控制台输入到 MySQL 落库的完整链路。适合刚学完 Java 面向对象、正在学 MySQL 但没做过完整 CRUD 的初学者也适合面试前想快速捡起 JDBC 细节的人。我下面按自己做过课设评审时最常被问到的点来讲尽量把坑提前说透。2. 从 MySQL 开始三张表的设计与建库 SQL2.1 表结构设计为什么学号用 VARCHAR 而性别用 CHAR学生管理系统听起来只有一张学生表实际做课设时最好拆成三张学生表student、课程表course、成绩表score。只做一张表虽然能应付「增删改查」的演示但老师和面试官一定会追问「学生选多门课怎么存」到时候再改结构就麻烦了。三张表的核心关系很清楚一个学生可以选多门课一门课可以被多个学生选成绩表就是学生和课程之间的关联表顺便带上分数。字段类型的选用是一个值得讲清楚的点。学号别用 INT用 VARCHAR(20)。理由很简单学号不是用来加减乘除的却经常带前导零或字母比如 2024001 和 001 开头的编号一旦存进 INT前导零直接消失显示出来就错了。性别用 CHAR(1) 就够了存 M/F 或 男/女不需要浪费空间。出生日期用 DATE成绩用 DECIMAL(5,2)能精确到 0.5 分。每个表都加一个 created_at 默认当前时间答辩时展示数据会更有说服力。字段类型说明student_idVARCHAR(20)主键逻辑学号nameVARCHAR(50)学生姓名genderCHAR(1)性别M/Fbirth_dateDATE出生日期class_nameVARCHAR(50)班级phoneVARCHAR(20)联系电话created_atTIMESTAMP默认当前时间这里有个网络热词里天天被搜的问题——mysql 设置默认值为 0 是怎么做的。比如课程表里加一个 selected_count INT DEFAULT 0表示已选人数插入课程时没指定值就自动是 0。2.2 建库建表 SQL 与必设的字符集参数建库之前先想好字符集。常见做法是统一用 utf8mb4它比 utf8 多支持 Emoji 和生僻字MySQL 5.7 和 8.0 都兼容。很多人都在这上面翻过车建库用默认 latin1插入中文直接变问号。下面这份 SQL 我建议直接复制字段可以按课设要求增删。CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_system; CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) NOT NULL DEFAULT M COMMENT 性别 M/F, birth_date DATE NULL COMMENT 出生日期, class_name VARCHAR(50) NULL COMMENT 班级, phone VARCHAR(20) NULL COMMENT 联系电话, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT学生信息表; CREATE TABLE course ( course_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 课程ID, course_code VARCHAR(20) UNIQUE COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, teacher_name VARCHAR(50) COMMENT 授课教师, selected_count INT DEFAULT 0 COMMENT 已选人数 ) ENGINEInnoDB COMMENT课程表; CREATE TABLE score ( student_id VARCHAR(20) COMMENT 学号, course_id INT COMMENT 课程ID, score DECIMAL(5,2) COMMENT 成绩0-100, exam_date DATE COMMENT 考试日期, PRIMARY KEY (student_id, course_id) ) ENGINEInnoDB COMMENT成绩表;逻辑说明student 和 course 的关联全靠 score 表的主键组合student_id course_id来体现这样同一个学生选多门课不会重复插入同一门课被多个学生选也不会产生冗余行。这里我没有写物理外键原因是课设阶段删除学生时物理外键会挡住 DELETE 操作程序里又没处理结果就是报错一堆。业务上先删成绩再删学生的顺序放在 Java 代码里控制更灵活。参数说明ENGINEInnoDB 是必须的它支持事务和外键约束MyISAM 虽然读快但不支持事务。COMMENT 给字段加注释答辩投影时老师一眼能看明白。PRIMARY KEY (student_id, course_id) 这种组合主键是关联表最常见的写法。2.3 初始化数据让程序一启动就有内容可查空表跑 CRUD 没有画面感建议插几条初始数据尤其是作为登录验证用的学生。初始化数据时要注意依赖关系先插 student再插 course最后插 score因为 score 引用前两张表的值。注意 MySQL 8.0.16 以后 CHECK 约束才真正生效如果你们机房还是 5.7成绩的 0-100 校验就得靠 Java 端做我在后面会写。INSERT INTO student (student_id, name, gender, birth_date, class_name, phone) VALUES (2024001, 张三, M, 2004-03-12, 计算机2401, 13800138001), (2024002, 李四, F, 2005-07-08, 计算机2401, 13800138002); INSERT INTO course (course_code, course_name, credit, teacher_name) VALUES (CS101, Java程序设计, 3.0, 王老师), (CS102, 数据库原理, 2.5, 陈老师); INSERT INTO score (student_id, course_id, score, exam_date) VALUES (2024001, 1, 88.5, 2024-06-20), (2024001, 2, 91.0, 2024-06-25);这里有个小技巧score 表里的 course_id 是 1、2很多人会手动写死但课程ID是自增的删掉重建课程表后 ID 可能对不上。更稳的做法是先查 course 表拿到 course_id再插 score。放到 Java 代码里就是先执行一条 SELECT再执行 INSERT 的顺序问题。3. 工程骨架与数据库连接从 JDBC 驱动到连接工具类3.1 项目结构与驱动 jar 的准备:Java 环境变量与 mysql 驱动缺一不可在做这门课设之前先确认两件事Java 环境变量配置好了没有mysql 安装配置教程里的服务能启动没有。命令行里输入 java -version 和 mysql -u root -p 都能通再继续。很多同学代码写对了最后卡在驱动 jar 包没引进工程里报出各种不知名的连接失败。工程目录常见做法如下图不依赖 IDE 的项目结构。如果你用 IntelliJ IDEA在项目根目录建一个 lib 文件夹把连接器 jar 放进去再右键 Add as Library。别把 jar 直接丢在 src 里Maven 工程另说这里我们先说手动配依赖的方式。student-system/ ├── src/ │ └── com/example/student/ │ ├── model/Student.java │ ├── dao/StudentDao.java │ ├── service/StudentService.java │ └── Main.java ├── lib/ │ └── mysql-connector-java-8.0.30.jar ├── db.properties └── sql/init.sqlMySQL 8.0 需要 mysql-connector-java 8.x 的驱动驱动类是 com.mysql.cj.jdbc.Driver。如果你还抱着 5.x 的驱动连接 8.0 的 MySQL虽然有时能连上但 SSL 和时区的报错会反复出现后面避坑章我会细说。3.2 连接工具类 DbUtils 的一个最小写法连接管理是整个项目的地基。我见过最糟糕的代码是在每个 DAO 方法里写一遍 DriverManager.getConnection结果 URLs 密码散落各处改一次数据库密码要改十几个文件。正确做法是抽一个 DbUtils 工具类统一放驱动加载、连接创建和资源关闭。package com.example.student.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DbUtils { private static final String URL jdbc:mysql://localhost:3306/student_system ?useSSLfalseserverTimezoneAsia/Shanghai characterEncodingutf8allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败请检查lib目录, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明静态代码块里 Class.forName 负责把驱动类加载进 JVM这一步在 JDBC 4.0 之后其实可以省略但保留它能让报错信息更直观——驱动缺失时报的是「MySQL驱动加载失败」而不是后面一串看不懂的 No suitable driver。getConnection 每次调用都新建一个物理连接课设规模下完全够用但下次连接前一定记得 close否则本机 MySQL 默认只有 151 个连接程序跑久了就卡死。参数说明URL 里的 useSSLfalse 是告诉驱动不要做 SSL 握手。MySQL 8 默认开 SSL本地开发不需要关掉能省一大截连接时间。serverTimezoneAsia/Shanghai 解决驱动和数据库时区不一致导致的Server returns invalid timezone报错。characterEncodingutf8 保证中文字符写入数据库不乱码。allowPublicKeyRetrievaltrue 针对 MySQL 8 的 caching_sha2_password 认证方式不加它客户端会报 Public Key Retrieval is not allowed。3.3 预编译与 try-with-resources:让 DAO 代码短一半连接有了接下来写第一个查询。这里直接上 PreparedStatement不用 Statement原因一句话预编译 SQL 既防 SQL 注入又让 MySQL 重复执行同一条 SQL 时不用反复解析。在 DAO 里配合 try-with-resources连手动 close 都能省代码长度直接砍半。public Student findById(String studentId) { String sql SELECT student_id, name, gender, class_name FROM student WHERE student_id ?; try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student stu new Student(); stu.setStudentId(rs.getString(student_id)); stu.setName(rs.getString(name)); stu.setGender(rs.getString(gender)); stu.setClassName(rs.getString(class_name)); return stu; } } } catch (SQLException e) { e.printStackTrace(); } return null; }逻辑说明try (Connection conn ...) 的写法会在代码块结束自动关闭 conn 和 psConnection 接口实现了 AutoCloseable这是 Java 7 引入的语法也是现在最主流的资源管理方式。注意 ResultSet 的 try 必须嵌套因为 rs 依赖 ps先关 rs 合理关闭顺序上内层先执行。参数说明setString(1, studentId) 里的 1 是占位符索引第一个问号对应 1第二个对应 2顺序不能反。建议所有查询只查需要的字段不要 SELECT *一是减少网络传输二是一旦改了表结构Java 代码里 getString(列名) 可能拿不到值。有一个热词场景值得说java 排序在系统里怎么做。学生列表按学号排序有两种做法一是 SQL 里 ORDER BY student_id ASC二是查出来放到 List 里用 Comparator。更推荐前者因为 MySQL 排序走索引更快Java 端拿到完整数据再排纯属浪费。4. 业务逻辑与增删改查把控制台菜单跑起来4.1 从实体类到 DAO一张表对应一个 Repository很多初学者喜欢把 SQL 直接写在 main 方法里那只是验证连通性不是系统。建议至少分三层实体层model、数据访问层dao、业务控制层service。实体类不需要包含每个数据库字段但 student_id、name、gender 这种核心字段必须有。DAO 层的职责只有一个——把 SQL 封装成方法业务层的职责是判断「能不能做」main 里的死循环负责菜单跳转这样各司其职调试时定位快。public class StudentDao { private static final String INSERT_SQL INSERT INTO student (student_id, name, gender, birth_date, class_name, phone) VALUES (?, ?, ?, ?, ?, ?); private static final String UPDATE_SQL UPDATE student SET name ?, gender ?, birth_date ?, class_name ?, phone ? WHERE student_id ?; private static final String DELETE_SQL DELETE FROM student WHERE student_id ?; public boolean exists(String studentId) throws SQLException { String sql SELECT COUNT(1) FROM student WHERE student_id ?; try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { return rs.next() rs.getInt(1) 0; } } } public int insert(Student stu) throws SQLException { try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(INSERT_SQL)) { ps.setString(1, stu.getStudentId()); ps.setString(2, stu.getName()); ps.setString(3, stu.getGender()); ps.setDate(4, stu.getBirthDate() null ? null : Date.valueOf(stu.getBirthDate())); ps.setString(5, stu.getClassName()); ps.setString(6, stu.getPhone()); return ps.executeUpdate(); } } public int update(String studentId, Student stu) throws SQLException { try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(UPDATE_SQL)) { ps.setString(1, stu.getName()); ps.setString(2, stu.getGender()); ps.setDate(3, stu.getBirthDate() null ? null : Date.valueOf(stu.getBirthDate())); ps.setString(4, stu.getClassName()); ps.setString(5, stu.getPhone()); ps.setString(6, studentId); return ps.executeUpdate(); } } public int delete(String studentId) throws SQLException { try (Connection conn DbUtils.getConnection(); PreparedStatement ps conn.prepareStatement(DELETE_SQL)) { ps.setString(1, studentId); return ps.executeUpdate(); } } }逻辑说明exists 方法用于插入前的查重用 COUNT(1) 而不是查出所有记录MySQL 只需要返回一个数字性能好很多。insert 和 update 都返回 executeUpdate 的结果这个 int 表示影响的行数等于 0 说明没插进去业务层可以根据返回值提示「操作失败」。注意日期参数的坑java.util.Date 不能直接 setDate 给 JDBC要先转成 java.sql.Date这里用 Date.valueOf(String) 做转换格式必须是 yyyy-MM-dd。参数说明四个方法各自 try-with-resources连接每次新建。这个规模下连接池不是必需品但你要知道 ConnectionHolder 这种线程池方案的存在面试常问。返回 int 而不抛业务异常是课设代码的一种简化但真正的系统里推荐抛自定义异常再统一包装。4.2 业务层的校验与组合操作DAO 写好后业务层做一件事——在合适时机调用 DAO并维护业务规则。比如新增学生前先查重删除学生前先删成绩这是「先删子表再删主表」的顺序就是我们在建表时没加物理外键换来的灵活性。public class StudentService { private StudentDao studentDao new StudentDao(); private ScoreDao scoreDao new ScoreDao(); public String addStudent(Student stu) { try { if (studentDao.exists(stu.getStudentId())) { return 学号已存在; } int rows studentDao.insert(stu); return rows 0 ? 添加成功 : 添加失败请检查输入; } catch (SQLException e) { e.printStackTrace(); return 数据库异常 e.getMessage(); } } public String deleteStudent(String studentId) { try { if (!studentDao.exists(studentId)) { return 学号不存在; } scoreDao.deleteByStudentId(studentId); studentDao.delete(studentId); return 删除成功; } catch (SQLException e) { e.printStackTrace(); return 删除失败请检查关联数据; } } }逻辑说明删除学生时先调 scoreDao.deleteByStudentId把成绩表里该学生的所有记录清掉再删 student 主表记录。如果反过来要么报外键错误要么留下没主表对应的孤儿成绩记录。选课场景同理——学生退选课程时先删 score 记录再让 course 表 selected_count 减一。这里我把返回字符串当结果给控制台用勉强可以但更好的做法是定义一个 Result 对象装 code、message、data这样一个方法既能提示又能带数据。课设我建议用最简单的字符串把代码量压下来但心里要清楚这是简化。4.3 登记成绩时的事务边界setAutoCommit(false) 的正确位置成绩登记是最容易出现数据不一致的场景往 score 表插入一条成绩同时 course 表的 selected_count 要加一。如果第一步成功第二步失败成绩存在但选课人数没变这种状态就是脏数据。这时候用事务把两步包起来。public String enrollCourse(String studentId, int courseId, double scoreValue) { String sql1 INSERT INTO score (student_id, course_id, score, exam_date) VALUES (?, ?, ?, NOW()); String sql2 UPDATE course SET selected_count selected_count 1 WHERE course_id ?; try (Connection conn DbUtils.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { ps1.setString(1, studentId); ps1.setInt(2, courseId); ps1.setDouble(3, scoreValue); ps1.executeUpdate(); ps2.setInt(1, courseId); ps2.executeUpdate(); conn.commit(); return 选课成功; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } } catch (SQLException e) { e.printStackTrace(); return 选课失败事务已回滚; } }逻辑说明setAutoCommit(false) 之后所有 SQL 都先执行在事务里只有 commit() 才真正落库。第二个 catch 里 rollback() 把前面已执行的 INSERT 也撤销保证「成绩没插进去人数也没加」。finally 里把 autoCommit 设回 true防止连接归还后影响下一次调用。参数说明这条事务用到两步 SQL加了两个 PreparedStatement说明一个事务可以跨多个 statement 对象只要用的是同一个 Connection。注意这里的 NOW() 函数生成当前时间避免 Java 和 MySQL 时钟差异导致日期错乱。4.4 控制台菜单:主函数里的死循环最后是主函数用一个死循环加 switch 做菜单选择。Scanner 读入用户输入按数字调用 service 层的方法。这里有个细节——读学号用 nextLine()读整数用 nextInt()两者混用会出现换行符残留。最稳妥的做法是全部 nextLine()需要数字时用 Integer.parseInt 转换这样不容易出奇怪的跳菜单问题。菜单做完一个「能跑起来」的学生信息管理系统就成型了。之后再看第 5 章的问题清单把运行过程中遇到的报错逐个对上号。5. 踩坑自查清单课设答辩前的五个致命细节5.1 MySQL 8 的密码认证报错Public Key Retrieval is not allowed现象程序启动时连接数据库控制台抛java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed偶尔连上但偶尔连不上。原因MySQL 8.0 默认的用户认证插件是 caching_sha2_password它要求在数据传输前先获取 RSA 公钥。第一次连接时客户端没缓存公钥于是报这个错。mysql 安装教程里很多教程让你换 mysql_native_password但换插件要改用户配置不如直接在 URL 里加参数。解决在 JDBC URL 的最后加上allowPublicKeyRetrievaltrue和已有参数用 连接。这不是 SSL 传输是本地开发最省事的做法。如果做生产系统建议配置 SSL 并关闭 allowPublicKeyRetrieval避免公钥泄露风险。这条算是最常见的坑我见过一百多次。5.2 中文乱码URL 字符集与表字符集必须对齐现象插入学生姓名后SELECT 出来全是???或者 Java 控制台打印数据库里的中文显示乱码。看起来像玄学其实层层都有固定原因。原因三层字符集只要有一层不对就乱码——第一层是数据库和表的字符集建库没指定就用默认 latin1第二层是 JDBC URL 里的 characterEncoding 没设或设成 utf8第三层是控制台输出的编码Windows 默认 GBKIDEA 的默认编码也经常和数据库不一致。解决建库建表统一用 utf8mb4参见 2.2 节JDBC URL 里写characterEncodingutf8IDEA 在 Settings 里把 Project Encoding、Default encoding for properties files 都设成 UTF-8。验证方法执行SHOW VARIABLES LIKE character_set%;Client 和 Server 都是 utf8mb4 就基本干净了。5.3 SQL 时区报错与 SSL 连接错误两个 URL 参数一起解决现象连接 MySQL 8 时抛The server time zone value Öйú±ê׼ʱ¼ä is unrecognized或者SSL connection error: protocol version mismatch。很多同学在这个报错上卡一下午觉得是 MySQL 安装的问题其实问题都在驱动侧。原因MySQL 8.0 驱动要求客户端提供明确的时区驱动从 JVM 默认时区去换算如果拿不到就报错。SSL 报错则常见于用了老版本 mysql-connector-java 5.x 连接 MySQL 8.0或者 MySQL 服务端配置了 SSL 但客户端没关闭。解决URL 里加上serverTimezoneAsia/Shanghai和useSSLfalse这两个参数通常是成对出现的。驱动版本和数据库版本尽量匹配MySQL 8 数据库配 8.x 驱动MySQL 5.7 配 5.1.47 或 8.x 向下兼容均可。建议直接把驱动升级到 8.0.30 及以上不要留恋 5.x 版本。5.4 外键报错删除学生时先删成绩表现象删除学生时抛Cannot delete or update a parent row或者删了学生之后成绩表里还留着这个学号的记录列表里出现一堆查不到对应学生的成绩。原因建表时如果加了物理外键DELETE 会被数据库拦截如果没加业务层又没处理子表数据就会产生孤儿记录。这是课设答辩中老师最爱问的问题之一。解决建表时不加物理外键在业务层参考 4.2 节按顺序先删 score 表再删 student 表。如果要保留物理外键请把 score 表外键加上 ON DELETE CASCADE让数据库帮你级联删除。两种方案各有利弊Java 层控制更安全但要求写代码的人不能漏掉调用。5.5 ClassNotFoundExceptionjar 依赖没有挂上现象启动 main 方法一执行到 Class.forName 就抛java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。很多人第一反应是代码写错了实际上代码没错。原因jar 包没有放进编译路径里。IDEA 里把 jar 放进 lib 目录但没右键 Add as LibraryEclipse 里没 Build Path或者命令行编译时没加-cp lib/mysql-connector-java-8.0.30.jar。解决IDEA 中最稳的确认方式是在 Project Structure 的 Libraries 面板里看到这个 jar命令行运行时用java -cp .;lib/mysql-connector-java-8.0.30.jar com.Main方式启动。注意 Windows 用分号分隔 classpathLinux/macOS 用冒号。如果代码里用的是老驱动类名 com.mysql.jdbc.Driver8.x 包里已不提供要么改回 com.mysql.cj.jdbc.Driver要么换老 jar。6. 从能用到好看给系统加分的小改进与验证习惯6.1 分页查询先算总数再取当前页答辩时老师大概率会问「数据量大了怎么办」。控制台版系统不做分页也能过但加一个分页查询马上显得专业很多。核心 SQL 是SELECT * FROM student ORDER BY student_id LIMIT ?, ?第一个 ? 是偏移量 offset第二个 ? 是页大小 pageSize。偏移量计算公式是(currentPage - 1) * pageSize和 SELECT COUNT(*) 拿到总数算出总页数。这比 Java 内存分页更高效也符合 MySQL 排序的惯用做法。6.2 验证连接关闭观察 MySQL 连接数一个很实用的验证习惯程序跑完一批增删改查打开 MySQL 执行SHOW STATUS LIKE Threads_connected;看连接数是不是涨上去没回落。如果一直在涨说明代码里有连接没关把 try-with-resources 漏掉了。我之前有一版代码把 Connection 封装成 static 单例程序连续跑两小时就报 Too many connections后来逐个方法排查发现是某个 DAO 的 finally 块里 close 抛了异常资源释放被中断。这种问题用连接数监控最直观比看日志快。要说最值得养成的习惯就是每次写完一个 DAO 方法先去 MySQL 看一眼连接数再继续写下一个。这比留到答辩前临时查有效得多。项目从「能跑」到「好看」这一步跨过去你会发现 JDBC 的边界、事务的粒度、资源的生命周期全都有了真实体感之后再学 Spring JDBC 或 MyBatis 时会轻松很多。这一块也算我自己的血泪经验——第一次做课设时把连接写在循环里几十条数据就跑崩了被同学笑了一周。希望帮到你至少不要再在同样的坑里摔一遍。本文还有配套的精品资源点击获取
返回列表