☰
Java Swing + MySQL 高校教材管理系统设计与实现详解
2026/10/2 2:41:07 网站建设 项目流程

简介:基于Java Swing与MySQL的高校教材管理系统,面向正在学习Java桌面应用开发和数据库课程设计的学生及开发者。系统覆盖出版社、教材类型、教材订购、入库、领用等完整业务流程,并通过正则规则确保书号符合ISBN加10位数字格式,同时区分管理员、老师、学生三种角色,可作为高校教材管理场景的参考项目,也是课程设计或毕业设计的基础。资源包共357个文件,含Java源文件、编译后的class文件、XML配置、SQL脚本和少量界面截图,整体约2.57MB,结构清晰便于按模块阅读。目前已有537人学习下载,借助源码与SQL脚本可快速搭建运行环境,结合界面预览能直观体会Swing组件布局与MySQL数据交互方式,适合用来掌握这类管理系统的整体开发思路,也为后续功能扩展提供了清晰框架。

1. 高校教材管理系统:为什么用 Java Swing 和 MySQL 而不是 Web 框架

Java+Swing+mysql高校教材管理系统,听起来是再普通不过的课设题,但它把 Java 基础语法、面向对象、GUI 事件、JDBC 和 SQL 串成了一条完整链路。对要交课程设计的人来说,它比控制台版图书管理多了一个能直观点击的界面;对刚学完 Java 基础的人来说,它逼着你去面对窗口事件、表格模型、PreparedStatement 和事务边界,而不是只写两个类就跑。这套组合能解决“教材订单靠 Excel 手工统计、库存对不上”这类小型管理问题,也够你在答辩时讲清楚界面交互、数据存储和异常处理三层逻辑。适合大二大三课设、Java 阶段综合练习,以及想从“会写语法”过渡到“能写系统”的人。

2. 教材管理系统表设计:从 ER 图到可运行建表 SQL

2.1 先梳理角色和流程:管理员、学生、教材、订单

拿到这个题目,我一般不会先写界面,而是先画实体关系。别急着搭 JFrame,JFrame 里的列最终取决于表结构,表结构改了,界面代码要重写,顺序反了很容易翻车。

一个高校教材管理系统至少要有教材、班级/学生、订单三个核心域。为什么要有学生?因为教材是发给学生的;如果只有教材和订单,演示时只能说“我录入了一本书”,评委问“这本书发给谁”就答不上来。加上班级和学生后,可以演示按班级筛订单、按学生查待领教材,业务就完整了。

主流程可以拆成四步:

  1. 管理员维护班级和学生信息,学生必须挂在某个班级下。
  2. 管理员录入教材的 ISBN、书名、作者、单价、库存。
  3. 为某位学生创建订单,一次选择多本教材。
  4. 订状态流转:待领、已领、取消。

实体关系上,一个班级有多个学生,一个学生可以有多张订单,一张订单包含多本教材,所以必须拆成订单主表和订单明细表。如果不拆主表和明细表,一份订单买了三本书就要存三行,订单号反复重复,改状态时要 UPDATE 多行,统计数量也不方便。拆成tb_order和tb_order_item之后,改订单状态只需要改主表一行,明细只负责记录书和数量。

2.2 用 MySQL 建表:字段类型、默认值和索引怎么设

如果你是刚照着 MySQL 安装教程装完 8.0,建议先用 MySQL Workbench 把下面的 DDL 跑一遍,Workbench 能看到表结构,后面写 Swing 列名也方便。这个 SQL 在 MySQL 8.0 和 5.7 都能跑。

CREATE DATABASE IF NOT EXISTS textbook_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE textbook_db; CREATE TABLE tb_class ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_class_name (name) ) ENGINE=InnoDB; CREATE TABLE tb_student ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, name VARCHAR(50) NOT NULL, class_id INT UNSIGNED NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0停用', PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no), KEY idx_class_id (class_id), CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES tb_class(id) ) ENGINE=InnoDB; CREATE TABLE tb_book ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL, title VARCHAR(100) NOT NULL, author VARCHAR(50) DEFAULT '', price DECIMAL(10,2) NOT NULL DEFAULT 0.00, stock INT UNSIGNED NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0停用', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_title (title) ) ENGINE=InnoDB; CREATE TABLE tb_order ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, student_id INT UNSIGNED NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0待领 1已领 2取消', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_student (student_id), CONSTRAINT fk_order_student FOREIGN KEY (student_id) REFERENCES tb_student(id) ) ENGINE=InnoDB; CREATE TABLE tb_order_item ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, order_id INT UNSIGNED NOT NULL, book_id INT UNSIGNED NOT NULL, quantity INT UNSIGNED NOT NULL DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY uk_order_book (order_id, book_id), KEY idx_book (book_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES tb_order(id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES tb_book(id) ) ENGINE=InnoDB;

