尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot+JavaFX+MyBatis本地桌面应用实战
简介本资源是一个基于SpringBootMyBatisJavaFX构建的轻量级档案扫描管理桌面应用面向Java全栈初学者与桌面端项目实践者聚焦于解决传统档案数字化过程中服务端逻辑、数据库交互与本地GUI界面协同开发的学习痛点。压缩包共130个文件含24个核心Java源码如LoginStageController、BusDocumentInfo等、65个XML配置/映射文件支撑Spring自动装配与MyBatis SQL绑定、4个FXML界面定义文件承载JavaFX视图层结构以及CSS样式、Properties配置等整体仅146KB结构紧凑、模块职责清晰。已有4208人学习下载适合通过小而完整的项目理解三层架构落地用SpringBoot快速搭建内嵌Web服务与依赖管理借助MyBatis实现Oracle数据库的增删改查与事务控制并依托JavaFX构建跨平台、响应式的扫描操作界面。1. 这不是个“Java桌面程序”Demo而是一套能跑通完整业务闭环的本地化管理系统SpringBoot做后端API、MyBatis管数据持久化、JavaFX搭交互界面三者在单机环境里真正拧成一股绳你见过多少所谓“SpringBoot JavaFX”项目点开一看要么是JavaFX窗体里硬塞了个RestTemplate调测试接口后端连RestController都没写全要么是MyBatis只配了application.yml连Mapper.xml都空着更别说事务和分页。这个项目不一样——它把一个真实可运行的「本地库存管理」场景从头到尾串起来了JavaFX主界面点击“新增商品”触发HTTP请求打到本机8080端口的SpringBoot ControllerController调用Service层Service用MyBatis的SelectProvider动态拼SQL查数据库H2嵌入式新增成功后JavaFX表格实时刷新连带库存预警颜色自动变红/绿。整个链路不依赖外部服务、不走公网、不启动Tomcat外置容器打包成JAR双击就能用。适合正在做课程设计、毕设选题或想落地“前后端一体化桌面应用”的开发者——尤其当你被导师问“你这JavaFX怎么跟数据库联动的”时能直接打开ProductController.java和ProductTableViewController.java两份文件指着代码说清楚每一跳。2. 项目结构与技术栈拆解三层物理隔离逻辑紧耦合的设计真相2.1 目录结构src/main下三个平行世界但package命名暴露了强绑定意图项目采用标准Maven多模块结构但实际是单模块聚合无pom.xml子模块声明目录树如下src/ ├── main/ │ ├── java/ │ │ └── com.example.inventory/ │ │ ├── config/ # SpringBoot配置类DataSourceConfig、MyBatisConfig │ │ ├── controller/ # REST接口ProductController、CategoryController │ │ ├── dao/ # MyBatis Mapper接口ProductMapper.java │ │ ├── entity/ # JPA风格实体Product.java、Category.java含Lombok注解 │ │ ├── service/ # 业务逻辑ProductServiceImpl.java含Transactional │ │ └── javafx/ # JavaFX专属包AppLauncher.java、main.fxml、controllers/ │ ├── resources/ │ │ ├── application.yml # 端口8080、H2数据库路径、MyBatis日志级别 │ │ └── static/ # 空目录预留Web资源位当前未启用 │ └── webapp/ # 空目录传统WAR部署位本项目不用 └── test/ └── java/ # JUnit5测试ProductServiceTest.java含Sql注解初始化H2数据提示javafx/包不在Spring Boot默认扫描路径内所以AppLauncher.java里必须显式调用SpringApplication.run()并传入InventoryApplication.class否则JavaFX线程无法注入Spring Bean。这是新手最容易卡住的点——不是FXML加载失败而是Autowired ProductController为null。2.2 技术栈选型逻辑为什么用H2而不是MySQL为什么MyBatis没上XMLH2数据库application.yml中配置spring.datasource.url: jdbc:h2:file:./data/inventory;DB_CLOSE_ON_EXITFALSE意味着数据文件inventory.mv.db会生成在项目根目录。好处是零安装、免运维关机重启数据不丢坏处是并发写入能力弱本项目单用户场景够用。若要换MySQL只需改三处url、username、password并确保h2依赖换成mysql-connector-java。MyBatis纯注解驱动所有Mapper接口方法均用Select、Insert、Update等注解定义SQL例如Mapper public interface ProductMapper { Select(SELECT * FROM product WHERE category_id #{categoryId} AND stock #{lowStockThreshold}) ListProduct findLowStockProducts(Param(categoryId) Long categoryId, Param(lowStockThreshold) Integer lowStockThreshold); }这种写法省去XML文件维护成本且IDE能直接跳转SQL适合中小型CRUD。但复杂动态SQL如多条件组合查询会变得臃肿——本项目用SelectProvider解决见3.2节。JavaFX与Spring Boot通信机制JavaFX线程Platform.runLater不直接调用Spring Bean而是通过HttpURLConnection发HTTP请求非RestTemplate避免引入额外依赖。ProductTableViewController.java中新增商品代码片段private void handleAddProduct() { String urlStr http://localhost:8080/api/products; try { URL url new URL(urlStr); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, application/json; utf-8); conn.setDoOutput(true); String jsonPayload new ObjectMapper().writeValueAsString(product); try (OutputStream os conn.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); } int responseCode conn.getResponseCode(); if (responseCode 201) { // CREATED refreshTable(); // 刷新JavaFX表格 } } catch (Exception e) { showErrorAlert(保存失败 e.getMessage()); } }关键点conn.getResponseCode()必须判断201而非200因为Controller返回的是ResponseEntity.created()refreshTable()需在JavaFX主线程执行否则抛IllegalStateException。2.3 模块间依赖关系图箭头方向即调用流向虚线表示跨进程通信调用方被调用方通信方式是否跨进程备注ProductTableViewControllerProductControllerHTTP POST/GET是JavaFX进程 → SpringBoot进程ProductControllerProductServiceImplSpring Bean注入否同进程IoC容器管理ProductServiceImplProductMapperMyBatis代理对象否接口代理底层JDBC执行ProductMapperH2数据库文件JDBC Driver否文件IO非网络连接注意JavaFX和SpringBoot虽在同一JVM启动AppLauncher.main()先启SpringBoot再启JavaFX Stage但HTTP通信本质是进程间调用——这意味着你能用Postman直接访问http://localhost:8080/api/products验证后端是否独立可用。这是调试分层问题的黄金法则先确认后端API能跑通再查JavaFX前端调用逻辑。3. 核心模块代码逐行解析从JavaFX按钮点击到H2数据库落盘的7个关键节点3.1 JavaFX界面层FXML绑定与事件处理的隐式契约main.fxml中商品列表表格定义TableView fx:idproductTable prefHeight400.0 prefWidth700.0 columns TableColumn textID fx:ididColumn/ TableColumn text名称 fx:idnameColumn/ TableColumn text分类 fx:idcategoryColumn/ TableColumn text库存 fx:idstockColumn/ TableColumn text操作 fx:idactionColumn/ /columns /TableView对应ProductTableViewController.java中字段注入FXML private TableViewProduct productTable; FXML private TableColumnProduct, Long idColumn; FXML private TableColumnProduct, String nameColumn; FXML private TableColumnProduct, String categoryColumn; FXML private TableColumnProduct, Integer stockColumn; FXML private TableColumnProduct, Void actionColumn; // Void类型用于放置按钮列关键细节TableColumn泛型必须与TableView泛型一致Product否则setCellValueFactory()会编译报错actionColumn用Void是因为该列只放按钮不绑定数据属性。setCellValueFactory()设置单元格值来源idColumn.setCellValueFactory(new PropertyValueFactory(id)); nameColumn.setCellValueFactory(new PropertyValueFactory(name)); // ...其他列同理这里id是Product类的getter方法名getId()不是字段名——若写成Id或ID运行时抛NoSuchMethodException且IDE不会提示。3.2 SpringBoot后端Controller接收JSON并校验的防御性写法ProductController.java中新增接口PostMapping(/api/products) public ResponseEntityProduct createProduct(Valid RequestBody Product product, BindingResult result) { if (result.hasErrors()) { throw new IllegalArgumentException(参数校验失败 result.getFieldErrors().stream() .map(e - e.getField() : e.getDefaultMessage()) .collect(Collectors.joining(; ))); } Product saved productService.save(product); return ResponseEntity.status(HttpStatus.CREATED) .header(Location, /api/products/ saved.getId()) .body(saved); }Valid触发JSR-303校验Product实体类需加注解public class Product { NotNull(message ID不能为空) private Long id; NotBlank(message 名称不能为空) Size(max 50, message 名称长度不能超过50字符) private String name; Min(value 0, message 库存不能为负数) private Integer stock; // ...getter/setter }BindingResult result必须紧跟RequestBody参数后否则Spring无法注入校验结果ResponseEntity.created()返回201 Created状态码并设置Location头指向新资源URI符合RESTful规范若校验失败手动抛IllegalArgumentException而非返回400 Bad Request因全局异常处理器GlobalExceptionHandler.java会捕获并转为统一JSON错误格式。3.3 MyBatis持久层动态SQL与事务边界的精确控制ProductServiceImpl.java中保存逻辑Service public class ProductServiceImpl implements ProductService { Autowired private ProductMapper productMapper; Override Transactional(rollbackFor Exception.class) // 显式声明回滚异常类型 public Product save(Product product) { if (product.getId() null) { productMapper.insert(product); // 新增 } else { productMapper.updateById(product); // 更新 } return product; } }ProductMapper.java中动态SQL实现Mapper public interface ProductMapper { Insert(INSERT INTO product (name, category_id, stock, created_time) VALUES (#{name}, #{categoryId}, #{stock}, NOW())) Options(useGeneratedKeys true, keyProperty id) void insert(Product product); SelectProvider(type ProductSqlProvider.class, method selectByCondition) ListProduct selectByCondition(ProductQuery query); } // 动态SQL提供类 public class ProductSqlProvider { public String selectByCondition(ProductQuery query) { return new SQL(){{ SELECT(*); FROM(product); if (query.getCategoryId() ! null) { WHERE(category_id #{categoryId}); } if (query.getLowStockThreshold() ! null) { WHERE(stock #{lowStockThreshold}); } if (StringUtils.hasText(query.getSearchKeyword())) { WHERE(name LIKE CONCAT(%, #{searchKeyword}, %)); } }}.toString(); } }注意Options(useGeneratedKeys true, keyProperty id)让MyBatis将H2生成的自增ID回填到product.id字段否则JavaFX端拿到的id仍是nullProductQuery是专门用于查询条件的DTO与Product实体分离避免污染领域模型。4. 运行与调试全流程从零开始启动的6个必做步骤及3个致命陷阱4.1 启动前检查清单6个动作决定你能否看到第一个窗口确认JDK版本项目pom.xml中java.version17/java.version必须用JDK 17JavaFX 17已集成进JDK无需额外下载检查H2数据库路径权限application.yml中./data/inventory目录需有读写权限Windows建议放在非系统盘如D:/inventory/data避免UAC拦截验证Maven依赖完整性执行mvn dependency:tree | grep -E (spring-boot|mybatis|javafx)应看到[INFO] - org.springframework.boot:spring-boot-starter-web:jar:3.1.0:compile [INFO] - org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:3.0.2:compile [INFO] - org.openjfx:javafx-controls:jar:17.0.2:compile编译JavaFX模块若用IntelliJ IDEA需在Project Structure → Modules → Dependencies中添加javafx.controls和javafx.fxml库并勾选Export设置VM Options关键运行配置中添加--module-path C:\path\to\javafx-sdk-17.0.2\lib --add-modules javafx.controls,javafx.fxml路径需替换为本地JavaFX SDK解压路径否则启动报java.lang.module.FindException: Module javafx.controls not found首次运行前清空H2数据删除项目根目录下的data/文件夹避免旧数据导致主键冲突。4.2 启动命令与预期输出终端里出现这三行才算成功在项目根目录执行mvn clean compile exec:java -Dexec.mainClasscom.example.inventory.javafx.AppLauncher终端应依次出现[INFO] Starting AppLauncher using Java 17.0.2 on DESKTOP-XXXX with PID 12345 [INFO] Started AppLauncher in 3.2 seconds (process running for 3.5) [INFO] JavaFX Application started. Server running at http://localhost:8080此时浏览器访问http://localhost:8080/actuator/health应返回{status:UP}JavaFX主窗口弹出表格显示“暂无数据”。4.3 常见问题排查3条血泪经验专治启动失败、数据不更新、HTTP 404现象1JavaFX窗口一闪而退控制台无报错原因AppLauncher.java中launch()调用位置错误。常见误写// ❌ 错误在SpringApplication.run()前调用launch() public static void main(String[] args) { launch(args); // 此时JavaFX Toolkit未初始化 SpringApplication.run(InventoryApplication.class, args); }解决必须在Spring Boot完全启动后再启动JavaFX线程// ✅ 正确用ApplicationRunner延迟执行 Component public class JavaFXStarter implements ApplicationRunner { Override public void run(ApplicationArguments args) throws Exception { Platform.startup(() - { // JavaFX初始化代码 AppLauncher.launch(AppLauncher.class, args.getSourceArgs()); }); } }现象2点击“新增商品”按钮JavaFX报错java.net.ConnectException: Connection refused原因SpringBoot服务未启动成功或端口被占用。检查application.yml中server.port: 8080是否与其他程序冲突如Chrome DevTools、旧版IDEA内置服务器。解决执行netstat -ano | findstr :8080Windows或lsof -i :8080Mac/Linux查PID杀死进程后重试或临时改server.port: 8081同步修改JavaFX端URL字符串。现象3JavaFX表格始终空白但Postman调GET /api/products能返回JSON数据原因TableView未设置ObservableList数据源或setCellValueFactory()绑定字段名与实体getter不匹配。解决在ProductTableViewController.initialize()中必须调用productTable.setItems(FXCollections.observableArrayList(productService.findAll()));检查Product类是否有getId()方法非getid()或getID()IDEA可按AltInsert生成Getter确保命名规范。5. 二次开发实战如何快速接入MySQL、增加导出Excel功能、改造为多窗口架构5.1 替换H2为MySQL5步完成数据库迁移不改一行业务代码步骤操作配置文件位置关键参数说明1添加MySQL驱动依赖pom.xmlartifactIdmysql-connector-java/artifactIdversion8.0.33/version2修改数据库连接配置application.ymlspring.datasource.url: jdbc:mysql://localhost:3306/inventory?useSSLfalseserverTimezoneAsia/Shanghai3创建MySQL数据库MySQL客户端CREATE DATABASE inventory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4初始化表结构schema.sql新建文件复制H2建表SQL调整AUTO_INCREMENT和DATETIME类型5关闭H2控制台application.ymlspring.h2.console.enabled: false避免端口冲突注意MySQL时区必须与JVM一致否则NOW()函数存入时间偏移8小时。serverTimezoneAsia/Shanghai是刚需不是可选项。5.2 增加Excel导出功能Apache POI集成与JavaFX进度条联动在ProductTableViewController.java中新增导出按钮事件FXML private void handleExportToExcel() { FileChooser fileChooser new FileChooser(); fileChooser.setTitle(导出Excel); fileChooser.getExtensionFilters().add( new FileChooser.ExtensionFilter(Excel Files, *.xlsx)); File file fileChooser.showSaveDialog(productTable.getScene().getWindow()); if (file ! null) { // 启动后台任务避免阻塞UI TaskVoid exportTask new TaskVoid() { Override protected Void call() throws Exception { ListProduct products productService.findAll(); XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(商品列表); // 写入表头 Row headerRow sheet.createRow(0); String[] headers {ID, 名称, 分类, 库存, 创建时间}; for (int i 0; i headers.length; i) { Cell cell headerRow.createCell(i); cell.setCellValue(headers[i]); } // 写入数据 for (int i 0; i products.size(); i) { Row row sheet.createRow(i 1); Product p products.get(i); row.createCell(0).setCellValue(p.getId()); row.createCell(1).setCellValue(p.getName()); row.createCell(2).setCellValue(p.getCategoryName()); // 假设已关联分类名 row.createCell(3).setCellValue(p.getStock()); row.createCell(4).setCellValue(p.getCreatedTime().toString()); } try (FileOutputStream out new FileOutputStream(file)) { workbook.write(out); } return null; } }; // 绑定进度条 ProgressBar progressBar new ProgressBar(); progressBar.progressProperty().bind(exportTask.progressProperty()); exportTask.setOnSucceeded(e - { showAlert(导出成功, 文件已保存至 file.getAbsolutePath()); }); exportTask.setOnFailed(e - { showAlert(导出失败, exportTask.getException().getMessage()); }); new Thread(exportTask).start(); } }关键点必须用Task封装耗时操作否则Excel生成过程冻结JavaFX界面progressBar.progressProperty().bind()实现进度实时更新XSSFWorkbook支持.xlsx格式比HSSFWorkbook.xls更现代。5.3 改造为多窗口架构主窗口编辑弹窗的父子级联控制当前项目所有操作在单窗口完成扩展性差。改造思路主窗口MainViewController.java只负责列表展示和新增/编辑按钮编辑弹窗EditProductDialogController.java独立FXML通过Stage模态显示弹窗关闭后主窗口监听onHidden事件刷新表格。MainViewController.java中打开弹窗逻辑FXML private void handleEditProduct() { try { FXMLLoader loader new FXMLLoader( getClass().getResource(/fxml/edit_product_dialog.fxml)); Parent root loader.load(); EditProductDialogController controller loader.getController(); controller.setProduct(selectedProduct); // 传入待编辑对象 Stage dialogStage new Stage(); dialogStage.setTitle(编辑商品); dialogStage.initModality(Modality.WINDOW_MODAL); dialogStage.initOwner(productTable.getScene().getWindow()); dialogStage.setScene(new Scene(root)); // 监听弹窗关闭事件 dialogStage.setOnHidden(e - { if (controller.isSaved()) { // controller内设标志位 refreshTable(); // 刷新主表格 } }); dialogStage.showAndWait(); // 阻塞直到弹窗关闭 } catch (IOException e) { showErrorAlert(打开编辑窗口失败 e.getMessage()); } }优势职责分离清晰编辑逻辑不污染主窗口模态窗口Modality.WINDOW_MODAL防止用户切换到主窗口操作showAndWait()保证弹窗关闭后才执行后续代码避免竞态。从那以后我每次接到“桌面后端”需求第一件事就是检查三点JavaFX线程是否绕过Spring IoC直接new对象、HTTP通信是否用HttpURLConnection而非RestTemplate减少依赖、H2路径是否写死相对路径。这三条踩过坑现在成了我新建项目的checklist前三项。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Qt QWidget界面美化工程:QSS主题、无边框窗口与高分屏适配

