新闻详情

新闻详情

首页 / 资讯中心 / 详情

从零实现Java图书管理系统:Swing+JDBC+MySQL实战教程

发布时间:2026/9/28 13:01:45来源:尧图网络
从零实现Java图书管理系统:Swing+JDBC+MySQL实战教程
简介Java 图书管理系统GUI图形界面版源代码及数据库是一套完整的学习案例适合计算机专业学生作为课程设计、期末大作业或项目实战的参考项目经导师指导并通过成绩 98 分。系统覆盖图书入库、借阅、归还、查询等核心功能集中体现了 Java 基础语法、面向对象、事件处理、GUI 设计与 JDBC 数据库连接等关键知识点。压缩包内共 37 个文件以 29 个 Java 源文件为主并包含 SQL 创建脚本、XML 配置文件、README 说明文档和界面图片体积仅 2.23MB目录结构清晰。目前已有 87 人学习浏览尤其适合编程初学者或正在做课程设计的同学借鉴学习。通过阅读源码与数据库脚本可了解从需求分析、界面布局到功能实现、数据表构建的完整流程同时学习代码注释风格与项目组织方式有助于提升编码规范和独立开发能力。1. 一个课设题为什么值得你从零写一遍而不是解压别人的源代码很多电脑里都躺着几个“Java图书管理系统GUI图形界面源代码及数据库”的压缩包解压之后要么缺jar包要么数据库脚本建的表和代码里对不上。这个题目的本质其实是一道组合题用Swing把窗口画出来用JDBC把数据存进RDBMS再把两者接成增删改查。它很适合正在赶课程设计的人也适合刚刷完Java基础想练手的人。最难的不是某个单一技术而是“界面、数据库、业务”三者之间的衔接。你把这套东西完整走一遍后续面试问到的JDBC连接池、PreparedStatement防注入、GUI线程模型全都有了真实场景不是背题。2. 选型决定工作量Swing还是JavaFXSQLite还是MySQL这个标题里写了GUI和数据库但没规定具体组合。选型才是第一个决定你工作量的大坑我见过有人用JavaFX写了一个月界面最后发现老师要求只能交Swing。选型一定要先问清楚交付要求再动手。2.1 Swing和JavaFX一张对比表帮你做决定先给结论课设默认选Swing理由只有一个——JDK内置不用额外装第三方依赖打包也省事。JavaFX从Java 11开始不再随JDK分发需要单独引入OpenJFX依赖IDE配置和打包又多一层变量。除非老师明确要求JavaFX否则别给自己加戏。对比项SwingJavaFX环境依赖JDK自带JDK 11需引入OpenJFX模块学习曲线低监听器模型直观中FXML与控制器分离要懂事件绑定界面美观度偏老需要自定义LookAndFeel内置CSS观感明显更现代部署打包一个jar就能跑需要包含模块路径命令行参数多资料成熟度博客、课设代码极多相对少但官方文档更正规我一般会直接在包下放一个lib/目录把MySQL驱动jar放进去然后在IDE里把该jar加入Library。这样换机器跑的时候不会出现ClassNotFound。Swing这边还有个冷门但实用的操作UIManager.setLookAndFeel(com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel)一行代码就能让默认控件不难看。2.2 数据库选型MySQL还是SQLite如果你的课设要求里写了“使用MySQL”那就直接用MySQL 8.x别用5.7因为8.0的驱动类和连接URL都比5.7简单。如果没有硬性数据库要求SQLite其实是更省事的方案——它只有一个文件不需要安装服务驱动还是一个jar。但是我从实操角度建议优先MySQL。原因有两个一是图书管理系统最常见的场景是“老师要求提供.sql脚本”SQLite没有独立建库脚本的概念二是你后续想把项目扩充成Spring Boot或MyBatis的版本时MySQL的迁移经验是通用的。SQLite更适合只想快速交差、不想装数据库服务的场景。连接URL的编码参数必须写清楚后面乱码坑全靠它jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai其中useUnicodetrue和characterEncodingutf8必须成对出现少一个就会在插入中文时出现乱码。serverTimezoneAsia/Shanghai解决的是MySQL 8.x的时区报错这个参数不加你的getConnection()会直接抛SQLException报错信息提示的是服务器时区未识别不是密码错误。2.3 三层结构别把SQL写进窗体的按钮事件里很多从压缩包里解压出来的课设代码打开就是几百行的JButton事件里面直接拼字符串执行Statement。这种代码交差可以但你要是想拿它去面试基本是减分项。建议从开始就按三层结构摆包view层放窗体dao层放数据库访问model层放实体类。不引入框架就是纯JDBC也能这样拆。推荐包结构com.example.library ├── Main.java // 入口只负责启动LoginFrame ├── db/DBUtil.java // 数据库连接工具 ├── model/Book.java // 图书实体 ├── model/Reader.java // 读者实体 ├── model/Borrow.java // 借阅记录实体 ├── dao/BookDao.java // 图书表操作 ├── dao/ReaderDao.java // 读者表操作 ├── dao/BorrowDao.java // 借阅表操作 └── view/LoginFrame.java // 登录窗体 └── view/MainFrame.java // 主窗体关键点是view层不要出现任何SQL字符串。窗体只调用BookDao.findByKeyword()之类的业务方法得到ListBook后再往表格模型里填数据。这样就算你后面把数据源从MySQL换成SQLite窗体代码一行都不用改。3. 先建库再写代码三张表的设计与JDBC连接工具类这一章的顺序很关键先建表、再写连接工具、最后才写GUI。我见过太多人一上来就调窗体调了两三天最后插不进中文字段又回头改表结构白白浪费一个晚上。3.1 图书管理系统最少需要三张表图书管理系统不是只建一张book表就够了。借书、还书、查询谁借了某本书这三个动作都会落到关联关系上。最基础的设计是图书表、读者表、借阅表三张。如果后续要做超期罚款再单独加一张penalty表现在先不碰。每张表的主键我都建议用自增id再用一个业务编号字段做唯一约束。原因很实际界面里显示给用户看的是编目号、学号这种业务编号比如“BK-0001”但程序内部关联外键时用自增id最省事。你在GUI里做下拉选择的时候拿到的应该是id展示的才是编号。3.2 建表SQL脚本主键、外键和初始化数据建库和建表脚本可以放在一个library.sql文件里导入的时候一次性执行。注意先建库再use库最后建表顺序不要倒。CREATE DATABASE IF NOT EXISTS library DEFAULT CHARSET utf8mb4; USE library; CREATE TABLE book ( id INT AUTO_INCREMENT PRIMARY KEY, book_no VARCHAR(20) NOT NULL UNIQUE COMMENT 图书编号, title VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) COMMENT 作者, publisher VARCHAR(50) COMMENT 出版社, price DECIMAL(10, 2) COMMENT 定价, stock INT DEFAULT 1 COMMENT 库存, category VARCHAR(30) COMMENT 分类 ); CREATE TABLE reader ( id INT AUTO_INCREMENT PRIMARY KEY, reader_no VARCHAR(20) NOT NULL UNIQUE COMMENT 读者编号, password VARCHAR(64) NOT NULL COMMENT 登录密码, name VARCHAR(50) NOT NULL COMMENT 姓名, phone VARCHAR(20) COMMENT 手机号, max_borrow INT DEFAULT 5 COMMENT 最大可借数 ); CREATE TABLE borrow ( id INT AUTO_INCREMENT PRIMARY KEY, reader_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, status TINYINT DEFAULT 0 COMMENT 0未归还, 1已归还, FOREIGN KEY (reader_id) REFERENCES reader(id), FOREIGN KEY (book_id) REFERENCES book(id) ); INSERT INTO book (book_no, title, author, publisher, price, stock, category) VALUES (BK-0001, Java核心技术卷I, Cay S. Horstmann, 机械工业出版社, 119.00, 3, 编程); INSERT INTO reader (reader_no, password, name, phone, max_borrow) VALUES (U2023001, 123456, 张三, 13800000000, 5);borrow表里我用的是reader_id和book_id作为外键指向两个原始表的自增主键。这样做比直接存reader_no和book_no更规范。如果你在GUI里维护的是业务编号那就需要先用编号反查一次主键id再插入借阅记录这个动作会在后面代码里体现。初始化数据必须做没有初始读者你的登录界面就永远走不到主窗口。密码字段我这里直接存了明文课设足够用如果是做毕设建议至少做一次MD5或SHA-256再入库。3.3 JDBC连接工具类把连接写在一处别到处new Connection所有DAO层需要拿数据库连接的时候都应该从一个工具类获取。这不是讲洁癖而是改数据库密码、换驱动的时候只要改一个文件。package com.example.library.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { // 驱动jar没放对位置时就会走到这里 throw new RuntimeException(MySQL驱动加载失败请检查lib目录, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }Class.forName在MySQL 8.x驱动里其实可以省略因为JDBC 4.0之后驱动通过SPI自动注册。但保留它有两个好处一是如果驱动缺失静态块会抛异常程序一启动就能发现而不是等到第一次查询才报错二是在一些老课程设计要求的JDK 8环境里显式加载驱动更稳妥。getConnection()每个DAO方法里都会调用一次用完就关闭。这一点在课设阶段没问题因为关闭连接会把物理连接也还回去量小无所谓。后面如果你认真做了连接池这段会换成从HikariDataSource拿连接。4. GUI层手把手登录、主窗口、增删改查和借阅动作这章是本项目的重心也是“GUI图形界面源代码”这个标题里藏得最深的部分。你会发现界面的代码量不大真正的复杂性在表格刷新和事件响应里。4.1 登录界面窗口、回车触发登录登录窗体的典型构成是用户名输入框、密码输入框、登录按钮。一个可以提升体验的细节给密码框绑定回车监听让用户输完密码直接按回车就能登录不用非得点按钮。package com.example.library.view; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passwordField; public LoginFrame() { setTitle(图书管理系统 - 登录); setSize(360, 200); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); setResizable(false); JPanel panel new JPanel(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(5, 5, 5, 5); userField new JTextField(15); passwordField new JPasswordField(15); JButton loginButton new JButton(登 录); // 回车触发登录不强制点击按钮 passwordField.addActionListener(e - doLogin()); loginButton.addActionListener(e - doLogin()); gbc.gridx 0; gbc.gridy 0; gbc.anchor GridBagConstraints.EAST; panel.add(new JLabel(读者编号), gbc); gbc.gridx 1; gbc.gridy 0; gbc.anchor GridBagConstraints.WEST; panel.add(userField, gbc); gbc.gridx 0; gbc.gridy 1; gbc.anchor GridBagConstraints.EAST; panel.add(new JLabel(密 码), gbc); gbc.gridx 1; gbc.gridy 1; gbc.anchor GridBagConstraints.WEST; panel.add(passwordField, gbc); gbc.gridx 1; gbc.gridy 2; gbc.gridwidth 2; gbc.anchor GridBagConstraints.CENTER; panel.add(loginButton, gbc); setContentPane(panel); } private void doLogin() { String readerNo userField.getText().trim(); String password new String(passwordField.getPassword()).trim(); if (readerNo.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 编号和密码不能为空); return; } com.example.library.dao.ReaderDao dao new com.example.library.dao.ReaderDao(); if (dao.checkLogin(readerNo, password)) { new MainFrame(readerNo).setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 编号或密码错误); } } }这段代码里的GridBagLayout是Swing里布局最麻烦的一个但它能保证窗口拉伸时控件相对位置不变。如果你觉得难记可以直接换成FlowLayout竖直排列三个组件课程设计要求不高完全够用。new String(passwordField.getPassword())是有意为之getPassword()返回的是char数组而不是String是为了避免密码字符串驻留在常量池里。Swing密码框的这个设计经常被新手忽略直接调getText()其实也能编译但会有一个IDE警告建议养成好习惯。4.2 主窗口和JTable表格模型必须和查询结果解耦主窗口通常左边一个表格上面一行操作按钮。表格装在JScrollPane里数据由DefaultTableModel承载。这里最关键的一点是查询数据后要重新给TableModel填充数据而不是再次revalidate()后者只重绘界面不会更新数据。package com.example.library.view; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.util.List; public class MainFrame extends JFrame { private JTable table; private DefaultTableModel tableModel; private JTextField searchField; private String readerNo; public MainFrame(String readerNo) { this.readerNo readerNo; setTitle(图书管理系统 - 当前读者: readerNo); setSize(900, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); String[] columns {ID, 图书编号, 书名, 作者, 出版社, 价格, 库存, 分类}; tableModel new DefaultTableModel(columns, 0); table new JTable(tableModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(table), BorderLayout.CENTER); JPanel toolbar new JPanel(); searchField new JTextField(12); JButton searchButton new JButton(查询); JButton allButton new JButton(显示全部); JButton borrowButton new JButton(借书); JButton returnButton new JButton(还书); JButton addButton new JButton(新增图书); JButton updateButton new JButton(修改图书); JButton deleteButton new JButton(删除图书); searchButton.addActionListener(e - refreshTable(searchField.getText().trim())); allButton.addActionListener(e - refreshTable(null)); borrowButton.addActionListener(e - borrowBook()); returnButton.addActionListener(e - returnBook()); addButton.addActionListener(e - openEditDialog(null)); updateButton.addActionListener(e - openEditDialog(getSelectedBookId())); deleteButton.addActionListener(e - deleteBook()); toolbar.add(new JLabel(关键字)); toolbar.add(searchField); toolbar.add(searchButton); toolbar.add(allButton); toolbar.add(borrowButton); toolbar.add(returnButton); toolbar.add(addButton); toolbar.add(updateButton); toolbar.add(deleteButton); add(toolbar, BorderLayout.NORTH); refreshTable(null); } private void refreshTable(String keyword) { tableModel.setRowCount(0); ListBook list new BookDao().findByKeyword(keyword); for (Book b : list) { tableModel.addRow(new Object[]{b.getId(), b.getBookNo(), b.getTitle(), b.getAuthor(), b.getPublisher(), b.getPrice(), b.getStock(), b.getCategory()}); } } }tableModel.setRowCount(0)是刷新表格最关键的一行。它的作用是清空所有行。如果不调用它第二次查询出来的数据会追加到旧的末尾看起来像数据重复。很多新人在这里翻车查了两次之后发现行数是两倍就是这个原因。findByKeyword方法在别的地方已经讲过了里面如果keyword为空就执行不带WHERE的查询否则按书名和作者的模糊匹配。这段代码里我用了ListBook和实体类对应了前面三层结构的model层。主窗口的布局我选了BorderLayout工具栏放在NORTH表格放在CENTER。这样窗口拉伸时表格跟随变大工具栏保持原高度行为符合直觉。4.3 图书新增和修改复用同一个对话框新增和修改本质上都是打开一个表单区别只是数据是否预填。我一般用一个showBookDialog(Book existing)方法通吃existing为null代表新增否则代表修改。private void openEditDialog(Integer bookId) { BookDao dao new BookDao(); Book book null; if (bookId ! null) { book dao.findById(bookId); } JTextField noField new JTextField(book null ? : book.getBookNo()); JTextField titleField new JTextField(book null ? : book.getTitle()); JTextField authorField new JTextField(book null ? : book.getAuthor()); JTextField priceField new JTextField(book null ? : String.valueOf(book.getPrice())); JTextField stockField new JTextField(book null ? : String.valueOf(book.getStock())); JTextField categoryField new JTextField(book null ? : book.getCategory()); JPanel form new JPanel(new GridLayout(6, 2, 6, 6)); form.add(new JLabel(图书编号)); form.add(noField); form.add(new JLabel(书名)); form.add(titleField); form.add(new JLabel(作者)); form.add(authorField); form.add(new JLabel(价格)); form.add(priceField); form.add(new JLabel(库存)); form.add(stockField); form.add(new JLabel(分类)); form.add(categoryField); int result JOptionPane.showConfirmDialog(this, form, 图书信息, JOptionPane.OK_CANCEL_OPTION); if (result ! JOptionPane.OK_OPTION) { return; } if (book null) { book new Book(); } book.setBookNo(noField.getText().trim()); book.setTitle(titleField.getText().trim()); book.setAuthor(authorField.getText().trim()); book.setPrice(Double.parseDouble(priceField.getText().trim())); book.setStock(Integer.parseInt(stockField.getText().trim())); book.setCategory(categoryField.getText().trim()); if (book.getId() null) { dao.insert(book); } else { dao.update(book); } refreshTable(null); }这段代码里两个细节可以聊聊。第一JOptionPane.showConfirmDialog接受一个JPanel作第三个参数这样就能把多个输入框组合成一个对话框窗口不额外建JFrame省事。第二Double.parseDouble没做异常捕获正式使用建议包在try-catch里对非法输入弹出提示。属于课设能跑、上线要补的类型。这里最实用的是复用逻辑唯一的区别是book.getId()是否为nullnull则走insert否则走update。DAO层内部会判断SQL是INSERT还是UPDATE。4.4 借书和还书库存扣减与归还判断借书逻辑看起来简单但要处理两个边界一是库存不能为负数二是同一本书同一读者不能重复借。我一般用事务来包住“扣库存”和“插借阅记录”两步操作。public boolean borrowBook(int readerId, int bookId) { String checkSql SELECT stock FROM book WHERE id ? FOR UPDATE; String insertSql INSERT INTO borrow(reader_id, book_id, borrow_date, due_date, status) VALUES (?, ?, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY), 0); String updateSql UPDATE book SET stock stock - 1 WHERE id ? AND stock 0; try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement check conn.prepareStatement(checkSql)) { check.setInt(1, bookId); ResultSet rs check.executeQuery(); if (rs.next() rs.getInt(stock) 0) { return false; } } try (PreparedStatement insert conn.prepareStatement(insertSql)) { insert.setInt(1, readerId); insert.setInt(2, bookId); insert.executeUpdate(); } try (PreparedStatement update conn.prepareStatement(updateSql)) { update.setInt(1, bookId); if (update.executeUpdate() 0) { conn.rollback(); return false; } } conn.commit(); return true; } catch (SQLException e) { return false; } }这里的关键是conn.setAutoCommit(false)和conn.commit()配合使用。如果不包事务只要第二步插入借阅记录成功了但第三步库存扣减失败就会出现借了一本书但库存没变化的脏数据。程序会直接崩溃吗不会但这种数据错位很难排查比报错更麻烦。FOR UPDATE是数据库层面的行锁防止两个线程同时借同一本库存只剩1的书。课程设计里多线程并发概率极低但这个写法可以在面试时当成亮点讲。还书逻辑对称把borrow表的status从0改成1同时把book表的stock加1。记得也要用同一个连接别在两个连接上执行。5. 常见问题与排查界面卡死、中文乱码、驱动找不到、数据不刷新、占位符对不上这一章写得都是真实踩过的坑按“现象 → 原因 → 解决”的顺序写。遇到报错时先翻对应条目能省下不少时间。5.1 现象点击“查询”按钮后界面直接卡死转圈圈原因数据库查询是同步阻塞的如果数据量大或者数据库响应慢Swing事件线程被占住没法处理重绘事件。解决使用SwingWorker把耗时操作丢到后台线程。searchButton.addActionListener(e - { String keyword searchField.getText().trim(); new SwingWorkerListBook, Void() { Override protected ListBook doInBackground() { return new BookDao().findByKeyword(keyword); } Override protected void done() { try { ListBook list get(); tableModel.setRowCount(0); for (Book b : list) { tableModel.addRow(new Object[]{b.getId(), b.getBookNo(), b.getTitle()}); } } catch (Exception ex) { JOptionPane.showMessageDialog(MainFrame.this, 查询失败: ex.getMessage()); } } }.execute(); });doInBackground里执行耗时任务done里更新界面。SwingWorker保证了done一定在事件线程执行所以在done里操作JTable是线程安全的。5.2 现象同一套代码在自己机器上中文正常拷到别人机器上全是问号原因MySQL客户端连接参数缺了characterEncodingutf8或者数据库/表本身的charset不是utf8mb4。解决分两步排查。先查库SHOW CREATE DATABASE library; SHOW CREATE TABLE book;如果用的是MySQL Workbench或命令行工具建的库而建库语句里没带DEFAULT CHARSET utf8mb4即使JDBC连接URL写了编码参数也会乱码。正确的做法是DDL脚本从一开始就写明表字符集已经建错的库用ALTER DATABASE library CHARACTER SET utf8mb4;和ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4;补救。5.3 现象ClassNotFoundException: com.mysql.cj.jdbc.Driver原因两个版本常见。第一种驱动jar没进classpath这是最常发生的第二种MySQL 5.x环境里用了8.x驱动初始化路径从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver。解决先确认lib/目录下有mysql-connector-java-8.x.jar或mysql-connector-j-8.x.jar并在IDE的Project Structure里Exported勾选。如果是Maven项目pom.xml里加依赖后检查maven-dependency-plugin是否把jar打进了最终的包。5.4 现象表格查询两次之后数据重复叠加行数越来越多原因DefaultTableModel不会自动清空旧数据第二次查询的结果被追加在旧行后面。解决在填充前调用tableModel.setRowCount(0);。如果是自定义TableModel对应方法是清空内部的List数据源。还有一类相似问题是在refreshTable里直接table.revalidate()这个方法只触发重绘不会清数据——重绘是视觉层面数据模型才是源头。5.5 现象SQLException: Parameter index out of range (2 number of parameters)原因PreparedStatement里的问号数量和setXxx调用数量不匹配。解决数一下SQL里的每个问号按顺序对应。另一个常见的坑是查出来的SQL字符串里混进了?字符比如WHERE title LIKE ?%这种写法把问号写进了字符串字面量参数绑定只认占位符。排查方式是把SQL打印出来直接在MySQL客户端执行一遍能跑通说明SQL没问题问题出在参数绑定。6. 从课设到面试把代码拆出DAO层顺手补上连接池课设能跑只是一个起点。Java面试里高频出现的“数据库连接池”“PreparedStatement防注入”“DAO层设计”这些词你手里的这个项目正好能当素材讲。6.1 把DAO按「查询一个对象 / 查询列表 / 插入 / 更新 / 删除」五件事归类我现在写DAO的习惯是固定五个方法签名所有业务都从这五件套组装。以BookDao为例方法SQL特点返回findById(Integer id)WHERE id?Book或nullfindByKeyword(String keyword)LIKE模糊查询ListBookinsert(Book book)INSERT返回自增idintupdate(Book book)UPDATE按id定位intdeleteById(Integer id)DELETE按id定位int这套方法的命名和语义可以直接映射到MyBatis的Mapper上以后转框架几乎没有学习成本。面试时你可以说“我这个课设虽然是纯JDBC但DAO接口设计参照了持久层框架的通用模式。”6.2 从Statement到PreparedStatement不只是防SQL注入PreparedStatement比Statement多出来的不只是setString这层反引号它还有两个实际好处一是SQL语句预编译同一条SQL重复执行只换参数数据库端省去再次解析二是类型转换交给驱动处理不用自己拼单引号和转义符。把这两点讲清楚比单说“防注入”更有说服力。6.3 连接池改写三行配置切换HikariCP课设阶段每次getConnection()都新建物理连接量小没事。但如果有一天你想把这个项目跑上真实服务器或参加比赛连接展示换成HikariCP会更稳妥。private static final HikariConfig config new HikariConfig(); private static HikariDataSource dataSource; static { config.setJdbcUrl(URL); config.setUsername(USER); config.setPassword(PASSWORD); config.setMaximumPoolSize(10); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }记得先加入hika连接池jar包。MaximumPoolSize10对于课设完全够用。真要在服务器上跑再看压力测试数据调整。这里要提醒一句如果拿到连接后不用try-with-resources关闭连接池会慢慢耗尽表现是程序跑到某个时刻突然所有页面报超时。做完这三件事你的项目就从“能交差”升级成了“能写进简历的作品集”。我最想说的是别再守着解压来的那堆源码了那个压缩包能帮你过查重但帮不了你面试。自己从零敲一遍把上面几个坑都踩过、修过这个项目的每一行代码才能真正变成你自己的东西。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