这段 DDL 里有几个参数值得说清楚。id全部用INT UNSIGNED AUTO_INCREMENT,主键自增,而且不允许负数;如果手动插入测试数据,不要乱给 id,让它自己增长。student_no和order_no都建了唯一索引,学生重名很常见,但学号不会重;订单号用时间戳生成,唯一索引能兜住并发重复。

price用DECIMAL(10,2),绝对不用float或double。单价算总价时,浮点会丢精度,比如 19.9 存成 19.899999,打印出来很难看。stock和status用TINYINT,并且NOT NULL DEFAULT 0,这就是常说的 MySQL 设置默认值为 0。如果允许 null,Java 代码里每个取值都要判空,很啰嗦;用默认值 0,程序里可以直接rs.getInt("stock"),不会有空指针隐患。

所有外键字段都显式建了普通索引,比如idx_class_id、idx_student。虽然 InnoDB 会自动为外键建索引,但写出来能让人一眼看到查询路径。按班级查学生时,MySQL 会走idx_class_id,不用全表扫。表引擎统一InnoDB,因为后面订单要开事务,MyISAM 不支持。

建完表后可以插两条班级做基础数据:

INSERT INTO tb_class (name) VALUES ('软件2101'), ('软件2102');

插入时没有指定create_time和status,它们会自动走默认值。如果你在管理界面看到某行停用了,说明status被改成 0,这种逻辑删除在教材订单场景里比物理删除更安全。

3. Swing 主界面实操:JFrame 布局、JTable 数据展示和增删改查事件

3.1 能直接跑的 Swing 最小骨架:JFrame + BorderLayout

表结构定了以后,Swing 要做的事就是显示数据、接收输入、调用 JDBC。我的起步代码一般是这样,一个主窗口加一个查询区,中间放表格,底部放状态栏,整个窗体用BorderLayout分三块。

public class MainFrame extends JFrame { private JTextField keywordField = new JTextField(15); private JTable table = new JTable(); public MainFrame() { setTitle("高校教材管理系统"); setSize(960, 600); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); buildContentPane(); } private void buildContentPane() { JPanel top = new JPanel(); top.add(new JLabel("教材名称/ISBN:")); top.add(keywordField); JButton searchBtn = new JButton("查询"); top.add(searchBtn); add(top, BorderLayout.NORTH); add(new JScrollPane(table), BorderLayout.CENTER); JLabel statusLabel = new JLabel("就绪"); add(statusLabel, BorderLayout.SOUTH); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new MainFrame().setVisible(true)); } }

这段代码里最值得注意的不是布局,而是SwingUtilities.invokeLater。它把窗口创建丢到事件派发线程,也就是 EDT 上执行。Swing 的组件不是线程安全的,直接在 main 线程setVisible,窗口弹出来后和重绘线程抢资源,偶发卡死很难查。用invokeLater是标准写法,答辩时被问到界面为什么卡,这就是第一条防线。

BorderLayout是主窗体最省事的布局。北边放查询工具条,中间放表格,南边放状态栏,东西两边不占位置。JTextField(15)的 15 是期望列数,不是像素,关键词输入框够用了。setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)保证点关闭按钮时整个进程退出,不用额外写窗口事件。

3.2 把查询结果塞进 JTable:DefaultTableModel 的写入与刷新

初学者最容易在这里把 JTable 当二维数组用。实际上 JTable 只负责渲染,真正存数据的是TableModel。快速做法是用DefaultTableModel,把列名和初始行数传进去,再通过addRow一行行加。下面就是一个按关键词查询教材的完整方法,先用最直观的DriverManager写法,第 4 章再换成连接池。

private void loadBooks() { String keyword = keywordField.getText().trim(); DefaultTableModel model = new DefaultTableModel( new Object[]{"ID", "ISBN", "书名", "作者", "单价", "库存"}, 0) { @Override public boolean isCellEditable(int row, int column) { return false; } }; table.setModel(model); String url = "jdbc:mysql://localhost:3306/textbook_db" + "?serverTimezone=Asia/Shanghai&useSSL=false" + "&characterEncoding=utf8&allowPublicKeyRetrieval=true"; String sql = "SELECT id, isbn, title, author, price, stock FROM tb_book " + "WHERE title LIKE ? OR isbn LIKE ? ORDER BY id DESC"; try (Connection conn = DriverManager.getConnection(url, "root", "123456"); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + keyword + "%"); ps.setString(2, "%" + keyword + "%"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { model.addRow(new Object[]{ rs.getInt("id"), rs.getString("isbn"), rs.getString("title"), rs.getString("author"), rs.getBigDecimal("price"), rs.getInt("stock") }); } } } catch (SQLException e) { JOptionPane.showMessageDialog(this, "查询失败:" + e.getMessage()); } }

