在Spring Boot项目中使用Hibernate

Spring Data JPA是Spring框架提供的简化JPA(Java Persistence API)操作的模块,而Hibernate是最流行的JPA实现之一

Hibernate是一个流行的ORM(对象关系映射)框架,它可以将Java对象映射到数据库表,从而方便地进行持久化操作。

在Spring Boot项目中,集成Hibernate可以帮助我们更轻松地进行数据库操作。

在Spring Boot项目中如何使用这两者,先写一个例子,我会在项目中,边写边用详细的注释来分析

项目示例

依赖导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<!-- Spring Boot Starter Data JPA (包含Hibernate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- 数据库驱动,这里以MySQL为例 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<!-- 其他你可能需要的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

进行配置文件的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 应用名称,建议与项目模块名一致
spring.application.name=SpringAndHibernate

# 数据库连接配置(HikariCP 连接池)
spring.datasource.url=jdbc:mysql://localhost:3306/hibernate_demo?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=Asia/Shanghai
# 说明:
# - createDatabaseIfNotExist=true:开发环境自动创建数据库(生产环境需删除)
# - serverTimezone=Asia/Shanghai:使用中国标准时区,避免时间转换问题
spring.datasource.username=root # 数据库用户名(生产环境建议使用独立权限用户)
spring.datasource.password=zjm10086 # 数据库密码(敏感信息,生产环境建议通过环境变量注入)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MySQL 8.0+ 驱动类
spring.datasource.hikari.maximum-pool-size=10 # 最大连接数(开发环境默认 10,生产环境可调整为 20-50)
spring.datasource.hikari.minimum-idle=5 # 最小空闲连接数(默认与 max 一致,可按需调整)
spring.datasource.hikari.idle-timeout=30000 # 空闲连接超时时间(毫秒,默认 60000)

# Hibernate 配置(JPA 标准)
spring.jpa.hibernate.ddl-auto=update # 开发环境自动更新表结构(生产环境用 validate)
spring.jpa.show-sql=true # 开发环境显示 SQL(生产环境关闭)
spring.jpa.properties.hibernate.format_sql=true # 格式化 SQL 输出,便于阅读
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # MySQL 8.0+ 方言
# 以下为可选配置(按需启用):
# spring.jpa.properties.hibernate.cache.use_second_level_cache=true # 二级缓存
# spring.jpa.properties.hibernate.generate_statistics=true # 生成性能统计信息

# 日志配置(Logback 级别控制)
logging.level.org.hibernate.SQL=DEBUG # 显示执行的 SQL 语句
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE # 显示 SQL 参数绑定细节
# 生产环境建议配置:
# logging.level.org.hibernate.SQL=INFO
# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=INFO

参数说明:

  • spring.datasource.*:配置数据源的基本信息。
  • spring.jpa.hibernate.ddl-auto:控制Hibernate的DDL操作(如updatecreatenone)。
  • spring.jpa.show-sql:是否显示SQL语句。
  • spring.jpa.properties.hibernate.*:Hibernate的高级配置。

这里使用了MySQL数据库,可以根据实际情况进行修改。其中,spring.jpa.hibernate.ddl-auto属性指定了Hibernate如何自动生成数据库表,create-drop表示每次启动应用程序时都会创建表,并在关闭应用程序时删除表。

ddl-auto选项说明:

  • create: 每次启动都重新创建表结构,数据会丢失

  • create-drop: 加载hibernate时创建,退出时删除表结构

  • update: 加载hibernate自动更新数据库结构,保留数据

  • validate: 加载hibernate时,验证创建数据库表结构,会和数据库中的表进行比较,不会创建新表,但是会插入新值

  • none`: 不执行任何操作。 永远以数据表字段为准

创建实体类

使用JPA注解定义实体类,用于映射到数据库表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package edu.software.ergoutree.springandhibernate.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 学生实体类 - 映射数据库中的学生表
* 该类使用JPA注解定义了与数据库表的映射关系,
* 并通过Lombok注解简化了JavaBean的常规代码。
*
* @Entity 声明该类为JPA实体类
* @Table 指定对应数据库表名为"students"
* @Data 自动生成getter/setter、toString、equals和hashCode方法
* @NoArgsConstructor 自动生成无参构造函数
* @AllArgsConstructor 自动生成包含所有字段的构造函数
*/
@Entity
@Data
@Table(name = "students")
@NoArgsConstructor
@AllArgsConstructor
public class Student {

/**
* 学生唯一标识
* @Id 声明该字段为主键
* @GeneratedValue 指定主键生成策略为自增(对应数据库自增字段)
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 学生姓名
* @Column 定义字段映射属性:
* - nullable = false: 不允许为空
* - length = 100: 最大长度100字符
*/
@Column(name = "name", nullable = false, length = 100)
private String name;

/**
* 学生邮箱
* @Column 定义字段映射属性:
* - unique = true: 要求邮箱地址唯一
*/
@Column(name = "email", unique = true)
private String email;

/**
* 学生年龄
* 使用包装类Integer允许值为null
*/
@Column(name = "age")
private Integer age;

/**
* 用于创建新学生的构造函数
* 不包含id字段,因为id由数据库自动生成
* @param name 学生姓名(必填)
* @param email 学生邮箱(必填且唯一)
* @param age 学生年龄(可选)
*/
public Student(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
}

其中@Entity表示这是一个JPA实体类,@Table(name = “students”)指定实体类对应数据库表名为students,@Id表示数据库的标识字段,也就是主键,@GeneratedValue注解指定了主键的生成策略@Column指定对应数据库字段

创建DAO层

创建一个简单的Repository,用于访问数据库:

DAO层需要继承JpaRepository接口,这样才能根据方法名自动获得基本的CRUD操作,该接口有两个参数化类型,第一个表示实体类的类型,第二个表示主键的类型,也就是@Id注解标注的字段的类型,这里是Long。

需要在启动类上面使用@EnableJpaRepositories注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package edu.software.ergoutree.springandhibernate.repository;

import edu.software.ergoutree.springandhibernate.model.Student;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

/**
* 学生数据访问层
* 继承JpaRepository,自动获得基本的CRUD操作
*/
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

/**
* 方法名约定查询:根据名字模糊匹配学生
* 等效SQL:SELECT * FROM students WHERE name LIKE %?%
*/
List<Student> findByNameContaining(String name);

/**
* 方法名约定查询:根据年龄范围查询学生
* 等效SQL:SELECT * FROM students WHERE age BETWEEN ? AND ?
*/
List<Student> findByAgeBetween(Integer minAge, Integer maxAge);

/**
* JPQL查询:根据邮箱模式查询学生
* @Param 注解绑定命名参数到JPQL中的:email
*/
@Query("SELECT s FROM Student s WHERE s.email LIKE %:email%")
List<Student> findStudentsByEmailPattern(@Param("email") String email);

/**
* 原生SQL查询:查询年龄大于指定值的学生,并按名字排序
* nativeQuery=true:启用原生SQL(直接操作数据库表)
*/
@Query(value = "SELECT * FROM students WHERE age > :age ORDER BY name", nativeQuery = true)
List<Student> findStudentsOlderThanAge(@Param("age") Integer age);

/**
* 分页查询:根据名字模糊匹配学生,并支持分页和排序
* @param name 搜索关键词
* @param pageable 分页和排序参数
* @return 分页结果(包含总页数、总记录数等信息)
*/
Page<Student> findByNameContaining(String name, Pageable pageable);

/**
* 自定义更新操作:修改学生年龄
* @Modifying 注解:标识该查询为修改操作
* @Transactional 需在调用层声明事务
*/
@Modifying
@Query("UPDATE Student s SET s.age = :age WHERE s.id = :id")
int updateStudentAge(@Param("id") Long id, @Param("age") Integer age);
}

在Repository上使用@Repository注解,表示这是一个Spring组件,并且用于访问数据库。PersonRepository继承自JpaRepository,这个接口提供了许多通用的数据库操作方法,如save、findById等。

接口整体注解与继承

1
2
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
  • @Repository

    • 声明该接口为 Spring 数据访问组件,自动注册为 Bean。
    • 作用:
      • 将数据访问层的异常(如 SQLException)转换为 Spring 的 DataAccessException
      • 支持组件扫描,无需在 XML 中手动配置 Bean。
  • JpaRepository

    • Spring Data JPA 提供的核心接口,继承关系

      1
      JpaRepository ← PagingAndSortingRepository ← CrudRepository
    • 内置方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // CrudRepository 提供的基础方法
      <S extends T> S save(S entity);
      Optional<T> findById(ID id);
      boolean existsById(ID id);
      Iterable<T> findAll();
      void deleteById(ID id);
      long count();

      // PagingAndSortingRepository 扩展的分页排序方法
      Page<T> findAll(Pageable pageable);
      Iterable<T> findAll(Sort sort);

      // JpaRepository 特有的方法
      void flush();
      <S extends T> S saveAndFlush(S entity);
      List<T> findAllById(Iterable<ID> ids);

方法名约定查询

  1. 模糊查询(Containing)
1
List<Student> findByNameContaining(String name);
  • 解析规则

    • findBy:固定前缀,表示查询操作。
    • NameContaining:字段名(Name) + 匹配方式(Containing)。
    • 等效 SQL:WHERE name LIKE %?%
  • 参数处理

    • 传入 name="John" 时,生成 SQL:WHERE name LIKE '%John%'
  • 其他常用匹配方式

    关键字 SQL 等效 示例方法名
    Containing LIKE %?% findByNameContaining
    StartingWith LIKE ?% findByNameStartingWith
    EndingWith LIKE %? findByNameEndingWith
    IsNotNull IS NOT NULL findByNameIsNotNull
    GreaterThan > findByAgeGreaterThan
  1. 范围查询(Between)
1
List<Student> findByAgeBetween(Integer minAge, Integer maxAge);
  • 解析规则
    • findBy:查询前缀。
    • AgeBetween:字段名(Age) + 范围操作(Between)。
    • 等效 SQL:WHERE age BETWEEN ? AND ?
  • 参数顺序
    • 第一个参数对应下限(minAge),第二个对应上限(maxAge)。
  1. JPQL 查询(@Query 注解)
1
2
@Query("SELECT s FROM Student s WHERE s.email LIKE %:email%")
List<Student> findStudentsByEmailPattern(@Param("email") String email);
  • @Query 注解

    • 使用 JPA 查询语言(JPQL),语法类似 SQL,但操作对象是实体类而非表。

    • ``` %:email%

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      - `:` 表示命名参数(对应 `@Param("email")`)。
      - `%` 是 JPQL 的模糊匹配符(等效 SQL 的 `LIKE`)。

      - **命名参数绑定**:

      - `@Param("email")`:将方法参数 `email` 绑定到 JPQL 中的 `:email`。

      - **替代方案:位置参数**:



      ```java
      @Query("SELECT s FROM Student s WHERE s.email LIKE %?1%")
      List<Student> findStudentsByEmailPattern(String email);

    • ?1 表示第一个参数(从 1 开始),但命名参数更易维护。

  1. 原生 SQL 查询(nativeQuery = true
1
2
@Query(value = "SELECT * FROM students WHERE age > :age ORDER BY name", nativeQuery = true)
List<Student> findStudentsOlderThanAge(@Param("age") Integer age);
  • nativeQuery = true
    • 启用原生 SQL 查询,直接执行数据库 SQL 语句。
    • 表名 students 和字段名需与数据库一致(而非实体类名和属性名)。
  • 适用场景
    • JPQL 无法表达复杂查询(如存储过程、函数调用)。
    • 需利用特定数据库特性(如 MySQL 的 GROUP_CONCAT)。
  • 风险提示
    • 失去数据库无关性(如切换到 Oracle 可能需要修改 SQL)。
    • 字段名与实体属性映射需严格匹配(可通过 SELECT s.id AS id, s.name AS name 显式映射)。
  1. 分页与排序
    1. 分页查询示例
1
2
3
4
5
6
// 在接口中添加分页方法
Page<Student> findByNameContaining(String name, Pageable pageable);

// 调用示例
Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
Page<Student> result = studentRepository.findByNameContaining("John", pageable);
  • Pageable 参数

    • ``` PageRequest.of(page, size, sort)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - `page`:页码(从 0 开始)。
      - `size`:每页数量。
      - `sort`:排序规则(如 `Sort.by("name").ascending()`)。

      - **返回类型 `Page`**:

      - 包含分页信息(总页数、总记录数、当前页数据):

      ```java
      long totalElements = result.getTotalElements(); // 总记录数
      int totalPages = result.getTotalPages(); // 总页数
      List<Student> content = result.getContent(); // 当前页数据
  1. 排序查询示例
1
2
3
4
5
6
// 方法名中添加排序关键词
List<Student> findByAgeGreaterThan(Integer age, Sort sort);

// 调用示例
Sort sort = Sort.by("name").descending().and(Sort.by("age").ascending());
List<Student> result = studentRepository.findByAgeGreaterThan(18, sort);
  • Sort 参数
    • 支持多字段排序(如 name DESC, age ASC)。
  1. 自定义方法实现

若默认方法和查询注解无法满足需求,可通过以下方式扩展:

  1. 自定义 Repository 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 定义扩展接口
interface StudentRepositoryCustom {
List<Student> findStudentsByCustomCriteria(String criteria);
}

// 2. 实现扩展接口
class StudentRepositoryImpl implements StudentRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;

@Override
public List<Student> findStudentsByCustomCriteria(String criteria) {
// 使用 EntityManager 实现自定义查询
String jpql = "SELECT s FROM Student s WHERE ...";
return entityManager.createQuery(jpql, Student.class).getResultList();
}
}

// 3. 让主 Repository 继承扩展接口
public interface StudentRepository extends JpaRepository<Student, Long>, StudentRepositoryCustom {
// 现有方法保持不变
}
  1. 使用 @Modifying 注解执行更新操作
1
2
3
@Modifying
@Query("UPDATE Student s SET s.age = :age WHERE s.id = :id")
int updateStudentAge(@Param("id") Long id, @Param("age") Integer age);
  • 注意事项
    • 需配合 @Transactional 使用(在 Service 层声明)。
    • 返回值为受影响的行数。

Service层的编写

先写接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package edu.software.ergoutree.springandhibernate.service;

import edu.software.ergoutree.springandhibernate.model.Student;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;

/**
* 学生服务接口
* 定义学生相关的业务逻辑方法,通过 Spring Data JPA 间接使用 Hibernate 实现数据持久化
*/
public interface StudentService {

/**
* 保存或更新学生(新增/修改)
* - 新增:学生 ID 为空时,Hibernate 生成 INSERT 语句
* - 更新:学生 ID 存在时,Hibernate 通过脏检查生成 UPDATE 语句
* @param student 学生实体(瞬时态或脱管态)
* @return 持久态学生对象(含数据库生成的 ID 或更新后的状态)
* @transactional 该方法需在事务内执行(由 Service 实现类注解控制)
*/
Student saveStudent(Student student);

/**
* 根据 ID 查找学生(优先从一级缓存获取)
* @param id 学生 ID
* @return Optional 包装的持久态学生对象(若存在)
* @hibernate 操作:调用 StudentRepository.findById(id),触发 Session.get() 查询
* @二级缓存扩展:可通过 @Cacheable 注解启用二级缓存
*/
Optional<Student> findStudentById(Long id);

/**
* 查询所有学生(支持分页优化)
* @return 学生列表(持久态对象集合)
* @建议:实际开发中添加 Pageable 参数,避免全量加载:List<Student> findAllStudents(Pageable pageable)
* @hibernate 操作:生成 JPQL SELECT s FROM Student s,支持排序和分页
*/
List<Student> findAllStudents();

/**
* 根据 ID 更新学生信息(需先查询原实体以确保处于持久态)
* @param student 包含更新字段的脱管态实体(需携带 ID)
* @return 更新后的持久态学生对象
* @hibernate 操作:
* 1. 通过 repository.findById(student.getId()) 将实体重新关联到 Session
* 2. 对比字段差异,生成 UPDATE 语句(仅更新变更字段)
*/
Student updateStudent(Student student);

/**
* 根据 ID 删除学生(级联处理关联对象,如 @OneToMany(cascade = REMOVE))
* @param id 学生 ID
* @throws EmptyResultDataAccessException 若 ID 不存在
* @hibernate 操作:调用 repository.deleteById(id),生成 DELETE 语句
*/
void deleteStudentById(Long id);

/**
* 根据名字模糊查询学生(方法名解析查询)
* @param name 姓名关键词(如 "李")
* @return 匹配的持久态学生列表
* @hibernate 生成:JPQL SELECT s FROM Student s WHERE s.name LIKE %?%
*/
List<Student> findStudentsByName(String name);

/**
* 根据年龄范围查询学生(方法名解析范围查询)
* @param minAge 最小年龄(包含)
* @param maxAge 最大年龄(包含)
* @return 符合条件的持久态学生列表
* @hibernate 生成:JPQL SELECT s FROM Student s WHERE s.age BETWEEN ? AND ?
*/
List<Student> findStudentsByAgeRange(Integer minAge, Integer maxAge);

/**
* 根据邮箱模式查询学生(JPQL 自定义查询)
* @param email 邮箱关键词(支持 %,如 "admin@")
* @return 匹配的持久态学生列表
* @hibernate 操作:执行 @Query("SELECT s FROM Student s WHERE s.email LIKE %:email%")
*/
List<Student> findStudentsByEmailPattern(String email);

/**
* 根据年龄阈值查询学生(原生 SQL 示例)
* @param age 年龄阈值
* @return 符合条件的持久态学生列表
* @hibernate 操作:执行 @Query(value = "SELECT * FROM students WHERE age > :age", nativeQuery = true)
* @注意:表名 students 需与数据库一致(通过 @Table(name = "students") 映射)
*/
List<Student> findStudentsOlderThan(Integer age);
}

再写实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package edu.software.ergoutree.springandhibernate.service.impl;

import edu.software.ergoutree.springandhibernate.model.Student;
import edu.software.ergoutree.springandhibernate.repository.StudentRepository;
import edu.software.ergoutree.springandhibernate.service.StudentService;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* 学生服务实现类
* 实现 StudentService 接口,基于 Spring Data JPA 和 Hibernate 完成数据操作
* Hibernate 核心:
* 1. 通过 StudentRepository 间接调用 Hibernate 的 Session 方法
* 2. 事务管理由 Spring 整合 Hibernate 的 Transaction 机制实现
* 3. 实体对象状态自动管理(持久态、脱管态转换)
*/
@Service // 标记为 Spring 服务组件,纳入依赖注入容器
public class StudentServiceImpl implements StudentService {

// 注入数据访问层接口(由 Spring 自动装配实现类)
private final StudentRepository studentRepository;

@Autowired // 构造函数注入,保证依赖不可变
public StudentServiceImpl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}

/**
* 保存学生(新增或更新)
* - 新增:当 student.getId() 为 null 时,Hibernate 执行 INSERT
* - 更新:当 student.getId() 存在时,Hibernate 执行 UPDATE(基于脏检查)
* @param student 学生实体(瞬时态或脱管态)
* @return 持久态学生对象(数据库操作后状态)
* @Transactional 注解:
* - 开启事务,Hibernate 在事务提交时同步数据到数据库
* - 等效于 Hibernate 的 session.beginTransaction() 和 session.commit()
*/
@Override
@Transactional // 声明式事务控制
public Student saveStudent(Student student) {
return studentRepository.save(student); // 调用 JpaRepository.save(),触发 Hibernate 持久化
}

/**
* 根据 ID 查询学生
* @param id 学生 ID
* @return Optional<Student> 包含持久态对象(若存在)
* @Hibernate 操作:
* 1. 调用 studentRepository.findById(id)
* 2. 内部通过 session.get(Student.class, id) 查询,使用一级缓存
*/
@Override
public Optional<Student> findStudentById(Long id) {
return studentRepository.findById(id); // 调用 JpaRepository.findById()
}

/**
* 查询所有学生
* @return 持久态学生列表(由 Hibernate 维护对象状态)
* @Hibernate 生成 SQL:SELECT * FROM students(通过实体映射规则)
*/
@Override
public List<Student> findAllStudents() {
return studentRepository.findAll(); // 调用 JpaRepository.findAll()
}

/**
* 更新学生信息(需验证学生存在)
* @param student 脱管态学生实体(必须包含 ID)
* @return 持久态学生对象(更新后状态)
* @Hibernate 步骤:
* 1. existsById() 检查实体是否存在(调用 session.contains())
* 2. save() 执行 merge 操作,合并脱管态对象到持久化上下文
*/
@Override
@Transactional
public Student updateStudent(Student student) {
// 校验 ID 有效性(防止更新不存在的记录)
if (student.getId() != null && studentRepository.existsById(student.getId())) {
return studentRepository.save(student); // 执行更新操作
}
throw new IllegalArgumentException("学生 ID 无效或记录不存在");
}

/**
* 删除学生记录
* @param id 学生 ID
* @Hibernate 操作:
* 1. 调用 studentRepository.deleteById(id)
* 2. 生成 DELETE FROM students WHERE id = ? 语句
* @注意:若存在关联实体,需配置级联删除(如 @OneToMany(cascade = REMOVE))
*/
@Override
@Transactional
public void deleteStudentById(Long id) {
studentRepository.deleteById(id); // 调用 JpaRepository.deleteById()
}

/**
* 按姓名模糊查询(方法名解析查询)
* @param name 姓名关键词(如 "王")
* @Hibernate 自动生成:SELECT s FROM Student s WHERE s.name LIKE %name%
*/
@Override
public List<Student> findStudentsByName(String name) {
return studentRepository.findByNameContaining(name); // 方法名映射为 LIKE 查询
}

/**
* 按年龄范围查询(方法名解析范围查询)
* @param minAge 最小年龄(包含)
* @param maxAge 最大年龄(包含)
* @Hibernate 自动生成:SELECT s FROM Student s WHERE s.age BETWEEN minAge AND maxAge
*/
@Override
public List<Student> findStudentsByAgeRange(Integer minAge, Integer maxAge) {
return studentRepository.findByAgeBetween(minAge, maxAge); // BETWEEN 查询
}

/**
* 按邮箱模式查询(JPQL 自定义查询)
* @param email 邮箱关键词(如 "%@qq.com")
* @Hibernate 执行:@Query("SELECT s FROM Student s WHERE s.email LIKE %:email%")
*/
@Override
public List<Student> findStudentsByEmailPattern(String email) {
return studentRepository.findStudentsByEmailPattern(email); // 调用自定义 JPQL 查询
}

/**
* 按年龄阈值查询(原生 SQL 查询)
* @param age 年龄阈值
* @Hibernate 执行:@Query(value = "SELECT * FROM students WHERE age > :age", nativeQuery = true)
*/
@Override
public List<Student> findStudentsOlderThan(Integer age) {
return studentRepository.findStudentsOlderThanAge(age); // 调用原生 SQL 查询
}
}

依赖注入与构造函数

1
2
3
4
5
6
private final StudentRepository studentRepository; // 注入数据访问层接口

@Autowired // 自动装配 StudentRepository 的实现类(由 Spring 管理)
public StudentServiceImpl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
  • 与 Hibernate 的关联StudentRepository 继承自 JpaRepository,其底层实现由 Spring Data JPA 提供,内部通过 Hibernate 的 SessionFactory 创建 Session 操作数据库。

核心方法

保存学生(新增 / 更新)

  • Hibernate 实现

    1
    return studentRepository.save(student); // 调用 JpaRepository.save()

    会根据实体是否有 ID 判断操作类型:

    • 无 ID(瞬时态):调用 Session.persist(student),生成 INSERT 语句。

    • 有 ID(脱管态):调用 Session.merge(student),生成 UPDATE 语句(仅更新变更字段)。

    • 脏检查(Dirty Checking):Hibernate 自动对比实体属性与数据库记录,仅生成必要的 SQL。

根据 ID 查询学生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据 ID 查询学生
* @param id 学生 ID
* @return Optional<Student> 包含持久态对象(若存在)
*
* @Hibernate 操作:
* 1. 调用 StudentRepository.findById(id)
* 2. 内部通过 Session.get(Student.class, id) 查询,优先使用一级缓存
* @缓存机制:
* - 一级缓存(Session 级):同一事务内多次查询直接从缓存获取
* - 二级缓存(需配置 @Cacheable):跨事务/请求缓存数据
*/
@Override
public Optional<Student> findStudentById(Long id) {
return studentRepository.findById(id); // 调用 JpaRepository.findById()
}
  • Hibernate 实现
    • findById(id) 内部调用 Session.get(),该方法会立即查询数据库(与 Session.load() 的延迟加载不同)。
    • 若启用二级缓存(如 Ehcache),需在实体类添加 @Cacheable(true),并配置缓存提供者。

查询所有学生

1
return studentRepository.findAll(); // 调用 JpaRepository.findAll()
  • Hibernate 实现
    • findAll() 生成 SELECT * FROM students(假设表名为 students)。
    • 结果集中的对象自动转为持久态,纳入当前 Session 管理。

更新学生信息

1
return studentRepository.save(student); // 执行更新
  • Hibernate 实现
    • merge(student) 会创建新的持久态副本,原脱管态对象不会被修改。
    • 若实体已存在于当前 Session(持久态),merge 会将脱管态对象的属性复制到持久态对象,触发脏检查。

删除学生

1
studentRepository.deleteById(id); // 调用 JpaRepository.deleteById()
  • Hibernate 实现
    • 直接执行 DELETE 语句,若实体有关联对象且未配置级联删除,会抛出外键约束异常。
    • 实体类中通过 @OneToMany(cascade = CascadeType.ALL) 自动处理关联删除。

业务查询方法(以姓名模糊查询为例)

1
return studentRepository.findByNameContaining(name); // 调用方法名解析查询
  • Hibernate 实现

    • 方法名中的 Containing 对应 LIKE %name%,Hibernate 自动处理参数拼接,防止 SQL 注入。

    • 生成的 JPQL 会通过 Session.createQuery() 执行,结果集对象自动转为持久态。

    • @OneToMany 关联,使用 FetchType.LAZY 延迟加载,避免 SELECT N+1 问题

      1
      @OneToMany(mappedBy = "student", fetch = FetchType.LAZY)

事务管理与 Hibernate 集成

1
@Transactional // 类级别或方法级别声明事务
  • 作用范围
    • 类上声明 @Transactional,对所有 public 方法生效(可通过 @Transactional(propagation = ...) 覆盖)。
    • 方法上声明 @Transactional,优先级高于类级别配置。
  • Hibernate 事务机制
    • 事务开始时,Hibernate 创建 Session 并绑定到当前线程(ThreadLocal)。
    • 事务中所有 Repository 方法共享同一 Session,确保数据一致性。
    • 事务提交时,Hibernate 执行 Session.flush(),将缓存中的变更同步到数据库。
    • 事务回滚时,Session 清空,所有未提交变更被撤销。

Controller层的编写

创建REST控制器,用于处理HTTP请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package edu.software.ergoutree.springandhibernate.controller;

import edu.software.ergoutree.springandhibernate.model.Student;
import edu.software.ergoutree.springandhibernate.service.StudentService;
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;

/**
* 学生控制器
* 提供 RESTful API 接口,处理 HTTP 请求与响应
*
* - Spring MVC:处理路由、参数绑定、序列化
* - Hibernate:通过 Service 层间接实现数据持久化
* - REST 规范:使用 HTTP 方法和状态码表示资源操作
*/
@RestController // 声明为 REST 控制器,返回值自动序列化为 JSON
@RequestMapping("/api/students") // 基础 URL 路径
public class StudentController {

private final StudentService studentService; // 注入服务层

@Autowired // 构造函数注入(推荐方式)
public StudentController(StudentService studentService) {
this.studentService = studentService;
}

/**
* 创建学生资源
* @param student 学生实体(JSON 请求体)
* @return 新创建的学生(含数据库生成的 ID)
* @HTTP 201 Created - 资源创建成功
* @Hibernate 操作:
* 1. Service 层调用 repository.save(student)
* 2. Hibernate 执行 INSERT 语句
* 3. 返回持久态对象(包含生成的 ID)
*/
@PostMapping
public ResponseEntity<Student> createStudent(@RequestBody Student student) {
Student savedStudent = studentService.saveStudent(student);
return new ResponseEntity<>(savedStudent, HttpStatus.CREATED);
}

/**
* 获取所有学生列表
* @return 学生列表(JSON 数组)
* @HTTP 200 OK - 请求成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findAll()
* 2. Hibernate 生成 SQL:SELECT * FROM students
* @注意:未分页,可能导致大数据量性能问题
*/
@GetMapping
public ResponseEntity<List<Student>> getAllStudents() {
List<Student> students = studentService.findAllStudents();
return new ResponseEntity<>(students, HttpStatus.OK);
}

/**
* 根据 ID 获取单个学生
* @param id 学生 ID
* @return 学生对象(JSON)或 404 Not Found
* @HTTP 200 OK - 资源存在 | 404 Not Found - 资源不存在
* @Hibernate 操作:
* 1. Service 层调用 repository.findById(id)
* 2. Hibernate 通过一级缓存或数据库查询实体
*/
@GetMapping("/{id}") // 修正:原代码缺少路径变量
public ResponseEntity<Student> getStudentById(@PathVariable Long id) {
return studentService.findStudentById(id)
.map(student -> new ResponseEntity<>(student, HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

/**
* 更新学生信息
* @param id 学生 ID(路径参数)
* @param student 学生实体(JSON 请求体,需包含 ID)
* @return 更新后的学生或 404 Not Found
* @HTTP 200 OK - 更新成功 | 404 Not Found - 学生不存在
* @Hibernate 操作:
* 1. Service 层验证学生存在性
* 2. 调用 repository.save(student) 触发 Hibernate 的 merge()
* 3. 基于脏检查生成 UPDATE 语句
*/
@PutMapping("/{id}") // 修正:原代码路径格式错误(//{id})
public ResponseEntity<Student> updateStudent(@PathVariable("id") Long id, @RequestBody Student student) {
try {
student.setId(id); // 确保路径 ID 与请求体 ID 一致
Student updatedStudent = studentService.updateStudent(student);
return new ResponseEntity<>(updatedStudent, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

/**
* 删除学生资源
* @param id 学生 ID
* @return 204 No Content - 删除成功(无返回体)
* @HTTP 204 No Content - 删除成功 | 500 Internal Server Error - 删除失败
* @Hibernate 操作:
* 1. Service 层调用 repository.deleteById(id)
* 2. Hibernate 执行 DELETE 语句
* @注意:若 ID 不存在,Hibernate 抛出 EmptyResultDataAccessException(Spring 自动处理为 500)
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStudent(@PathVariable("id") Long id) {
studentService.deleteStudentById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

/**
* 根据姓名模糊查询学生
* @param name 姓名关键词(如 "张")
* @return 匹配的学生列表
* @HTTP 200 OK - 查询成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findByNameContaining(name)
* 2. Hibernate 生成 JPQL:SELECT s FROM Student s WHERE s.name LIKE %:name%
*/
@GetMapping("/search/name")
public ResponseEntity<List<Student>> getStudentsByName(@RequestParam String name) {
List<Student> students = studentService.findStudentsByName(name);
return new ResponseEntity<>(students, HttpStatus.OK);
}

/**
* 根据年龄范围查询学生
* @param minage 最小年龄(包含)
* @param maxage 最大年龄(包含)
* @return 匹配的学生列表
* @HTTP 200 OK - 查询成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findByAgeBetween(minage, maxage)
* 2. Hibernate 生成 JPQL:SELECT s FROM Student s WHERE s.age BETWEEN :minage AND :maxage
*/
@GetMapping("/search/age")
public ResponseEntity<List<Student>> getStudentsByAge(@RequestParam Integer minage, @RequestParam Integer maxage) {
List<Student> students = studentService.findStudentsByAgeRange(minage, maxage);
return new ResponseEntity<>(students, HttpStatus.OK);
}

/**
* 根据邮箱模式查询学生
* @param pattern 邮箱关键词(如 "%@gmail.com")
* @return 匹配的学生列表
* @HTTP 200 OK - 查询成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findStudentsByEmailPattern(pattern)
* 2. Hibernate 执行自定义 JPQL:SELECT s FROM Student s WHERE s.email LIKE %:email%
*/
@GetMapping("/search/email")
public ResponseEntity<List<Student>> getStudentsByEmailPattern(@RequestParam String pattern) {
List<Student> students = studentService.findStudentsByEmailPattern(pattern);
return new ResponseEntity<>(students, HttpStatus.OK);
}

/**
* 查询年龄大于指定值的学生
* @param age 年龄阈值
* @return 匹配的学生列表
* @HTTP 200 OK - 查询成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findStudentsOlderThanAge(age)
* 2. Hibernate 执行原生 SQL:SELECT * FROM students WHERE age > :age
*/
@GetMapping("/search/older-than")
public ResponseEntity<List<Student>> getStudentsOlderThan(@RequestParam Integer age) {
List<Student> students = studentService.findStudentsOlderThan(age);
return new ResponseEntity<>(students, HttpStatus.OK);
}
}

在控制器上使用@RestController注解,表示这是一个Spring组件,并且用于处理HTTP请求。

控制器整体架构与 REST 设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 学生控制器
* 提供 RESTful API 接口,处理 HTTP 请求与响应
* 架构层次:
* 1. 接收客户端请求(HTTP 协议)
* 2. 调用 Service 层处理业务逻辑
* 3. 返回标准化 JSON 响应(含 HTTP 状态码)
* Hibernate 集成点:
* - 通过 Service 层间接使用 Hibernate 持久化能力
* - 处理 Hibernate 可能抛出的异常(如 EntityNotFoundException)
*/
@RestController // 声明为 REST 控制器,自动将返回值序列化为 JSON
@RequestMapping("/api/students") // 基础路径
public class StudentController {
  • REST 规范
    • 使用 HTTP 方法映射操作:POST(创建)、GET(查询)、PUT(更新)、DELETE(删除)。
    • 路径设计遵循资源导向:/api/students/{id} 表示学生资源。
    • 返回标准 HTTP 状态码(如 201 Created404 Not Found

依赖注入与服务层调用

1
2
3
4
5
6
private final StudentService studentService;

@Autowired
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
  • Hibernate 操作 StudentService 内部通过 StudentRepository 调用 Hibernate 的 Session 方法,实现数据库操作。
  • 设计模式: 依赖注入(DI)遵循单一职责原则,控制器专注于请求处理,业务逻辑委托给 Service 层。

核心方法注释与 Hibernate 集成**

创建学生(POST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 创建学生资源
* @param student 学生实体(JSON 格式请求体)
* @return 新创建的学生(含数据库生成的 ID)
* @HTTP 201 Created - 资源创建成功
* @Hibernate 操作:
* 1. Service 层调用 repository.save(student)
* 2. Hibernate 执行 INSERT 语句,生成自增 ID
* 3. 返回持久态对象(包含数据库生成的 ID)
*/
@PostMapping
public ResponseEntity<Student> createStudent(@RequestBody Student student) {
Student savedStudent = studentService.saveStudent(student);
return new ResponseEntity<>(savedStudent, HttpStatus.CREATED);
}
  • Hibernate 集成体现
    • 实体对象通过 @RequestBody 从 JSON 反序列化,初始为瞬时态(Transient)。
    • repository.save(student) 将其转换为持久态(Persistent),触发 Hibernate 的 INSERT 操作。
    • 返回的 savedStudent 包含数据库生成的 ID(如自增主键)。

查询所有学生(GET)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取所有学生列表
* @return 学生列表(JSON 数组)
* @HTTP 200 OK - 请求成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findAll()
* 2. Hibernate 生成 SQL:SELECT * FROM students
* 3. 结果集映射为持久态对象列表
* @注意:
* - 未分页,可能导致内存溢出(生产环境建议添加分页)
*/
@GetMapping
public ResponseEntity<List<Student>> getAllStudents() {
List<Student> students = studentService.findAllStudents();
return new ResponseEntity<>(students, HttpStatus.OK);
}
  • Hibernate 集成体现
    • 返回的 students 列表中每个对象均为持久态,处于 Hibernate 的 Session 管理下。
    • 若实体存在懒加载关联(如 @OneToMany(fetch = LAZY)),在 Session 关闭后访问会触发 LazyInitializationException

更新学生(PUT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 更新学生信息
* @param id 学生 ID(路径参数)
* @param student 学生实体(JSON 请求体,需包含 ID)
* @return 更新后的学生或 404 Not Found
* @HTTP 200 OK - 更新成功 | 404 Not Found - 学生不存在
* @Hibernate 操作:
* 1. Service 层验证学生存在性
* 2. 调用 repository.save(student) 触发 Hibernate 的 merge()
* 3. Hibernate 执行 UPDATE 语句(基于脏检查)
*/
@PutMapping("/{id}") // 修正:原代码路径格式错误(//{id})
public ResponseEntity<Student> updateStudent(@PathVariable("id") Long id, @RequestBody Student student) {
try {
student.setId(id); // 确保 ID 一致
Student updatedStudent = studentService.updateStudent(student);
return new ResponseEntity<>(updatedStudent, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
  • Hibernate 集成体现:
    • 传入的 student脱管态(Detached)对象(已存在但不在当前 Session 中)。
    • repository.save(student) 调用 Hibernate 的 Session.merge(),将脱管态对象合并到当前 Session,生成 UPDATE 语句。
    • 脏检查机制:Hibernate 仅更新实际变更的字段(通过 @DynamicUpdate 可进一步优化)。

删除学生(Delete)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 删除学生资源
* @param id 学生 ID
* @return 204 No Content - 删除成功(无返回体)
* @HTTP 204 No Content - 删除成功 | 500 Internal Server Error - 删除失败
* @Hibernate 操作:
* 1. Service 层调用 repository.deleteById(id)
* 2. Hibernate 执行 DELETE FROM students WHERE id = ?
* @注意:
* - 若 ID 不存在,Hibernate 抛出 EmptyResultDataAccessException(Spring 自动处理为 500)
* - 建议在 Service 层添加 existsById() 校验,返回 404
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStudent(@PathVariable("id") Long id) {
studentService.deleteStudentById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
  • Hibernate 集成体现
    • 直接执行 DELETE 语句,无需先查询实体。
    • 若存在关联实体(如 Student 关联 Course),需确保配置了级联删除(如 @OneToMany(cascade = CascadeType.REMOVE)),否则会触发外键约束异常。

业务查询方法(以姓名模糊查询为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 根据姓名模糊查询学生
* @param name 姓名关键词(如 "张")
* @return 匹配的学生列表
* @HTTP 200 OK - 查询成功
* @Hibernate 操作:
* 1. Service 层调用 repository.findByNameContaining(name)
* 2. Hibernate 解析方法名生成 JPQL:SELECT s FROM Student s WHERE s.name LIKE %:name%
*/
@GetMapping("/search/name")
public ResponseEntity<List<Student>> getStudentsByName(@RequestParam String name) {
List<Student> students = studentService.findStudentsByName(name);
return new ResponseEntity<>(students, HttpStatus.OK);
}
  • Hibernate 集成体现:
    • 方法名中的 Containing 自动映射为 LIKE %name% 查询,由 Hibernate 动态生成 SQL。
    • 返回的 students 列表为持久态,若包含懒加载关联,需确保在事务内访问。

异常处理

显式异常处理(如更新不存在的学生)

1
2
3
4
5
6
7
try {
student.setId(id);
Student updatedStudent = studentService.updateStudent(student);
return new ResponseEntity<>(updatedStudent, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
  • Hibernate 集成体现
    • studentService.updateStudent() 发现学生不存在时,抛出 IllegalArgumentException,控制器捕获后返回 404 Not Found
    • 避免将 Hibernate 底层异常(如 EntityNotFoundException)直接暴露给客户端。

Spring 自动异常处理

  • 默认行为
    • 若删除不存在的学生,Hibernate 抛出 EmptyResultDataAccessException,Spring 自动返回 500 Internal Server Error
  • 优化: 在 Service 层添加存在性检查,并抛出业务异常(如 StudentNotFoundException),由全局异常处理器统一返回 404
1
2
3
4
@ExceptionHandler(StudentNotFoundException.class)
public ResponseEntity<Void> handleNotFoundException() {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

启动应用

启动Spring Boot应用后,Spring Data JPA会自动配置Hibernate,并根据实体类的定义生成表结构(取决于spring.jpa.hibernate.ddl-auto的值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package edu.software.ergoutree.springandhibernate;

import edu.software.ergoutree.springandhibernate.entity.Student;
import edu.software.ergoutree.springandhibernate.repository.StudentRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

/**
* Spring Boot 应用程序入口类
*
* @SpringBootApplication 注解是一个组合注解,它包含了:
* - @Configuration: 标记该类为配置类
* - @EnableAutoConfiguration: 启用Spring Boot的自动配置机制
* - @ComponentScan: 启用组件扫描,自动发现和注册Bean
*/
@SpringBootApplication
public class SpringAndHibernateApplication {

public static void main(String[] args) {
SpringApplication.run(SpringAndHibernateApplication.class, args);
}

/**
* 应用启动后初始化一些测试数据
* CommandLineRunner 是Spring Boot提供的一个接口,用于在应用启动后执行一些初始化操作
*/
@Bean
public CommandLineRunner initData(StudentRepository studentRepository) {
return args -> {
// 检查数据库中是否已有数据
if (studentRepository.count() == 0) {
System.out.println("初始化学生数据...");

// 创建并保存几个学生实体
studentRepository.save(new Student("张三", "zhangsan@example.com", 20));
studentRepository.save(new Student("李四", "lisi@example.com", 22));
studentRepository.save(new Student("王五", "wangwu@example.com", 21));
studentRepository.save(new Student("赵六", "zhaoliu@example.com", 23));

System.out.println("数据初始化完成!");
} else {
System.out.println("数据库中已有数据,跳过初始化步骤。");
}

// 显示所有学生信息
System.out.println("学生列表:");
studentRepository.findAll().forEach(student ->
System.out.println(student.getId() + ": " + student.getName() + ", " +
student.getEmail() + ", " + student.getAge() + "岁"));
};
}
}

总结这个项目的集成 Hibernate 的要点

Spring Boot 通过 Spring Data JPA 自动集成了 Hibernate,这种集成方式非常优雅且简化了大量配置工作。

依赖关系

pom.xml中,我们添加了以下关键依赖:

  • ```xml spring-boot-starter-data-jpa

    1
    2
    3
    4
    5

    这个启动器包含了 Spring Data JPA 和 Hibernate 核心依赖

    - ```xml
    mysql-connector-j

    MySQL 数据库驱动

  • ```xml spring-boot-starter-web

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    Web 应用支持

    **自动配置机制**

    Spring Boot 的核心优势在于其自动配置机制。通过 `@SpringBootApplication`注解,它会:

    - 自动配置 Hibernate 的 `SessionFactory`
    - 自动配置事务管理器
    - 自动配置数据源
    - 自动扫描实体类并映射到数据库表

    **配置文件**

    在 `application.properties`中,我们配置了:

    ```properties
    propertiesCopyInsert# 数据库连接配置
    spring.datasource.url=jdbc:mysql://localhost:3306/hibernate_demo?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=root

    # Hibernate配置
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

这些配置告诉 Spring Boot:

  • 使用 MySQL 数据库
  • 自动创建/更新表结构 ddl-auto=update
  • 显示 SQL 语句以便调试
  • 使用 MySQL 方言

实体类映射

我们使用 JPA 注解来定义实体类与数据库表的映射关系:

1
2
3
4
5
6
7
8
9
10
11
12
javaCopyInsert@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name", nullable = false, length = 100)
private String name;

// 其他字段...
}

这些注解是 JPA 规范的一部分,Hibernate 作为 JPA 的实现,会解析这些注解并将实体类映射到数据库表。

Repository 层

Spring Data JPA 提供了强大的 Repository 接口,大大简化了数据访问层的开发:

1
2
3
4
5
6
7
8
9
10
11
12
13
javaCopyInsert@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
// 方法名约定查询
List<Student> findByNameContaining(String name);

// JPQL 查询
@Query("SELECT s FROM Student s WHERE s.email LIKE %:email%")
List<Student> findStudentsByEmailPattern(@Param("email") String email);

// 原生 SQL 查询
@Query(value = "SELECT * FROM students WHERE age > :age", nativeQuery = true)
List<Student> findStudentsOlderThanAge(@Param("age") Integer age);
}

这里展示了三种查询方式:

  • 方法名约定查询:通过方法名自动生成查询语句
  • JPQL 查询:使用面向对象的查询语言
  • 原生 SQL 查询:直接使用 SQL 语句

事务管理

Spring Boot 自动配置了事务管理器,我们只需使用 @Transactional注解即可:

1
2
3
4
5
6
7
8
9
10
javaCopyInsert@Service
public class StudentServiceImpl implements StudentService {
@Override
@Transactional
public Student saveStudent(Student student) {
return studentRepository.save(student);
}

// 其他方法...
}

数据初始化

在启动类中,我们使用 CommandLineRunner来初始化测试数据:

1
2
3
4
5
6
7
8
9
10
javaCopyInsert@Bean
public CommandLineRunner initData(StudentRepository studentRepository) {
return args -> {
if (studentRepository.count() == 0) {
// 创建并保存几个学生实体
studentRepository.save(new Student("张三", "zhangsan@example.com", 20));
// 更多学生...
}
};
}

Spring Boot 与 Hibernate 的集成主要通过 Spring Data JPA 实现,具有以下优势:

  1. 简化配置:无需编写大量 XML 配置文件
  2. 约定优于配置:遵循默认约定,减少配置量
  3. 自动化:自动创建表结构、自动生成查询语句
  4. 类型安全:编译时类型检查,减少运行时错误
  5. 声明式事务:使用注解简化事务管理
  6. 多种查询方式:支持方法名约定、JPQL 和原生 SQL

这种集成方式大大提高了开发效率,让开发者可以专注于业务逻辑而非底层数据访问细节。

要运行此应用程序,只需确保 MySQL 服务已启动,然后运行 Spring Boot 应用程序即可。应用启动后,将自动创建数据库表并初始化测试数据。