ARTICLE DETAIL

资讯详情

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

Java Swing+MySQL教务管理系统开发实战:从建表到打包全解析

Java Swing+MySQL教务管理系统开发实战:从建表到打包全解析 简介Java课程设计常让初学者卡在界面与数据的衔接上。从基础概念看桌面应用开发需要一套清晰的UI框架与持久化存储方案Swing负责构建用户交互界面MySQL承担数据存储二者通过JDBC建立连接。理解PreparedStatement防注入、事务边界和连接参数配置是任何Java数据库应用的核心技术价值。无论是教务管理、学生选课还是成绩录入这类典型的CRUD系统都是学习Java桌面开发的常见应用场景。本文以学校教务管理系统为载体围绕MySQL建表、JDBC驱动配置、Swing窗体布局、JTable数据展示以及打包部署展开系统梳理从零到可运行的全过程并针对中文乱码、时区错误、界面卡死等高频工程问题给出排查思路帮助读者快速交付一个能上台演示的完整课设项目。1. 教务管理系统这套组合拳Swing做界面、MySQL存数据课程设计最稳的选择每年毕业季和期末季总有一批人被Java课程设计折磨到凌晨两点。网上能下载的源码要么是SSM框架的Web项目看不懂要么是纯控制台程序拿不出手。而JavaSwingMySQL这套组合恰好卡在最讨巧的位置Swing负责把界面画出来MySQL负责把数据存进去两者用JDBC一连接就是一个看得见摸得着的桌面应用。学校教务管理系统这类题目本质上是学生管理、教师管理、课程管理、选课和成绩录入这五个模块的CRUD组合用Swing做前端表格展示用MySQL做持久化存储逻辑链路短出成果快答辩时也能把每个类的作用讲清楚。这篇文章从建表到跑通把整条路上的关键代码和坑都摊开讲。2. 先把数据层立住教务系统的MySQL表设计与JDBC连接细节2.1 六张核心表的字段设计从ER图到建表SQL教务管理系统不管界面长什么样数据层总是那几件事用户要登录所以要有一张用户表学生和教师是两个不同的角色信息字段不一样所以要分开建课程要开课和选课需要课程表选课记录和成绩是强关联的可以合并成一张选课成绩表也可以拆开。我一般拆成六张表字段设计如下。学生表student、教师表teacher、课程表course这三张是基础信息表字段设计都不复杂。选课表select_course是关联表它同时关联student_id和course_id还要存一个score字段用于成绩录入。用户表sys_user单独存在不跟学生教师表合并原因是学生的学号和教师的工号可能重复但登录用户名必须全局唯一。建表SQL直接放在MySQL命令行或Navicat里执行。前面提到的mysql安装教程、mysql安装配置教程这些热搜词的搜索结果里安装的坑基本集中在服务启动失败和root密码遗忘这些跟建表无关但需要注意的是字符集一定要用utf8mb4否则后面存中文姓名时会出现问号。CREATE DATABASE IF NOT EXISTS edu_admin DEFAULT CHARSET utf8mb4; USE edu_admin; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(10) NOT NULL COMMENT ADMIN/TEACHER/STUDENT ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender VARCHAR(4), class_name VARCHAR(30) ); CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, title VARCHAR(20) ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(50) NOT NULL, credit INT, teacher_id INT, FOREIGN KEY (teacher_id) REFERENCES teacher(id) ); CREATE TABLE select_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL, UNIQUE KEY uk_stu_course (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );这段SQL的AUTO_INCREMENT是MySQL的自增主键写法跟Oracle的序列不一样Swing程序里插入记录后要马上拿到自增ID的话可以用JDBC的getGeneratedKeys方法后面写DAO层时会讲到。UNIQUE KEY uk_stu_course这里是个两层含义的约束它既防止同一个学生重复选同一门课又是后续做选课逻辑时捕获重复异常的判定依据。角色字段role用字符串类型取值就ADMIN、TEACHER、STUDENT三个不要用数字代替程序里读出来还要翻译一遍纯属给自己找麻烦。2.2 JDBC连接MySQL驱动加载、URL参数与连接池取舍数据表建好之后Java这边要解决的是怎么连上MySQL。JDBC的套路是固定的四步加载驱动、获取连接、执行SQL、关闭资源。这里最关键的是MySQL 8以上的驱动类名和URL参数跟MySQL 5有区别。MySQL 5用com.mysql.jdbc.DriverMySQL 8必须换成com.mysql.cj.jdbc.DriverURL里还要额外加serverTimezone和useSSL两个参数不然会报时区错误。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/edu_admin ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败请检查mysql-connector-java的jar包是否导入, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }数据库连接池是否要用是很多新手纠结的地方。课程设计这个体量同时在线操作的人数不超过几十个每次操作新建一个Connection完全够用。C3P0、Druid这些连接池是给Web应用的高并发场景用的Swing单机程序引入它们反而是负优化——配置麻烦、依赖变多、程序启动变慢。所以DBUtil里直接写DriverManager获取连接简单直接。URL参数里的characterEncodingutf8值得单独说它的作用是告诉MySQL驱动这边传输的字节流是UTF-8编码。这个参数和建表时的DEFAULT CHARSET utf8mb4是两个层面的东西表的字符集决定数据怎么存URL的编码参数决定连接怎么传两个都要配缺一个都会在写入中文时出问题。serverTimezoneAsia/Shanghai是针对MySQL 8的强制要求MySQL 5.6及以下版本不填也能跑但填了也不会出错建议统一加上。2.3 数据库操作封装用PreparedStatement代替Statement连接写好后接下来的数据库操作封装直接决定后面DAO层的代码量。我见过不少课设源码在DAO里大量拼接字符串SQL然后用Statement执行。这种写法在单机课设里能跑但有两个隐患一是SQL注入不是Web项目专有的Swing程序里用户在JTextField里输入的内容同样能拼进SQL二是字符串拼接遇到单引号就得手工转义中文姓名里有的情况很常见。PreparedStatement能同时解决这两个问题代价只是多写两行代码。public boolean addStudent(Student s) { String sql INSERT INTO student(student_no, name, gender, class_name) 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.setString(4, s.getClassName()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }这里用了try-with-resources语法Java 7以后的推荐写法Connection和PreparedStatement都会自动关闭不用在finally里手工判断null。setString那几行就是把用户输入的内容作为参数传给SQL有特殊字符时由驱动负责转义不需要自己处理。executeUpdate的返回值是受影响的行数插入成功返回1更新或删除返回对应行数。这个方法返回boolean是为了让Swing界面的按钮监听器拿返回值做提示比如「添加成功」或「添加失败」。3. Swing界面从登录页到主窗体布局、表格与事件监听3.1 登录窗体JFrame、JTextField与用户校验的完整链路登录表单是系统的入口也是第一眼印象。Swing的登录窗体有个很典型的布局套路中间放一个JPanel用GridBagLayout或空布局绝对定位放两个JLabel、两个JTextField密码框用JPasswordField、一个JButton。绝对布局在这里其实比GridBagLayout更好用因为登录框的位置固定就那么几个控件手写坐标反而直观。public class LoginFrame extends JFrame { private JTextField usernameField; private JPasswordField passwordField; public LoginFrame() { setTitle(教务管理系统登录); setSize(400, 280); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel new JPanel(null); JLabel userLabel new JLabel(用户名:); userLabel.setBounds(60, 60, 60, 30); usernameField new JTextField(); usernameField.setBounds(130, 60, 180, 30); JLabel pwdLabel new JLabel(密码:); pwdLabel.setBounds(60, 110, 60, 30); passwordField new JPasswordField(); passwordField.setBounds(130, 110, 180, 30); JButton loginBtn new JButton(登录); loginBtn.setBounds(150, 170, 100, 35); loginBtn.addActionListener(e - doLogin()); panel.add(userLabel); panel.add(usernameField); panel.add(pwdLabel); panel.add(passwordField); panel.add(loginBtn); add(panel); } private void doLogin() { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } // 调用UserDao的login方法校验成功后跳转主窗体 } }JPasswordField的getPassword返回的是char数组而不是String这是Swing故意做的设计目的是避免密码字符串驻留在内存中。实际使用时new String(passwordField.getPassword())把它转成String用的地方顺手缺点是这个字符串会留在堆里对课设项目来说不算什么大问题。登录成功后要做的动作是dispose掉当前窗体然后new一个MainFrame并setVisible(true)。注意不要用setVisible(false)替代dispose登录窗体被dispose后资源会释放用户关掉主窗体时整个进程能正常退出。3.2 主窗体布局JTabbedPane挂载功能面板登录后的主窗体用JTabbedPane是最主流的选择顶部是选项卡每个选项卡对应一个功能模块。教务管理系统的功能模块通常是学生管理、教师管理、课程管理、选课管理、成绩管理再加一个个人信息。这样设计的理由是JTabbedPane天然隔离了各个模块的界面复杂度每个模块一个JPanel面板内部自己管自己的布局跟其他模块互不干扰。public class MainFrame extends JFrame { private JTabbedPane tabbedPane; private String currentUser; private String currentRole; public MainFrame(String username, String role) { this.currentUser username; this.currentRole role; setTitle(学校教务管理系统 - 当前用户: username); setSize(900, 600); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); tabbedPane new JTabbedPane(); tabbedPane.addTab(学生管理, new StudentPanel()); tabbedPane.addTab(课程管理, new CoursePanel()); tabbedPane.addTab(选课管理, new SelectCoursePanel()); add(tabbedPane, BorderLayout.CENTER); } }根据角色隐藏选项卡是权限控制里最简单的一种做法学生登录后不应该看到教师管理和成绩录入教师登录后不应该看到学生管理。实现方式是在构造MainFrame时判断currentRole对需要隐藏的模块不执行addTab。角色权限过滤的问题在于它只控制了界面入口并没有拦截底层DAO方法——如果学生通过某种方式调用TeacherDao方法数据还是能被改。课设答辩一般不会追问到这一层但知道这个薄弱点在哪里总比不知道要好。3.3 JTable显示数据DefaultTableModel与数据刷新机制JTable是Swing里展示数据库记录的核心控件。它本身只负责显示数据放在TableModel里。初学者常见的误区是直接往JTable里塞数据数组然后发现表格列头和排序功能都没法正常用。正确的做法是创建一个DefaultTableModel用setColumnIdentifiers设置列名再用setDataVector或逐行addRow填充数据。public class StudentPanel extends JPanel { private JTable table; private DefaultTableModel model; public StudentPanel() { setLayout(new BorderLayout()); model new DefaultTableModel(); model.setColumnIdentifiers(new Object[]{ID, 学号, 姓名, 性别, 班级}); table new JTable(model); add(new JScrollPane(table), BorderLayout.CENTER); refreshData(); } public void refreshData() { model.setRowCount(0); // 清空旧数据 ListStudent list new StudentDao().findAll(); for (Student s : list) { model.addRow(new Object[]{ s.getId(), s.getStudentNo(), s.getName(), s.getGender(), s.getClassName() }); } } }refreshData放在每次增删改操作后调用一次界面就能立即显示最新数据。setRowCount(0)是清空表格常用手段它的作用是把所有行删掉但保留列结构比反复new DefaultTableModel更轻量。操作按钮通常放在表格下方的按钮面板里点击「添加」时弹出一个JDialog收集输入点击「删除」时通过table.getSelectedRow()获取选中行再通过model.getValueAt(row, 1)拿到学号传给删除方法。这里有个隐藏坑getSelectedRow返回的是视图行索引当表格排序功能被启用后视图行和数据行可能不一致。课上不启用排序就没这个问题但一旦调用了table.setAutoCreateRowSorter(true)取值逻辑就要用convertRowIndexToModel转换。4. 主流程串起来学生管理、选课与成绩录入的完整功能链4.1 学生管理的增删改查DAO层与UI层怎么配合教务系统最核心的模块是学生管理。这个模块的完整链路是UI层在事件监听器里调用DAO层方法DAO层返回数据给UI层UI层用JOptionPane提示结果并刷新表格。核心操作就四件事查询全部、按学号查询、新增、删除。修改可以不单独做用「删除后重新添加」的替代方案很多课设就是这么干的虽然不够优雅但工作量能省一半。public class StudentDao { public ListStudent findAll() { ListStudent list new ArrayList(); String sql SELECT id, student_no, name, gender, class_name FROM student; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setClassName(rs.getString(class_name)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; } public boolean deleteByStudentNo(String studentNo) { String sql DELETE FROM student WHERE student_no ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentNo); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } }删除学生时有个关联数据问题如果这个学生已经有选课记录直接在student表删掉会报外键约束错误。两个解决办法一是删除前先查select_course表有没有关联记录有就提示「请先删除选课记录」二是建表时给外键加上ON DELETE CASCADE让MySQL自动级联删除选课记录。课设里推荐第一种因为第二种会让数据在用户无感知的情况下丢失答辩时被问到会很难解释清楚。4.2 选课模块一个事务搞定选课与选课记录写入选课的流程是界面左侧一个学生列表右侧一个可选课程列表点「选课」按钮后把当前选中的学生和课程组合写入select_course表。这个动作本身很简单关键是对重复选课的处理。前面建表时已经定义了UNIQUE KEY uk_stu_course所以重复插入会抛SQLIntegrityConstraintViolationException。public boolean selectCourse(int studentId, int courseId) { String sql INSERT INTO select_course(student_id, course_id) VALUES(?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); ps.setInt(2, courseId); return ps.executeUpdate() 0; } catch (SQLIntegrityConstraintViolationException e) { // 重复选课唯一索引拦截 return false; } catch (SQLException e) { e.printStackTrace(); return false; } }业务层捕捉SQLIntegrityConstraintViolationException并返回falseUI层拿到false后提示「该学生已选此课程」。这个方案比先SELECT再INSERT的高明之处在于两条SQL之间有时间窗口并发时两个请求同时通过SELECT判断然后同时执行INSERT还是会产生重复数据。数据库唯一约束加上异常捕获是从根上规避问题。课程设计虽然是单机程序但养成这种意识对后续做Web项目帮助很大。事务在这里没有强需求因为一次选课只操作一张表不需要引入事务。4.3 成绩录入跨表联查与JTable回填成绩录入模块是最能体现SQL能力的部分。它需要关联select_course表、student表和course表查出某门课程的所有选课学生再允许教师录入或修改分数。查询SQL用JOIN实现比多次查库然后内存里拼数据要高效得多代码也更短。界面用一个下拉框选择课程选中后表格刷新显示该课程下的学生列表和当前分数。public ListMapString, Object findScoreByCourse(int courseId) { ListMapString, Object list new ArrayList(); String sql SELECT sc.id, st.student_no, st.name, sc.score FROM select_course sc JOIN student st ON sc.student_id st.id WHERE sc.course_id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, courseId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { MapString, Object row new HashMap(); row.put(id, rs.getInt(id)); row.put(studentNo, rs.getString(student_no)); row.put(name, rs.getString(name)); row.put(score, rs.getObject(score)); list.add(row); } } } catch (SQLException e) { e.printStackTrace(); } return list; }表格里显示score这一列后双击单元格即可修改分数Swing的JTable默认不开启单元格编辑需要table.setRowSelectionAllowed(true)配合DefaultCellEditor才能实现双击编辑。设置方式可以单独配置某一列可编辑重写DefaultTableModel的isCellEditable方法只对score列返回true其他列都返回false。这样能防止学生信息被误改同时允许教师直接双击录入分数。分数列设置成可编辑后还需要在单元格失去焦点时触发保存写法是给JTable添加CellEditorListener监听editingStopped事件在事件里读取编辑后的值并调用DAO更新数据库。5. 教务系统开发避坑乱码、时区、线程阻塞与SQL注入的五条血泪记录5.1 中文乱码从数据库到界面三层逐个排查现象Swing界面显示中文姓名变成「???」或「鏉庣幇」。原因通常有三层第一层是建表时字符集不是utf8mb4存进去就是乱码第二层是JDBC的URL里缺少characterEncodingutf8驱动和MySQL之间传输时转换出错第三层是Swing程序本身的编译编码不对用javac编译时默认使用平台编码Windows中文系统会用GBK去读UTF-8的源文件字符串常量直接变乱码。解决建表用第2章的SQL指定utf8mb4JDBC的URL加characterEncodingutf8编译时用javac -encoding UTF-8或者在IntelliJ IDEA里把Project Encoding和File Encoding都设为UTF-8。绝大多数乱码问题逃不出这三个位置。5.2 MySQL 8连接报错serverTimezone不配就翻车现象连接数据库时报java.sql.SQLException: The server time zone value Öйú±ê׼ʱ¼ä is unrecognized或者Commucations link failure。原因MySQL 8的驱动对时区敏感默认要跟服务器协商时区而JDBC URL里没指定时区时驱动会使用JVM默认时区双方对不上就报错。解决JDBC URL里加serverTimezoneAsia/Shanghai。这个参数的效果是指定驱动使用中国时区如果服务器部署在国外机房而用户在国内建议用UTC或系统环境变量动态获取避免写死。课设本地跑写Asia/Shanghai就够了。5.3 Swing界面卡死耗时操作阻塞了EDT线程现象点击「查询所有学生」按钮后整个界面变成白板鼠标变成转圈状态几秒或十几秒后才恢复。原因Swing的UI刷新和事件处理都在同一个线程EDTEvent Dispatch Thread上执行查询数据库是耗时操作在事件监听器里同步执行时整个UI线程被阻塞。解决查询操作拿到子线程里执行执行完再用SwingUtilities.invokeLater回到EDT刷新表格。new Thread(() - { ListStudent list new StudentDao().findAll(); SwingUtilities.invokeLater(() - { model.setRowCount(0); for (Student s : list) { model.addRow(...); } }); }).start();数据量在几百行以下时同步查询的卡顿感知不强但一旦select_course表数据量大或者和远程MySQL相连卡顿立刻出现。线程方案的副作用是快速连续点击按钮会创建多个线程同时查询课设可以忽略这个并发问题但知道有这个问题存在。5.4 SQL注入防御PreparedStatement不是可选项现象在登录界面输入用户名admin OR 11和任意密码直接登录成功。原因登录SQL如果写成SELECT * FROM sys_user WHERE username username AND password password 输入内容被拼接进SQL引号提前闭合OR条件让整个WHERE永真。解决所有需要拼接用户输入的地方一律用PreparedStatement参数占位。第2章和第4章的DAO代码已经全部使用占位符风格。另外一个容易忽略的注入点是排序字段ORDER BY子句不能使用PreparedStatement占位符如果排序字段来自用户输入必须用白名单校验。5.5 驱动版本与MySQL版本不匹配ClassNotFoundException的根源现象运行程序时抛ClassNotFoundException: com.mysql.jdbc.Driver。原因程序里写了com.mysql.jdbc.Driver但实际导入的jar包是mysql-connector-java 8.x版本8.x版本已经把这个类移除了驱动类换成了com.mysql.cj.jdbc.Driver。解决要么把代码里的DRIVER常量改成com.mysql.cj.jdbc.Driver要么把依赖降级到mysql-connector-java 5.1.x版本并保持代码不变。建议直接改代码因为MySQL 8是当前主流新装的mysql安装教程基本都是8.x把代码适配到新驱动比找老jar包更省事。6. 让系统更像个产品登录状态保持、报表导出与一键打包的进阶验证6.1 登录状态保持用ThreadLocal模拟SessionSwing没有Web容器那样的Session对象跨窗口传递用户信息全靠构造参数或静态变量。构造参数方式在代码上要层层透传从LoginFrame传到MainFrame再传到每个Panel类一多就乱。静态变量的方式简单但不够隔离。一个折中方案是用ThreadLocal存当前登录用户代码里任何地方都能取到又不会污染全局命名空间。public class UserContext { private static final ThreadLocalString CURRENT_USER new ThreadLocal(); private static final ThreadLocalString CURRENT_ROLE new ThreadLocal(); public static void login(String username, String role) { CURRENT_USER.set(username); CURRENT_ROLE.set(role); } public static String getUsername() { return CURRENT_USER.get(); } public static String getRole() { return CURRENT_ROLE.get(); } public static void logout() { CURRENT_USER.remove(); CURRENT_ROLE.remove(); } }登录成功时调用UserContext.login退出登录时调用logout。这个设计在Swing里是用不上但值得练手的形态它背后的ThreadLocal原理在java面试题里出现频率极高面试官问「ThreadLocal怎么保证数据一致性」「内存泄漏怎么避免」时亲手用过和只在八股文里背过是完全不一样的回答状态。这样就可以说data source。jnuitangele出一个tab panel。leTab。leTab。6.2 成绩单导出从JTable生成Excel兼容的CSV文件教务系统的成绩模块做出来后通常会有「导出成绩单」的需求。直接引入Apache POI这个重量级依赖对于课程设计有点过剩为了导出一个表格引入一个几MB的jar包性价比不高。简单做法是生成CSV文件Excel能直接打开而且它是纯文本格式编码控制好了不会有乱码问题。关键点是Excel打开CSV默认按ANSI编码解释文本中文内容必须先写入UTF-8 BOM头否则打开后中文全乱。public void exportCsv(JTable table, File file) throws IOException { try (FileOutputStream fos new FileOutputStream(file)) { fos.write(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}); // UTF-8 BOM ByteArrayOutputStream baos new ByteArrayOutputStream(); baos.write((table.getColumnName(0) ,).getBytes(StandardCharsets.UTF_8)); for (int i 1; i table.getColumnCount(); i) { baos.write((, table.getColumnName(i)).getBytes(StandardCharsets.UTF_8)); } baos.write(\n.getBytes(StandardCharsets.UTF_8)); for (int r 0; r table.getRowCount(); r) { for (int c 0; c table.getColumnCount(); c) { Object val table.getValueAt(r, c); baos.write((val null ? : val.toString()).getBytes(StandardCharsets.UTF_8)); if (c table.getColumnCount() - 1) { baos.write(,.getBytes(StandardCharsets.UTF_8)); } } baos.write(\n.getBytes(StandardCharsets.UTF_8)); } fos.write(baos.toByteArray()); } }这段代码写的CSV没有对字段做双引号包裹正常成绩数据全是数字和姓名不会出问题但如果某个字段内容里含逗号打开时会导致错列。稳妥做法是每个字段拼入CSV前先判断是否含逗号或引号含的话用双引号包起来并把内部引号加倍。原始实现没做是因为课设成绩数据一般不涉及标点符号补上包裹逻辑也就是多一个if判断想做得严谨的可以自己加。6.3 一键打包IntelliJ IDEA的Artifacts配置程序写完后要交作业或答辩演示不能每次都在IDE里点运行。打包成可执行jar是常规操作。IDEA里的配置路径是File - Project Structure - Artifacts - 点加号 - JAR - From modules with dependenciesMain Class选择登录窗体所在的类。配置完成后Build - Build Artifacts会在out目录生成jar文件。双击运行jar若没反应大概率是双击时用的javaw.exe把标准输出吞掉了命令行用java -jar xxx.jar跑会看到具体报错。打包时容易漏掉MySQL驱动jar包IDEA的From modules with dependencies会把依赖一起打进去但要是手写MANIFEST.MF就很容易忘。我踩过的一个坑是打包后运行报「No suitable driver found」排查半天发现驱动jar没被包含进产物。验证jar包内容用jar tf命令看一眼有没有com/mysql/cj/jdbc/Driver.class的路径没有就重新检查Artifacts配置的依赖范围。带界面的jar包还有一个体验优化点把启动命令写成一个start.bat批处理内容两行——第一行设置编码chcp 65001第二行java -jar edu-admin.jar。这样用户双击bat文件启动时中文不会乱码不用手动打开命令行。交付物一般是jar加bat加一个说明文档说明文档里写明JDK版本要求Swing程序建议JDK 8以上但不要用JDK 16因为JDK 9模块化后Swing某些行为有调整和MySQL初始化步骤。从Swing学到真正能迁移到Web项目的能力是DAO层的PreparedStatement写法、事务边界意识和JDBC异常处理这套东西。Swing本身会被淘汰但这些底层的数据库交互模式是Java Web开发的起点。做这个系统的过程中如果遇到「界面加载慢」「数据不对」「连接失败」这类问题排错路径基本固定先看MySQL服务有没有启动再看驱动类名和URL参数接着看SQL在Navicat里能不能跑通最后才是Swing事件逻辑的问题。这套排查顺序是我做过几个课设项目后才总结出来的血泪经验希望能帮你在做这个方向时少走两步弯路。本文还有配套的精品资源点击获取
返回列表