这段代码里有几个容易被忽略的细节。isCellEditable返回 false 很关键,默认 JTable 双击单元格会进入编辑状态,但课设里不应该让用户直接在表格里改 ID,关闭单元格编辑能省掉很多脏数据。SQL 里必须用?占位符,不要用字符串拼接,用户输入一个单引号就能把 SQL 语法破坏掉,PreparedStatement 的setString会处理转义,这是最基本的防注入手段。

setString(1, "%" + keyword + "%")实现了模糊查询,注意百分号必须放在参数值里,不能写进 SQL 模板。ORDER BY id DESC表示新增的教材排在前面,这个排序只针对当前查询结果,数据库负责排序比 Java 里用Comparator再排一次简单得多。rs.getBigDecimal("price")对应表里的DECIMAL字段,如果改成getFloat,19.90 可能变成 19.9,表格显示不整齐。

按钮事件是增删改查的入口。搜索按钮直接调loadBooks(),新增和修改按钮弹一个模态对话框。我一般让BookDialog同时处理新增和修改,构造参数传null表示新增,传非 null 表示要修改某条记录。

searchBtn.addActionListener(e -> loadBooks()); addBtn.addActionListener(e -> new BookDialog(this, null).setVisible(true)); updateBtn.addActionListener(e -> { int row = table.getSelectedRow(); if (row < 0) { JOptionPane.showMessageDialog(this, "请先选择一行"); return; } int id = (int) table.getValueAt(row, 0); new BookDialog(this, id).setVisible(true); });

Lambda 表达式是 Java 8 之后的写法,本质上还是ActionListener的匿名内部类,只是代码更短。table.getSelectedRow()返回的是视图行号,不是数据库主键;取主键要用table.getValueAt(row, 0),因为第一列存的是 ID。弹窗记得setModal(true),否则父窗口还能被点击,保存后刷新时机很难控制。模态弹窗关闭后,父窗口再调用一次loadBooks(),表格数据就刷新了。

4. 用 JDBC + Druid 连接池访问 MySQL:驱动、URL 参数与事务边界

4.1 用 Druid 连接池替换 DriverManager:配置和 DBUtil

第 3 章的loadBooks还在用DriverManager。它能跑,但每点一次查询就新建一次 TCP 连接到 MySQL,课设数据量小看不出问题,演示时一旦数据库连接被占用、超时,很多问题就变成玄学。更常见的做法是引入 Druid 连接池:连接提前建好并复用,避免反复创建和销毁连接带来的开销。

在src/main/resources下建一个druid.properties:

driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/textbook_db?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true username=root password=123456 initialSize=5 maxActive=20 maxWait=3000

连接池工具类写成一个DBUtil,整个项目只初始化一次:

public class DBUtil { private static DataSource dataSource; static { try (InputStream in = DBUtil.class.getResourceAsStream("/druid.properties")) { Properties props = new Properties(); props.load(in); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

driverClassName=com.mysql.cj.jdbc.Driver是 MySQL 8 的驱动路径,老一点 MySQL 5.x 用的是com.mysql.jdbc.Driver,不要混用。URL 上的serverTimezone=Asia/Shanghai必须加,MySQL 8 驱动不指定时区会直接报错。allowPublicKeyRetrieval=true解决 MySQL 8 默认认证插件caching_sha2_password首次握手拿不到公钥的问题,这个参数丢了,启动时就会报连接失败。useSSL=false只是本地课设的省事做法,生产环境不能为了省事关 SSL,内部系统也要单独评估证书方案。

initialSize=5表示启动时预建 5 个连接,maxActive=20是最大连接数,maxWait=3000是等不到连接时最多等 3 秒。对课程设计来说,5 到 20 个连接完全够用;如果你在答辩时说“我用线程池管理连接”,面试官一般会追问 maxWait 为什么是 3000,回答“避免请求无限阻塞”就行。

DBUtil.getConnection()拿到的 Connection 用完必须 close。注意连接池的 close 不是真的断开,而是把连接还回池子,所以必须放在try-with-resources或 finally 里。第 3 章的loadBooks改成Connection conn = DBUtil.getConnection(),其余代码完全不用动,这就是封装的好处。

4.2 教材订购事务:同一个 Connection 保证数据一致性

只做教材表的增删改查,用不到复杂事务。但一旦做“学生下单”,就必须面对订单主表、订单明细、库存扣减三个操作,这三件事不能只成功一半。比如主表插入成功、明细插入失败,学生就会看到一张没内容的订单;如果库存扣了但订单没生成,账就对不上。

下面这段createOrder是一个典型的事务方法:

public void createOrder(int studentId, List<Integer> bookIds) throws SQLException { Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); String orderNo = "SO" + System.currentTimeMillis(); int orderId; try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO tb_order(order_no, student_id, status) VALUES (?,?,0)", Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, orderNo); ps.setInt(2, studentId); ps.executeUpdate(); try (ResultSet keys = ps.getGeneratedKeys()) { keys.next(); orderId = keys.getInt(1); } } try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO tb_order_item(order_id, book_id, quantity) VALUES (?,?,1)")) { for (Integer bookId : bookIds) { ps.setInt(1, orderId); ps.setInt(2, bookId); ps.addBatch(); } ps.executeBatch(); } try (PreparedStatement ps = conn.prepareStatement( "UPDATE tb_book SET stock = stock - 1 WHERE id = ? AND stock > 0")) { for (Integer bookId : bookIds) { ps.setInt(1, bookId); if (ps.executeUpdate() == 0) { throw new SQLException("库存不足,教材ID=" + bookId); } } } conn.commit(); } catch (SQLException e) { try { conn.rollback(); } catch (SQLException ignored) {} throw e; } finally { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} conn.close(); } }

