Java Swing宿舍管理系统课程设计:JDBC+MySQL从建库到答辩避坑指南
发布时间:2026/9/28 3:33:59来源:尧图网络
简介面向Java课程设计的宿舍管理系统完整项目包适合正在完成GUI开发作业或想学习JDBC连接SQL Server的在校生与初学者。系统功能覆盖住宿学生信息维护、宿舍信息管理、报修处理及离返校记录查询前台基于Java Swing构建数据库连接与操作集中在DataBaseConnection类便于二次修改。压缩包共64个文件含10个Java源文件、34个class编译文件、SQL Server建库脚本、2个Jar依赖、10张运行截图及实验报告、PPT整体仅2.7MB结构简洁。已有1523人学习资源附带“使用必看”说明与数据库课程设计报告读者可快速部署项目、掌握Swing界面与JDBC的整合方式并用于课程答辩演示。1. 课程设计选型为什么宿舍管理系统还在用 Java Swing很多同学拿到课程设计任务第一反应是想用 Spring Boot Vue 搞个前后端分离结果被老师一句「我们验收只看 Java 桌面程序」打回。这里说的「基于 Java swing 带 GUI 界面的宿舍管理系统」就是课程设计里最典型的选题技术栈固定为 Java Swing JDBC MySQL交付物是源码、数据库脚本、课程设计报告和演示 PPT。这个项目难度适中但正因为门槛低老师会把重点放在数据库设计规不规范、CRUD 完不完整、界面事件健不健壮、报告能不能讲清楚。做过这个项目等于把 Java 基础、JDBC、集合、事件监听这些课设必考知识点全部串了一遍性价比很高。2. 搭建开发环境与项目骨架JDK 8 MySQL 5.7 的最小组合2.1 版本选型JDK 8 最稳驱动类名是个分水岭我一般会在这个项目上固定使用 JDK 8。原因不是更高版本不行而是答辩机器常常是实验室的老系统装的是 JDK 8你要是用 JDK 17 写 JFrame 代码大概率也能跑但万一对方机器只有一个 JRE 8你编译出来的 class 文件直接报 UnsupportedClassVersionError开场即翻车。Swing 从 JDK 9 开始没有破坏性变化课设代码也不涉及模块化JDK 8 是风险最低的选择。MySQL 版本我推荐 5.7。如果你自己电脑装的是 8.0也完全能用关键是驱动 jar 要匹配5.x 系列的驱动类名是com.mysql.jdbc.Driver8.0 以后变成了com.mysql.cj.jdbc.Driver。很多同学把 8.0 的驱动 jar 拷进项目代码还写旧类名一启动就报 ClassNotFoundException。为什么不在这个项目里引入 Maven课程设计交付的是完整工程文件夹老师拿到后要能直接打开运行。Maven 项目在没配好镜像源的机器上要下载半天依赖演示现场最容易卡壳。更稳的做法是用 IDE 新建普通 Java 项目把 mysql-connector-java-5.1.49.jar 放到项目根目录下的 lib 文件夹然后 Add to Build Path提交时把整个目录一起交。2.2 工程目录规划与 DBHelper 工具类先规划目录后面所有代码都按这个结构放报告里也好写「分层设计」DormManager/ ├── src/ │ ├── com/dorm/MainApp.java # 程序入口 │ ├── com/dorm/util/DBHelper.java # JDBC 工具类 │ ├── com/dorm/dao/ # DAO 层放 SQL 操作 │ │ ├── AdminDao.java │ │ ├── StudentDao.java │ │ ├── DormDao.java │ │ └── RepairDao.java │ ├── com/dorm/ui/ # Swing 界面层 │ │ ├── LoginFrame.java │ │ ├── MainFrame.java │ │ ├── StudentPanel.java │ │ └── DormPanel.java │ └── com/dorm/model/ # 实体类 │ ├── Student.java │ ├── Dorm.java │ └── Admin.java ├── lib/ │ └── mysql-connector-java-5.1.49.jar ├── sql/ │ └── dorm.sql └── README.txtmodel 层放实体类dao 层放 JDBC 操作ui 层只做界面展示。这个分层写进课程设计报告就是「三层结构设计」也方便后续把连接管理替换成连接池。如果进度有富余可以在 DAO 层用 DBCP 或 HikariCP 替换DriverManager.getConnection这是报告里能写的「连接池优化」亮点但课设里直接用 DBHelper 静态方法已经足够。数据库连接工具类是整个程序的地基package com.dorm.util; import java.sql.*; public class DBHelper { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/dorm ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement st, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (st ! null) st.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }URL 里三个参数是课设最容易漏的。useUnicodetrue和characterEncodingutf8是中文写入数据库不乱码的前提useSSLfalse是为了省去 MySQL 8.0 在本地环境的 SSL 握手警告serverTimezoneAsia/Shanghai是 8.0 驱动的要求5.1 驱动加不加都不报错统一写上更省心。USER 和 PASSWORD 按你自己本机改如果记不清密码ini 文件里找一下或者干脆重装 MySQL 把密码设成 123456课设阶段用默认密码最省时间。2.3 主窗体骨架CardLayout 管理页面切换登录成功之后进入主窗体。我习惯用一个 JFrame 左侧 JButton 菜单 右侧 CardLayout 容器每个功能面板独立成一个 JPanel互不干扰。package com.dorm.ui; import javax.swing.*; import java.awt.*; public class MainFrame extends JFrame { private JPanel cardPanel; private CardLayout cardLayout; public MainFrame() { setTitle(宿舍管理系统); setSize(1000, 650); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); cardLayout new CardLayout(); cardPanel new JPanel(cardLayout); cardPanel.add(new StudentPanel(), student); cardPanel.add(new DormPanel(), dorm); cardPanel.add(new RepairPanel(), repair); JPanel leftMenu new JPanel(new GridLayout(0, 1)); JButton btnStudent new JButton(学生管理); JButton btnDorm new JButton(宿舍管理); JButton btnRepair new JButton(报修管理); btnStudent.addActionListener(e - cardLayout.show(cardPanel, student)); btnDorm.addActionListener(e - cardLayout.show(cardPanel, dorm)); btnRepair.addActionListener(e - cardLayout.show(cardPanel, repair)); leftMenu.add(btnStudent); leftMenu.add(btnDorm); leftMenu.add(btnRepair); add(leftMenu, BorderLayout.WEST); add(cardPanel, BorderLayout.CENTER); } }CardLayout 的 key 就是 add 方法里的第二个字符串show(cardPanel, student)时必须完全一致否则会静默地什么都不切换这是新手容易盯半天找不出来的问题。EXIT_ON_CLOSE放在主窗体登录窗口已经 dispose进程能干净退出。左侧菜单用GridLayout(0, 1)表示一列多行按钮会垂直均匀分布比手动设坐标省事得多。3. 数据库设计五张核心表与 ER 关系怎么定3.1 需求拆解与五张表的结构宿舍管理系统本质是「人、房、记录」三类对象的组合。人包括管理员和学生房是宿舍记录包括入住历史、报修。不要一上来建十几张表课设评审时间只有几分钟五张表把主流程讲清楚就够了表名职责admin管理员账号登录用dorm宿舍基础信息楼栋、房间、容量student学生基本信息含当前宿舍和床号check_in入住/换宿/退宿流水保留历史repair报修工单记录内容和状态admin 表只做登录认证字段够用就行。student 表的dorm_id是逻辑外键指向 dorm 表的主键bed_no表示床号。check_in 表是典型的流水表每次入住插一行退宿时把 status 改成 0这样能保留入住历史报告里可以写「支持换宿追踪」。repair 表存报修内容、上报人、联系电话、处理状态。这五张表的主线是管理员登录维护学生和宿舍给学生分配宿舍形成入住记录宿舍出问题生成报修单。3.2 建库建表 SQL字符集与索引一次配好CREATE DATABASE IF NOT EXISTS dorm DEFAULT CHARACTER SET utf8mb4; USE dorm; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(30) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE dorm ( id INT PRIMARY KEY AUTO_INCREMENT, building VARCHAR(20) NOT NULL, room_no VARCHAR(20) NOT NULL, capacity INT NOT NULL DEFAULT 4, gender TINYINT NOT NULL DEFAULT 1, UNIQUE KEY uk_building_room (building, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender TINYINT NOT NULL, major VARCHAR(50), phone VARCHAR(20), dorm_id INT, bed_no INT, KEY idx_dorm (dorm_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE check_in ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, dorm_id INT NOT NULL, bed_no INT NOT NULL, checkin_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1, KEY idx_student (student_id), KEY idx_dorm (dorm_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE repair ( id INT PRIMARY KEY AUTO_INCREMENT, dorm_id INT NOT NULL, content VARCHAR(255) NOT NULL, reporter VARCHAR(30) NOT NULL, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT NOT NULL DEFAULT 0, KEY idx_dorm (dorm_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO admin (username, password, real_name) VALUES (admin, 123456, 系统管理员);建表脚本最后别忘了插入一条默认管理员账号否则登录界面永远进不去。密码我这里先放明文正确做法是存 MD5 或 SHA-256但课程设计里老师经常要现场打开数据库看密码明文还原度高一点。真正要提交报告时在报告里补一句「生产环境应加密存储」这句话在答辩时很加分。性别用 TINYINT1 代表男0 代表女不要直接存字符串。排序、统计、切换筛选都比字符串快界面层再映射成「男/女」。capacity 默认 4对应四人间。UNIQUE KEY uk_building_room (building, room_no)防止同一栋楼出现两个相同房号这是数据规范性的体现。3.3 外键用不用课设里的「逻辑外键」很多课程设计模板会在建表语句里直接写 FOREIGN KEY但我做这个项目时建议不建物理外键。原因有两个一是 Swing 的删除操作经常需要先删子表再删父表物理外键会在演示时不断弹约束异常老师会觉得你代码不稳二是课设数据量小物理外键的约束优势体现不出来。正确做法是在 ER 图上把这些字段标成逻辑外键用虚线连接然后在 DAO 层用一条 SQL 做关联查询。报告里写「为保证数据一致性对关键字段做逻辑关联并在应用层二次校验」。索引只需要加在查询频繁的字段上student 表的stu_no一进系统就要按学号查dorm 表按(building, room_no)建联合唯一索引check_in 表的student_id、dorm_id加普通索引。不要给 gender、status 这类低区分度字段加索引区分度太低MySQL 优化器根本不会走这个索引。4. 用 Swing 写 GUI登录界面、主窗体与增删改查的代码骨架4.1 登录界面JFrame JPasswordField 与防注入登录界面是第一个展示给老师的也是最容易挑刺的地方。用 JFrame 而不是 JDialog启动类直接 new LoginFrame() 更直观。密码框用 JPasswordField取密码时用getPassword()返回 char[]不要用getText()这是安全习惯。package com.dorm.ui; import com.dorm.util.DBHelper; import javax.swing.*; import java.awt.*; import java.sql.*; public class LoginFrame extends JFrame { private JTextField tfUser new JTextField(15); private JPasswordField pfPwd new JPasswordField(15); public LoginFrame() { setTitle(宿舍管理系统登录); setSize(360, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel new JPanel(new GridBagLayout()); panel.add(new JLabel(用户名:)); panel.add(tfUser); panel.add(new JLabel(密码:)); panel.add(pfPwd); JButton btn new JButton(登录); panel.add(btn); btn.addActionListener(e - doLogin()); add(panel); } private void doLogin() { String user tfUser.getText().trim(); String pwd new String(pfPwd.getPassword()); if (user.isEmpty() || pwd.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } String sql SELECT id FROM admin WHERE username? AND password?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user); ps.setString(2, pwd); ResultSet rs ps.executeQuery(); if (rs.next()) { new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 数据库异常 ex.getMessage()); } } }try-with-resources 是 JDK 7 以后的标准写法会自动关闭 Connection 和 PreparedStatement报告里可以写「使用 try-with-resources 规范管理数据库资源」。ps.setString(1, user)的参数下标从 1 开始不要写 0。这里最忌讳拼字符串 SQL老师现场会在密码框里输入一个 OR 11拼 SQL 的话直接就被穿库了用占位符是底线。4.2 学生管理面板JTable 加载与增删改查学生管理是整个系统最核心的页面。JTable 本身不存数据数据放在 TableModel 里。所以「增删改查」里的查本质是查完数据库后调table.setModel重新灌数据。下面这段是加载数据的方法也是所有列表页的模板package com.dorm.ui; import com.dorm.util.DBHelper; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.sql.*; public class StudentPanel extends JPanel { private JTable table new JTable(); private DefaultTableModel model; private JTextField tfSearch new JTextField(10); public StudentPanel() { setLayout(new BorderLayout()); model new DefaultTableModel( new String[]{学号, 姓名, 性别, 专业, 宿舍号, 床号}, 0); table.setModel(model); JScrollPane scroll new JScrollPane(table); JPanel top new JPanel(); top.add(tfSearch); JButton btnSearch new JButton(查询); btnSearch.addActionListener(e - loadData(tfSearch.getText().trim())); top.add(btnSearch); add(top, BorderLayout.NORTH); add(scroll, BorderLayout.CENTER); loadData(); } private void loadData(String keyword) { model.setRowCount(0); String sql SELECT s.stu_no, s.name, s.gender, s.major, d.room_no, s.bed_no FROM student s LEFT JOIN dorm d ON s.dorm_id d.id WHERE s.stu_no LIKE ? OR s.name LIKE ?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ResultSet rs ps.executeQuery(); while (rs.next()) { model.addRow(new Object[]{ rs.getString(1), rs.getString(2), rs.getInt(3) 1 ? 男 : 女, rs.getString(4), rs.getString(5), rs.getInt(6) }); } } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 加载学生数据失败: ex.getMessage()); } } }model.setRowCount(0)是清空表格旧数据的标准做法比重新 new DefaultTableModel 更省内存。LEFT JOIN 保证没分宿舍的学生也能显示出来宿舍列为空白比 INNER JOIN 直接丢数据好。LIKE 查询在课设数据量下没有性能问题但 keyword 里如果带了 % 或 _MySQL 会当成通配符需要时可以用 ESCAPE 处理课设阶段一般不追究。增删改的按钮逻辑可以做在同一个面板里。录入按钮弹出一个模态对话框收集学号、姓名、性别、专业、电话然后执行 INSERTbtAdd.addActionListener(e - { // 弹 JDialog 收集信息这里省略对话框内部布局 String sql INSERT INTO student (stu_no, name, gender, major, phone) VALUES (?,?,?,?,?); try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuNo); ps.setString(2, name); ps.setInt(3, gender); ps.setString(4, major); ps.setString(5, phone); ps.executeUpdate(); loadData(); JOptionPane.showMessageDialog(this, 录入成功); } catch (SQLException ex) { if (ex.getMessage().contains(Duplicate)) { JOptionPane.showMessageDialog(this, 学号已存在); } else { JOptionPane.showMessageDialog(this, 录入失败: ex.getMessage()); } } });executeUpdate()返回受影响行数这里不关心具体值但用 try-with-resources 保证连接释放。捕获 SQLException 后判断错误信息里是否包含 Duplicate是主键或唯一键冲突的常见信号用一句话提示比直接甩异常堆栈给用户强得多。这是 Java 基础里异常处理的典型应用答辩时能说清楚。4.3 宿舍状态面板用 GridLayout 画床铺宿舍分配最直观的展示方式是画格子。用一个 JPanel 套 GridLayout房间有几张床位就放几个 JLabel每个 JLabel 根据数据库里的入住状态设置背景色或文字「已住/空床」。不需要引入绘图库Swing 自带能力就够public void renderDormStatus(ListInteger occupiedBeds, int capacity) { panel.removeAll(); panel.setLayout(new GridLayout(0, 4, 8, 8)); for (int i 1; i capacity; i) { boolean occupied occupiedBeds.contains(i); JLabel bed new JLabel( occupied ? 床 i 已住 : 床 i 空床, SwingConstants.CENTER); bed.setOpaque(true); bed.setBackground(occupied ? new Color(255, 182, 193) : new Color(144, 238, 144)); panel.add(bed); } panel.revalidate(); panel.repaint(); }GridLayout(0, 4)表示列数固定 4行数随内容自动增长适合四人间布局。removeAll()之后必须调revalidate()和repaint()否则界面不会刷新这是 Swing 里最常见的坑。颜色用 RGB 构造浅红和浅绿在演示时比默认灰色直观很多截图放到 PPT 里也清晰。5. 避坑清单Swing 课程设计最容易翻车的 5 个地方这个项目我前后帮人调过很多版本翻车点高度集中。下面五个坑按出现频率排序每一条都是实际踩过的现象、原因、解决一次说清。5.1 中文乱码界面、数据库和 SQL 文件三个口子现象登录界面按钮上的中文显示成方框或者数据库里查出来是 ??????。原因Swing 界面乱码多半是操作系统默认编码不是 UTF-8数据库乱码是连接 URL 里少了characterEncodingutf8或者建库时用了默认 latin1 字符集。解决数据库连接 URL 加上useUnicodetruecharacterEncodingutf8建库语句显式写成DEFAULT CHARACTER SET utf8mb4。Swing 界面在 IDE 里把项目编码统一设成 UTF-8Windows 下如果按钮还是方框在 JVM 启动参数加-Dfile.encodingUTF-8。SQL 脚本文件本身也必须是 UTF-8 保存用记事本另存时尤其容易存成 ANSI文件里的中文在导入 MySQL 时就会变成乱码这是最阴间的坑。5.2 界面卡死JDBC 操作跑到了 EDT 线程上现象点击「查询」按钮后整个窗口变成白板过几秒才恢复Windows 标题栏显示「未响应」。原因Swing 的所有界面刷新都发生在 EDT 单线程上ActionListener 里的代码默认就在 EDT 里执行。你在里面跑 select 全表 JOIN 甚至嵌套查询数据库慢一点就把界面线程堵死了。本地数据库数据量小感觉不出来演示时一旦卡住一次就很尴尬。解决耗时操作丢到 SwingWorker 或普通线程里。最简做法是new Thread(() - loadData(keyword)).start()注意 loadData 里操作 JTable 的部分要再用SwingUtilities.invokeLater包一层。课程设计里的查询基本都是毫秒级但写线程的代码可以在报告里单独讲一节「GUI 线程优化」显得考虑周全。5.3 JTable 数据不刷新重新 setModel 而不是改单元格现象增删改之后表格还是旧数据重启才看到变化。原因JTable 显示的内容取决于 TableModel你操作的是数据库不是 TableModel。没有把数据库最新结果重新灌回 TableModel界面当然不会变。解决每次增删改成功后都调用loadData()在 loadData 开头执行model.setRowCount(0)清空再 addRow 填充。如果只改了某个单元格也可以用table.setValueAt(...)但课程设计里整表重灌最简单可靠。记住这条数据库变了不等于界面变了界面变了不等于数据库变了两者必须显式同步。5.4 驱动类名和 jar 版本不匹配ClassNotFoundException现象程序一启动就报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或者报Public Key Retrieval is not allowed。原因lib 里放的是 MySQL 8.x 驱动代码写的却是 5.x 的老类名或者反过来。8.x 驱动连 8.0 库还要求额外确认serverTimezone和 SSL 参数。解决先看 jar 版本再定类名。mysql-connector-java 5.1.x 用com.mysql.jdbc.Driver8.0.x 用com.mysql.cj.jdbc.Driver。不想纠结版本就统一用 5.1.49 配 MySQL 5.7这是最稳的组合网上能找到的课设模板大多也是这个组合。8.0 驱动连 5.7 库一般能通反过来 5.1 驱动连 8.0 库大概率报认证或 SSL 错误。5.5 打包成可执行 jar 后连不上数据库不要用绝对路径现象直接在 IDE 里运行一切正常导出 Runnable JAR 之后就报配置文件找不到或数据库连不上。原因导出 jar 时lib 里的驱动没有一并打包或者 jdbc.properties 用了绝对盘符路径D:/project/config.properties换台机器就失效。解决导出时在 IDE 里勾选 Extract required libraries into generated JAR把 mysql 驱动打进同一个可执行 jar。数据库连接参数直接写成常量放 DBHelper 里课设规模不值得引入配置文件加载逻辑。如果坚持用配置文件用class.getResourceAsStream(/config.properties)读取而不是new FileInputStream。提交前在另一台机器上试跑一遍可执行 jar这是最实在的验证方式。6. 从能跑到能答辩数据完整性验证与报告 PPT 打磨6.1 一个验证 CRUD 的测试类答辩前我习惯写一个临时入口类不启动 GUI直接对每个 DAO 方法调一遍。这不只是给老师看更是给自己省心public class DataVerify { public static void main(String[] args) { System.out.println(登录验证: AdminDao.login(admin, 123456)); System.out.println(新增: StudentDao.insert(new Student(...))); System.out.println(查询: StudentDao.findByNo(2024001)); System.out.println(更新: StudentDao.update(...)); System.out.println(删除: StudentDao.delete(1)); System.out.println(CRUD 验证通过); } }每个方法都返回 boolean跑一遍就知道哪里断。演示时如果时间紧直接把这个类的运行结果截图放报告里比界面截图更有说服力。6.2 报告与 PPT 的截图顺序报告里放三张图就够ER 图、主界面截图、增删改查前后对比截图。截图逻辑是「先建表、再插数据、再操作、最后看结果」按这个顺序拼图老师能一眼看懂数据流。PPT 不要放太多代码重点放表结构截图和运行效果图每页一句话讲清楚「这页要说明什么」。演示时先跑登录再演示一次完整的新增学生、分配宿舍、退宿流程最后点开数据库表展示数据变化整个闭环不超过五分钟。6.3 最后的习惯我去年帮一个学弟验收这个项目界面做得花里胡哨结果演示时换宿功能崩溃了两次。后来我养成的习惯是任何改动之后先用 DataVerify 把数据库层跑一遍再开 GUI 点一遍主流程。宁可界面简单也不能让老师在演示时看到异常弹窗。这个项目真正值钱的地方不是代码量而是你能把自己的数据流讲清楚把每个操作对应的 SQL 说出来把每个异常的可能原因说出来。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网