ARTICLE DETAIL

资讯详情

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

Java+MySQL学生信息管理系统:JDBC连接、CRUD实现与性能优化实践

Java+MySQL学生信息管理系统:JDBC连接、CRUD实现与性能优化实践 简介这是一份面向Java课程设计与初学者的学生信息管理系统配套说明文档核心基于Java Swing界面与MySQL数据库通过JDBC完成对学生信息的增删改查操作适合需要快速搭建管理类课设项目的人群参考。文档中给出了完整的数据库建表语句涵盖管理员表与学生表的字段设计、初始测试数据并展示了实体类的代码结构与项目分层思路model-dao-view对理解Java连接数据库、封装业务对象及基础CRUD实现有直接帮助。同时说明了项目开发环境jdk7MySQL5win7便于读者复现运行环境。资源包仅含1个PDF文件体积约135KB内容精简集中便于离线查阅。目前已有5010人学习/下载是同类学生管理系统中较受关注的一份资料尤其适合用于复习JDBC操作、Swing界面设计与MySQL基础配置。1. 学生信息管理系统JavaMySQL为什么到现在还是课堂、实习和外包项目的标配你在搜索栏里敲下“JavaMySQL实现学生信息管理系统”大概率是要交课设、给单位做内部工具或者接了个练手的小外包。这个组合看起来很“老”但恰恰因为老它的技术边界、失败模式、验收标准都已经被前人踩得透透的。用Java写好业务逻辑用MySQL存好结构化数据两者通过JDBC或ORM框架对接这是一条从Java SE到Web开发都能覆盖的练习路径也是面试里问“你做过什么项目”时最容易被追问细节的项目。它能解决的问题很具体学生的增删改查、按条件筛选、成绩录入与统计、登录鉴权。说白了就是一个带数据库交互的CRUD系统但在CRUD之外你还要应付连接管理、SQL注入、中文乱码、事务一致性这些真实生产环境才会露头的毛病。这篇文章的目标不是给你贴一大段可抄的完整代码而是把从建库到跑通、再到排错和优化的一整条路线拆开讲每一步为什么这样做、参数怎么调、翻车点在哪。适合正在做课设的学生也适合刚入门想搞懂“Java怎么碰MySQL”的开发者——我尽量用做过类似方案的口吻把黑匣子打开给你看。2. 技术选型与库表设计先决定主键和字符集再写第一行Java代码2.1 JDBC还是MyBatis从项目规模倒推选择而不是追热点做学生信息管理系统最常见的路线有两条纯JDBC和MyBatis。JDBC是Java访问数据库的原生接口所有ORM框架底层都是它。MyBatis则是半自动的持久层框架SQL你自己写映射帮你做。对于这个标题下的系统如果目标是练底层原理、应付“手写JDBC”的考试或面试那就用原生JDBC如果目标是一个带界面的Web项目且后续要加Spring Boot那我一般建议直接上MyBatis但本文核心讲JDBC方式因为MyBatis的坑有一大半来自JDBC的坑。选型的另一个维度是并发量。学生信息管理系统通常是几十人同时使用的内部系统连接数最多几十个JDBC连池足够。如果用MyBatis也离不开连接池。所以先别急着引入Spring Boot全家桶把JDBC这套跑通后面加任何框架都只是换壳。库表设计是决定后面少改代码的关键。学生信息管理系统最少需要三张表学生表、班级表、成绩表。学生表存基本信息班级表独立出来是为了避免“班级名称”字段在数百行学生记录里重复存储成绩表则关联学生ID。如果还要做登录再建一张用户表或直接给学生表加账号字段。下面给出建表SQL按MySQL 8.0语法写。CREATE DATABASE student_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_db; CREATE TABLE tb_class ( class_id INT AUTO_INCREMENT PRIMARY KEY, class_name VARCHAR(50) NOT NULL UNIQUE ) ENGINEInnoDB; CREATE TABLE tb_student ( student_id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender ENUM(M,F) DEFAULT M, age TINYINT UNSIGNED, class_id INT, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES tb_class(class_id) ) ENGINEInnoDB; CREATE TABLE tb_score ( score_id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_name VARCHAR(50) NOT NULL, score DECIMAL(5,2) CHECK (score BETWEEN 0 AND 100), exam_time DATE, CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES tb_student(student_id) ) ENGINEInnoDB;这段SQL里有几个参数值得单独说明。DEFAULT CHARACTER SET utf8mb4不是可有可无的装饰MySQL 5.7 之前很多系统默认用utf8它只能存基本多语言字符存不了emoji和某些生僻字utf8mb4是真正的四字节UTF-8。如果建库时漏掉这个后面Java写入中文时一旦字段长度按字符数计算可能直接报“Data truncation”或者出现乱码。COLLATE utf8mb4_unicode_ci是排序规则ci表示大小写不敏感查询where name Zhang能匹配到zhang这符合学生姓名检索的直觉。主键统一用INT AUTO_INCREMENT。如果以前用过UUID做主键在这个系统里要慎重UUID作为字符串主键占用空间大且随机无序导致B树索引页分裂频繁写入性能明显下降。学号student_no虽然业务上唯一但可能包含格式变更风险比如“2023级”改成“2024级”时学号规则调整所以不把它当主键而是用额外的唯一索引约束。ENGINEInnoDB是MySQL 8.0默认存储引擎支持事务和外键MyISAM在崩溃恢复上的脆弱性在课程设计这种多轮增删改场景下容易丢数据。外键设计上tb_score引用tb_student这样删除学生时如果成绩表里有引用MySQL会拒绝删除或级联删除由你决定。我一般用ON DELETE RESTRICT默认防止误删学生留下孤儿成绩记录。别在Java里手工维护“先删成绩再删学生”的逻辑数据库外键是最后一道保险。2.2 性别字段用ENUM还是TINYINT一个影响取值的判断性别这个字段很多教材用CHAR(2)存‘男’/‘女’也有用BIT存0/1。两种都有代价。ENUM(M,F)在MySQL内部是整数枚举读取出来是字符串Java里用String接就行避免你在代码里写if (gender 0)这种魔法数字。但ENUM有个坑如果你后续要加“未知”这个值必须ALTER TABLE修改枚举列表否则插入失败。TINYINT更灵活但Java端就要维护“0女1男”的映射表一旦有人写错就产生脏数据。我的建议是如果系统只有一个入口Java代码写死用ENUM足够直观且不易出错如果未来可能有数据迁移或报表工具直接读库用TINYINT加注释更稳妥。本文前面SQL用了ENUM你在自己的项目里可以按这个权衡调整。重点是把约束放在数据库层面而不是依赖应用层判断。2.3 连接参数里必须手写的三个配置任何时代写Java连MySQLjava.sql.DriverManager或连接池都需要一个JDBC URL。常见写法是String url jdbc:mysql://localhost:3306/student_db ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; String user root; String password your_password;useUnicodetruecharacterEncodingutf8是让驱动以UTF-8字节流与MySQL通信。注意这里characterEncodingutf8是给驱动用的字符集名不是数据库的字符集MySQL驱动能识别utf8和utf8mb4写utf8不影响与utf8mb4库通信。useSSLfalse是因为本地开发环境MySQL默认没配SSL证书驱动8.x默认尝试SSL连接如果不关掉会报SSL connection error或者握手警告生产环境如果真有网络加密需求再改成useSSLtrue并配证书。serverTimezoneAsia/Shanghai是MySQL 8.0驱动的强制要求项。MySQL服务器端time_zone如果设置成SYSTEM而运行Java的机器时区不是UTC驱动读取日期时间时会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized乱码一样的时区名。直接指定Asia/Shanghai最省事。allowPublicKeyRetrievaltrue是MySQL 8.0使用caching_sha2_password插件时客户端首次连接需要从服务器获取公钥如果不允许会报Public Key Retrieval is not allowed。本地开发直接放开生产环境建议改用mysql_native_password或配置SSL后再去掉这个参数。3. 用JDBC手写一套CRUD从Connection到PreparedStatement的完整路径3.1 写一个连接工具类把连接参数收敛到一个方法里有了库表结构开始写Java。最忌讳的做法是在每个业务方法里重复写Class.forName(com.mysql.cj.jdbc.Driver)和DriverManager.getConnection(...)因为一旦连接参数要改你得全局替换。我一般先写一个DBUtil用静态代码块加载驱动把连接参数放在常量里。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/student_db ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败检查依赖); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }Class.forName在MySQL 8.x驱动里其实可以省略因为JDBC 4.0以后驱动通过META-INF/services自动注册。但保留它有一个好处如果依赖没导对能明确抛出ClassNotFoundException而不是在getConnection时才报“No suitable driver found”。注意驱动类名MySQL 5.x 是com.mysql.jdbc.Driver8.x 改成com.mysql.cj.jdbc.Driver写错一个字母就翻车。这个工具类的另一个重点是静态代码块抛ExceptionInInitializerError。因为ClassNotFoundException是受检异常静态块里不能直接抛给调用方包一层运行时异常是常见做法。调用方只需要DBUtil.getConnection()然后在finally里关连接。3.2 新增学生用PreparedStatement而不是Statement三个原因插入一条学生记录是最常见的操作。新手容易写成Statement拼接SQL比如Statement stmt conn.createStatement(); stmt.executeUpdate(INSERT INTO tb_student(student_no, name, gender, age, class_id) VALUES( no , name , gender , age , classId ));这段代码在课设里能跑但存在SQL注入、引号转义、类型拼接错误三个隐患。如果name是OBrien这样的值SQL直接语法错误如果name是 OR 11后果不必多说。正确做法是用PreparedStatementpublic int addStudent(Student s) { String sql INSERT INTO tb_student(student_no, name, gender, age, class_id, phone) VALUES(?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getStudentNo()); ps.setString(2, s.getName()); ps.setString(3, s.getGender()); ps.setInt(4, s.getAge()); ps.setInt(5, s.getClassId()); ps.setString(6, s.getPhone()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }try-with-resources语法Java 7会自动关闭Connection和PreparedStatement不需要手写finally。注意关闭顺序驱动文档要求先关ResultSet、再关Statement、最后关Connectiontry-with-resources按声明逆序关闭正好满足。如果把Connection放在最前面声明PreparedStatement在后面关闭时会先关ps再关conn没问题反过来如果把Connection放最后就会先关连接再关语句可能抛异常。参数索引从1开始不是0写错下标会报Parameter index out of range。setString和setInt的调用顺序不必和SQL里的问号数字完全一致只要?的位置能对上代码顺序可以灵活但为了可读性保持一致。3.3 查询学生列表ResultSet的 next() 和 getXxx() 边界查询是比插入更容易出错的环节。很多初学者用while (rs.next())读取数据但不是所有场景都应该用while。如果查询结果是按学号唯一取一条应该用if (rs.next())否则误用while时会多读一个空行。更关键的是getXxx的列索引或列名。public ListStudent findStudentsByClass(int classId) { String sql SELECT student_id, student_no, name, gender, age, phone FROM tb_student WHERE class_id ? ORDER BY student_id; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, classId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setStudentId(rs.getInt(student_id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getInt(age)); s.setPhone(rs.getString(phone)); list.add(s); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码里ResultSet也放进try-with-resources避免忘记关闭。getString(gender)能直接返回ENUM的字符串值这是MySQL驱动帮我们做的映射不用getObject再转型。另外一个潜在坑age字段在数据库是TINYINT UNSIGNED范围0~255Java端用getInt接没有问题但如果用getByte接超过127会溢出这在自己的库里不容易触发一旦有留学生或教职工录入就可能变成负数。所以能用int就用int别为了省内存用byte。还有一个容易忽略的点查询语句里的ORDER BY student_id。如果不写排序MySQL返回结果的顺序不保证稳定同样数据在不同时间查出来可能不同分页时就会出现“上一页最后一条跑到下一页”的怪象。加一个显式排序是分页功能能稳定的前提。3.4 更新与删除executeUpdate的返回值就是校验依据更新和删除的写法与插入类似但返回值int代表受影响行数。这是判断操作是否成功的官方依据而不要用“有没有抛异常”来判断。public boolean updateStudentPhone(int studentId, String newPhone) { String sql UPDATE tb_student SET phone ? WHERE student_id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, newPhone); ps.setInt(2, studentId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public boolean deleteStudent(int studentId) { String sql DELETE FROM tb_student WHERE student_id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }删除学生时如果成绩表里有外键引用DELETE会抛Cannot delete or update a parent row: a foreign key constraint fails。这是数据库保护你的数据不是程序bug。处理方式有两种业务上确认该学生没有成绩记录再删或者把删除改成逻辑删除——加一个is_deleted TINYINT字段查询时过滤is_deleted 0。课程设计阶段用物理删除问题不大但如果在真实系统里我强烈建议用逻辑删除因为学生可能选过课、交过费、有奖惩记录物理删除会让历史数据全部断裂。4. 常见问题排查驱动、时区、中文乱码与连接泄漏的5个踩坑记录4.1 现象java.sql.SQLException: No suitable driver found原因驱动依赖没进运行环境新手最常见的问题代码写了Class.forName(com.mysql.cj.jdbc.Driver)却没报错但一跑到getConnection就报 “No suitable driver found”。排查顺序是先看Maven/Gradle依赖里是否真有mysql-connector-j注意8.x的artifact名不再是mysql-connector-java而是com.mysql:mysql-connector-j再看运行配置的classpath是否包含jar包。用IDEA跑时Maven依赖是自动引入但如果你用java -jar打包没有把依赖打进Fat Jar就会缺。解决不要自己下载jar包放进lib目录直接用构建工具管理。如果是Maven加上dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.4.0/version /dependency版本号以你实际用的为准但注意MySQL Connector/J 8.x对应MySQL 5.7和8.x都能连。连MySQL 5.7时URL里也要加serverTimezone一样的原因驱动时区解析逻辑是统一的。4.2 现象插入的中文变成问号原因客户端连接字符集和服务器不一致明明数据库建表用了utf8mb4Java字符串也是中文存进去却变成???。这是连接层面的字符集没对齐。在MySQL命令行里查SHOW VARIABLES LIKE character_set_client如果看到latin1说明服务器默认客户端字符集是拉丁而你的JDBC URL里characterEncodingutf8只在连接建立时设了客户端字符集但可能被后端参数覆盖。解决在my.cnf或my.ini的[mysqld]段下加配置character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci同时保证JDBC URL里characterEncodingutf8utf8mb4也接受。改完重启MySQL再确认character_set_client变成utf8mb4。另外如果SQL文件本身不是UTF-8编码用命令行导入时加--default-character-setutf8mb4否则表结构和数据在源头就乱。4.3 现象Public Key Retrieval is not allowed原因MySQL 8.0默认认证插件机制我们已经在前文URL里加了allowPublicKeyRetrievaltrue。如果去掉这个参数MySQL 8.0的用户以caching_sha2_password方式认证客户端第一次连时没有缓存公钥又没启用SSL服务端不能明文传公钥就抛这个错。解决本地开发直接加allowPublicKeyRetrievaltrue生产环境优先配置SSL证书或者把用户的认证插件改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;但注意mysql_native_password在MySQL 8.4里已被标记为废弃未来版本可能移除所以治本方案是配SSL而不是换插件。课设阶段不用纠结本地加参数就好。4.4 现象程序跑一段时间后变慢或报Too many connections原因连接泄漏这个坑我在接手一个老课设时踩过。学生管理系统打开页面越来越慢最后报Data source rejected establishment of connection, message from server: Too many connections。一看代码Connection是在方法里getConnection()后没有关闭或者只有ps.close()忘了关conn。MySQL默认最大连接数151每个页面打开一次连不上就积累。解决把所有JDBC操作都改成try-with-resources或用finally关连接。这是一个习惯问题不是技术难点。另外如果是Web应用别再每次请求都新建连接引入连接池比如HikariCP它能把连接复用起来并配置最大连接数上限dataSource.maximumPoolSize20 dataSource.connectionTimeout300004.5 现象查询结果时好时坏报The database returned no natively generated identity value原因主键生成策略与数据库自增不匹配如果用了JPA/MyBatis Plus这类框架并在Java实体类里配了GeneratedValue(strategy GenerationType.IDENTITY)但实际表的主键不是AUTO_INCREMENT插入后想获取自增主键就会报这个错。在纯JDBC里我们如果想拿到自增主键需要额外指定。解决在prepareStatement时传入Statement.RETURN_GENERATED_KEYSString sql INSERT INTO tb_student(...) VALUES(...); try (PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, ...); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { int newId rs.getInt(1); } } }这个写法适合“插入学生后立即使用他的ID去插成绩”的场景。如果不传这个参数getGeneratedKeys()返回空结果集ID就拿不到。5. 登录校验与分页查询从控制台到Web层都绕不开的四个实现细节5.1 登录会话管理用HttpSession还是自己写Token先看你有没有Web容器如果这个学生信息管理系统是带Servlet的Web项目登录状态用HttpSession是最常见的做法。用户登录成功后把学生ID放进Session后续请求通过过滤器检查session是否存在。如果是不带Servlet的纯控制台程序则要自己用MapString, UserSession管理。我先说Web方式。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 这里应该调用UserDAO校验而不是在Servlet里写SQL User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(index.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }密码校验有个绝对底线不要把明文密码直接比对。至少用MD5加盐或BCrypt。MD5本身已被破解碰撞但课设里用MessageDigest做个加盐MD5比明文强很多。生产级建议用BCryptPasswordEncoderSpring Security里的类。另外一个容易忽略的问题是Session超时配置。session-config session-timeout30/session-timeout /session-config这里单位是分钟30分钟内用户无操作Session失效。太短影响体验太长增加服务端内存压力按内部系统的开放时间调。5.2 分页查询LIMIT ? OFFSET ? 的边界计算与总页数学生列表几十条是小事但一旦上千条全量查询不仅慢页面显示也难。分页查询的核心参数是page和pageSizeSQL用LIMIT加OFFSET。public ListStudent findStudentsByPage(int page, int pageSize) { int offset (page - 1) * pageSize; String sql SELECT student_id, student_no, name, gender, age, phone FROM tb_student ORDER BY student_id LIMIT ? OFFSET ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, pageSize); ps.setInt(2, offset); try (ResultSet rs ps.executeQuery()) { // 封装成List } } }注意LIMIT ? OFFSET ?在MySQL支持用占位符但如果数据库是SQL Server语法不同那种情况得用OFFSET ... ROWS FETCH NEXT ... ROWS ONLY。这里锁定MySQL所以写法如上。分页查询要同时返回总条数才能计算总页数。常见做法是另外查一次SELECT COUNT(*)。如果数据量超过几万条COUNT也会慢可以先走缓存但学生系统没这个必要。int total countStudents(); // SELECT COUNT(*) FROM tb_student int totalPages (total pageSize - 1) / pageSize;(total pageSize - 1) / pageSize是向上取整的经典写法。例如total10pageSize3(102)/34正好3页。不要用Math.ceil(total / pageSize)因为整数除法会先取整再ceil产生错误结果。同时要注意page越界的问题。用户手输page999查询结果为空页面应跳转到最后一页。后端要把page限制在[1, totalPages]范围内if (page 1) page 1; if (page totalPages) page totalPages;这里最容易出的是totalPages0的情况没有学生时totalPages为0page 0永远成立所以至少要保证if (totalPages 0) totalPages 1;或者前端不显示分页条。5.3 按姓名模糊搜索LIKE的占位符拼接是注入重灾区搜索学生的功能SQL大致是SELECT * FROM tb_student WHERE name LIKE ? ORDER BY student_idJava里设置参数时需要拼接通配符ps.setString(1, % keyword %);很多教程会把通配符直接写进SQLString sql SELECT * FROM tb_student WHERE name LIKE % keyword %;这种写法是SQL注入训练的典型教材。PreparedStatement的占位符只保护参数值本身LIKE的%符号在参数值里是安全的因为%和_在MySQL LIKE中是通配符但不会被当成SQL语法解析。keyword里如果包含%那么% keyword %会把用户输入的%也当作通配符导致查出意外数据。更稳妥的做法是转义String escaped keyword.replace(\\, \\\\).replace(%, \\%).replace(_, \\_); String pattern % escaped %;这个细节课设里不要求但如果你的系统面向真实用户搜索框里输入%会让人困惑——搜“李%”把“李小明”搜出来。加上转义行为才符合预期。另外模糊搜索字段如果是中文姓名数据库排序规则是utf8mb4_unicode_ci它不区分大小写但中文是按拼音排序吗不一定MySQL默认的utf8mb4排序并不完全按拼音可能按Unicode编码。如果业务上要求按拼音排序就得用CONVERT(name USING gbk)或单独的拼音列。学生信息管理系统一般不要求但你要知道这个边界。6. 收尾优化连接池参数、预编译批量写和一行时间统计的实战习惯系统功能跑通只是开始真正能让它在课设答辩或实习评审里加分的往往是几个不起眼的工程习惯。第一个习惯是别再用DriverManager.getConnection换成HikariCP连接池。HikariCP是目前Java生态里性能最优的连接池之一Spring Boot 2.x默认就是它。即使不引入Spring单独用也行。核心配置就几个参数spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.idle-timeout600000maximum-pool-size不是越大越好。每个连接都会占用MySQL服务端线程和内存20个连接配50人的内部系统绰绰有余。如果设成200反而可能因MySQLmax_connections限制而连接失败。connection-timeout是客户端等待连接的秒数30秒内如果池中没有空闲连接且创建不了新连接就抛超时不要设成几小时那会让故障发现变得延迟。第二个习惯是批量插入。如果管理员导入一个包含500名新生的Excel用单条INSERT循环500次每次都提交一次事务那会慢到让人怀疑人生。改成批量提交String sql INSERT INTO tb_student(student_no, name, gender, age, class_id) VALUES(?, ?, ?, ?, ?); Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); PreparedStatement ps conn.prepareStatement(sql); for (Student s : studentList) { ps.setString(1, s.getStudentNo()); ps.setString(2, s.getName()); ps.setString(3, s.getGender()); ps.setInt(4, s.getAge()); ps.setInt(5, s.getClassId()); ps.addBatch(); } ps.executeBatch(); conn.commit();addBatch把SQL参数累积在客户端executeBatch一次发给服务端执行。这里有两个参数学问rewriteBatchedStatementstrue要加在JDBC URL里否则MySQL驱动默认一条条发送性能提升有限setAutoCommit(false)保证所有插入要么全部成功要么回滚避免导入一半时某条数据违反唯一约束导致前面数据已提交的惨状。事务开启后记得在catch里conn.rollback()否则连接池里的连接带着未提交事务返回池中下一次复用可能出现脏读或锁等待。第三个习惯是给每个DAO方法加上耗时统计在答辩时展示性能数据比喊“优化过”有力得多。别用System.currentTimeMillis()手动打点用StopWatchSpring的或Apache Commons的或者最土的方式long start System.currentTimeMillis(); // 执行查询 long cost System.currentTimeMillis() - start; System.out.println(findStudentsByPage cost cost ms);这种输出在日志里是噪音但课设阶段足够定位哪条SQL慢。更专业的是用MySQL慢查询日志定位slow_query_logON long_query_time1设置后超过1秒的SQL会记录到日志里回头看哪个表缺索引。学生系统通常卡在模糊搜索和分页排序试试给name建普通索引ALTER TABLE tb_student ADD INDEX idx_name(name);如果搜索WHERE name LIKE 张%前缀匹配索引生效如果LIKE %张%中间匹配索引基本失效全表扫描是常态。这是MySQL B树索引的边界不是优化能解决的。实践中最怕的不是功能缺失而是你拿着一个不可复现的“玄学bug”到处问人。上面这些习惯——统一连接入口、强制关闭资源、显式排序、限制分页边界、批量提交——能帮你把90%的运行时异常消灭在代码评审阶段。我当年做这个系统时就因为忘了关Connection在演示前一晚被Too many connections折腾到凌晨。后来把每个方法都检查了一遍try-with-resources从此再没遇过这种故障。希望帮到你——照这条路径走踩过的坑我替你填了。本文还有配套的精品资源点击获取
返回列表