setAutoCommit(false)是事务开始的位置,这之后的 SQL 都在同一个数据库连接上执行,直到commit()才一起生效。这里的连接必须来自同一个DBUtil.getConnection(),不能一个操作拿一次连接,否则每个连接各自提交,事务就失效了。

Statement.RETURN_GENERATED_KEYS让INSERT之后能拿到自增主键,通过getGeneratedKeys()读取,否则你刚插进去的订单 ID 只能靠再查一次表获取,既慢又有并发隐患。订单明细用addBatch()批量执行,executeBatch()一次把所有明细发给 MySQL,比循环执行单条 INSERT 快很多,也减少网络往返。

最后扣库存的 SQL 带了AND stock > 0,这是保证数据一致性的核心。两个人同时抢最后一本教材时,MySQL 行锁会让其中一个先执行成功,另一个执行时库存已经是 0,影响行数为 0,然后抛异常回滚。如果没有这个条件,第二个请求会把库存扣成负数,演示现场就会出现库存 -1 的尴尬结果。这其实也是 Java 面试八股文里经常问的“怎么保证数据一致性”的现实版本,你只要把这段逻辑讲清楚,比死记概念有用得多。

finally里必须把setAutoCommit(true)恢复再close()。如果不恢复,连接归还给 Druid 后还带着事务模式,下一次正常查询也可能被包进一个没提交的事务里,查完数据不释放锁,连接池很快耗尽。

5. 避坑指南:MySQL 连接、中文乱码和 Swing 界面刷新问题

5.1 MySQL 服务没起来或端口不对:ERROR 2002 与 Communications link failure

现象:无论是 MySQL 客户端还是 Java 程序,都报Communications link failure;在 Linux 上直接用 mysql 命令登录,还会看到ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。

原因:MySQL 服务没启动,或者 JDBC URL 里的端口和实际不一致。ERROR 2002 不是密码错,而是客户端连不到 socket 文件。刚在 Linux 上安装 MySQL 后最容易遇到这个,服务默认不一定开机启动。

解决:先确认服务状态,systemctl status mysqld或service mysql status;Windows 用户在“服务”窗口里看 MySQL80 是否在运行。服务起不来就看日志/var/log/mysqld.log,常见原因是 datadir 目录权限不对。确认 MySQL 能通过命令行登录后,再去调 Java 程序。这个排查顺序能省很多时间,不要一上来就改密码或重装。

5.2 中文乱码:建库、连接串、IDE 编码要统一

现象:Swing 界面显示中文正常,但数据库里存的是???;反过来数据库里是中文,Java 读出来又变成乱码。

原因:连接串没有characterEncoding=utf8,或者建库/建表默认字符集还是 latin1。Java 代码、JDBC 连接、MySQL 表结构三段编码只要有一段不一致,中文就保不住。

解决:统一用 utf8mb4,建库时写DEFAULT CHARACTER SET utf8mb4,连接串加characterEncoding=utf8。如果表已经建成 latin1,先用mysqldump备份,再执行ALTER TABLE tb_book CONVERT TO CHARACTER SET utf8mb4;。这种转换操作先备份再动手,否则连后悔药都没有。另外,Java 8 环境还要检查 IDE 的 file.encoding,源码文件是 UTF-8,编译时也必须是 UTF-8,否则中文字符串在 class 文件里就是乱码。

5.3 Swing 界面假死:数据库查询不能放在事件线程

现象:点击查询按钮后,窗口卡成白屏,拖动也没响应,过几秒才弹出结果。

原因:ActionListener里的代码就是在 EDT 上执行的。数据库查询是阻塞操作,EDT 被卡住后,Swing 没机会处理重绘,整个界面就“死”了。

解决:把数据库操作放到SwingWorker.doInBackground()里,完成后在done()再更新表格。骨架代码是这样的:

new SwingWorker<List<Object[]>, Void>() { @Override protected List<Object[]> doInBackground() throws Exception { // 在这里执行 JDBC 查询,返回 List<Object[]> return rows; } @Override protected void done() { try { List<Object[]> rows = get(); // 在这里更新 JTable 和状态栏 } catch (Exception e) { JOptionPane.showMessageDialog(MainFrame.this, "查询失败:" + e.getMessage()); } } }.execute();

execute()会在 Swing 的后台线程池里跑,不会阻塞 EDT;done()又回到 EDT 更新 UI,避免了后台线程直接操作 JTable 引发的线程竞争。这个改进量不大,但能在答辩时说明你理解 EDT,比只会new Thread的处理方式高一个层次。

5.4 MySQL 8 认证和 SSL 错误:Public Key Retrieval 与 SSL connection error

现象:第一次连 MySQL 8 报Public Key Retrieval is not allowed,或者SSL connection error: ...,旧版本驱动还会报Unable to load authentication plugin 'caching_sha2_password'。

原因:MySQL 8 默认认证插件是caching_sha2_password,连接前需要获取服务器公钥;驱动版本太旧,或者 URL 里没允许公钥检索,就会握手失败。SSL 错误一般是 URL 和 MySQL 服务端 SSL 配置不匹配导致的。

解决:驱动类换成com.mysql.cj.jdbc.Driver,使用 mysql-connector-j 8.x,URL 上补全allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai。这几个参数每次写项目都要用,最好直接放在druid.properties里,不要每次手敲。注意useSSL=false只是本地开发环境图省事,生产环境不要照抄。

5.5 外键约束导致删除失败:先删子表或做逻辑删除

现象:在管理界面删一个班级,弹窗报Cannot delete or update a parent row: a foreign key constraint fails。

原因:tb_student.class_id还引用着这个班级,InnoDB 的外键约束不允许直接删除有子记录的父表。

解决:不要硬删。两个做法,先删除学生或把学生的班级置空,再删班级;或者在删除按钮里改成逻辑删除,把status改成 0,界面默认不显示停用数据。对教材订单场景来说,逻辑删除更合理,因为历史订单里的学生信息不能从班级维度消失,否则后续统计“软件2101班订了多少书”就对不上。建表时多留一个status字段,就是为了这里能软删,而不是等报错再想办法找后悔药。

6. 打包成可运行 Jar 并给老师演示的收尾技巧

系统写完,最怕在老师电脑上打不开:双击 jar 没反应,或者数据库连不上。我一般会先做三件事:统一编译版本、打可执行 jar、准备一份干净的数据备份。如果工程是 Maven 管理的,用maven-shade-plugin把依赖打进去,启动类配成MainFrame;如果没有 Maven,在 IntelliJ 里用Artifacts -> JAR -> From modules with dependencies也能达到同样效果。打包后注意druid.properties必须一起进 jar,否则DBUtil.getResourceAsStream("/druid.properties")会返回 null,报ExceptionInInitializerError,而且错误信息非常不明显。

我还有个习惯,在MainFrame构造时先检查一次数据库连接,比用户点到某个按钮才报错体面很多:

try (Connection c = DBUtil.getConnection()) { JOptionPane.showMessageDialog(null, "数据库连接正常"); } catch (SQLException e) { JOptionPane.showMessageDialog(null, "请先启动 MySQL,并导入 textbook_db.sql"); }

这段代码在打包前跑一遍,能确认本机能连上;在老师电脑上跑一遍,能把“连不上数据库”的问题定位在环境上,而不是系统代码上。演示前用mysqldump -uroot -p textbook_db > textbook_demo.sql导一份干净数据,不要把课堂上乱点的脏数据直接带上演示机,否则查出来一堆测试记录很尴尬。我自己课设时,吃过“忘记恢复 autoCommit”的亏,也吃过“数据库字符集不一致”的亏,这些问题提前暴露比现场翻车好得多。用这个顺序把表、界面、连接池、事务逐层过一遍,答辩时心里就有底了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询