电机驱动自举电路设计指南:从原理到PCB布局的工程实践 2026/9/28 13:51:07

电机驱动自举电路设计指南:从原理到PCB布局的工程实践

手把手教你设计电机驱动的自举电路:从原理到PCB布局做电机驱动这件事,绕不开一个电路:自举电路。只要你不是用隔离电源给上管单独供电,只要你的半桥还是用IR2104、EG2134、L298N这类驱动芯片,就一定躲不开VB和VS这两个…

阅读更多 →
CLI-Anything:如何用命令行工具为Agent构建高效执行底座 2026/9/28 13:51:07

CLI-Anything:如何用命令行工具为Agent构建高效执行底座

1. 为什么“CLI-Anything”值得单独拿出来聊命令行工具正在经历一轮明显的复兴。过去几年,大量开发者习惯了图形界面和网页控制台,觉得敲命令是“上古时代”的产物。但如果你最近半年真正在项目里用过Agent类工具,就会发现一个反直觉的现象&a…

阅读更多 →
LLM论文投稿不被拒的关键:讲得通与做得全的完整实践指南 2026/9/28 13:51:07

LLM论文投稿不被拒的关键:讲得通与做得全的完整实践指南

有个现象我观察了好几年:LLM方向投稿被拒之后,作者的第一反应几乎都是“是不是我的idea不够新”,但我帮人改过的几十篇paper里,真正死在“不够新”上的其实很少。大部分reviewers给出的致命伤是另一句话——“The contribution is…

阅读更多 →
Univer 实战:Canvas 渲染与 Node.js 协同的在线表格搭建指南 2026/9/28 13:51:07

Univer 实战:Canvas 渲染与 Node.js 协同的在线表格搭建指南

1. 从“univer”这个标题说起:它到底是什么,能解决什么问题第一次看到“univer”这个词,很多人会以为是“universe”的缩写,或者某个新出的前端框架。实际上,Univer 是一套开源的、面向电子表格与文档场景的通用协同渲…

阅读更多 →
AI如何重新定义学习中的认知延迟 2026/9/28 13:51:07

AI如何重新定义学习中的认知延迟

1. “学习延迟”不是技术故障,而是认知系统固有的呼吸节奏“AI 正在改变学习的延迟”——这句话乍看像一句科技新闻标题,但如果你真把它当成一个待优化的“系统响应时间”问题来处理,那从第一秒就走偏了。我带过七届教育科技产品团队&#xf…

阅读更多 →
开关电源EMC整改:从PCB布局与变压器源头根治干扰 2026/9/28 13:51:00

开关电源EMC整改:从PCB布局与变压器源头根治干扰

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