ARTICLE DETAIL

资讯详情

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

Java+Oracle学生成绩管理系统实训:JDBC细节与Swing登录避坑

Java+Oracle学生成绩管理系统实训:JDBC细节与Swing登录避坑 简介这份资源是一份面向Java初学者、高校软件工程与数据库课程学生的数据库实训报告主题为「学生成绩管理系统」的设计与实现适合正在完成课程实训、需要参考完整报告结构与技术实现思路的读者。压缩包内共1个PDF文件整体约1.77MB以图文排版呈现涵盖实训概述、课题描述、系统模块详细设计、程序运行与测试、实训总结等章节可直接作为报告撰写与答辩的参照模板。报告围绕JDBC连接Oracle数据库展开讲解了登录模块的用户身份验证、管理员模块对学生信息的添加删除修改与成绩批量导入导出、学生模块的成绩查询与权限控制并配有系统功能层次图、数据表字段与约束设计说明同时给出单元测试、集成测试与系统测试的验证思路。目前已有4248人浏览学习适合借此梳理需求分析、编码实现到测试维护的完整软件工程流程快速补齐数据库增删查改的实操经验。1. 一周实训里最难缠的不是 Swing是那几行 JDBC同组同学把登录界面画得有模有样敲进 admin 和密码却总弹“用户名或密码不正确”SQL 单独拿到 SQLPlus 里跑得通表里也确实躺着那条记录。折腾半个下午才发现admin 表的主键定义成 NUMBER(10)登录框拿到的却是字符串DAO 里用setString绑进去Oracle 做了一次隐式转换正好在某个边界上没匹配上。这份 Java Oracle 的学生成绩管理系统实训报告价值就在这类细节上。整套结构小得可怜Swing 三个窗口登录、管理员操作、学生操作DAO 里两条查询加几条增删改数据库只有 admin 和 student 两张表。小到能在一张 A4 纸上把调用链画完反而适合拿它把 JDBC 的每个参数、每个关闭顺序反复抠清楚。正在赶数据库课程设计、或者准备 Java 后端面试里 JDBC 那部分八股的人都能从这里直接抄到窗口骨架和几个真实的坑位。2. admin 与 student 两张表的建模和 JDBC 连接工厂2.1 字段类型和约束定在哪里会咬人物理设计沿用报告里的定义把两张表并排摆出来顺带标出后面会出问题的地方。表字段名类型空值约束adminusernameNUMBER(10)NOT NULL主键adminpasswordVARCHAR2(10)NOT NULL-studentstudentidNUMBER(10)NOT NULL主键studentnameVARCHAR2(20)NOT NULL-studentgenderVARCHAR2(3)--studentageNUMBER(2)--studentpasswordVARCHAR2(10)NOT NULL-管理员 ID 选 NUMBER(10) 而不是 VARCHAR2是因为它承担的是标识语义不参与字符串拼接。但登录时必须跨过一道类型转换具体怎么处理在第 3 章展开。密码字段 VARCHAR2(10) 短得离谱而 AddStudentFrame 里还有一行s.setPassword(studentId)拿学号直接当初始密码。注意明文密码加超短长度只够应付课程演示真实项目至少要换成加盐哈希字段长度也得放到 VARCHAR2(60) 以上否则改一次加密方案就要重建表。性别用 VARCHAR2(3) 是有讲究的一个汉字在 GBK 下占 2 字节、UTF-8 下占 3 字节3 个字节刚好容得下“男”“女”两字中的一个。年龄 NUMBER(2) 上限是 99学生年龄段够用但要存教职工就得改。学号既是主键又是登录账号一列两用这是整份报告默认的前提改起来牵一发动全身。2.2 ConnectionFactory 的驱动加载与关闭顺序所有 DAO 都通过一个工厂类拿连接驱动名、连接串、账号密码只在一处出现。public final class ConnectionFactory { // Oracle thin 驱动主机 127.0.0.1监听端口 1521SID 为 orcl private static final String URL jdbc:oracle:thin:127.0.0.1:1521:orcl; private static final String USER scott; private static final String PWD tiger; public static Connection getConnection() throws Exception { // 老式写法JDBC 4.0 之后由 SPI 自动完成实训环境里显式写更保险 Class.forName(oracle.jdbc.OracleDriver); return DriverManager.getConnection(URL, USER, PWD); } // 关闭顺序必须与打开顺序相反先结果集、再语句、最后连接 public static void close(ResultSet rs, Statement st, Connection conn) { try { if (rs ! null) rs.close(); } catch (Exception ignored) { } try { if (st ! null) st.close(); } catch (Exception ignored) { } try { if (conn ! null) conn.close(); } catch (Exception ignored) { } } }Class.forName的作用是把oracle.jdbc.OracleDriver注册进DriverManagerJDBC 4.0 以后由 SPI 自动扫描 jar 里的服务文件完成但在依赖没打全的实训环境里显式写上更稳。URL 里的thin表示纯 Java 驱动客户端不需要额外装 Oracle 客户端127.0.0.1:1521:orcl依次是主机、监听端口和 SID。close里的三层 try/catch 是刻意分开的rs.close()抛异常不能影响st.close()被执行。图省事写成一条 try 包住三行第一个关闭动作失败后面两个就永远不会跑连接会越攒越多池子几分钟就满。2.3 建表语句和约束命名两张表的 DDL 比报告里多写了约束名出错时能按名字定位。CREATE TABLE admin ( username NUMBER(10) NOT NULL, password VARCHAR2(10) NOT NULL, CONSTRAINT pk_admin PRIMARY KEY (username) ); CREATE TABLE student ( studentid NUMBER(10) NOT NULL, name VARCHAR2(20) NOT NULL, gender VARCHAR2(3), age NUMBER(2), password VARCHAR2(10) NOT NULL, CONSTRAINT pk_student PRIMARY KEY (studentid) );显式命名约束的好处是以后报ORA-00001: unique constraint (SCOTT.PK_ADMIN) violated时一眼能看出是管理员表的主键冲突而不是对着一串SYS_C0012345猜。这里有个小细节报告正文把 admin 的主键写成“管理员 ID”而 DAO 里的列名是username脚本和代码必须统一否则第一次跑就是ORA-00904: 标识符无效。3. 登录窗口的身份分流从 CheckBox 一路查到 DAO3.1 登录窗体的组件布局与鼠标事件窗口用绝对定位摆放组件setLayout(null)之后每个控件都要手动setBounds。这种写法在教学代码里常见放到真实项目一改分辨率就错位但胜在直观。public class LoginFrame extends JFrame { private JCheckBox c; private JButton loginBtn, resetBtn; private JTextField nameInput; private JPasswordField pwdInput; private AdminDao adminDao new AdminDao(); private StudentDao studentDao new StudentDao(); private void event() { // 取消按钮直接结束进程登录前没有别的窗口需要保留 resetBtn.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { System.exit(0); } }); loginBtn.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { String username nameInput.getText().trim(); String password pwdInput.getText().trim(); if (c.isSelected()) { Admin a adminDao.findAdminByName(username); // 不存在与密码错合并成同一句提示不暴露账号是否有效 if (a null || !a.getPassword().equals(password)) { JOptionPane.showMessageDialog(null, 用户名或密码不正确, 提示信息, JOptionPane.WARNING_MESSAGE); return; } LoginFrame.this.dispose(); new AdminFrame(a.getUsername()).go(); } else { Student s studentDao.findStudentByStudentId(username); if (s null || !s.getPassword().equals(password)) { JOptionPane.showMessageDialog(null, 用户名或密码不正确, 提示信息, JOptionPane.WARNING_MESSAGE); return; } LoginFrame.this.dispose(); new StudentFrame(s, null).go(); } } }); } }getText().trim()里的trim()不能省复制粘贴用户名时很容易带一个尾随空格库里存的是干净值比对就失败而报错偏偏写着“用户名或密码不正确”够查半天。JPasswordField.getText()在新版本 JDK 里已被标记过时推荐用getPassword()拿char[]比较完顺手清零实训代码里用getText()只是为了字符串比较顺手。3.2 两种身份的分流逻辑分流判定只有c.isSelected()一个条件勾选框就是全部的身份标识。展开看是这样几步读取勾选框状态决定查 admin 表还是 student 表用输入的用户名去对应表里查记录查不到记录统一提示“用户名或密码不正确”查得到记录再用输入密码和库中密码做equals比对比对成功关闭登录窗口打开对应主界面。第 2 步和第 4 步的提示语故意合并成同一句区分“用户不存在”和“密码错误”等于告诉别人哪些账号有效。实训场景没人攻击但这个习惯保留下来成本极低。判定阶段输入来源查询目标失败提示分流JCheckBox 状态--查记录nameInputadmin / student用户名或密码不正确比对密码pwdInput上一步返回的对象用户名或密码不正确成功后--关闭登录窗并打开主界面3.3 DAO 里的查询闭环与游标判断查管理员的方法没有把ResultSet直接返回而是转成Admin对象上层就不需要认识结果集。public Admin findAdminByName(String username) { Admin admin null; Connection conn null; PreparedStatement pstat null; ResultSet rs null; try { conn ConnectionFactory.getConnection(); String sql select username,password from admin where username?; pstat conn.prepareStatement(sql); pstat.setString(1, username); // 占位符下标从 1 开始不是 0 rs pstat.executeQuery(); // 查不到数据时循环体一次都不执行admin 保持为 null while (rs.next()) { admin new Admin(); admin.setUsername(username); admin.setPassword(rs.getString(password)); } } catch (Exception e) { e.printStackTrace(); } finally { ConnectionFactory.close(rs, pstat, conn); } return admin; }setString(1, username)的下标从 1 开始这是 JDBC 与 Java 数组最大的认知冲突点写惯for (int i 0; ...)的人第一次用很容易写成 0然后收到“无效的列索引”。这里传字符串而admin.username是 NUMBER(10)Oracle 会做隐式转换能否命中主键索引取决于转换方式稳妥做法是按列类型选setLong或setString不要混着来。while (rs.next())用循环不用if是防御式写法即使主键查询理论上只返回一行也留个余量。finally里调工厂的close保证异常时连接也能归还。返回null还是抛异常是个取舍这里选返回null代价是调用方必须记得判空选抛异常的话登录失败这种正常业务流程就要用异常来表达反而更绕。4. 管理员模块的增删改查怎么落地4.1 新增学生的类型转换和主键冲突AddStudentFrame 把四个输入框的值拼成Student对象再落库中间两处parse是崩溃高发区。submitBtn.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { String studentId idInput.getText().trim(); String studentName nameInput.getText().trim(); String age ageInput.getText().trim(); String gender genderInput.getText().trim(); // 学号为空或含非数字字符时parseLong 会抛 NumberFormatException Student s new Student(); s.setStudentid(Long.parseLong(studentId)); s.setStudentName(studentName); s.setPassword(studentId); // 学号当作初始密码 s.setAge(Integer.parseInt(age)); s.setGender(gender); boolean ok studentDao.addStudent(s); if (ok) { JOptionPane.showMessageDialog(null, 添加成功); admin.setEnabled(true); // 放开之前被禁用的父窗口 AddStudentFrame.this.dispose(); } else { JOptionPane.showMessageDialog(null, 添加失败请检查学籍号是否已存在); } } });原报告这一段没有任何 try/catch学号框里敲个空格再点确定Long.parseLong()直接抛异常用户看到的现象就是点了没反应。补一层正则校验成本极低if (!studentId.matches(\\d{1,10})) { JOptionPane.showMessageDialog(null, 学号必须是 1 到 10 位数字); return; }\\d{1,10}同时挡住空串、负号、字母和超长数字和 NUMBER(10) 的容量也对得上年龄同理加\\d{1,2}更稳。DAO 那边则靠主键冲突判重public boolean addStudent(Student s) { Connection conn null; PreparedStatement pstat null; try { conn ConnectionFactory.getConnection(); String sql insert into student(studentid,name,gender,age,password) values(?,?,?,?,?); pstat conn.prepareStatement(sql); pstat.setLong(1, s.getStudentid()); pstat.setString(2, s.getStudentName()); pstat.setString(3, s.getGender()); pstat.setInt(4, s.getAge()); pstat.setString(5, s.getPassword()); return pstat.executeUpdate() 1; // 影响行数为 1 才算成功 } catch (SQLException e) { return false; // ORA-00001 主键重复业务上等价于学号已存在 } finally { ConnectionFactory.close(null, pstat, conn); } }executeUpdate()的返回值就是受影响行数单行插入时等于 1。把所有SQLException都吞成false是偷懒但可接受的写法代价是真正的连接故障也会被显示成“学籍号已存在”排查时容易跑偏。严谨一点判断e.getErrorCode() 1只有主键冲突才返回false其余异常继续往上抛。4.2 查询、修改、删除的三种 SQL 落地剩下三个功能骨架完全一样拿输入、拼 SQL、执行、看行数区别只在 SQL 和提示语。功能SQL 语句成功判定界面反馈查询select ... from student where studentid?rs.next() 为真显示学生信息修改update student set name?,gender?,age? where studentid?executeUpdate()1修改成功删除delete from student where studentid?executeUpdate()1删除完成修改和删除有个共同的坑用学号做条件时如果输入错了但恰好是另一个学生的学号操作会落到别人头上界面不会有任何提示。所以修改前一般先回查一次把原值回填到表单让操作者确认。public Student queryById(long studentId) { Connection conn null; PreparedStatement pstat null; ResultSet rs null; Student s null; try { conn ConnectionFactory.getConnection(); pstat conn.prepareStatement( select studentid,name,gender,age from student where studentid?); pstat.setLong(1, studentId); rs pstat.executeQuery(); // 按主键查询最多一行用 if 而不是 while语义更准 if (rs.next()) { s new Student(); s.setStudentid(rs.getLong(studentid)); s.setStudentName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getInt(age)); } } catch (SQLException e) { e.printStackTrace(); } finally { ConnectionFactory.close(rs, pstat, conn); } return s; }rs.getLong(studentid)用列名而不用下标好处是 SQL 里 select 顺序调整后 Java 侧不用跟着改代价是每次取值都要做一次列名匹配这个数据量下可以忽略。4.3 学生端的读取边界和密码修改学生模块只有两件事查自己的信息、改自己的登录密码。权限边界的实现方式很朴素——学生界面的构造方法直接接收一个查好的Student对象所有查询都以这个对象的学号为条件界面上根本没有输入其他学号的机会。// StudentFrame 构造时锁定身份后续所有操作都以 s 的学号为准 public StudentFrame(Student s, Object parent) { this.student s; }密码修改的三步校验必须写全输入旧密码、输入新密码、确认新密码。报告里只写了“旧密码正确则修改成功”实际还要补一条两次新密码一致性判断否则用户在两个框里各打错一个字改完之后自己都登不进去。提示改密码不要只凭 update 返回成功就认定完成最好在同一连接上回查一次。Oracle 默认setAutoCommit(true)时每条语句自动提交但一旦为了批量操作把它改成false就必须显式conn.commit()忘记提交的表现是“提示成功重新登录还是旧密码”这类问题在实训验收现场最容易翻车。5. 连接释放、批量导入和一条成绩统计 SQL5.1 关闭顺序与 try-with-resourcesConnectionFactory.close(rs, st, conn)的顺序必须是打开顺序的逆序这一点在第 2 章说过。日常写代码更推荐让编译器管public Student queryById(long studentId) { String sql select studentid,name,gender,age from student where studentid?; try (Connection conn ConnectionFactory.getConnection(); PreparedStatement pstat conn.prepareStatement(sql)) { pstat.setLong(1, studentId); try (ResultSet rs pstat.executeQuery()) { if (rs.next()) { /* 组装 Student */ } } } catch (SQLException e) { e.printStackTrace(); } return null; }try-with-resources 的关闭顺序同样是逆序编译器生成的 finally 块会从最后一个资源开始关少写一个finally也少一次漏关的机会。5.2 批量导入成绩管理员一次导入一个班的成绩逐条executeUpdate要往返数据库几十次。改成攒批再发conn.setAutoCommit(false); String sql insert into score(studentid,subject,score) values(?,?,?); try (PreparedStatement pstat conn.prepareStatement(sql)) { for (Score sc : list) { pstat.setLong(1, sc.getStudentId()); pstat.setString(2, sc.getSubject()); pstat.setBigDecimal(3, sc.getScore()); pstat.addBatch(); } pstat.executeBatch(); conn.commit(); // 关掉自动提交后必须显式提交 }addBatch只把参数攒在本地executeBatch才真正发出去。配合setAutoCommit(false)整批要么全成功要么全失败中间某行违反约束时抛出的BatchUpdateException里带着每行的执行结果可用来定位是哪一条出的问题。数据量到几千条以上还可以在循环里每 500 条执行一次executeBatch()并清空缓存避免客户端内存堆积。5.3 成绩统计的 GROUP BY 写法标题挂着成绩管理系统但只存学生基本信息的话统计功能无从谈起通常要再加一张成绩表把学号、科目、分数分开存-- 每个学生的总分和平均分按平均分倒序 SELECT s.studentid, s.name, SUM(c.score) AS total, AVG(c.score) AS avg_score FROM student s JOIN score c ON c.studentid s.studentid GROUP BY s.studentid, s.name ORDER BY avg_score DESC;GROUP BY里必须把 select 中出现的非聚合列全部列上Oracle 在这一点上一向严格少写s.name直接报ORA-00979: 不是 GROUP BY 表达式。AVG会自动跳过NULL意味着缺考科目不计入分母如果要按“缺考记 0 分”算写成AVG(NVL(c.score, 0))。同一条 SQL 换成RANK() OVER (ORDER BY AVG(c.score) DESC)就能带出排名列不用在 Java 里再排一次序。调这类语句时先在数据库工具里把?换成真实值跑通再挪回 Java——占位符下标一旦写错SQLException只会告诉你索引无效不会告诉你是哪一行。本文还有配套的精品资源点击获取
返回列表