资讯详情

Spring Boot+Hibernate+MySQL简单例子搭建与避坑指南

📅 2026/10/18 12:51:15 | 华诺云谱 👁 阅读
Spring Boot+Hibernate+MySQL简单例子搭建与避坑指南
简介这是一份面向Spring Boot初学者的HibernateMySQL入门实例清晰演示如何通过Hibernate连接MySQL并完成插入与查询操作适合正在学习Spring Boot持久层开发或想快速上手Hibernate配置的开发者。压缩包共155个文件大小约34.25MB主体为64个jar依赖包配合6个java源码、6个class编译文件、2个jsp页面、2个xml配置文件、3个properties属性配置及1个sql建表脚本另有部分SVN残留元数据文件不影响项目使用。项目已有1826人学习下载自带完整依赖与数据库脚本只需创建对应数据库并执行sql脚本即可直接运行观察插入与查询效果便于对照代码理解Hibernate实体映射、SessionFactory配置及基本事务操作。1. 为什么一个「springboothibernatemysql简单例子」能折腾掉一整个下午搜索「springboothibernatemysql简单例子」的人多半不是没写过代码而是被版本、方言、命名策略这三样东西卡住了。某个开发者接手某公司一个老模块的迁移照着一个很常见的示例代码敲了一遍结果启动直接报「Unable to build Hibernate SessionFactory」追了半天才发现是 MySQL 驱动类路径写错、时区参数没加、Hibernate 方言又配了 MySQL 5 的旧版。三段本来各自都能跑的技术凑在一起就成了玄学。这个组合解决的问题很具体Spring Boot 负责应用骨架和自动配置Hibernate 负责把 Java 对象映射成数据库表行MySQL 负责把数据落盘。读一遍能跑通的最小实现你就能看清楚三层到底在哪里接缝、哪些配置是多余的、哪些参数不能省。适合刚接触 Spring 持久层的新手也适合那些一直在用 MyBatis、想快速摸清 Hibernate 风格的熟手。2. 从 Hibernate 视角看 Spring Boot先弄懂这三层再动手写代码2.1 Hibernate 在各层扮演的角色SessionFactory、Session 与事务Hibernate 的核心概念在 Spring Boot 里被包装了一层但底层逻辑没变。SessionFactory 是重量级对象一个应用一般只创建一个对应 Spring 里的 EntityManagerFactorySession 是轻量级的操作入口对应 EntityManager。在 Spring Boot 里你很少直接写 SessionFactory 或 Session 的代码是因为这些对象在 JPA 规范里被重新命名为 EntityManagerFactory 和 EntityManager但它的生命周期规则、一级缓存行为、事务绑定方式全都从 Hibernate 继承下来。一级缓存值得先有概念。Session 打开期间Hibernate 会把加载过的实体存在缓存里同一个 Session 内重复查同一主键不会发第二条 SQL。在 Spring Boot 中Session 的生命周期一般绑定到一次事务或一次请求所以「先查出来、再改一下、然后事务提交」时脏检查机制会自动把改动同步到数据库不需要你手动写 update。这就是 Hibernate 和 JDBC 最大的差异你操作的是对象图不是 SQL 结果集。事务的边界也在这里。Hibernate 的 Session 依赖事务来确认提交点Spring 的 Transactional 注解负责在方法入口开启事务、在方法结束提交或回滚。很多新手翻车的 LazyInitializationException本质就是 Session 已经关了但你在 Controller 层又去访问了一个没有被加载的关联字段。理解了 Session 生命周期后面排错会快很多。2.2 为什么 Spring Boot 项目里只见得到 JPA 注解JPA 与 Hibernate 的关系Spring Boot 的 spring-boot-starter-data-jpa 依赖里面实际装了三层东西JPA 规范接口javax.persistence 包、Hibernate 作为 JPA 的实现、Spring Data JPA 作为更上层的仓库封装。你在实体类里写的 Entity、Id、Column 这些注解全部来自 JPA 规范而不是 Hibernate 独有的注解。之所以这样设计是为了让代码不绑定某一家 ORM 实现——理论上你把 Hibernate 换成 EclipseLink这些注解不用改。Hibernate 自己的独有特性比如 BatchSize、Cache、Fetch在 Spring Boot 里也能用但不写也不影响基本功能。实际的项目里实体类上的注解越标准越好。一个「springboothibernatemysql简单例子」应该只用 JPA 注解不夹带 Hibernate 私有注解这样代码可移植性最好。Spring Data JPA 是第三层封装。它把通用的增删改查逻辑抽象成接口方法你只要写一个接口继承 JpaRepository就能免费获得 findAll、findById、save、deleteById 这些能力。但这层封装也是对新手最不透明的地方——看不到 SQL、看不到 Session、看不到事务边界。所以把 Hibernate 本身的工作机制先搞清楚比急着抄代码更重要。2.3 Hibernate 与 MyBatis 的边界为什么有人觉得 Hibernate「重」很多从 MyBatis 转过来的人会觉得 Hibernate 太重这个印象一半来自配置复杂一半来自 SQL 不可控的恐惧。Hibernate 的理念是「让应用面向对象模型编程」表结构由实体映射关系推导MyBatis 的理念是「SQL 由开发者完全掌控」映射只解决结果集和对象的转换。两者不存在绝对优劣差别在于团队对数据层的掌控粒度。在简单例子里Hibernate 的 ddl-autoupdate 可以直接根据实体类生成表结构这是它最实用的优势之一。但对已经在运行的数据库update 策略不会帮你删字段、不会改字段类型生产环境一般切到 validate 或 none靠专门的迁移脚本控结构。这个边界要在动手前明确Hibernate 适合表结构相对稳定、ORM 映射明确的业务复杂报表查询、多表条件拼装频繁的系统直接用 MyBatis 更顺手。3. 搭建最小工程依赖、数据源与 Hibernate 方言的关键配置3.1 Maven 依赖清单与版本搭配原则这个组合的依赖选择核心只有两个spring-boot-starter-data-jpa 和 MySQL 驱动。其他像 spring-boot-starter-web 是提供 HTTP 入口才需要的如果只是想跑通持久层可以不加但既然要做成能通过浏览器验证的例子Web 依赖也一并加上。MySQL 驱动的坐标在 Spring Boot 2.7 时代有过一次改名从 mysql:mysql-connector-java 改成了 com.mysql:mysql-connector-j两者指向同一个驱动包新工程用后者就行。版本搭配的问题才是老大难。Spring Boot 2.7.x 默认管理的是 Hibernate 5.6.x 和 MySQL 驱动 8.0.x这是经过自动配置类验证过的稳定组合。Spring Boot 3.x 把持久层 API 从 javax 包全部换成了 jakarta 包Hibernate 也升到 6.x配置项有变化。如果你只是想跑通这个例子我建议直接锁在 Spring Boot 2.7.x 这条线资料最多、坑最少。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /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注意最后一项驱动的 scope 是 runtime因为代码里不会直接 import 驱动的类它只在运行时被加载。starter-data-jpa 会传递引入 Hibernate 核心和 Spring Data JPA不需要手动声明 Hibernate 版本号交给父 POM 统一管理即可。如果你想验证 Hibernate 的实际版本在 Maven 命令行执行mvn dependency:tree | grep hibernate就能看到完整依赖树。3.2 application.yml 里的 Hibernate 核心配置逐项说明配置是整个例子的重点也是最容易翻车的部分。下面这份配置是我常用的最小集每一行都能解释清楚它解决什么问题。spring: datasource: url: jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: demo password: demo123 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true open-in-view: falseurl 里的四个参数各有用途useUnicode 和 characterEncoding 保证中文以 UTF-8 编码传输useSSLfalse 是避免本机开发时出现 SSL 连接警告serverTimezone 必须设否则 MySQL 8 驱动会拿本地时区去对齐会话时区报错或者时间字段差 8 小时。driver-class-name 在 Spring Boot 2.7 里其实可以根据 url 自动推断但显式写出来启动报错时能更快定位。ddl-autoupdate 表示 Hibernate 启动时对比实体类与现有表结构自动补充缺失的表和字段。它不是数据库迁移工具不会删除任何列也不会修改已存在的列类型。show-sql 配合 format_sql 一起用控制台输出的 SQL 会有缩进查问题时舒服很多。dialect 告诉 Hibernate 该用哪一种 SQL 方言MySQL 8 必须用 MySQL8Dialect老项目里常见的 MySQL5Dialect 会导致分页 SQL 和下推优化行为不一致。open-in-view 这一项值得单独说。Spring Boot 默认把它设为 true意思是 请求期间 Session 一直开着Controller 层访问懒加载字段不会报错代价是数据库连接被占用到视图渲染结束。在纯接口模式下我建议设成 false逼自己在 Service 层把数据准备好避免长事务。新手刚开始跑通例子时如果遇到懒加载报错可以先把它设回 true 验证业务逻辑上线前再改。3.3 创建数据库与账号避开字符集和时区的默认坑MySQL 侧的准备分两步建库、建账号。数据库的字符集要明确指定 utf8mb4而不是默认的 latin1。以前很多人只建库不指定字符集然后发现英文一切正常、中文全是问号这类问题排查起来极其费时。utf8mb4 和 utf8 的区别在于 utf8mb4 能存 emoji 和生僻字是 MySQL 8 的推荐字符集。CREATE DATABASE IF NOT EXISTS demo_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS demolocalhost IDENTIFIED BY demo123; GRANT ALL PRIVILEGES ON demo_db.* TO demolocalhost; FLUSH PRIVILEGES;CREATE USER IF NOT EXISTS 是 MySQL 8 才支持的语法在 5.7 上会直接报语法错误要拆成 create user 和 grant 两条独立语句。本地开发时用本地 Linux 的 MySQL、Windows 的 MySQL、或者 Docker 起的 MySQL 实例连接串里的 host 会不一样容器映射端口后 url 里的 3306 也要跟着改。如果你用的 MySQL 是 5.7dialect 要换回 org.hibernate.dialect.MySQL5Dialecturl 里的驱动类名不变。MySQL 8 和 5.7 在认证插件上也有差异8 默认用 caching_sha2_password某些老版本的连接工具会连不上解决方案是在创建用户时指定 mysql_native_password但这不在本例子范围内知道有这回事即可。4. 写一个能跑通的最简增删改查实体、仓储、服务与控制层4.1 实体类与表结构的映射注解用得少反而更稳实体类是 Hibernate 映射的核心。最简单的做法是一个类对应一张表一个属性对应一列。下面用一个员工信息表做示例字段包括主键、姓名、岗位、入职日期。注解只用了 JPA 标准中的五个够用且不啰嗦。package com.example.demo.entity; import javax.persistence.*; import java.time.LocalDate; Entity Table(name employee) public class Employee { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name name, nullable false, length 50) private String name; Column(name position, length 100) private String position; Column(name hire_date) private LocalDate hireDate; public Employee() { } public Employee(String name, String position, LocalDate hireDate) { this.name name; this.position position; this.hireDate hireDate; } // getter / setter 省略IDE 自动生成 }Table(name employee) 如果不写Hibernate 默认用类名作为表名Employee 会被转成 employee 表这里显式写出来更明确。GeneratedValue(strategy GenerationType.IDENTITY) 对应 MySQL 的自增主键这是 MySQL 场景下的正确选择GenerationType.SEQUENCE 是 Oracle、PostgreSQL 的风格用的数据库不对会启动报错。Column 里的 nullable 和 length 会被 Hibernate 用来生成建表 DDL。name 字段的 nullablefalse 会让表里的 name 列带上 NOT NULL 约束length50 对应 VARCHAR(50)。这里有个细节Column 的 length 只在 ddl-auto 为 update/create 时生效如果表是手建的这个属性不影响任何行为。注意实体类必须有一个无参构造方法。Hibernate 在加载数据后通过反射创建对象它默认调用无参构造如果你只写了有参构造而忘了无参构造运行时会抛 InstantiationException。这是一个非常隐蔽的坑IDE 不会给你任何提示。4.2 Repository 接口继承 JpaRepository 后的默认能力Spring Data JPA 的 Repository 层极其简洁接口继承 JpaRepository 之后基本的增删改查方法就已经齐全了不需要写实现类。Spring Data 在启动时通过动态代理为这个接口生成实现这个机制是新手容易困惑的点——明明只有接口没有实现类为什么能直接注入使用package com.example.demo.repository; import com.example.demo.entity.Employee; import org.springframework.data.jpa.repository.JpaRepository; public interface EmployeeRepository extends JpaRepositoryEmployee, Long { ListEmployee findByPosition(String position); }JpaRepositoryEmployee, Long 里的两个泛型参数第一个是实体类型第二个是主键类型。继承之后你就拥有了 save、findById、findAll、deleteById 等方法。方法返回类型是 Optional 而不是实体本身这是 Spring Data 的约定写代码时要注意判空——直接 get 一个不存在的记录会抛 NoSuchElementException。方法名解析是 Spring Data JPA 的特色。findByPosition 方法名会被解析成查询条件以 find 开头表查询By 后面跟属性名 position运行时生成select * from employee where position ?这样的 SQL。属性名解析是精确匹配的方法写成 findByPositions 就会启动报错提示无法找到对应属性。不想记这套命名规则也可以在上面加 Query 注解写自定义 JPQL但简单例子里方法名解析足够用。4.3 Service 事务边界与 Controller 的返回结构Service 层负责事务边界和业务逻辑这里展示一个带增删改查的完整例子。事务注解放在 Service 方法上而不是 Controller 方法上这是重要的分层习惯。Controller 负责接收请求、调用服务、返回响应不直接访问 Repository更不直接操作 EntityManager。package com.example.demo.service; import com.example.demo.entity.Employee; import com.example.demo.repository.EmployeeRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; Service public class EmployeeService { private final EmployeeRepository employeeRepository; public EmployeeService(EmployeeRepository employeeRepository) { this.employeeRepository employeeRepository; } Transactional(readOnly true) public ListEmployee listAll() { return employeeRepository.findAll(); } Transactional public Employee create(Employee employee) { return employeeRepository.save(employee); } Transactional public Employee update(Long id, Employee employee) { Employee existing employeeRepository.findById(id) .orElseThrow(() - new RuntimeException(员工不存在)); existing.setName(employee.getName()); existing.setPosition(employee.getPosition()); existing.setHireDate(employee.getHireDate()); return existing; } Transactional public void delete(Long id) { employeeRepository.deleteById(id); } }构造器注入是 Spring 推荐的依赖注入方式比 Autowired 字段注入更利于测试代码也更明确。Transactional(readOnly true) 是查询方法的常用优化它告诉 Hibernate 这个事务不会产生写操作可以关闭脏检查节省一点点性能但它并不会真的跳过事务——Spring 仍然会开启一个只读事务因为懒加载仍然需要 Session 存在。注意 update 方法里的写法先从数据库查出已有实体然后修改它的字段最后返回这个实体。这里没有调用 save但事务提交时 Hibernate 的脏检查会检测到实体状态变化自动发出 update SQL。这是 Hibernate 对象状态管理的关键行为和 MyBatis 里必须显式 update 的思路完全不同。Controller 层保持薄的状态只做对象转换和响应封装。package com.example.demo.controller; import com.example.demo.entity.Employee; import com.example.demo.service.EmployeeService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/employees) public class EmployeeController { private final EmployeeService employeeService; public EmployeeController(EmployeeService employeeService) { this.employeeService employeeService; } GetMapping public ListEmployee list() { return employeeService.listAll(); } PostMapping public Employee create(RequestBody Employee employee) { return employeeService.create(employee); } PutMapping(/{id}) public Employee update(PathVariable Long id, RequestBody Employee employee) { return employeeService.update(id, employee); } DeleteMapping(/{id}) public ResponseEntityVoid delete(PathVariable Long id) { employeeService.delete(id); return ResponseEntity.noContent().build(); } }到这里这个例子已经能完整跑通增删改查了。启动应用后访问 GET /api/employees 会返回空数组因为表里还没有数据用 POST 提交一条 JSON然后再次 GET就能看到刚插入的那条记录。服务的默认端口是 8080如果被占用可以在 application.yml 里加server.port: 9090换端口。5. 避坑搭这个例子最容易踩的 5 个配置与运行问题5.1 启动抛 Unable to build Hibernate SessionFactory先查数据源再查方言现象应用启动到一半直接报错最底下写着Unable to build Hibernate SessionFactory紧接着还有Access denied for user demolocalhost或者Communications link failure。原因这个外层错误信息很有迷惑性它只是告诉你 SessionFactory 创建失败了真正的根源在堆栈里更深的位置。最常见的是数据库账号密码不对、数据库没创建、或者 MySQL 服务没启动。解决按三个顺序排查。第一步确认 MySQL 服务在运行命令行执行mysql -u demo -p能登录成功。第二步登录后执行SHOW DATABASES;确认 demo_db 存在。第三步核对 application.yml 里的 url、username、password 是否和建库语句一致。这三步做完90% 的启动失败都能找到答案。如果堆栈里出现Public Key Retrieval is not allowed在 url 后面追加allowPublicKeyRetrievaltrue。5.2 表建出来了但字段名对不上命名策略的隐形规则现象日志里 Hibernate 输出的建表 SQL 和预期不一样Java 属性 hireDate 被建成了 hire_date 列或者反过来你在数据库里手建的字段是 hireDate但 Hibernate 去查 hire_date报 Unknown column。原因Spring Boot 集成的 SpringPhysicalNamingStrategy 默认会把驼峰命名转成下划线命名。这个策略帮了你但也坑了你——如果表结构是手写 SQL 建的字段命名风格和 Java 属性不一致映射就会错位。前面实体类里我显式写了 Column(name hire_date)就是为了避免这个歧义。解决统一方案是 Java 属性用驼峰表字段用下划线所有 Column 都显式指定 name。如果你不想维护这些映射也可以把实体属性直接命名为 hireDate并把 physical-strategy 改成 PhysicalNamingStrategyStandardImpl这样 Hibernate 就不会做驼峰转下划线。但建议还是按业界规矩来表字段下划线Java 属性驼峰最省心。5.3 查询报 LazyInitializationException事务边界要划在 Service 层现象在一个没有懒加载字段的例子里不会遇到这个问题但只要你给实体加了关联关系比如 ManyToOne 关联部门表然后在 Controller 层访问 employee.getDepartment().getName()就会抛 LazyInitializationException。原因默认情况下查询方法事务在 Service 方法返回时就提交了Session 关闭。Controller 再去访问没有加载的懒加载属性Hibernate 已经没法打开 Session 去查询只能抛异常。Spring Boot 的 open-in-view 默认是 true 时不会报错因为这个配置会让 Session 在整个 HTTP 请求期间保持打开但这掩盖了事务边界的问题也占着连接不释放。解决如果关掉了 open-in-view必须在 Service 层内访问需要的懒加载属性让 Hibernate 在事务内把它查出来。更常见的方式是在 Repository 方法上加 EntityGraph 或用 Query 里的 join fetch 预加载关联对象。简单例子里建议直接用 join fetchQuery(select e from Employee e left join fetch e.department) ListEmployee findAllWithDepartment();5.4 MySQL 8 连接报 Public Key Retrieval 或时区错误现象启动报The server time zone value й׼ʱ is unrecognized或者Public Key Retrieval is not allowed。原因MySQL 8 默认的认证插件 caching_sha2_password 在第一次连接时需要从服务器获取公钥如果连接串没允许这一步驱动会拒绝连接。时区错误是 MySQL 8 驱动默认要求会话时区明确而服务器端配置的时区名驱动不认识。解决这两类问题都在 url 参数上解决。时区参数写成serverTimezoneAsia/Shanghai不要写CST这种缩写CST 在不同系统里可能表示中国标准时间、美国中部时间或古巴标准时间歧义很大。公钥问题在 url 后面加allowPublicKeyRetrievaltrue。完整连接串url: jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue5.5 Spring Boot 3.x 的代码搬到 2.7 上全部报编译错误现象从某个示例代码里复制过来的实体类import javax.persistence.Entity 全部红叉项目根本编译不过去。原因Spring Boot 3.x 把 Java 持久化 API 从 javax 包迁移到了 jakarta 包Hibernate 6.x 只认 jakarta 开头的注解。如果你看的那篇示例文章用的是 Spring Boot 3代码里的 import 全是 jakarta.persistence.*而你自己的工程是 Spring Boot 2.7自然编译失败。解决统一思路是确定一个基线版本。如果你在用 Spring Boot 2.7所有 import 都应该是 javax.persistence 开头如果你用 3.x就要用 jakarta.persistence 开头。判断方法很简单Spring Boot 2.x 用 javax3.x 用 jakarta。混用必挂。MySQL 驱动坐标也一样2.7 时代兼容 mysql-connector-java 和 mysql-connector-j 两种坐标3.x 只认后者。6. 验证与进阶用一次真实请求确认配置全部生效写完全部代码后验证分三步走。第一步启动应用观察控制台日志里 Hibernate 打印的建表 SQL确认表结构和实体类映射一致第二步用浏览器或命令行工具访问 GET 接口确认返回空数组而不是 500 错误第三步 POST 一条数据再 GET确认数据能写进 MySQL 并正确返回。这三步跑通说明从配置到代码的整条链路都通了。验证时最值得看的日志是启动过程中的 Hibernate DDL 输出。它会打出 create table employee 的完整语句你能直接看到字段名、类型、约束是否符合预期。如果发现某些字段没有生成回头检查 Column 的 name 属性和 ddl-auto 的配置。注意 update 模式不会在表已存在时打印任何 DDL这也是很多人的疑惑——改实体类字段后重新启动表结构没变其实是在预期内的。跑通之后有三件事值得继续做会让这个简单例子边的价值立刻放大。第一分页查询把 Repository 方法的参数加上 PageableController 接收 page 和 size 参数这是接口开发的标配功能。第二批量插入把 save 调用换成 saveAll并且在 yml 里加上spring.jpa.properties.hibernate.jdbc.batch_size30批量场景下性能差距非常明显。第三统一返回结构定义一个包含 code、message、data 三个字段的响应体替代现在直接返回实体对象的方式前端对接时更规范。我在搭这个组合时养成的习惯是先在 pom 里锁版本再写配置最后才写代码。版本定死了配置的真实含义清楚了代码反而没什么可纠结的。如果你按这个顺序走一遍大概一个小时候后就能跑通而且下次再遇到相关报错时至少知道该往哪几个方向排查。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