尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spring Boot+Hibernate+MySQL事务闭环实战
简介这是一份面向Java初学者与Spring Boot入门开发者的轻量级实战项目聚焦ORM框架集成与数据库基础操作帮助学习者快速掌握Spring Boot整合Hibernate访问MySQL的核心流程。资源包含完整的可运行工程已预置全部依赖JAR包及初始化SQL脚本仅需创建对应数据库并执行脚本即可启动项目完成用户数据的插入与查询演示。压缩包共155个文件主体为64个JAR包支撑Spring Boot与Hibernate运行、6个Java源文件含实体类、Repository与Controller、3个properties配置文件含数据库连接与JPA设置以及1个SQL建表脚本结构简洁、依赖内聚便于理解项目骨架与配置逻辑。目前已有1829人学习下载适合零基础接触Spring生态、希望跳过环境搭建陷阱、直接切入CRUD实践的学习者所见即所得地掌握从配置到执行的完整链路。1. Spring Boot Hibernate MySQL 简单例子不是“Hello World”而是能跑通事务、查出真实数据、改完立刻生效的最小可验证闭环你搜到这个资源时大概率正卡在这样一个黑匣子时刻Spring Boot 启动成功控制台刷了一堆Starting Servlet WebServer但RestController返回空 JSON或者Service里调repository.save()没报错MySQL 表里却死活没新记录又或者Transactional加了日志里也显示begin transaction但异常抛出后数据还是留在库里——这不是代码写错了是整个 ORM 层和数据库连接的握手没对上。这个「简单例子」不是教你怎么写注解而是把 Spring Boot 的自动配置、Hibernate 的 Session 生命周期、MySQL 的事务隔离级别这三股绳拧成一股能拉得动真实业务的线。它面向的是刚从纯 JDBC 或 MyBatis 切过来、对SessionFactory和EntityManager还分不清但手头有个小需求必须三天内联调通的开发者。它不讲 JPA 规范史只告诉你spring.jpa.hibernate.ddl-autoupdate在开发期怎么用才不翻车GeneratedValue(strategy GenerationType.IDENTITY)为什么在 MySQL 8.0 下必须配serverTimezoneUTC才不报错以及——最关键的一点——如何用一条curl命令验证你写的PostMapping真正把数据落进了磁盘而不是停在一级缓存里。2. 项目结构与核心依赖为什么只选spring-boot-starter-data-jpa而不是spring-boot-starter-jdbc2.1 依赖选型避开hibernate-core手动引入的坑这个例子严格使用 Spring Boot 官方 Starter 机制而非手动引入hibernate-core或mysql-connector-java。原因很实际Spring Boot 2.7.x本例基于此已将 Hibernate 5.6.x 与 MySQL 8.0 驱动深度对齐手动指定版本极易触发ClassCastException: org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator这类底层类加载冲突。正确做法是在pom.xml中声明dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies提示spring-boot-starter-data-jpa内置了hibernate-core、spring-orm、spring-jdbc三者协调版本且自动注册LocalContainerEntityManagerFactoryBean。手动加hibernate-core会破坏这一契约导致EnableJpaRepositories失效或EntityManager注入为 null。2.2 目录结构实体、仓库、服务、控制器四层必须物理隔离项目采用标准分层拒绝“全塞进一个包”的新手写法。目录结构强制如下src/main/java/com/example/demo/ ├── DemoApplication.java // 启动类必须在根包下 ├── entity/ // Entity 类专属包 │ └── User.java ├── repository/ // 继承 JpaRepository 的接口 │ └── UserRepository.java ├── service/ // Service 实现类 │ ├── UserService.java │ └── UserServiceImpl.java └── controller/ // RestController └── UserController.java这种结构不是为了“看起来规范”而是让 Spring Boot 的组件扫描ComponentScan能精准定位Entity必须被LocalContainerEntityManagerFactoryBean扫描到JpaRepository接口必须被JpaRepositoryFactoryBean代理生成实现类Service必须被Autowired正确注入。若把User.java放进controller包EntityScan默认不扫该路径启动时不会报错但运行时save()永远返回null。2.3application.yml关键配置每行都是血泪经验以下配置是本例能跑通的最小必要集删掉任意一行都可能触发静默失败spring: datasource: url: jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true dialect: org.hibernate.dialect.MySQL8Dialect database-platform: mysql main: allow-bean-definition-overriding: trueserverTimezoneUTCMySQL 8.0 默认时区为SYSTEMJava 8 的LocalDateTime无时区概念不显式指定会导致java.sql.SQLException: The server time zone value XXX is unrecognizedallow-bean-definition-overriding: true当多个Configuration类定义同名 Bean 时如测试环境覆盖数据源避免BeanDefinitionOverrideExceptiondialect: MySQL8Dialect强制 Hibernate 使用 MySQL 8 专用方言否则JSON类型字段、ROW_NUMBER()窗口函数等特性无法识别ddl-auto: update开发期唯一安全选项create会清空表validate不建表none要求表必须预先存在。3. 核心代码实现从实体定义到 REST 接口的完整链路3.1User.javaIdGeneratedValue的精确写法MySQL 主键自增必须与数据库引擎强绑定InnoDB 是唯一选择。实体类需严格匹配package com.example.demo.entity; import javax.persistence.*; Entity Table(name t_user) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) // 关键必须用 IDENTITY不能用 AUTO Column(name id, nullable false) private Long id; Column(name username, length 50, nullable false) private String username; Column(name email, length 100) private String email; // 必须有无参构造函数JPA 反射要求 public User() {} // 全参构造函数方便测试 public User(String username, String email) { this.username username; this.email email; } // getter/setter 省略但实际项目中必须完整 }参数说明GenerationType.IDENTITY对应 MySQL 的AUTO_INCREMENTHibernate 会执行INSERT INTO t_user (username, email) VALUES (?, ?)后调用SELECT LAST_INSERT_ID()获取主键。若误用GenerationType.AUTOHibernate 可能生成SELECT next_val FROM hibernate_sequence而 MySQL 并无此表导致Table demo_db.hibernate_sequence doesnt exist错误。3.2UserRepository.java继承JpaRepository的零配置能力JPA Repository 不需要写 SQL但方法命名必须遵循规范package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; Repository // 显式标注增强可读性 public interface UserRepository extends JpaRepositoryUser, Long { // Spring Data JPA 自动解析方法名 User findByUsername(String username); // 生成 SELECT * FROM t_user WHERE username ? User findFirstByEmailOrderByUsernameDesc(String email); // 生成 LIMIT 1 ORDER BY }逻辑说明JpaRepositoryUser, Long的第二个泛型Long必须与User.id类型一致。若id是Integer此处写Long会导致IllegalArgumentException: Parameter value [1] did not match expected type [java.lang.Integer]。Repository虽非必需Spring Boot 会自动扫描但显式声明能让 IDE 在Autowired UserRepository时提供更准的类型提示。3.3UserService事务边界与异常传播的实操控制服务层是事务控制的核心必须明确Transactional的作用域package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; Autowired public UserServiceImpl(UserRepository userRepository) { this.userRepository userRepository; } Override Transactional // 此注解必须在 public 方法上且由 Spring 代理调用才生效 public User createUser(String username, String email) { User user new User(username, email); return userRepository.save(user); // save() 在事务内执行 INSERT } Override Transactional(readOnly true) // 查询方法加 readOnly 提升性能 public ListUser findAllUsers() { return userRepository.findAll(); } Override Transactional public void deleteUser(Long id) { userRepository.deleteById(id); // DELETE 语句在此事务中执行 } }关键细节Transactional默认传播行为是REQUIRED即“有则加入无则新建”。若createUser()内部调用另一个Transactional方法两者共享同一事务。readOnly true会禁用 Hibernate 一级缓存的脏检查减少UPDATE语句生成对纯查询提升 10%~15% 性能。3.4UserController.javaREST 接口与 HTTP 状态码的严谨对应控制器需将业务异常转化为 HTTP 状态码而非返回 200 error messagepackage com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; RestController RequestMapping(/api/users) public class UserController { private final UserService userService; Autowired public UserController(UserService userService) { this.userService userService; } PostMapping public ResponseEntityUser createUser(RequestBody User user) { // 检查必填字段 if (user.getUsername() null || user.getUsername().trim().isEmpty()) { return ResponseEntity.badRequest().build(); // 400 } User savedUser userService.createUser(user.getUsername(), user.getEmail()); return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); // 201 } GetMapping public ResponseEntityListUser getAllUsers() { ListUser users userService.findAllUsers(); return ResponseEntity.ok(users); // 200 } DeleteMapping(/{id}) public ResponseEntityVoid deleteUser(PathVariable Long id) { try { userService.deleteUser(id); return ResponseEntity.noContent().build(); // 204 } catch (Exception e) { return ResponseEntity.notFound().build(); // 404 } } }参数说明RequestBody将 JSON 自动反序列化为User对象依赖 Jackson。若前端传{username:test}而User构造函数未处理null emailsave()仍会成功因Column(nullable true)但DeleteMapping的PathVariable Long id若传字符串如abcSpring MVC 会直接返回 400无需手动校验。4. 启动与验证用 curl 和 MySQL CLI 交叉验证数据落地4.1 启动应用并观察关键日志运行DemoApplication.main()后控制台必须出现以下三类日志缺一不可数据源初始化日志证明 MySQL 连接成功HikariPool-1 - Starting...→HikariPool-1 - Start completed.若卡在Starting...检查url中的localhost:3306是否可达或 MySQL 服务是否运行。Hibernate DDL 日志证明表已创建Hibernate: drop table if exists t_userHibernate: create table t_user ...若无create table检查spring.jpa.hibernate.ddl-autoupdate是否拼写错误或Entity类未被扫描到。JPA Repository 初始化日志证明仓库可用JpaRepositoryFactoryBean: Creating repository instance for ... UserRepository若缺失检查UserRepository是否在SpringBootApplication扫描路径下。4.2 用 curl 发起真实请求并验证响应在终端执行以下命令顺序不能乱# 1. 创建用户POST curl -X POST http://localhost:8080/api/users \ -H Content-Type: application/json \ -d {username:alice,email:aliceexample.com} # 预期响应HTTP 201 JSON body 包含 id 字段 # {id:1,username:alice,email:aliceexample.com} # 2. 查询所有用户GET curl http://localhost:8080/api/users # 预期响应HTTP 200 数组 # [{id:1,username:alice,email:aliceexample.com}] # 3. 删除用户DELETE curl -X DELETE http://localhost:8080/api/users/1 # 预期响应HTTP 204无响应体验证逻辑curl命令必须返回明确的 HTTP 状态码。若POST返回 200 而非 201说明ResponseEntity.status(HttpStatus.CREATED)未生效若GET返回空数组[]但POST成功说明数据未落库需立即检查 MySQL。4.3 用 MySQL CLI 直连验证数据一致性打开 MySQL 命令行执行-- 1. 连接数据库 mysql -u root -p -- 2. 选择数据库确保 application.yml 中的 demo_db 已存在 USE demo_db; -- 3. 查看表结构确认 Hibernate 创建的字段与 Column 一致 DESCRIBE t_user; -- 4. 查询数据必须看到与 curl POST 相同的记录 SELECT * FROM t_user; -- 5. 手动插入一条记录验证双向同步 INSERT INTO t_user (username, email) VALUES (bob, bobexample.com); -- 6. 用 curl GET 验证此时 /api/users 应返回两条记录参数说明DESCRIBE t_user输出中id字段的Extra列必须为auto_increment否则GeneratedValue(strategy GenerationType.IDENTITY)失效username的Null列必须为NO对应Column(nullable false)。若DESCRIBE显示NullYES说明Column(nullable false)未被 Hibernate 解析常见原因是Entity类未被扫描或Column拼写错误。5. 避坑指南五个高频静默失败场景与根因修复5.1 现象userRepository.save()返回对象的id为null原因MySQL 表的id字段未设为AUTO_INCREMENT或IdGeneratedValue注解未同时存在。Hibernate 执行INSERT后无法获取主键值。解决登录 MySQL执行SHOW CREATE TABLE t_user;确认id字段含AUTO_INCREMENT检查User.java中Id和GeneratedValue(strategy GenerationType.IDENTITY)是否在同一字段上若使用SequenceGeneratorMySQL 不支持必须换为IDENTITY。5.2 现象Transactional无效异常后数据仍保留在库中原因Transactional方法被同类内其他方法直接调用如A.method1()调用A.method2()绕过 Spring 代理事务不生效。解决确保事务方法被外部 Bean 调用如 Controller 调 Service若必须同类调用用ApplicationContext获取自身代理((UserService) applicationContext.getBean(UserService.class)).deleteUser(id)在application.yml中添加spring.aop.proxy-target-classtrue强制 CGLIB 代理。5.3 现象findByUsername(alice)返回null但 MySQL 中存在该记录原因Hibernate 一级缓存Session 级别未刷新或Query方法未加Modifying注解。解决在UserRepository中添加Query(SELECT u FROM User u WHERE u.username :name)时若该方法用于更新必须加Modifying查询前手动清除缓存entityManager.clear()不推荐影响性能更可靠方式在UserService中用Transactional(readOnly true)包裹查询确保每次查询走数据库。5.4 现象启动时报java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext原因JDK 11 移除了 JAXB API而旧版 Hibernate 依赖它。解决在pom.xml中添加 JAXB 依赖dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency或降级至 JDK 8开发环境推荐避免兼容性问题。5.5 现象curl POST返回 400但控制台无任何错误日志原因RequestBody反序列化失败Jackson 找不到User的无参构造函数或 setter 方法。解决确认User.java有public User() {}确认所有字段有public void setXxx()方法IDE 可自动生成在application.yml中添加spring.jackson.deserialization.fail-on-unknown-propertiesfalse临时跳过未知字段。6. 进阶验证技巧用 H2 内存数据库做单元测试彻底隔离 MySQL 依赖6.1 添加 H2 依赖并配置测试专用 Profile在pom.xml中添加test范围的 H2dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scopetest/scope /dependency创建src/test/resources/application-test.ymlspring: datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: create-drop h2: console: enabled: true6.2 编写UserServiceTest验证事务回滚与数据一致性测试类需启用DataJpaTest仅加载 JPA 相关 Bean和Import测试配置package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; import static org.junit.jupiter.api.Assertions.*; DataJpaTest Import(TestConfig.class) // 加载 application-test.yml ActiveProfiles(test) class UserServiceTest { Autowired private TestEntityManager entityManager; Autowired private UserRepository userRepository; Test void testCreateUser_WithRollbackOnException() { // Given User user new User(test, testexample.com); // When User savedUser userRepository.save(user); // Then assertNotNull(savedUser.getId()); // 主键已生成 assertEquals(test, savedUser.getUsername()); // 验证数据已写入内存数据库 User found userRepository.findByUsername(test); assertNotNull(found); assertEquals(testexample.com, found.getEmail()); } }验证逻辑DataJpaTest会自动配置 H2 内存库并在每个Test方法后执行TRUNCATE确保测试间无状态污染。TestEntityManager提供flush()和clear()方法可精确控制 Hibernate Session 行为比Transactional更细粒度。6.3 用 H2 Console 实时查看内存库状态启动测试后访问http://localhost:8080/h2-consoleH2 控制台默认开启输入JDBC URL:jdbc:h2:mem:testdbUsername:saPassword: 空即可像操作 MySQL 一样执行SELECT * FROM t_user;实时验证save()是否真正触发了 INSERT 语句。这是比日志更直观的“数据落地”证据。6.4 从那以后我每次写 JPA 代码都强制走一遍这三步验证第一步curl POST后立刻SELECT * FROM t_user确认数据在 MySQL 里第二步curl GET返回的数据与 MySQL 查询结果逐字段比对排除 Jackson 序列化干扰第三步写一个DataJpaTest用TestEntityManager的flush()强制同步再find()验证缓存一致性。这三步做完我才敢把代码推到 Git。因为 Hibernate 的缓存、事务、延迟加载像一层毛玻璃不亲手擦干净永远不知道后面是清晰的业务逻辑还是一团模糊的 bug。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

