ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Java课程设计实战:学生成绩管理系统从数据库建模到报表输出

Java课程设计实战:学生成绩管理系统从数据库建模到报表输出 简介Java学生成绩管理系统课程设计文档面向高校计算机、软件工程等专业学生适用于课程设计、软件实践或项目报告写作参考。其核心是一个基于Java与SQL Server的学生成绩管理系统方案采用C/S架构实现了学生信息管理、成绩增删改查、按学号或姓名检索、课程分数段统计、报表输出以及用户口令与权限管理等完整功能。压缩包内含1个doc文档约205KB内容包含课程设计任务书、进度安排、系统设计要求、指导教师评语、答辩记录、成绩评定表以及论文摘要与正文结构清晰、格式规范可作为课程设计报告的模板和设计思路的参考。目前已有786人学习下载尤其适合需要快速掌握成绩管理系统模块划分或撰写课程设计文档的学生。1. 从课程设计到能答辩的成绩管理系统差的就是这几步学生成绩管理系统几乎是 Java 课程设计里出现频率最高的题目之一但大多数提交上来的版本都停留在“能增删改查”的层面登录写死账号、查询只能按学号、分数段统计放在 Java 里用 if 硬切、报表直接截图粘贴到 Word 里。真正让这份设计能拿高分、也能在答辩时讲清楚的点在于你的系统是否具备三个层次数据模型的完整性、权限控制的有效性、以及报表输出的可操作性。这份以 2011 年西安文理学院软件课程设计为背景的题目要求基于 C/S 模式实现学生成绩记录查询系统支持学生信息维护、成绩维护、按学号或姓名查询、分数段统计、报表输出和用户权限管理。虽然原始报告里写的是 VB 6.0但代码示例是 Java——这说明用 Java Swing SQL Server 完全能覆盖同样的需求而且对计算机系学生来说更有说服力。本文按“数据库建模 → 登录权限 → 查询与统计 → 报表输出 → 调试排错”的顺序拆解一套可直接复现的实现方案末尾附上答辩时最容易被追问的几个细节。2. 数据库建模与 C/S 架构下的连接设计2.1 三张表足够支撑全部功能别过度设计原始需求里明确提到学生信息包含学号、姓名、年龄、性别、系别课程信息包含课程编号、课程名称、先行课成绩表包含学号、课程号、成绩。很多初学者会把“班级信息维护”也做成一张表但这在数据库设计上容易引入不必要的关联复杂度——班级本质上可以通过系别字段来区分或者在建表时加一个 class_name 字段即可。这里推荐的最小建模方案是三张表-- 学生表 CREATE TABLE student ( stno CHAR(10) PRIMARY KEY, sname NVARCHAR(20) NOT NULL, sage INT, ssex NCHAR(1), sdept NVARCHAR(30), class_name NVARCHAR(20) ); -- 课程表 CREATE TABLE course ( cno CHAR(6) PRIMARY KEY, cname NVARCHAR(30) NOT NULL, cpno CHAR(6) ); -- 成绩表联合主键避免重复录入 CREATE TABLE sc ( stno CHAR(10) NOT NULL, cno CHAR(6) NOT NULL, grade DECIMAL(5,1), PRIMARY KEY (stno, cno), FOREIGN KEY (stno) REFERENCES student(stno), FOREIGN KEY (cno) REFERENCES course(cno) );表结构里有两个容易被忽略但答辩时经常被问到的设计点。一是 sc 表为什么要用 (stno, cno) 联合主键——这是为了防止同一学生同一课程出现两条成绩记录如果你只建普通表不加主键成绩维护模块里“新增”按钮就会把重复数据写进去而联合主键会在数据库层面直接拦截。二是 grade 字段用 DECIMAL(5,1) 而不是 INT因为成绩可能出现 89.5 这种带小数的分数如果用 INT 存储会导致四舍五入后的数据与原始卷面分不一致后续做分数段统计时也会产生偏差。C/S 模式下客户端通过 JDBC 直连 SQL Server 的 1433 端口。原始报告要求的是 SQL Server 2000但 SQL Server 2000 的 JDBC 驱动已经很难在现代 JDK 上运行建议直接使用 SQL Server 2019 或 2022 的 mssql-jdbc 驱动连接串基本兼容。连接封装类可以写成这样public class DBUtil { private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; private static final String URL jdbc:sqlserver://localhost:1433; DatabaseNamestudent_scores;encryptfalse;trustServerCertificatetrue; private static final String USER sa; private static final String PASSWORD your_password; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这里的 URL 参数里有两个关键项encryptfalse和trustServerCertificatetrue。默认情况下新版 SQL Server JDBC 驱动会强制启用 TLS 加密如果数据库端没有配置证书连接会直接报 SSL 错误这两个参数用于开发环境快速绕过加密验证。生产环境或正式答辩演示时如果你不想改数据库配置保留这两个参数是合理做法——但答辩时老师可能会问“你的连接安全性如何保证”你要能答出这只是开发环境的简配正式部署时应该去掉encryptfalse并配置证书。2.2 用户表与密码存储权限管理的第一步原始需求里第七条要求“修改当前用户的密码及用户管理”这意味着系统除了学生、教师、管理员三类角色外还需要一张用户表来关联登录凭证。最简单的建模是在 student 表基础上扩展但更通用的做法是独立一张 users 表CREATE TABLE users ( uid VARCHAR(20) PRIMARY KEY, upwd VARCHAR(64) NOT NULL, role VARCHAR(10) NOT NULL CHECK (role IN (ADMIN, TEACHER, STUDENT)), bind_no CHAR(10) );bind_no 字段用于关联业务数据——学生角色的 bind_no 指向 student.stno教师角色的 bind_no 可以指向教师工号。这样一来登录模块只查 users 表拿到角色和绑定编号后再根据需要去 student 表或 sc 表读取具体数据。密码存储方面原始代码里是明文比较这在 2025 年显然说不过去至少要用 SHA-256 做哈希public static String sha256(String raw) { try { MessageDigest md MessageDigest.getInstance(SHA-256); byte[] hash md.digest(raw.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : hash) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }使用 SHA-256 哈希时注册用户时存储的是哈希值而非明文登录校验时对用户输入做同样的哈希运算再比较。这里需要向答辩老师强调一个安全认知哈希不是加密它是不可逆的摘要算法配合盐值salt可以防止彩虹表攻击。课程设计层面能做到 SHA-256 每个用户随机盐值已经足够体现你对安全性的理解但不要在演示时说得过于复杂——简单讲“密码不以明文落库即使数据库泄露也无法直接得到密码”就够了。3. Swing 界面分层与登录校验的完整实现3.1 登录界面从硬编码账号到按角色分发界面原始代码里的登录逻辑是将用户名和密码硬编码在 ActionListener 里if (f1.getText().equals(1410))这种写法在课程设计中很常见但带来的问题是无法动态添加用户、权限与业务数据没有关联、安全审计无从谈起。正确的做法是登录时查询 users 表校验通过后按 role 字段分发到不同的主界面。public class LoginFrame extends JFrame implements ActionListener { private JTextField tfUser; private JPasswordField tfPwd; private JButton btnLogin, btnReset, btnExit; public LoginFrame() { super(学生成绩管理系统 - 登录); setLayout(new GridLayout(3, 1)); JPanel p1 new JPanel(new FlowLayout()); p1.add(new JLabel(用户名)); tfUser new JTextField(12); p1.add(tfUser); JPanel p2 new JPanel(new FlowLayout()); p2.add(new JLabel(密 码)); tfPwd new JPasswordField(12); p2.add(tfPwd); JPanel p3 new JPanel(new FlowLayout()); btnLogin new JButton(登录); btnReset new JButton(重置); btnExit new JButton(退出); p3.add(btnLogin); p3.add(btnReset); p3.add(btnExit); add(p1); add(p2); add(p3); btnLogin.addActionListener(this); btnReset.addActionListener(this); btnExit.addActionListener(this); setSize(320, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } Override public void actionPerformed(ActionEvent e) { if (e.getSource() btnLogin) { doLogin(); } else if (e.getSource() btnReset) { tfUser.setText(); tfPwd.setText(); } else { System.exit(0); } } private void doLogin() { String uid tfUser.getText().trim(); String pwd new String(tfPwd.getPassword()); String role authenticate(uid, pwd); if (role null) { JOptionPane.showMessageDialog(this, 用户名或密码错误, 登录失败, JOptionPane.ERROR_MESSAGE); return; } // 按角色分发主界面 if (ADMIN.equals(role)) { new AdminMainFrame(uid).setVisible(true); } else if (TEACHER.equals(role)) { new TeacherMainFrame(uid).setVisible(true); } else { new StudentMainFrame(uid).setVisible(true); } dispose(); } private String authenticate(String uid, String pwd) { String sql SELECT role FROM users WHERE uid ? AND upwd ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, uid); ps.setString(2, sha256(pwd)); ResultSet rs ps.executeQuery(); if (rs.next()) { return rs.getString(role); } } catch (SQLException ex) { ex.printStackTrace(); } return null; } }代码里有几个细节值得在报告里展开密码框用JPasswordField而不是TextField这样输入时不会明文显示SQL 语句使用PreparedStatement的?占位符替代原始代码里的字符串拼接这是防止 SQL 注入的基本手段——如果答辩老师问“为什么不用 Statement”这就是标准答案。登录成功后调用dispose()关闭登录窗口同时将 uid 传入主界面后续操作比如修改密码都需要这个 uid 来定位用户。实际运行时你会发现按角色分发后各界面复用了大量代码——学生信息查询、成绩查询在三个角色下都可见。这涉及到一个设计取舍是把所有功能塞进一个 JFrame 里按角色隐藏按钮还是拆成三个独立 JFrame我的经验是拆开。虽然会产生类数量增多的代价但每个角色的界面逻辑清晰独立修改教师端功能不会影响学生端答辩时按角色介绍功能也更顺畅。3.2 学生信息维护模块增删改查的标准范式学生信息维护是整个系统的核心业务模块。原始需求里要求教师和管理员可以新增、删除、修改学生信息。Swing 中常用的模式是上方放查询条件中间放 JTable 展示数据底部放操作按钮。JTable 本身不直接绑定数据库需要借助 TableModel 来装载 ResultSet 数据。public class StudentPanel extends JPanel { private JTable table; private DefaultTableModel model; private JTextField tfStno, tfName; public StudentPanel() { setLayout(new BorderLayout()); JPanel queryPanel new JPanel(new FlowLayout()); queryPanel.add(new JLabel(学号)); tfStno new JTextField(10); queryPanel.add(tfStno); queryPanel.add(new JLabel(姓名)); tfName new JTextField(10); queryPanel.add(tfName); JButton btnQuery new JButton(查询); JButton btnAdd new JButton(新增); JButton btnUpdate new JButton(修改); JButton btnDelete new JButton(删除); queryPanel.add(btnQuery); queryPanel.add(btnAdd); queryPanel.add(btnUpdate); queryPanel.add(btnDelete); add(queryPanel, BorderLayout.NORTH); model new DefaultTableModel(new String[]{学号, 姓名, 年龄, 性别, 系别, 班级}, 0); table new JTable(model); add(new JScrollPane(table), BorderLayout.CENTER); } }这块界面代码并不复杂但真正的坑在于 JTable 刷新数据之后之前选中的数据行索引会失效——如果你选中第 3 行后执行了查询操作再点删除就不知道删的是谁了。解决办法是在执行增删改之前先从 table 的选中行读取业务主键不要在操作结束后再去取索引。查询方法的 SQL 要支持模糊匹配private void doQuery() { StringBuilder sql new StringBuilder(SELECT stno, sname, sage, ssex, sdept, class_name FROM student WHERE 11); ListObject params new ArrayList(); if (!tfStno.getText().trim().isEmpty()) { sql.append( AND stno LIKE ?); params.add(% tfStno.getText().trim() %); } if (!tfName.getText().trim().isEmpty()) { sql.append( AND sname LIKE ?); params.add(% tfName.getText().trim() %); } sql.append( ORDER BY stno); // 执行查询并刷新 model }这里WHERE 11是动态拼接 SQL 时常用的技巧它让后续每个条件的拼接不需要判断是不是第一个条件代码可读性和可维护性都更好。模糊查询用LIKE ?配合%关键字%这样学号输入14能搜出所有以 14 开头的学号姓名输入张能搜出所有姓张的同学——这比强制精确匹配的体验好得多也是需求里“按学号或姓名查询”的最自然实现方式。原始需求里教师可以添加成绩但不能修改已录入成绩成绩修改权限归管理员所以在界面设计上要体现差异化教师端只显示“添加成绩”按钮“修改成绩”按钮对教师不可见或置灰。这种权限在 UI 层做一次隐藏、在 Service 层做一次校验两层防御——单纯靠 UI 隐藏是不安全的因为客户端代码可以被反编译。4. 成绩查询与分数段统计SQL 写法比 Java 计算更优雅4.1 按学号或姓名查成绩JOIN 两张表后的模糊检索成绩查询是本系统使用频率最高的功能。学生登录系统后输入学号立刻能看到自己所有课程的成绩及平均分教师输入学号或姓名能看到对应学生的全部成绩记录。核心 SQL 是 student 表和 sc 表的 INNER JOINprivate void queryScore(String keyword) { String sql SELECT s.stno, s.sname, c.cname, sc.grade FROM sc JOIN student s ON sc.stno s.stno JOIN course c ON sc.cno c.cno WHERE s.stno LIKE ? OR s.sname LIKE ? ORDER BY s.stno, c.cno; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ResultSet rs ps.executeQuery(); // 填充到 JTable } catch (SQLException ex) { ex.printStackTrace(); } }JOIN 是理解这套系统的关键操作。sc 表里只有 stno、cno、grade 三个字段不直接存姓名和课程名这样设计的原因叫“减少数据冗余”——如果每个成绩记录里都冗余一份学生姓名那学生改名时sc 表里所有该学生的成绩记录都要同步更新而通过 JOIN 关联只需要修改 student 表一处即可。这个设计属于第三范式3NF的基本要求也是软件工程课程里“低耦合”思想在数据层面的体现可以写进报告的数据模型设计部分。有时候查询条件比较多比如“计算机系、成绩大于 80 分”这时候就要在 WHERE 里加多个条件。注意一个细节LIKE 模糊查询在姓名字段上可能命中多个人比如输入“王”会返回所有姓王的学生的所有课程成绩列表可能是几十行。答辩时如果被问到“如何优化这种场景”你可以回答在 UI 层先让用户选择查询类型按学号精确 / 按姓名模糊或者限定只能查询本人成绩学生端自动带出自己的学号。4.2 分数段统计CASE WHEN 一次搞定不要在 Java 里 if需求第四条“显示某课程 60 分以下、60~79、80~89、90 分以上各分数段的学生信息”是很多课程设计翻车的地方。一个常见的错误做法是把所有成绩查出来后在 Java 里用 if-else 把成绩一个个扔进四个数组或 List。这样做的缺陷是如果成绩表有几万条记录客户端内存里就得放几万条数据统计逻辑与业务代码耦合后续增加分数段比如 95 分以上就要改 Java 代码。正确的做法是在 SQL 层面完成分段聚合SELECT SUM(CASE WHEN grade 60 THEN 1 ELSE 0 END) AS fail_cnt, SUM(CASE WHEN grade 60 AND grade 80 THEN 1 ELSE 0 END) AS pass_cnt, SUM(CASE WHEN grade 80 AND grade 90 THEN 1 ELSE 0 END) AS good_cnt, SUM(CASE WHEN grade 90 THEN 1 ELSE 0 END) AS excellent_cnt FROM sc WHERE cno ?;查询结果只有一行四列既拿到各段人数也能用同样的 CASE WHEN 逻辑查出每个分数段下具体的学生明细SELECT s.stno, s.sname, sc.grade FROM sc JOIN student s ON sc.stno s.stno WHERE sc.cno ? AND sc.grade 60 AND sc.grade 80 ORDER BY sc.grade DESC;这里分段边界要特别注意grade 60 AND grade 80覆盖 60 到 79.9 分由于 grade 是 DECIMAL(5,1)79.5 会落在这个区间如果用grade 79那 79.5 就会掉到下一个区间统计就会失真。4 个分数段的边界条件是 60、 60 AND 80、 80 AND 90、 90前开后闭地连续覆盖任何分数都能且只能落在一个区间内。CASE WHEN 是 SQL 标准的条件表达式不是某个数据库的专有语法。它在统计场景下比 Java 计算更优的原因有三点数据库引擎在 C/S 模式下是集中式计算客户端只接收统计结果网络传输量小SQL 是声明式语言改分数段阈值只改 SQL 文本不需要重编译 Java 代码数据库可以配合索引对 WHERE 条件做下推优化。答辩时能讲清这三点分数段统计模块就能成为你的加分项。4.3 平均分与排序一条 SQL 解决“还需要算”的问题平均分的计算同样应该下推到 SQL。需求里“计算平均分”在功能模块图中有明确提及用AVG聚合函数按学号分组就能实现SELECT s.stno, s.sname, AVG(sc.grade) AS avg_grade, COUNT(sc.cno) AS course_cnt FROM sc JOIN student s ON sc.stno s.stno GROUP BY s.stno, s.sname HAVING COUNT(sc.cno) 1 ORDER BY avg_grade DESC;GROUP BY后面必须带上 s.stno 和 s.sname因为这两列都在 SELECT 列表中且不是聚合函数。如果漏掉 s.sname 只按 stno 分组SQL Server 在严格模式下会直接报错。HAVING与WHERE的区别是WHERE 在分组前过滤行HAVING 在分组后过滤组——这里用HAVING COUNT(sc.cno) 1是保底逻辑实际业务中每个学生都至少选了一门课所以这个条件可以去掉。但保留它有一个好处如果以后系统扩展到允许只注册不选课的学生存在这个查询依然正确。5. 报表输出与用户管理从 JTable 到可打印文档5.1 报表输出AWT 的 PrintJob 比想象中好用需求第五条“报表输出根据用户需要打印有关资料”往往是课程设计里最容易被糊弄的部分。很多学生直接在界面上截个图粘贴到 Word 打印这既不符合设计要求答辩时也容易被质疑“打印功能在哪里”。Java 原生提供的打印方案是基于 AWT 的java.awt.print.PrinterJob配合 Printable 接口可以实现任意组件的打印。public class ScoreReportPrinter implements Printable { private ListString[] rows; private String[] headers; private String title; public ScoreReportPrinter(ListString[] rows, String[] headers, String title) { this.rows rows; this.headers headers; this.title title; } Override public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { if (pageIndex 0) { return NO_SUCH_PAGE; } Graphics2D g2d (Graphics2D) g; g2d.translate(pf.getImageableX(), pf.getImageableY()); // 打印标题 g2d.setFont(new Font(宋体, Font.BOLD, 16)); g2d.drawString(title, 200, 50); // 打印表头 g2d.setFont(new Font(宋体, Font.BOLD, 12)); int y 100; for (int i 0; i headers.length; i) { g2d.drawString(headers[i], 50 i * 100, y); } // 打印数据行 g2d.setFont(new Font(宋体, Font.PLAIN, 12)); y 20; for (String[] row : rows) { for (int i 0; i row.length; i) { g2d.drawString(row[i], 50 i * 100, y); } y 20; if (y pf.getImageableHeight() - 50) { return NO_SUCH_PAGE; } } return PAGE_EXISTS; } public static void printReport(ListString[] rows, String[] headers, String title) { PrinterJob job PrinterJob.getPrinterJob(); job.setPrintable(new ScoreReportPrinter(rows, headers, title)); if (job.printDialog()) { try { job.print(); } catch (PrinterException e) { e.printStackTrace(); } } } }打印实现里最关键的是坐标计算。pf.getImageableX()和getImageableY()返回的是页面可打印区域的左上角坐标不是从纸张边缘的 (0,0) 开始因为打印机有物理边距。翻译坐标后再绘制内容是避免内容被裁切的标准做法。列宽这里用了固定的 100 像素偏移对于学号10 位、姓名2-3 个汉字、课程名最多 6 个字、成绩4 个字符的典型数据已经够用如果需要更精确的排版可以用FontMetrics.getStringBounds()根据字体实际宽度动态计算列位置。调用打印入口放在成绩查询结果界面用户勾选要打印的成绩记录点击“报表输出”按钮程序把 JTable 里的数据行转换为ListString[]再调用ScoreReportPrinter.printReport()。教师端可以选课程打印全班成绩单学生端打印自己的成绩单管理员打印全部成绩报表。打印功能在答辩现场演示时不一定有真实打印机但printDialog()弹出的系统打印对话框就足以证明功能实现了——这也是为什么上面代码里保留了这个方法调用。5.2 修改密码与用户管理万变不离 UPDATE需求第六条和第七条涉及用户标识、口令判断以及密码修改。修改密码的本质是一条 UPDATE 语句但要注意先验证旧密码再更新新密码public boolean changePassword(String uid, String oldPwd, String newPwd) { String checkSql SELECT upwd FROM users WHERE uid ?; String updateSql UPDATE users SET upwd ? WHERE uid ?; try (Connection conn DBUtil.getConnection(); PreparedStatement psCheck conn.prepareStatement(checkSql)) { psCheck.setString(1, uid); ResultSet rs psCheck.executeQuery(); if (rs.next()) { if (!rs.getString(upwd).equals(sha256(oldPwd))) { return false; // 原密码不正确 } } else { return false; } try (PreparedStatement psUpdate conn.prepareStatement(updateSql)) { psUpdate.setString(1, sha256(newPwd)); psUpdate.setString(2, uid); int affected psUpdate.executeUpdate(); return affected 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }这段代码体现了一个数据库操作的规范查询与更新分别使用独立的 PreparedStatement且在 try-with-resources 中自动关闭连接避免资源泄漏。新密码和旧密码都不能以明文出现在日志或输出中——System.out.println调试完记得删掉。用户管理功能管理员新增/删除用户本质上是对 users 表做增删改查可以把 users 表管理界面和学生管理界面共用一套模板代码只是字段不同而已。这里有一个设计上的小技巧新增教师用户时如果教师表还没有对应记录可以等教师基本信息录入后再绑定 uid或者先建用户占位再让用户自行完善——课程设计选前者更稳妥逻辑简单不容易出错。修改密码后是否需要强制重新登录原始需求没有明说但从安全角度建议实行密码变更后将当前登录状态置为失效下次操作时自动跳回登录界面。实现方式是在主界面加一个布尔变量passwordChanged修改成功后置为 true在每次打开新窗口或执行敏感操作前检查这个变量。这个功能可以在报告里作为“安全性增强”单独提一笔。6. 调试复盘JDBC 驱动、中文乱码与连接池的三个坑6.1 JDBC 驱动加载失败与连接串参数课程设计答辩时最常见的翻车现场是在家里运行好好的程序到答辩教室的电脑上双击运行直接报ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。原因通常是两种项目里没有把 mssql-jdbc 的 JAR 包加到构建路径或者 JAR 包虽然下载了但没有复制到运行目录。Maven 工程在pom.xml中声明依赖是最可靠的dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version12.4.2.jre11/version /dependency如果你是 Eclipse 课程设计项目没有 Maven右键项目 → Build Path → Configure Build Path → Add External JARs选择 mssql-jdbc 的 JAR 文件。特别注意JAR 包版本要和 JDK 版本匹配JDK 8 用jre8后缀的版本JDK 11 用jre11后缀混用可能会出现UnsupportedClassVersionError。连接串参数方面DatabaseNamestudent_scores是指定数据库名等价于登录后执行USE student_scores。如果你的 SQL Server 实例不是默认实例即安装时指定了命名实例需要写成localhost\实例名。如果数据库开的是动态端口还需要加portNumber参数。把encryptfalse和trustServerCertificatetrue一起使用是开发环境的常见配置。6.2 中文乱码从 JDBC URL 到控制台输出的全链路排查Windows 下开发 Java SQL Server 时中文乱码是高频问题。现象包括存入数据库的中文变成??、查询出来的中文显示成乱码、控制台输出中文乱码。这三类问题要分开定位第一类是存储乱码写入数据库前就已经乱掉。检查点是 JDBC URL 是否添加了字符集参数SQL Server 的 JDBC 驱动用sendStringParametersAsUnicodetrue默认就是 true一般不需要显式指定。但如果你的表字段用的是VARCHAR而非NVARCHAR中文字符会被截断转码解决办法是把表字段改成NVARCHAR——SQL Server 中NVARCHAR使用 UCS-2 编码存储能正确保存中文。第二类是查询乱码即数据在数据库里是对的用 SSMS 查询能看到正常中文但 JTable 里显示乱码。检查点是项目源代码文件的编码格式Eclipse 默认项目编码是 GBK早期版本或 UTF-8新版本如果你在 UTF-8 编码的文件里写中文且编译时没有指定-encoding参数Class 文件里的字面量中文就会乱。在build.gradle或 Eclipse 编译设置里强制指定 UTF-8javac -encoding UTF-8 -d bin src/com/kk/*.java第三类是控制台乱码这往往是 Windows 控制台的默认代码页是 GBK而 System.out 输出的是 UTF-8 字节流。这不是程序逻辑问题但容易被误判。解决方式在 IDE 的运行配置 VM 参数里加-Dfile.encodingUTF-8或者在代码入口最前面设置System.setProperty(file.encoding, UTF-8)——注意这个设置必须在使用任何 IO 操作之前执行。6.3 多窗口打开后旧连接未关闭资源泄漏的排查思路课程设计的功能窗口互相跳转很容易出现“打开学生管理界面再打开成绩查询界面两个界面各建立一次数据库连接但关闭界面时只 dispose 了窗口连接没有归还”的情况。程序运行初期一切正常但反复开关窗口十几次后数据库连接耗尽后续操作全部超时或报Connection refused。排查方法打开 SQL Server Management Studio执行SELECT DB_NAME(dbid) AS DBName, COUNT(*) AS Connections FROM sys.sysprocesses GROUP BY dbid如果看到某个数据库的连接数持续增长不下降基本可以确定是连接未关闭。修复方式很简单所有 JDBC 操作都使用 try-with-resourcestry (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 处理结果集 } catch (SQLException e) { e.printStackTrace(); }try-with-resources 会自动调用 Connection、PreparedStatement、ResultSet 的 close 方法无论正常结束还是抛出异常。ResultSet 关闭后对应查询的数据库游标也随之释放。注意一个边界情况如果你在结果集遍历过程中又执行了另一个数据库操作同一个 Connection 会被复用而此时 ResultSet 尚未关闭某些数据库驱动会报“ResultSet already closed”或“Connection busy with another command”错误——这是逐行处理数据时最常见的坑。避免方式有两种一是把 ResultSet 数据先全部装入ListString[]关闭结果集后再做后续操作二是主从查询使用不同的 Connection。课程设计推荐前者代码更简单也更安全。连接池如 HikariCP虽然是生产环境的标准配置但课程设计阶段不建议引入连接池本身的概念讲不透会变成答辩减分项而且复杂化后出了问题排查成本高。先做到“每个数据库操作都正确关闭连接”再在报告最后提一句“生产环境可以引入 HikariCP 连接池优化性能”说明你是有意识、有进阶方向认识的——这一句话就能让报告达到“中等偏上”的水准。本文还有配套的精品资源点击获取
返回列表