尧图精选

Spring Boot整合Hibernate与MySQL:手把手实现增删改查接口

🕒 发布时间:2026/9/7 3:59:03 📁 来源:尧图网络
简介面向Spring Boot初学者的整合示例项目基于Spring BootHibernateMySQL实现基础插入与查询功能演示从实体映射、数据访问到控制器响应的完整链路适合刚接触ORM或需要快速搭建课程设计原型的开发者。压缩包共155个文件、约34.25MB内置64个jar依赖包、6个Java源码、3个properties配置文件、2个JSP页面、2个XML配置和1个SQL建表脚本等依赖与数据库脚本均已打包创建数据库并执行SQL即可直接运行省去手动下载依赖的步骤。项目采用Spring Boot标准目录结构Hibernate实体、Repository及Controller分层清晰配合SQL脚本可快速理解表结构与对象映射关系JSP示例直观展示插入结果和查询列表便于从前端页面反向验证后端逻辑。资料已有1824人学习/下载适合作为Spring Boot整合Hibernate的入门实践参考也可为简单业务系统提供可复用的基础CRUD骨架。 最近好几个刚转后端的朋友私信我问Spring Boot配合Hibernate和MySQL到底怎么写出一个可以运行的简单例子。说实话这类需求在初学者里非常典型但网上能找到的教程要么太零碎要么版本老旧照着敲都会报错。我干脆自己新建了一个最小化的用户管理工程从依赖配置到接口联调完整走了一遍把中间踩过的坑、需要注意的版本差异、以及Hibernate在Spring Boot中扮演的角色都整理在下面。这篇文章适合刚接触Java后端、准备做课程设计或接手老项目的人照着一步步做就能得到一个可运行的增删改查接口还能理解每行配置背后的意义。1. 项目整体设计与思路拆解1.1 为什么选 Spring Boot Hibernate MySQL 这套组合先说结论这套组合在Java后端圈子里使用范围很广尤其是企业内部管理系统和中小型项目。Spring Boot负责解决项目搭建和自动配置的繁琐问题Hibernate作为JPA的标准实现负责把Java对象和数据库表之间的映射工作接过去MySQL则是免费且稳定的关系型数据库。三者配合开发效率很高。有人会问为什么不直接上MyBatis我在实际开发中两种都用过感受比较明显。Hibernate走的是“全自动ORM”路线只要实体类和表映射好基础的增删改查SQL完全不用手写Repository接口里声明一个方法名就能生成查询适合业务模型清晰、以CRUD为主的项目。MyBatis则是“半自动”SQL由自己掌控复杂报表查询、多表关联优化更灵活但随之而来的是需要维护大量XML或注解SQL。如果目标是快速理解“对象和表怎么映射”Hibernate显然是更好的切入点而且Spring Data JPA的抽象层会进一步简化数据访问代码这也是后面会看到的效果。另外Hibernate具备自动建表能力配置好ddl-autoupdate后启动时可以根据实体类自动生成或更新表结构对本地开发和演示来说非常方便。这是它吸引新手的一个亮点也是后面会聊到的需要谨慎使用的注意点。1.2 演示需求与表结构设计为了不绕弯子我设计了一个“用户管理”的小需求保存用户的用户名、邮箱和创建时间支持新增、查询、修改和删除。这个场景刚好覆盖了最常用的CRUD操作也方便演示Hibernate的映射、校验和事务控制。数据库方面我准备了一张app_user表没有直接叫user因为user在某些数据库中是保留字容易引发奇怪问题。字段设计如下CREATE TABLE app_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100), created_at TIMESTAMP );这个结构很朴素但足够说明问题。实体类中每个属性对应一个字段接下来就是通过JPA注解把它们关联起来。2. 环境准备与依赖配置2.1 开发环境版本选型与坑点开始写代码前先把版本对齐这是新手最容易翻车的地方。我用的组合是JDK 17Spring Boot 2.7.18MySQL 8.0.36Maven 3.8为什么要强调版本因为Spring Boot 3.x之后包名从javax.*迁移到了jakarta.*很多网上的老代码在Spring Boot 3项目里直接编译不通过报错类似程序包javax.persistence不存在。如果单纯为了学习我推荐先用Spring Boot 2.7.18稳定且资料多等理解了整个流程再去看Spring Boot 3的差异也不迟。另外MySQL驱动坐标在Spring Boot 2.7.18里也有变化。老教程会让你引入mysql:mysql-connector-java但新版建议用com.mysql:mysql-connector-j版本由Spring Boot自动管理不需要手动写version。如果你发现启动报错找不到驱动类优先检查是不是还在用旧坐标。2.2 用IDEA创建项目并引入核心依赖打开IDEA在Spring Initializr里选择项目Java版本选17依赖部分只需要勾选两个核心组件Spring Web和Spring Data JPA。不过它们不会自动带上MySQL驱动所以需要在生成的pom.xml里手动补一条依赖。我工程的完整核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency这里有个关键点spring-boot-starter-data-jpa内部会引入Hibernate所以不需要再单独加Hibernate的Maven依赖。如果你想自定义Hibernate版本可以额外加一条但初学者完全没必要。2.3 application.yml 配置详解依赖准备好之后在src/main/resources/application.yml里完成数据源和JPA配置。这是我实际跑通的配置spring: datasource: url: jdbc:mysql://localhost:3306/test_db?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true open-in-view: false逐个说清楚每个配置的作用不然你也只是抄了一遍。数据源部分url必须带上时区参数serverTimezoneAsia/ShanghaiMySQL 8.0对时区要求很严格不带会报“The server time zone value”错误。allowPublicKeyRetrievaltrue和useSSLfalse是配合MySQL 8.0的认证方式避免连接时出现“Public Key Retrieval is not allowed”的提示。driver-class-name用com.mysql.cj.jdbc.Driver这是新版驱动类老的这个com.mysql.jdbc.Driver已经废弃。JPA部分ddl-auto: update表示每次启动时根据实体类自动更新表结构本地开发很省事。但要注意生产环境千万别这么用一旦实体字段调整Hibernate会尝试修改表结构可能导致不可控的数据迁移操作后面我会专门说。show-sql: true可以打印Hibernate生成的SQL方便调试生产环境应关闭。open-in-view: false是很多人忽略的配置。Spring Boot默认open-in-view: true它会把数据库Session绑定到整个请求周期虽然能缓解页面渲染时的懒加载问题但长事务占用连接高并发下容易把数据库连接池拖垮所以我在演示中直接关闭。3. 核心代码实现从实体类到接口3.1 实体类与表映射创建entity包新建User.java。这里用JPA注解完成实体和表的映射package com.example.demo.entity; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name app_user) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name user_name, nullable false, unique true, length 50) private String userName; Column(name email, length 100) private String email; Column(name created_at) private LocalDateTime createdAt; PrePersist public void prePersist() { if (createdAt null) { createdAt LocalDateTime.now(); } } // 省略 getter/setter }Entity告诉Hibernate这是一个需要持久化的实体Table(name app_user)指定对应表名。主键用Id声明GeneratedValue(strategy GenerationType.IDENTITY)表示依赖数据库自增主键和前面建表SQL的AUTO_INCREMENT对应。字段映射上我刻意写了Column(name user_name)而没有依赖Hibernate的默认命名策略。虽然Hibernate 5.4之后的CamelCaseToUnderscoresNamingStrategy会自动把userName转成user_name但显式声明可以避免不同配置环境下行为不一致这个习惯在公司多人协作项目中特别重要。另外Column里的nullable、unique、length等属性会参与自动建表DDL生成这也是为什么表结构能由Hibernate自动维护。PrePersist是JPA生命周期回调在持久化之前自动填充创建时间省去手动赋值的麻烦。如果你使用Hibernate 6.3也可以直接用CreationTimestamp但PrePersist任何时候都能用。3.2 Repository 数据访问层数据访问层不需要写任何实现类Spring Data JPA会根据接口定义自动生成代理实现。在repository包中新建package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepositoryUser, Long { OptionalUser findByUserName(String userName); Query(select u from User u where u.email like %:keyword%) ListUser searchByEmail(Param(keyword) String keyword); }JpaRepositoryUser, Long内置了save、findById、findAll、deleteById等方法这些都不需要自己写代码。方法名findByUserName会被自动解析成“根据userName字段查询”Spring Data JPA会帮我们生成对应的查询。如果想写更复杂的查询就用Query这里我演示了一个按邮箱模糊查询的JPQL写法。虽然这套机制很方便但也要知道它的边界。简单的关联查询、条件查询靠命名方法很高效一旦逻辑变复杂JPQL不够用或性能有问题时应该考虑原生SQL或转用MyBatis这是架构层面的取舍。3.3 Service 与 ControllerService层负责业务逻辑和事务控制。新建UserServicepackage 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 UserService { Autowired private UserRepository userRepository; Transactional(readOnly true) public ListUser listAll() { return userRepository.findAll(); } Transactional(readOnly true) public OptionalUser getById(Long id) { return userRepository.findById(id); } Transactional public User save(User user) { return userRepository.save(user); } Transactional public User update(Long id, User user) { User existing userRepository.findById(id) .orElseThrow(() - new RuntimeException(用户不存在)); existing.setUserName(user.getUserName()); existing.setEmail(user.getEmail()); return userRepository.save(existing); } Transactional public void delete(Long id) { userRepository.deleteById(id); } }Transactional决定了方法运行在事务边界内。查询方法加readOnly true可以告诉数据库只需要只读连接有助于性能优化写方法只加Transactional确保操作失败时能回滚。当然在实际项目中我不建议直接返回实体类给前端更常见的做法是返回VO/DTO把实体和接口协议隔离。这里为了简化演示直接返回实体但在第4部分会看到这会引发一个“懒加载序列化”问题。Controller层提供REST接口放在controller包中package 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.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/users) public class UserController { Autowired private UserService userService; GetMapping public ListUser list() { return userService.listAll(); } GetMapping(/{id}) public User getById(PathVariable Long id) { return userService.getById(id) .orElseThrow(() - new RuntimeException(用户不存在)); } PostMapping public User create(RequestBody User user) { return userService.save(user); } PutMapping(/{id}) public User update(PathVariable Long id, RequestBody User user) { return userService.update(id, user); } DeleteMapping(/{id}) public void delete(PathVariable Long id) { userService.delete(id); } }RestController会把返回值自动转成JSON。RequestBody负责把前端提交的JSON绑定到User对象PathVariable从URL路径中取ID。这里要提醒一句为了代码简洁我用RuntimeException处理用户不存在的情况真实项目应该使用自定义业务异常配合全局异常处理器RestControllerAdvice统一返回错误信息否则框架默认返回的500错误页面很不友好。这只是演示理解思路就好。3.4 启动项目与接口联调一切就绪后运行标注SpringBootApplication的主类。启动日志里如果看到“Tomcat started on port(s): 8080”且没有异常说明启动成功。此时ddl-auto: update会帮我们自动创建app_user表你可以打开MySQL客户端执行SHOW TABLES;和DESC app_user;查看结果。然后拿Postman或直接命令行测试接口。我习惯用curl快速验证# 新增用户 curl -X POST http://localhost:8080/api/users \ -H Content-Type: application/json \ -d {userName:张三,email:zhangsanexample.com} # 查询列表 curl http://localhost:8080/api/users # 根据ID查询 curl http://localhost:8080/api/users/1 # 修改用户 curl -X PUT http://localhost:8080/api/users/1 \ -H Content-Type: application/json \ -d {userName:张三修改,email:newexample.com} # 删除用户 curl -X DELETE http://localhost:8080/api/users/1第一次新增后控制台会打印Hibernate生成的INSERT语句通过show-sql: true能直观看到对象到SQL的转换过程这对初学者理解ORM机制非常有帮助。4. 常见问题与排查技巧实录4.1 启动报错时区、连接与权限我重新搭建这套环境时几乎把所有新手问题都踩了个遍。最典型的是启动时报The server time zone value ???ú±ê×??±?? is unrecognized这是MySQL连接URL没有带时区参数造成的在url后面加上serverTimezoneAsia/Shanghai即可解决。另一种常见报错是Public Key Retrieval is not allowed出现在MySQL 8.0使用caching_sha2_password认证时。解决方案就是在连接URL中追加allowPublicKeyRetrievaltrueuseSSLfalse这组参数配合本地开发和测试足够。还有Access denied for user rootlocalhost这是用户名或密码配置错误或者root账号权限受限。检查application.yml里的账号密码是否与MySQL实际一致并确认账号允许从localhost连接。必要时可以通过CREATE USER demolocalhost IDENTIFIED BY password;创建一个专用账号并授权。4.2 Hibernate 自动建表方便但别依赖ddl-auto: update在演示项目里非常爽但我见过不少新手把它原封不动的带到生产环境结果就是某次代码升级时Hibernate试图把表中的字段类型改成自己想要的类型轻则锁表重则数据丢失。生产环境更稳妥的方案是使用Flyway或Liquibase这类数据库迁移工具把表结构变更脚本纳入版本管理。另外ddl-auto有四个可选值none表示不做任何DDL操作update会更新表结构create每次启动先删表再建表create-drop在关闭时删除表。初学者最容易误用create它会把已有数据全部清空。我一般本地开发用update跑测试用例时用create-drop生产环境设置为none表结构与脚本完全由迁移工具管理。还有一点Hibernate的命名策略在不同版本中行为有差异。为了让表结构可预测我建议实体类字段全部使用Column(name user_name)这样的显式写法不要靠默认策略去猜。这样就算以后升级Spring Boot导致命名策略变化也不会出现表字段对不上的问题。4.3 lazy loading 与JSON序列化失败如果你在这个简单例子基础上给User添加了一个ManyToOne关联比如一个用户关联一个部门返回JSON时很可能会遇到could not initialize proxy - no Session异常。原因是懒加载的关联属性在事务提交后又被Jackson序列化器访问此时Session已关闭。我现在的习惯是Controller层绝不直接返回实体类而是返回DTO把需要暴露的字段手动映射出去。这样既避免懒加载问题又能防止某些敏感字段如密码不小心被序列化出去。如果只做快速演示也可以在关联属性上增加JsonIgnoreProperties({hibernateLazyInitializer, handler})或者在实体属性上使用JsonIgnore但这不是根治方案。open-in-view: false会让这个问题表现得更加明显即使普通单表查询也可能因为代理对象而报错所以我建议从一开始就坚持DTO模式别让实体类承担协议层职责。4.4 事务失效的几种隐蔽情况Transactional并不是随便加个注解就能保证事务。我复盘过的失效场景包括私有方法或内部自调用。Spring的声明式事务基于代理如果save方法内部通过this.otherMethod()调用另一个Transactional方法事务不会开启因为调用发生在对象内部没有经过代理对象。异常被捕获吞掉。方法内使用try-catch捕获异常后不重新抛出Spring不会感知异常自然不会回滚。正确做法是捕获后抛出RuntimeException或者使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。抛出的是受检异常。Spring默认只对RuntimeException和Error回滚如果方法声明throws Exception且抛出了受检异常事务不会回滚。可以在Transactional(rollbackFor Exception.class)中明确指定。所以在设计Service方法时尽量保持事务边界清晰一个方法就是一个业务单元。异常处理不要放在事务方法内部处理完就结束应该让异常冒泡到边界由外面统一决策。4.5 Spring Boot 3 与 javax 迁移差异最后提一个高频问题如果你手头的项目是Spring Boot 3上面所有代码里的javax.persistence.*都要改为jakarta.persistence.*。不仅仅是实体注解javax.transaction.Transactional也要改成jakarta.transaction.Transactional。这个变化来自Jakarta EE的命名空间转移Spring官方在3.0版本直接升级了基础依赖。如果你是从旧项目升级到Spring Boot 3除了包名之外还有spring.factories配置方式的变化、Redis等客户端连接池参数变更等问题会更多。我的建议是除非有硬性需求否则学习阶段从Spring Boot 2.7开始等完全熟悉后再接触Spring Boot 3。最后再分享一个我自己的习惯每次搭这种演示工程我都会在本地MySQL中单独建一个demo_db这样的数据库避免和真实开发库混在一起。连接数据库时把账号密码放到环境变量或application-dev.yml中不要让默认配置里的明文密码被提交到代码仓库。还有启动失败时第一件事就是看target目录下有没有生成完整的类文件以及控制台最顶部的异常信息而不是只贴最后一两行错误。很多时候答案就在第一行堆栈里。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →