在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 > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <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 spring.datasource.url =jdbc:mysql://localhost:3306/hibernate_demo?createDatabaseIfNotExist=true&useSSL=false&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) 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+ 方言 logging.level.org.hibernate.SQL =DEBUG # 显示执行的 SQL 语句 logging.level.org.hibernate.type.descriptor.sql.BasicBinder =TRACE # 显示 SQL 参数绑定细节
参数说明:
spring.datasource.*
:配置数据源的基本信息。
spring.jpa.hibernate.ddl-auto
:控制Hibernate的DDL操作(如update
、create
、none
)。
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;@Entity @Data @Table(name = "students") @NoArgsConstructor @AllArgsConstructor public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name", nullable = false, length = 100) private String name; @Column(name = "email", unique = true) private String email; @Column(name = "age") private Integer 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;@Repository public interface StudentRepository extends JpaRepository <Student, Long> { List<Student> findByNameContaining (String name) ; List<Student> findByAgeBetween (Integer minAge, Integer maxAge) ; @Query("SELECT s FROM Student s WHERE s.email LIKE %:email%") List<Student> findStudentsByEmailPattern (@Param("email") String email) ; @Query(value = "SELECT * FROM students WHERE age > :age ORDER BY name", nativeQuery = true) List<Student> findStudentsOlderThanAge (@Param("age") Integer age) ; Page<Student> findByNameContaining (String name, Pageable pageable) ; @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
:
方法名约定查询
模糊查询(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
范围查询(Between)
1 List<Student> findByAgeBetween (Integer minAge, Integer maxAge) ;
解析规则 :
findBy
:查询前缀。
AgeBetween
:字段名(Age
) +
范围操作(Between
)。
等效 SQL:WHERE age BETWEEN ? AND ?
。
参数顺序 :
第一个参数对应下限(minAge
),第二个对应上限(maxAge
)。
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
开始),但命名参数更易维护。
原生 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 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 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
)。
自定义方法实现
若默认方法和查询注解无法满足需求,可通过以下方式扩展:
自定义 Repository 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface StudentRepositoryCustom { List<Student> findStudentsByCustomCriteria (String criteria) ; } class StudentRepositoryImpl implements StudentRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<Student> findStudentsByCustomCriteria (String criteria) { String jpql = "SELECT s FROM Student s WHERE ..." ; return entityManager.createQuery(jpql, Student.class).getResultList(); } } public interface StudentRepository extends JpaRepository <Student, Long>, StudentRepositoryCustom { }
使用 @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;public interface StudentService { Student saveStudent (Student student) ; Optional<Student> findStudentById (Long id) ; List<Student> findAllStudents () ; Student updateStudent (Student student) ; void deleteStudentById (Long id) ; List<Student> findStudentsByName (String name) ; List<Student> findStudentsByAgeRange (Integer minAge, Integer maxAge) ; List<Student> findStudentsByEmailPattern (String email) ; 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;@Service public class StudentServiceImpl implements StudentService { private final StudentRepository studentRepository; @Autowired public StudentServiceImpl (StudentRepository studentRepository) { this .studentRepository = studentRepository; } @Override @Transactional public Student saveStudent (Student student) { return studentRepository.save(student); } @Override public Optional<Student> findStudentById (Long id) { return studentRepository.findById(id); } @Override public List<Student> findAllStudents () { return studentRepository.findAll(); } @Override @Transactional public Student updateStudent (Student student) { if (student.getId() != null && studentRepository.existsById(student.getId())) { return studentRepository.save(student); } throw new IllegalArgumentException ("学生 ID 无效或记录不存在" ); } @Override @Transactional public void deleteStudentById (Long id) { studentRepository.deleteById(id); } @Override public List<Student> findStudentsByName (String name) { return studentRepository.findByNameContaining(name); } @Override public List<Student> findStudentsByAgeRange (Integer minAge, Integer maxAge) { return studentRepository.findByAgeBetween(minAge, maxAge); } @Override public List<Student> findStudentsByEmailPattern (String email) { return studentRepository.findStudentsByEmailPattern(email); } @Override public List<Student> findStudentsOlderThan (Integer age) { return studentRepository.findStudentsOlderThanAge(age); } }
依赖注入与构造函数
1 2 3 4 5 6 private final StudentRepository studentRepository; @Autowired public StudentServiceImpl (StudentRepository studentRepository) { this .studentRepository = studentRepository; }
与 Hibernate 的关联 :
StudentRepository
继承自
JpaRepository
,其底层实现由 Spring Data JPA 提供,内部通过
Hibernate 的 SessionFactory
创建 Session
操作数据库。
核心方法
保存学生(新增 / 更新)
Hibernate 实现
1 return studentRepository.save(student);
会根据实体是否有 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 @Override public Optional<Student> findStudentById (Long id) { return studentRepository.findById(id); }
Hibernate 实现
findById(id)
内部调用
Session.get()
,该方法会立即查询数据库(与
Session.load()
的延迟加载不同)。
若启用二级缓存(如 Ehcache),需在实体类添加
@Cacheable(true)
,并配置缓存提供者。
查询所有学生
1 return studentRepository.findAll();
Hibernate 实现
findAll()
生成
SELECT * FROM students
(假设表名为
students
)。
结果集中的对象自动转为持久态,纳入当前 Session
管理。
更新学生信息
1 return studentRepository.save(student);
Hibernate 实现
merge(student)
会创建新的持久态副本,原脱管态对象不会被修改。
若实体已存在于当前
Session
(持久态),merge
会将脱管态对象的属性复制到持久态对象,触发脏检查。
删除学生
1 studentRepository.deleteById(id);
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 集成
作用范围
类上声明 @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;@RestController @RequestMapping("/api/students") public class StudentController { private final StudentService studentService; @Autowired public StudentController (StudentService studentService) { this .studentService = studentService; } @PostMapping public ResponseEntity<Student> createStudent (@RequestBody Student student) { Student savedStudent = studentService.saveStudent(student); return new ResponseEntity <>(savedStudent, HttpStatus.CREATED); } @GetMapping public ResponseEntity<List<Student>> getAllStudents () { List<Student> students = studentService.findAllStudents(); return new ResponseEntity <>(students, HttpStatus.OK); } @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)); } @PutMapping("/{id}") public ResponseEntity<Student> updateStudent (@PathVariable("id") Long id, @RequestBody Student student) { try { student.setId(id); Student updatedStudent = studentService.updateStudent(student); return new ResponseEntity <>(updatedStudent, HttpStatus.OK); } catch (IllegalArgumentException e) { return new ResponseEntity <>(HttpStatus.NOT_FOUND); } } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteStudent (@PathVariable("id") Long id) { studentService.deleteStudentById(id); return new ResponseEntity <>(HttpStatus.NO_CONTENT); } @GetMapping("/search/name") public ResponseEntity<List<Student>> getStudentsByName (@RequestParam String name) { List<Student> students = studentService.findStudentsByName(name); return new ResponseEntity <>(students, HttpStatus.OK); } @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); } @GetMapping("/search/email") public ResponseEntity<List<Student>> getStudentsByEmailPattern (@RequestParam String pattern) { List<Student> students = studentService.findStudentsByEmailPattern(pattern); return new ResponseEntity <>(students, HttpStatus.OK); } @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 @RestController @RequestMapping("/api/students") public class StudentController {
REST 规范 :
使用 HTTP
方法映射操作:POST
(创建)、GET
(查询)、PUT
(更新)、DELETE
(删除)。
路径设计遵循资源导向:/api/students/{id}
表示学生资源。
返回标准 HTTP 状态码(如
201 Created
、404 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 @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 @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 @PutMapping("/{id}") public ResponseEntity<Student> updateStudent (@PathVariable("id") Long id, @RequestBody Student student) { 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 集成体现:
传入的 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 @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 @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;@SpringBootApplication public class SpringAndHibernateApplication { public static void main (String[] args) { SpringApplication.run(SpringAndHibernateApplication.class, args); } @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) ; @Query("SELECT s FROM Student s WHERE s.email LIKE %:email%") List<Student> findStudentsByEmailPattern (@Param("email") String email) ; @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
实现,具有以下优势:
简化配置 :无需编写大量 XML 配置文件
约定优于配置 :遵循默认约定,减少配置量
自动化 :自动创建表结构、自动生成查询语句
类型安全 :编译时类型检查,减少运行时错误
声明式事务 :使用注解简化事务管理
多种查询方式 :支持方法名约定、JPQL 和原生 SQL
这种集成方式大大提高了开发效率,让开发者可以专注于业务逻辑而非底层数据访问细节。
要运行此应用程序,只需确保 MySQL 服务已启动,然后运行 Spring Boot
应用程序即可。应用启动后,将自动创建数据库表并初始化测试数据。