C++无锁数据结构实现示例详解

C++无锁数据结构实现示例详解

前言 "无锁"(lock-free)是并发编程里被误用最多的词之一。很多资料把它简化成"不使用 std::mutex",于是把一段用 std::atomic 保护计数器的代码也称作无锁队列;另一些资料则暗示无锁一定比加锁快。这两点都不…

📅 2026/11/5 5:41:20
【算法学习笔记】LeetCode 15:三数之和(排序 + 双指针)

【算法学习笔记】LeetCode 15:三数之和(排序 + 双指针)

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

📅 2026/11/5 5:36:20
五金工具小批量多批次,网易外贸通怎么接住

五金工具小批量多批次,网易外贸通怎么接住

五金工具、紧固件、手工具这类品类,外贸生意有个共同特征:单笔金额不大,客户数量却很多,一家企业常年挂着几百个买家,每人每年下几单。 这种结构下,获客的账不是"投多少钱换多少询盘"&#xff0c…

📅 2026/11/5 5:36:20
MORE NEWS

更多资讯

📰

PySceneDetect视频场景智能检测:从零开始的完整指南

PySceneDetect视频场景智能检测:从零开始的完整指南 【免费下载链接】PySceneDetect :movie_camera: Python and OpenCV-based scene cut/transition detection program & library. 项目地址: https://gitcode.com/GitHub_Trending/py/PySceneDetect PyS…

