Spring Boot+Hibernate+MySQL从零实现用户增删改查实战

发布时间:2026/9/7 14:40:28
Spring Boot+Hibernate+MySQL从零实现用户增删改查实战 简介这是一份面向Spring Boot初学者的轻量级实战项目演示如何整合Hibernate与MySQL完成基本的插入和查询操作。项目将依赖的64个jar包一并打入压缩包省去手工配置Maven的麻烦同时附带了SQL建表脚本读者只需创建数据库并执行脚本即可运行。整个包共155个文件总大小34.25MB除核心的6个Java源码、6个编译后的class文件外还包含3个properties配置、2个JSP页面、2个XML配置文件以及1个SQL脚本代码量精简目录结构直观。目前已有1824人学习适合正在学习Spring Boot持久层开发、希望快速理解Hibernate映射与事务管理的初学者。通过该实例可以掌握application.properties中的数据库连接配置、实体类注解映射、基于Hibernate的增删查改写法以及Controller与JSP页面的简单交互为进一步开发复杂业务打下基础。 先别急着写代码。我见过太多新手拿到 Spring Boot 项目后第一步就是往里面塞依赖然后启动报错最后卡在数据库连接上。这套 Spring Boot Hibernate MySQL 的组合说白了就是解决一件事怎么让 Java 程序把对象存进关系型数据库再读出来。如果你正准备搭一个带数据库的 Java Web 项目或者正在学 Spring Boot 不知道数据层怎么写这篇文章就是照着抄就能跑通的那一份。我会从一个真实的“用户表增删改查”例子讲起不绕弯子把依赖、配置、实体类、Repository、Controller 一条链路全部写完。中途会解释每个关键参数为什么这么配也会把我踩过的坑直接列出来——这些坑文档里一般不会写。1. 动手前先想清楚这套技术组合到底在做什么1.1 JPA、Hibernate、Spring Data JPA 是什么关系很多新人第一次看到这三个名词直接蒙了我到底用的哪个一句话解释JPA 是一套规范像一份“接口合同”Hibernate 是这个合同的实现真正干活的 ORM 框架Spring Data JPA 是 Spring 在 JPA 规范之上又包了一层“快捷方式”让你少写实现类。打个比方JPA 是“驾驶规则”Hibernate 是“一辆实际的车”Spring Data JPA 则是“自动驾驶辅助”。你用 Spring Boot 写项目时引入 spring-boot-starter-data-jpa 这个依赖底层默认就是 Hibernate但你在代码里写的是 JPA 标准注解和 Spring Data JPA 的接口方法所以哪天想换实现框架业务代码基本不用动。1.2 我的选型考量JDK、MySQL 版本、IDE先说 JDK。如果你用的是 Spring Boot 3.xJDK 必须 17 以上Spring Boot 2.x 用 JDK 8 也能跑。2024 年之后我建议直接上 JDK 17 加 Spring Boot 3.x别守着旧版本新项目没必要给自己埋兼容性包袱。再说 MySQL。8.0 系列是当前最主流的选择驱动类名、时区处理、认证方式都和 5.x 有差别网上很多教程还在用旧版驱动类 com.mysql.jdbc.Driver放到 8.0 上会直接报错。新版要用 com.mysql.cj.jdbc.Driver这点后面配置章节会重点说。IDE 的话IDEA 社区版或者免费版都够用别纠结工具重点是项目能跑。还有一个小细节初始化项目和首次下载依赖时网络容易超时尤其在国内网络环境下解决办法是配置 Maven 阿里云镜像这个我放在下一节操作里。2. 搭一个能跑起来的 Spring Boot 项目创建、依赖、启动类2.1 项目创建两种常见的坑与解决方式创建 Spring Boot 项目主流是去 start.spring.io 生成压缩包或者在 IDEA 里直接用 Spring Initializr 新建。IDEA 新建时容易遇到“Initialization failed for ‘https://start.spring.io’”这个报错本质就是连不上初始化服务我碰到过好多次。解决办法有两个一是把初始化服务的 URL 改成阿里云镜像 https://start.aliyun.comIDEA 里在设置项的 HTTP Proxy 或者 Project Defaults 里能找到二是直接去官网下载压缩包再解压导入。我更推荐第二种简单粗暴还能顺便看一眼默认生成的目录结构。项目结构上第一次做建议就保持默认启动类放在外层包下controller、service、entity、repository 全部放在启动类的子包或同级子包内否则 Spring 扫描不到 Bean启动时会出现“Consider defining a bean”之类的问题。2.2 引入依赖版本管理交给 Spring Bootpom.xml 里我最小化引入三个依赖spring-boot-starter-web、spring-boot-starter-data-jpa、mysql-connector-j。注意不需要手动指定版本号Spring Boot 的 parent 已经把兼容版本管好了。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 groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies这里有个细节新版 Spring Boot 中 mysql-connector-j 的 artifactId 变了老版本用的是 mysql-connector-java。虽然老坐标还能用但官方已经把新坐标统一为 mysql-connector-j直接用新的就行。依赖导入完成后先不要写任何业务代码直接启动一次空项目。如果控制台出现 Spring Boot 的启动日志并且 HTTP 端口 8080 正常起监听说明环境基本通了。这一步能帮你在后续真正开工前排除掉大部分环境问题。3. 数据库设计、数据源配置与 Hibernate 参数配置3.1 先建库还是先建表我的习惯是先建库Hibernate 能帮你自动建表但建库这件事它不管。所以第一步是连上 MySQL 手动创建数据库。命令行连上 MySQL 或者用 Navicat、MySQL Workbench 都行我习惯执行这条 SQLCREATE DATABASE IF NOT EXISTS demo_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;字符集用 utf8mb4 而不是 utf8是为了能正确存储中文和 emoji 表情。很多老项目出现中文乱码根子就在库和表的字符集不是 utf8mb4。至于要不要让 Hibernate 自动建表开发阶段可以生产环境千万慎用。我的建议是开发时用 update 方便迭代上生产前把 ddl-auto 关掉或者改成 validate表结构用专门的迁移脚本管理。3.2 application.yml 数据源配置逐行拆解新版 Spring Boot 配置文件我推荐用 yml 格式缩进比 properties 清爽太多。下面是我项目里最小可用的配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true逐行说几个关键点URL 里的 useSSLfalse 一定要加。MySQL 8.0 默认会尝试 SSL 握手如果你本地 MySQL 没配 SSL 证书连接时会报 SSL 相关异常加上这个参数能少很多麻烦。serverTimezoneAsia/Shanghai 解决时区问题。不加的话数据库连接可能会报 “The server time zone value ... is unrecognized” 这种错因为 8.0 的驱动会校验服务端时区。driver-class-name 也必须写全旧教程里写的 com.mysql.jdbc.Driver 在新驱动上已经废弃了启动时多半会报 ClassNotFoundException 或者直接被忽略。3.3 Hibernate 核心配置ddl-auto、show-sql、命名策略ddl-auto 有五个取值none、validate、update、create、create-drop。实际使用中开发阶段我推荐 update它只会新增表和字段不会删除已有数据create 每次启动都会先删表再建表数据瞬间蒸发我吃过这个亏——测试时插了一堆数据重启一次全没了create-drop 一般只用于测试环境或嵌入式数据库。show-sql 设置为 true 后控制台能打印 Hibernate 实际执行的 SQL。注意这只是打印“日志”并不会真正让 SQL 以更优的方式执行。我建议开发时打开排查问题时很有用线上一定要关不然日志量大到你想哭。spring: jpa: properties: hibernate: format_sql: true physical_naming_strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy物理命名策略这里值得展开讲。CamelCaseToUnderscoresNamingStrategy 是 Spring Boot 默认的它会把实体类字段驼峰命名自动转换成下划线表字段名。userName 映射到 user_namenickName 映射到 nick_name这种转换看着省事但如果你数据库里字段名偏偏不是这个规范就会出现“Unknown column”报错。所以我的习惯是实体类字段与表字段都遵循同一种命名规范要么全驼峰要么全下划线要么明确用 Column 指定字段名不要混着来。3.4 关于连接池先别折腾用默认的就行Spring Boot 默认会使用 HikariCP 作为连接池这是目前性能最好的 Java 连接池之一。新人阶段完全不需要额外配置最多在 yml 里加几个参数防止自己调试时连接数打满spring: datasource: hikari: maximum-pool-size: 10 minimum-idle: 2maximum-pool-size 控制连接池最大连接数默认是 10够用minimum-idle 是最小空闲连接数。这些参数等以后项目压力上来了再优化也不迟现在知道有这回事就行。4. 写一个完整例子用户表的增删改查4.1 实体类 User注解与字段映射依赖、配置、数据库都就绪后正式开始写业务代码。先建一个实体类 User对应数据库里的 t_user 表。我加了一个 Table(name t_user)这样表和实体类名称可以脱钩以后表改名不用动 Java 类。package com.example.demo.entity; import jakarta.persistence.*; import java.time.LocalDateTime; Entity Table(name t_user) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, unique true, length 50) private String username; Column(nullable false, length 100) private String password; Column(length 100) private String email; Column(name created_at, updatable false) private LocalDateTime createdAt; Column(name updated_at) private LocalDateTime updatedAt; PrePersist public void prePersist() { this.createdAt LocalDateTime.now(); this.updatedAt LocalDateTime.now(); } PreUpdate public void preUpdate() { this.updatedAt LocalDateTime.now(); } // getter 和 setter 省略实际开发用 Lombok 的 Data 即可 }几个注解逐个说Entity 标注这是一个 JPA 实体对应数据库一张表。Table 可以指定表名不写的话默认用类名当表名。Id 和 GeneratedValue(strategy GenerationType.IDENTITY) 是主键自增策略。这里有个容易被坑的点MySQL 下推荐用 IDENTITY也就是让数据库自增主键如果用了 AUTOHibernate 在某些版本下会额外创建一张 hibernate_sequence 表来管理主键如果你数据库账号没有建表权限启动时就会报错。Column 里我顺手定义了 nullable、unique、length这些在 ddl-autoupdate 时会同步生效等于把建表约束写在了 Java 类里比直接写 SQL 建表更直观。PrePersist 和 PreUpdate 是 JPA 的生命周期回调分别在插入前和更新前自动执行用来自动维护 created_at 和 updated_at 字段省得每次业务代码手动 set。4.2 Repository 接口Spring Data JPA 的魔法实体类写好以后下一步是定义 Repository 接口。这是 Spring Data JPA 最神奇的地方只需要写一个接口不需要写实现类框架会在启动时动态生成实现类并注册为 Spring Bean。package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; Repository public interface UserRepository extends JpaRepositoryUser, Long { ListUser findByUsername(String username); boolean existsByEmail(String email); }JpaRepositoryUser, Long 里两个泛型第一个是实体类型第二个是主键类型。继承它之后你立刻拥有了一堆现成方法save、findById、findAll、deleteById、count全部免写实现。上面还定义了两个按字段名推导的查询方法findByUsername 和 existsByEmailSpring Data JPA 会按照方法名自动生成查询不用写一句 SQL。方法名解析规则是findBy 后跟实体字段名字段名首字母大写支持 And、Or、OrderBy 等关键字组合比如 findByUsernameAndEmail、findAllByOrderByCreatedAtDesc。刚开始记不住没关系写复杂查询时再看文档最简单的单字段查询记住就行。有一个需要注意的细节如果你把启动类之外的 Bean 都放在同一个包下Repository 接口其实不用加 Repository 也能被扫描到。但为了代码清晰和避免某些老版本 IDE 的告警我还是习惯加上。4.3 Service 层与 Controller打通请求链路很多人写 demo 时喜欢跳过 Service 层直接从 Controller 调 Repository。这在学校作业里无所谓但真实业务里 Service 层用来放事务、缓存、业务校验是必不可少的一层。这个例子我还是按标准三层结构来写。Service 类就是一个普通类加上 Service 注解方法里加上 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; Service public class UserService { Autowired private UserRepository userRepository; Transactional public User createUser(User user) { if (userRepository.existsByEmail(user.getEmail())) { throw new RuntimeException(邮箱已被注册); } return userRepository.save(user); } public ListUser listUsers() { return userRepository.findAll(); } public User getUser(Long id) { return userRepository.findById(id).orElse(null); } }Transactional 会保证方法内多个数据库操作要么全部成功要么全部回滚新手最容易漏掉的就是这个注解导致部分写入成功、部分失败数据对不上。createUser 里的 existsByEmail 就是刚才在 Repository 接口里定义的方法你看 Service 调用的时候就像调用自家方法一样完全不用关心实现。Controller 就更直接了就是一个 HTTP 入口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; PostMapping public User create(RequestBody User user) { return userService.createUser(user); } GetMapping public ListUser list() { return userService.listUsers(); } GetMapping(/{id}) public User detail(PathVariable Long id) { return userService.getUser(id); } }到此从 HTTP 请求到数据库的一条完整链路就通了Controller 接收请求Service 处理业务Repository 操作数据库Hibernate 负责把 User 对象映射成 SQL最终写入 MySQL。4.4 联调自测启动应用用 Postman 验证代码写完后启动应用控制台会出现 Hibernate 生成的建表语句类似 create table t_user (...) 这样。如果没看到检查 ddl-auto 是否设置正确以及数据库账号是否有建表权限。然后用 Postman 或者任何 HTTP 客户端发两个请求。先发一个 POST 请求创建用户请求地址POST http://localhost:8080/api/users 请求体JSON{ username: zhangsan, password: 123456, email: zhangsanexample.com }返回结果里应该包含自动生成的 id 和 created_at。再发一个 GET 请求到 http://localhost:8080/api/users能看到刚才插入的数据列表。如果走到这一步说明整条链路没有任何问题了。5. 这些坑我基本都踩过问题排查与实战心得5.1 常见报错与解决方案速查表我在不同电脑上搭过好几次这套环境也帮不少人排查过问题下面这几类报错出现频率最高报错信息根本原因解决办法Access denied for user rootlocalhost密码错误或用户权限不对检查 yml 里 username/password在 MySQL 里用同样账号手动连接验证Communications link failure数据库没启动或 URL 端口错误或防火墙拦截确认 MySQL 端口是 3306等服务启动后再运行 Spring BootThe server time zone value ... is unrecognized连接串缺少时区参数URL 加 serverTimezoneAsia/ShanghaiUnknown column user0_.email in field list实体类和表字段对不上或表是旧的缺字段如果是 update 模式下删除旧表重启不然就手动 ALTER TABLE 加字段Table demo_db.hibernate_sequence doesnt exist主键生成策略使用 AUTO 且数据库账号无建表权限GeneratedValue 改用 GenerationType.IDENTITYConsider defining a bean of type UserRepositoryRepository 接口和启动类不在同一扫描路径下把启动类移到包外层或检查 Repository 是否被扫描到Loading class com.mysql.jdbc.Driver ... 新驱动提示使用了废弃驱动类driver-class-name 改为 com.mysql.cj.jdbc.Driver5.2 save() 方法的一个迷惑行为新增还是更新JpaRepository 的 save 方法很多人会误以为它就是纯粹的“插入”。实际上它会先判断实体对象的主键如果主键为 null执行 insert如果主键不为 null执行 update也就是先把已存在的数据查出来再覆盖更新。这个机制在更新操作不完整时会导致一个问题你只想改 username但 password 传 null最后数据库里的 password 也变成 null 了。我的处理习惯是更新场景单独写 update 方法先从数据库查出已有实体再对字段逐个赋值最后 save。或者用 Spring Data JPA 的 Modifying Query 自定义更新 SQL但新手阶段不建议碰太容易写错语法。什么时候用 save 的更新效果呢就是你在一个事务里先查出了对象改了它的某个字段然后调 save 提交这只是顺带更新一下不是主推用法。5.3 连接池和事务先保证能跑通再追求细节这里聊一个新手容易忽略的细节Spring Boot 默认的数据源 HikariCP 会在项目启动时初始化连接池如果 MySQL 没启动项目启动可能不会立刻报错但一旦第一次请求数据库连接池迟迟拿不到连接就会抛出一堆超时异常。我的经验是启动 Spring Boot 之前先确认 MySQL 已经运行并且用一个 MySQL 客户端工具手动连一下看看通不通能排除掉一大半“假性 Spring 问题”。另外事务不生效也是一个隐蔽问题。如果你在同一个类里调用自己的另一个 Transactional 方法事务是不会生效的因为 Spring 事务基于 AOP 代理内部自调用不会经过代理对象。这个例子中 createUser 调用的 userRepository 是注入的没有问题但如果你以后在 Controller 里直接调 Service 内部私有方法记得事务是作用不到私有方法上的。最后一个小建议把 show-sql 打开的课余时间可以顺手看一眼 Hibernate 生成的 SQL 长什么样。你会发现在增删改查时Hibernate 会自己拼 SQL、管理参数、处理结果集这些“看不看得见”的过程就是你理解 ORM 运作逻辑最好的教材。等你看懂了 SQL后面遇到性能问题时才不会被“莫名其妙慢”的问题困住。本文还有配套的精品资源点击获取

相关新闻