Qt QWidget界面美化工程:QSS主题、无边框窗口与高分屏适配

简介:这份QT界面美化小工程面向希望提升桌面软件视觉质感与交互体验的C/Qt开发者,尤其适合已掌握基础控件、想进一步学习自定义绘制的初中级程序员。项目围绕按钮、提示框、表格等常用界面元素提供可复用的美化示例,涉及QWidget重绘、QSS样式…

📅 2026/10/24 4:20:54
Windows打印机驱动开发实战:WDK源码编译、安装与调试全解析

Windows打印机驱动开发实战:WDK源码编译、安装与调试全解析

简介:这份资源面向希望深入理解 Windows 平台打印机驱动开发的开发者,尤其适合具备 C/C 基础、想通过真实案例掌握 WDK 驱动框架的中高级学习者。内容围绕打印机驱动的核心结构展开,涵盖微型驱动、端口驱动与类驱动的协同方式,并涉…

📅 2026/10/24 4:20:54
聚焦科研创新发展 夯实核心竞争力赋能高质量发展新路径

聚焦科研创新发展 夯实核心竞争力赋能高质量发展新路径

对于研究生来说,查文献、读论文、做实验和写综述往往需要投入大量时间。现在,AI工具可以辅助完成资料检索、长文本阅读、代码分析和内容整理。不同工具适合不同场景,合理搭配使用,能够减少重复劳动,提高科研效率。 **…

📅 2026/10/24 4:15:54
MORE NEWS

更多资讯

📰

MT5 ZeroMQ通信协议栈:低延迟双向IPC集成方案

简介:本资源是面向量化交易开发者与MT5平台进阶用户的MQL5-JSON-API实战工具包,聚焦于通过ZeroMQ套接字实现MetaTrader 5与外部系统间低延迟、跨语言的实时行情(Bid/Ask、OHLC等)与历史数据交互。它解决了MQL5原生网络能力受限下难…

📰

Linux登录审计:last与lastlog命令实战与排查流程

在 Linux 运维和服务器安全排查中,有一类问题非常典型:某个服务突然异常、某台机器上出现了陌生进程、某个配置被改过,排查到一半却卡在同一个问题上——这段时间到底有哪些账号登录过这台机器?如果平时没有做过登录记录留存和审计…

📰

无人机俯拍行人检测数据集:小目标、多尺度、强鲁棒性训练实战

简介:本资源是面向计算机视觉研究者与深度学习工程师的无人机视角行人检测专用数据集,聚焦于解决低空航拍场景下小目标、多尺度、遮挡严重等检测难点,适用于安全监控、应急搜救、城市交通巡检等实际应用的算法训练与模型评估。压缩包共1895个…

📰

IDEA数据库表一键生成Spring Boot+MyBatis完整代码

简介:这是一款专为IntelliJ IDEA用户设计的独立数据库代码生成插件,面向使用Spring Boot与MyBatis框架的Java开发者,尤其适用于需频繁根据数据库表结构快速搭建CRUD基础模块的中高级后端工程师。插件无需依赖项目源码,可直连数据库…

📰

Linux文件与目录rwx权限详解:从Permission denied到安全配置

在实际运维和开发中,Permission denied大概是出现频率最高的报错之一。很多人第一次遇到它时,第一反应是chmod 777,把所有权限开到最大,问题确实"消失"了,但为什么消失、这样做有什么后果,往往说…

📰

十万条数据导出实战:避免内存爆掉的终极技巧

上周帮同事处理一个后台导出需求,数据量大概十万条出头,一开始用最土的办法拼 CSV,结果运营那边打开就乱码,还丢了几列。后来换成 PhpSpreadsheet,折腾了一下午总算跑通,顺手把踩过的坑记下来,给…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