📰

palera1n 越狱完整手把手教程:从编译到还原

palera1n 越狱完整手把手教程:从编译到还原 【免费下载链接】palera1n Jailbreak for A8 through A11, T2 devices, on iOS/iPadOS/tvOS 15.0, bridgeOS 5.0 and higher. 项目地址: https://gitcode.com/GitHub_Trending/pa/palera1n palera1n 是一款面向 ar…

📰

PySceneDetect场景检测实战指南:5种核心算法与应用技巧

PySceneDetect场景检测实战指南:5种核心算法与应用技巧 【免费下载链接】PySceneDetect :movie_camera: Python and OpenCV-based scene cut/transition detection program & library. 项目地址: https://gitcode.com/GitHub_Trending/py/PySceneDetect …

📰

rea 快速指南:三条命令逆向你的第一个二进制,每个结论带证据

rea 快速指南:三条命令逆向你的第一个二进制,每个结论带证据 【免费下载链接】rea Reverse engineer anything with agents, from app behavior down to native binaries. 项目地址: https://gitcode.com/GitHub_Trending/rea2/rea Functions 窗口…

📰

dsh-market 快速上手:DSH 插件市场 3 步装好,主题一键安装免重启

dsh-market 快速上手:DSH 插件市场 3 步装好,主题一键安装免重启 【免费下载链接】dsh-market The plugin market inside DeepSeek Harness — browse, search, one-click install DSH 可视化插件市场 项目地址: https://gitcode.com/gh_mirrors/ds/d…

📰

编译过程理解

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

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

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

📞 💬