Spring中使用Hibernate

Hibernate是最流行的ORM框架之一,而Spring提供了对Hibernate的出色集成支持。

本文介绍如何在传统Spring框架(非Spring Boot)中集成Hibernate。

项目搭建结构

我们先写一个例子,来看看 Spring 中如何使用和体现 Hibernate

项目结构

典型的Spring框架集成Hibernate的项目目录结构(Maven项目):

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
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── yourcompany/
│ │ └── yourapp/
│ │ ├── config/ # Spring配置类
│ │ │ └── HibernateConfig.java
│ │ ├── controller/ # 控制器层
│ │ │ └── EmployeeController.java
│ │ ├── service/ # 服务层
│ │ │ ├── EmployeeService.java
│ │ │ └── impl/
│ │ │ └── EmployeeServiceImpl.java
│ │ ├── dao/ # 数据访问层
│ │ │ ├── EmployeeDao.java
│ │ │ └── impl/
│ │ │ └── EmployeeDaoImpl.java
│ │ ├── model/ # 实体类
│ │ │ ├── Employee.java
│ │ │ ├── Department.java
│ │ │ └── enums/ # 枚举类型
│ │ │ └── EmployeeStatus.java
│ │ ├── dto/ # 数据传输对象
│ │ │ └── EmployeeDTO.java
│ │ └── AppMain.java # 主启动类
│ ├── resources/
│ │ ├── application.properties # 应用配置
│ │ ├── hibernate.cfg.xml # Hibernate配置(可选)
│ │ ├── messages/ # 国际化资源
│ │ │ └── messages.properties
│ │ └── META-INF/
│ │ └── persistence.xml # JPA配置(可选)
│ └── webapp/ # Web相关资源(如果是Web应用)
│ ├── WEB-INF/
│ │ ├── views/ # 视图文件
│ │ │ └── employees.jsp
│ │ └── web.xml # Web部署描述符
│ └── resources/ # 静态资源
│ ├── css/
│ ├── js/
│ └── images/
└── test/ # 测试代码
└── java/
└── com/
└── yourcompany/
└── yourapp/
├── service/
│ └── EmployeeServiceTest.java
└── dao/
└── EmployeeDaoTest.java

但是本次演示,还是打算写成简单一些的方式

添加依赖

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
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>

<!-- Spring ORM (包含对Hibernate的支持) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.20</version>
</dependency>

<!-- Hibernate核心 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.10.Final</version>
</dependency>

<!-- 数据库驱动 (以MySQL为例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

<!-- 连接池 (HikariCP推荐) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>

<!-- JTA API (可选,需要事务管理时) -->
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.3</version>
</dependency>
</dependencies>

配置 Spring 配置文件

创建Spring配置文件applicationContext.xmlapplication.properties,或者使用Java配置类:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 启用注解驱动 -->
<context:annotation-config/>
<!--配置了组件扫描路径,Spring 会自动扫描指定包下的 @Component、@Service 等注解的类-->
<context:component-scan base-package="com.yourpackage"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<!--配置 MySQL 数据库连接信息,包括驱动类、URL、用户名和密码-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/your_db?useSSL=false&amp;serverTimezone=UTC"/>
<property name="username" value="your_username"/>
<property name="password" value="your_password"/>
<property name="maximumPoolSize" value="10"/>
</bean>

<!-- 配置Hibernate SessionFactory -->
<!-- 指定扫描实体类的包路径 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!--设置 Hibernate 的方言、SQL 显示、格式化和 DDL 自动更新等属性-->
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.yourpackage.model"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<!-- 其他Hibernate属性 -->
</props>
</property>
</bean>

<!-- 配置事务管理器 -->
<!-- 配置 Hibernate 事务管理器,启用注解驱动的事务管理 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 启用注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

properties 貌似是给 spring boot 用的,纯framework貌似都用xml 我感觉这个更方便

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.hikari.maximum-pool-size=10

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

# 扫描实体类的包路径
spring.jpa.hibernate.packages-to-scan=com.yourpackage.model

确保主应用类上有 @SpringBootApplication 注解,它包含了 @ComponentScan 功能

Java 配置方式

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
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
public class HibernateConfig {

@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTC");
dataSource.setUsername("your_username");
dataSource.setPassword("your_password");
dataSource.setMaximumPoolSize(10);
return dataSource;
}

@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.yourpackage.model");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}

private Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "true");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
// 其他Hibernate属性
return properties;
}

@Bean
public PlatformTransactionManager transactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
}

创建实体类

使用Hibernate注解定义实体类

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
import javax.persistence.*;
import java.util.Date;

/**
* 员工实体类,映射数据库中的 employees 表
* 包含员工的基本信息和所属部门关联
*/
@Entity
@Table(name = "employees") // 映射到 employees 表
public class Employee {

/**
* 员工ID,主键,自增长
* 使用 GenerationType.IDENTITY 依赖数据库的自增机制
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 员工名,不可为空,最大长度50
*/
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;

/**
* 员工姓,不可为空,最大长度50
*/
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;

/**
* 员工邮箱,不可为空,必须唯一
* 通常用于系统登录或联系信息
*/
@Column(nullable = false, unique = true)
private String email;

/**
* 雇佣日期,仅存储日期部分(忽略时间)
* 使用 TemporalType.DATE 映射到数据库的 DATE 类型
*/
@Column(name = "hire_date")
@Temporal(TemporalType.DATE)
private Date hireDate;

/**
* 所属部门,多对一关联
* 使用 FetchType.LAZY 实现延迟加载,避免查询员工时立即加载部门信息
* 通过 department_id 外键关联 departments 表
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;

// 构造函数
public Employee() { }

public Employee(String firstName, String lastName, String email, Date hireDate) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.hireDate = hireDate;
}
}

创建DAO层

传统Hibernate DAO实现

这里使用了大量 session 的方法和 query 等常用 api 的方法,体现了 Hibernate 的使用

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
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

/**
* 员工数据访问对象(DAO)
* 负责与数据库进行交互,处理 Employee 实体的持久化操作
*/
@Repository
@Transactional
public class EmployeeDao {

/**
* Hibernate SessionFactory,由 Spring 容器注入
* 用于获取与数据库的会话(Session)
*/
@Autowired
private SessionFactory sessionFactory;

/**
* 获取当前线程绑定的 Hibernate Session
* 在 @Transactional 注解的事务上下文中,Spring 会自动管理 Session 的生命周期
*/
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}

/**
* 根据 ID 查询员工
* @param id 员工 ID
* @return 对应的 Employee 对象,若不存在则返回 null
*/
public Employee findById(Long id) {
return getCurrentSession().get(Employee.class, id);
}

/**
* 保存或更新员工信息
* - 若员工 ID 为空(null),则执行插入操作
* - 若员工 ID 已存在,则执行更新操作
* @param employee 要保存或更新的 Employee 对象
*/
public void save(Employee employee) {
getCurrentSession().saveOrUpdate(employee);
}

/**
* 删除员工记录
* @param employee 要删除的 Employee 对象
*/
public void delete(Employee employee) {
getCurrentSession().delete(employee);
}

/**
* 查询所有员工
* @return 包含所有 Employee 对象的列表
*/
@SuppressWarnings("unchecked")
public List<Employee> findAll() {
return getCurrentSession().createQuery("from Employee").list();
}

/**
* 根据姓氏查询员工
* @param lastName 要查询的姓氏
* @return 包含匹配 Employee 对象的列表
*/
public List<Employee> findByLastName(String lastName) {
return getCurrentSession()
.createQuery("from Employee where lastName = :lastName", Employee.class)
.setParameter("lastName", lastName)
.list();
}
}

主要注解解析

  1. @Repository
  • 作用
    • 声明该类为 Spring 的数据访问组件(DAO),是 @Component 的特殊化版本。
    • 自动将 DAO 类注册为 Spring Bean,并支持 Spring 的数据访问异常转换(将 Hibernate/JPA 异常转换为 Spring 的 DataAccessException 体系)。
  1. @Transactional
  • 作用
    • 声明该类的所有公共方法都在事务管理下执行。
    • 默认配置下:
      • 事务传播行为为 PROPAGATION_REQUIRED(若当前无事务,则创建新事务;否则加入当前事务)。
      • 事务隔离级别为数据库默认级别。
      • 所有 RuntimeException 会触发事务回滚,受检异常(如 IOException)不会触发回滚。
  1. @Autowired
  • 作用
    • 通过类型自动注入依赖的 Bean(此处注入 SessionFactory)。
    • Spring 会在容器中查找 SessionFactory 类型的 Bean 并注入。

方法解析

  1. getCurrentSession()
  • 功能:获取当前线程绑定的 Session
  • 关键点
    • @Transactional 注解的事务上下文中,Session 由 Spring 自动管理,无需手动关闭。
    • 若不在事务中调用此方法,会抛出异常。
  1. findById(Long id)
  • 功能:根据 ID 查询员工。
  • Hibernate 方法
    • Session.get(Class, id):立即查询数据库,若记录不存在返回 null
  1. save(Employee employee)
  • 功能:保存或更新员工。

  • Hibernate 方法

    • ``` Session.saveOrUpdate(Object)
      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

      - 若对象的 ID 为 `null` 或未持久化状态,执行 `INSERT`。
      - 若对象的 ID 已存在,执行 `UPDATE`。

      4. `delete(Employee employee)`

      - **功能**:删除员工记录。
      - Hibernate 方法
      - `Session.delete(Object)`:将对象从持久化状态变为删除状态,事务提交时执行 `DELETE` 语句。

      5. `findAll()`

      - **功能**:查询所有员工。
      - Hibernate 查询
      - `Session.createQuery("from Employee")`:使用 HQL(Hibernate Query Language)查询,等价于 SQL 的 `SELECT * FROM employees`。
      - `@SuppressWarnings("unchecked")`:抑制原始类型警告(Hibernate 5.2 前的 `createQuery()` 返回未泛型化的 `Query` 对象)。

      6. `findByLastName(String lastName)`

      - **功能**:根据姓氏查询员工。
      - Hibernate 查询
      - `createQuery("from Employee where lastName = :lastName", Employee.class)`:使用具名参数的 HQL 查询,防止 SQL 注入。
      - `setParameter("lastName", lastName)`:绑定参数值。



      ### 服务层实现

      ```java
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      import java.util.List;

      /**
      * 员工服务层
      * 处理员工相关的业务逻辑,调用 DAO 层完成数据持久化操作
      */
      @Service
      public class EmployeeService {

      /**
      * 员工数据访问对象
      * 由 Spring 容器自动注入,用于执行数据库操作
      */
      @Autowired
      private EmployeeDao employeeDao;

      /**
      * 根据 ID 获取员工信息
      * @param id 员工 ID
      * @return 对应的员工对象,若不存在则返回 null
      */
      @Transactional(readOnly = true)
      public Employee getEmployeeById(Long id) {
      return employeeDao.findById(id);
      }

      /**
      * 保存或更新员工信息
      * - 新增员工:当员工对象 ID 为空时
      * - 更新员工:当员工对象 ID 已存在时
      * @param employee 员工对象
      */
      @Transactional
      public void saveEmployee(Employee employee) {
      employeeDao.save(employee);
      }

      /**
      * 删除员工信息
      * @param employee 要删除的员工对象
      */
      @Transactional
      public void deleteEmployee(Employee employee) {
      employeeDao.delete(employee);
      }

      /**
      * 获取所有员工列表
      * @return 包含所有员工的列表
      */
      @Transactional(readOnly = true)
      public List<Employee> getAllEmployees() {
      return employeeDao.findAll();
      }

      /**
      * 根据姓氏搜索员工
      * @param lastName 姓氏
      * @return 匹配的员工列表
      */
      @Transactional(readOnly = true)
      public List<Employee> searchByLastName(String lastName) {
      return employeeDao.findByLastName(lastName);
      }
      }

主要注解解析

  1. @Service
  • 作用
    • 声明该类为 Spring 的服务层组件,是 @Component 的特殊化版本。
    • 用于标识业务逻辑层,提高代码可读性和可维护性。
  • Spring 容器行为
    • 自动扫描并注册为 Bean,可通过依赖注入使用。
  1. @Autowired
  • 作用
    • 通过类型自动注入依赖的 Bean(此处注入 EmployeeDao)。
  • 依赖注入方式
    • 字段注入:简洁但不利于单元测试(建议使用构造器注入,Spring 4.3+ 支持单参构造器省略 @Autowired)。
  1. @Transactional
  • 作用

    • 声明方法在事务管理下执行,确保数据操作的原子性、一致性、隔离性和持久性(ACID)。
  • 关键属性

    • ```java readOnly = true
      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

      - 标记为只读事务,优化查询性能(如禁用缓存刷新、数据库优化等)。
      - 仅用于查询方法,不可用于增删改操作。

      - 默认配置

      - 传播行为:`PROPAGATION_REQUIRED`(若当前无事务,则创建新事务;否则加入当前事务)。
      - 隔离级别:使用数据库默认级别(如 MySQL 的 `REPEATABLE READ`)。
      - 回滚规则:默认对 `RuntimeException` 和 `Error` 回滚,对受检异常(如 `IOException`)不回滚。

      #### **方法解析**

      1. `getEmployeeById(Long id)`

      - **功能**:根据 ID 查询员工。
      - 事务特性
      - `readOnly = true`:优化查询性能。
      - 业务逻辑
      - 直接委派给 DAO 层执行数据库查询。

      2. `saveEmployee(Employee employee)`

      - **功能**:保存或更新员工信息。
      - 事务特性
      - 读写事务,确保操作的原子性。
      - 业务逻辑
      - 调用 DAO 层的 `save` 方法,可能触发 `INSERT` 或 `UPDATE`。

      3. `deleteEmployee(Employee employee)`

      - **功能**:删除员工信息。
      - 事务特性
      - 读写事务,确保操作的原子性。
      - 业务逻辑
      - 调用 DAO 层的 `delete` 方法执行物理删除。

      4. `getAllEmployees()`

      - **功能**:获取所有员工列表。
      - 事务特性
      - `readOnly = true`:优化查询性能。
      - 业务逻辑
      - 直接委派给 DAO 层查询所有记录。

      5. `searchByLastName(String lastName)`

      - **功能**:根据姓氏搜索员工。
      - 事务特性
      - `readOnly = true`:优化查询性能。
      - 业务逻辑
      - 直接委派给 DAO 层执行条件查询。

      #### **服务层设计原则**

      1. **单一职责**:
      - 专注业务逻辑处理,不涉及数据访问细节(由 DAO 负责)。
      2. **事务边界**:
      - 在服务层控制事务边界,确保业务操作的原子性(如转账需同时更新两个账户)。
      3. **数据校验**:
      - 可在此层添加参数校验逻辑(示例中未体现,实际项目需添加)。
      4. **异常处理**:
      - 可捕获 DAO 层异常并转换为业务异常(示例中未体现,实际项目需添加)。
      5. **事务传播**:
      - 若方法间存在调用关系,需注意事务传播行为(默认 `REQUIRED` 可满足多数场景)。

      #### **注意事项**

      1. **事务嵌套**:
      - 若服务层方法相互调用,需注意事务传播行为可能导致的意外结果(如内层方法异常导致整个事务回滚)。
      2. **只读事务**:
      - 确保 `readOnly = true` 仅用于纯查询方法,否则可能导致数据无法持久化。
      3. **异常类型**:
      - 业务异常建议继承 `RuntimeException`,确保事务自动回滚。



      ### 事务管理

      Spring提供了声明式事务管理,可以通过注解轻松配置:

      ```java
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      import org.hibernate.Hibernate; // Hibernate 特定 API

      @Service
      @Transactional
      public class DepartmentService {

      @Autowired
      private DepartmentDao departmentDao;

      @Autowired
      private EmployeeDao employeeDao;

      /**
      * 获取部门及其所有员工(包含延迟加载的员工集合)
      * @param deptId 部门 ID
      * @return 包含完整员工列表的部门对象
      */
      @Transactional(readOnly = true)
      public Department getDepartmentWithEmployees(Long deptId) {
      // 1. 通过 DAO 查询部门(此时员工集合为延迟加载的代理对象)
      Department dept = departmentDao.findById(deptId);

      // 2. 强制初始化延迟加载的员工集合
      // Hibernate 特定方法,在事务未关闭前触发 SQL 查询
      Hibernate.initialize(dept.getEmployees());

      // 3. 返回已初始化员工集合的部门对象
      return dept;
      }

      /**
      * 批量转移员工到新部门
      * @param fromDeptId 源部门 ID
      * @param toDeptId 目标部门 ID
      * @param employeeIds 待转移员工 ID 列表
      */
      @Transactional(rollbackFor = Exception.class)
      public void transferEmployees(Long fromDeptId, Long toDeptId, List<Long> employeeIds) {
      // 1. 获取源部门和目标部门
      Department fromDept = departmentDao.findById(fromDeptId);
      Department toDept = departmentDao.findById(toDeptId);

      // 2. 遍历员工 ID 列表,逐个转移
      for (Long empId : employeeIds) {
      Employee emp = employeeDao.findById(empId);

      // 3. 从源部门移除员工(业务逻辑由实体类方法实现)
      fromDept.removeEmployee(emp);

      // 4. 添加到目标部门(业务逻辑由实体类方法实现)
      toDept.addEmployee(emp);

      // 5. 保存员工(更新部门关联)
      employeeDao.save(emp);
      }

      // 6. 保存部门变更(可选,取决于实体关系配置)
      departmentDao.save(fromDept);
      departmentDao.save(toDept);
      }
      }

主要注解解析

  1. @Service
  • 作用
    • 声明该类为 Spring 的服务层组件,负责业务逻辑处理。
  • Spring 容器行为
    • 自动扫描并注册为 Bean,支持依赖注入。
  1. @Transactional(类级别)
  • 作用
    • 为所有公共方法提供默认事务配置。
  • 默认配置
    • 传播行为:PROPAGATION_REQUIRED(必要时创建新事务)。
    • 隔离级别:使用数据库默认级别。
    • 回滚规则:仅对 RuntimeExceptionError 回滚。
  1. @Transactional(readOnly = true)(方法级别)
  • 作用
    • 覆盖类级别的事务配置,标记方法为只读事务。
  • 优化点
    • Hibernate 会优化查询执行(如禁用脏检查)。
    • 数据库可优化只读事务的锁策略。
  1. @Transactional(rollbackFor = Exception.class)(方法级别)
  • 作用
    • 覆盖类级别的回滚规则,指定所有异常(包括受检异常)都触发事务回滚。
  • 适用场景
    • 确保业务异常(如 ValidationException)也能触发回滚。

方法解析

  1. getDepartmentWithEmployees(Long deptId)
  • 功能:获取部门及其所有员工。
  • Hibernate 集成点
    • 延迟加载处理
      • departmentDao.findById(deptId) 返回的 Department 对象中,employees 集合是 Hibernate 代理对象(未初始化)。
      • Hibernate.initialize(dept.getEmployees()) 强制触发 SQL 查询,在事务未关闭前加载员工数据。
    • Session 生命周期
      • 方法在事务内执行,Session 保持打开状态,允许延迟加载初始化。
  1. transferEmployees(...)
  • 功能:批量转移员工到新部门。
  • Hibernate 集成点
    • 实体状态管理
      • 通过 DAO 获取的 DepartmentEmployee 对象处于持久化状态。
      • 修改持久化对象的关联关系(如 removeEmployeeaddEmployee)会被 Hibernate 自动跟踪。
    • 级联操作
      • employeeDao.save(emp) 可能无需调用(取决于实体类的 cascade 配置)。
      • Department 实体配置了 cascade = CascadeType.ALL,则直接保存部门即可同步员工关联。
    • 事务一致性
      • 整个方法在单个事务中执行,确保数据一致性(要么全部转移成功,要么全部失败)。

Hibernate 集成关键点

  1. 延迟加载(Lazy Loading)处理
    • 通过 Hibernate.initialize() 强制初始化延迟集合,避免在事务外访问时抛出 LazyInitializationException
    • 依赖 @Transactional 保持 Session 打开状态。
  2. 实体状态管理
    • Hibernate 跟踪持久化对象的状态变化,事务提交时自动生成 SQL。
    • 示例中通过 removeEmployeeaddEmployee 修改实体关系,无需手动编写 SQL。
  3. 级联操作
    • 实体类的 @OneToMany@ManyToOne 注解可能配置了 cascade 属性(如 CascadeType.PERSISTCascadeType.MERGE),决定关联对象的自动持久化行为。
  4. Session 生命周期
    • Spring 的 @Transactional 管理 Hibernate Session 的打开、关闭和刷新。
    • 方法执行期间 Session 保持打开,允许多次数据库操作。

注意事项

  1. N+1 查询问题
    • getDepartmentWithEmployees 方法通过 Hibernate.initialize() 解决了 N+1 查询问题(先查部门,再查所有员工)。
    • 更优方案:使用 JOIN FETCH 优化查询(如 SELECT d FROM Department d JOIN FETCH d.employees WHERE d.id = :id)。
  2. 事务边界
    • transferEmployees 方法必须在单个事务内执行,否则可能导致数据不一致(如部分员工转移成功,部分失败)。
  3. 异常处理
    • rollbackFor = Exception.class 确保所有异常都触发回滚,但需谨慎使用,避免掩盖业务问题。
  4. 实体关系维护
    • 示例假设 Department 实体的 removeEmployeeaddEmployee 方法同时维护双向关联(即更新 Employeedepartment 字段)。
    • 若未正确维护双向关联,可能导致数据库与对象状态不一致。

高级配置

二级缓存配置

为什么配置二级缓存,什么时候配置

  1. 减少数据库访问压力
  • 原理:将常用数据存储在应用服务器内存或分布式缓存中(如 Ehcache),避免重复查询数据库。
  • 场景:高并发读场景(如商品详情、字典表),减少数据库 I/O 负载,提升系统吞吐量。
  1. 提升查询性能
  • 原理:直接从缓存中读取数据,响应速度远快于数据库查询(内存访问速度比磁盘快 10 万倍以上)。
  • 场景:频繁查询但不常更新的数据(如用户档案、配置信息),降低延迟,改善用户体验。
  1. 减轻应用层压力
  • 原理:缓存分担了应用层的数据处理压力,尤其在复杂关联查询(如Department关联Employee)时,避免重复执行 Hibernate 的对象关系映射(ORM)操作。
  1. 降低资源消耗
  • 原理:减少数据库连接的创建和释放频率,降低 JDBC 操作的资源开销(如连接池压力)。

添加Ehcache依赖

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.6.10.Final</version>
</dependency>

在Hibernate配置中添加

1
2
3
4
5
6
7
8
9
private Properties hibernateProperties() {
Properties properties = new Properties();
// 其他配置...
properties.setProperty("hibernate.cache.use_second_level_cache", "true");
properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
properties.setProperty("hibernate.javax.cache.provider", "org.ehcache.jsr107.EhcacheCachingProvider");
properties.setProperty("hibernate.cache.use_query_cache", "true");
return properties;
}

在实体类上添加缓存注解

1
2
3
4
5
6
7
@Entity
@Cacheable // 声明实体类可缓存
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // 配置缓存策略
public class Department {
// 使用READ_WRITE策略:支持读写操作,通过数据库事务保证缓存与数据库一致性
// 适用于读多写少、需要事务安全的场景
}

延迟加载与缓存

1
2
3
4
5
public Department getDepartmentWithEmployees(Long deptId) {
Department dept = departmentDao.findById(deptId);
Hibernate.initialize(dept.getEmployees()); // 手动初始化懒加载集合
return dept;
}

Departmentemployees集合使用二级缓存,初始化时会直接从缓存加载数据,避免执行SELECT * FROM employees WHERE department_id = ?的 SQL 查询

集成测试

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
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {HibernateConfig.class})
@Transactional
public class EmployeeServiceTest {

@Autowired
private EmployeeService employeeService;

@Test
public void testSaveAndFindEmployee() {
Employee emp = new Employee();
emp.setFirstName("John");
emp.setLastName("Doe");
emp.setEmail("john.doe@example.com");
emp.setHireDate(new Date());

employeeService.saveEmployee(emp);

Employee found = employeeService.getEmployeeById(emp.getId());
assertNotNull(found);
assertEquals("John", found.getFirstName());
}
}

与传统Hibernate的区别

在纯Hibernate应用中,你需要手动管理:

  • SessionFactory的创建和关闭
  • Session的生命周期
  • 事务边界
  • 异常处理

而在Spring集成Hibernate中:

  • Spring管理SessionFactory的生命周期
  • 通过HibernateTemplate@Transactional自动管理Session
  • 声明式事务管理
  • Spring的统一异常体系转换Hibernate异常

关于配置 SessionFactory

本部分感谢 https://www.cnblogs.com/jwen1994/p/11299355.html,个人做出完善和修改

使用 Hibernate 框架的首要工作是编写Hibernate的配置文件,其次是如何使用这些配置文件实例化 SessionFactory,创建 Hibernate 的基础设施。

Spring 为创建 SessionFactory 提供了一个好用的 FactoryBean 工厂类:org.springframework.orm.hibernateX.LocalSessionFactoryBean,通过配置一些必要的属性,就可以获取一个SessionFactoryBean

LocalSessionFactoryBean配置灵活度很高,支持开发者的不同习惯,让开发者拥有充分的选择权——这是Spring一贯的风格。

贴合Hibernate的配置方式

使用 HibernateAPI 创建一个 SessionFactory 的过程

  • 首先编写好对象关系的映射文件 xxx.hbm.xml;

  • 然后通过 Hibernate 的配置文件 hibernate.cfg.xml 将所有的 xxx.hbm.xml 映射文件组装起来;

  • 最后通过以下经典的代码得到 SessionFactory 的实例:

    1
    2
    Configuration cfg = new Configuration().configure("hibernate.cfg.xml"):
    SessionFactory sessionFactory = cfg.buildSessionFactory();

hibernate.cfg.xml 配置文件拥有创建 Hibernate 基础设施所需的配置信息,来看一个最简单的 Hibernate 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="connection.url">
jdbc:mysql://localhost:3306/sampledb
</property>
<property name="connection.username">root</property>
<property name="connection.password">1234</property>
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="current_session_context_class">thread</property>
<mapping resource="com/smart/domain/Forum.hbm.xml" />
</session-factory>
</hibernate-configuration>

这个配置文件定义了3个方面的信息:数据源、映射文件及 Hibernate 控制属性。

既然在 Hibernate 中可以使用一个配置文件创建一个 SessionFactory 实例,在 Spring 中也可以顺理成章地通过指定一个Hibernate配置文件,利用 LocalSessionFactoryBean 来达到相同的目的。

1
2
3
4
5
6
7
8
<!-- 直接使用hibernate配置 -->
<bean id="sessionFactory"
  class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
  p:configLocation="classpath:hibernate.cfg.xml"/>①

<!--对应我上述xml文件中,也是使用了这种方式-->
<!-- 配置Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">

如①处所示,通过 configLocation 属性指定了一个Hibernate 配置文件。如果有多个 Hibernate 配置文件,则可以通过 configLocations 属性指定,多个文件之间用逗号分隔。

LocalSessionFactoryBean 将利用 Hibernate 配置文件创建一个 SessionFactory 代理对象,以便和 Spring 的事务管理机制配合工作:当数据访问代码使用 SessionFactory 时,可以获取线程绑定的 Session,不管工作在本地或全局的事务,都能正确参与到当前的 Spring 事务管理中去。

更具 Spring 风格的配置

Spring 对 ORM 技术的一个重要支持就是提供统一的数据源管理机制,也许更多的开发者更愿意使用 Spring 配置数据源,即在 Spring 容器中定义数据源、指定映射文件、设置 Hibernate 控制属性等信息,完成集成组装的工作,完全抛开 hibernate.cfg.xml 配置文件,如下面代码所示。

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
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

<!-- 1. 加载外部属性文件 -->
<!--加载类路径下的 jdbc.properties 文件,使配置中可使用 ${key} 占位符引用属性值。-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 2. 配置数据源 (使用 DBCP 连接池) -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>

<!-- 3. 配置 Hibernate SessionFactory -->
<!-- 数据源注入:通过 p:dataSource-ref="dataSource" 关联数据源 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
p:dataSource-ref="dataSource"> <!-- 注入数据源 -->

<!-- ②指定实体类映射文件位置 -->
<property name="mappingLocations">
<list>
<value>classpath*:/com/smart/orm/domain/Forum.hbm.xml</value>
<value>classpath*:/com/smart/orm/domain/Topic.hbm.xml</value>
</list>
</property>

<!-- 配置 Hibernate 核心属性 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect <!-- 指定数据库方言 -->
</prop>
<prop key="hibernate.show_sql">
true <!-- 显示执行的 SQL 语句 -->
</prop>
<!-- 其他可配置属性:
<prop key="hibernate.hbm2ddl.auto">update</prop> <!-- 自动更新数据库表结构 -->
<prop key="hibernate.format_sql">true</prop> <!-- 格式化 SQL 输出 -->
<prop key="hibernate.use_sql_comments">true</prop> <!-- 添加 SQL 注释 -->
<prop key="hibernate.connection.autocommit">false</prop> <!-- 禁用自动提交 -->
</props>
</property>
</bean>

<!-- 4. 配置事务管理器 -->
<!--集成 Hibernate 事务管理-->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 5. 启用注解驱动的事务管理 -->
<!--启用 @Transactional 注解支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 6. 其他 Bean 配置 (示例) -->
<bean id="forumService" class="com.smart.service.ForumServiceImpl">
<property name="forumDao" ref="forumDao"/>
</bean>

<bean id="forumDao" class="com.smart.dao.hibernate.ForumDaoHibernate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>

数据源、映射文件及 Hibernate 控制属性这三方面的信息在 LocalSessionFactoryBean 中得到了完美集成,完全替代了 hibernate.cfg.xml 的作用,但这种配置对于 Spring 开发者而言更加亲切。

首先

1
2
3
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
p:dataSource-ref="dataSource"> <!-- 注入数据源 -->

处指定的数据源是 Spring 容器中的数据源,不管是直接在 Spring 容器中配置,还是通过 从 EJB 容器中获取,对引用者而言是完全透明的。

其次,凭借 Spring 资源处理的强大功能,指定 Hibernate 映射文件变得相当灵活。在②<property name="mappingLocations">处采用了逐个指定映射文件的方法,其实这个方法是最笨拙的。由于 mappingLocations 属性的类型是 Resource[],因此它还支持以下简洁的配置方式:

通过 Ant 风格的通配符 批量加载指定包下的所有映射文件,避免逐个罗列。 示例配置

1
2
3
4
5
6
7
8
<property name="mappingLocations">
<list>
<!-- 扫描 com.smart.orm.domain 包及其所有子包下的所有 hbm.xml 文件 -->
<value>classpath*:/com/smart/orm/domain/**/*.hbm.xml</value>
<!-- 扫描 classpath 根目录下的所有 hbm.xml 文件 -->
<value>classpath*:/*.hbm.xml</value>
</list>
</property>

若使用 JPA 注解(如 @Entity 替代传统 .hbm.xml 映射文件,可通过 packagesToScan 属性自动扫描实体类所在包,彻底摆脱手动配置映射路径的繁琐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- 数据源注入 -->
<property name="dataSource" ref="dataSource" />

<!-- 自动扫描实体类所在包(替代 mappingLocations) -->
<property name="packagesToScan">
<list>
<value>com.smart.orm.domain</value> <!-- 扫描单个包 -->
<value>com.other.module.entity</value> <!-- 支持多包 -->
</list>
</property>

<!-- Hibernate 属性配置 -->
<property name="hibernateProperties">
<props>
<!-- 启用 JPA 注解驱动 -->
<prop key="hibernate.archive.autodetection">class, hbm</prop>
</props>
</property>
</bean>
  • Spring 会扫描 packagesToScan 指定的包,自动识别标注 @Entity@MappedSuperclass 等注解的类。
  • 无需手动维护映射文件列表,完全契合 “约定优于配置” 原则。

若厌倦 XML 配置,可改用纯 Java 代码配置 SessionFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableTransactionManagement
public class HibernateConfig {
@Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.smart.orm.domain"); // 包扫描
Properties hibernateProps = new Properties();
hibernateProps.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
factory.setHibernateProperties(hibernateProps);
return factory;
}
}

对应了我上面java配置类配置 SessionFactory 的部分

使用 HibernateTemplate

基于模板类使用 Hibernate 是最简单的方式,它可以在不牺牲 Hibernate 强大功能的前提下,以一种更简洁的方式使用 Hibernate,极大地降低了 Hibernate 的使用难度。按照 Spring 的风格,它提供了使用模板的支持类 HibernateDaoSupport,并通过 getHibernateTemplate()方法向子类开放模板类实例的调用。

把我的Dao类拿过来作为分析

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
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class EmployeeDao {

@Autowired
private SessionFactory sessionFactory;

protected Session getCurrentSession() { // 获取 Session
return sessionFactory.getCurrentSession();
}

public Employee findById(Long id) {
return getCurrentSession().get(Employee.class, id); // 获取实体对象
}

public void save(Employee employee) {
getCurrentSession().saveOrUpdate(employee); //①保存实体对象
}

public void update(Employee employee) {
getCurrentSession().update(employee);//②更改实体对象
}

public void delete(Employee employee) {
getCurrentSession().delete(employee);
}

@SuppressWarnings("unchecked")
public List<Employee> findAll() {
return getCurrentSession().createQuery("from Employee").list();
}

public List<Employee> findByLastName(String lastName) {
// 使用 hql 查询
return getCurrentSession()
.createQuery("from Employee where lastName = :lastName", Employee.class)
.setParameter("lastName", lastName)
.list();
}
}

HibernateTemplate 代理了 HibernateSession 的大多数持久化操作,并以一种更简洁的方式提供调用。 HibernateTemplate 所提供的大部分方法对于 Hibernate 开发者来说都是熟悉亲切的,模板类的方法大都可以在 Session 接口中找到镜像。

基于 HibernateTemplate 的 DAO 重构

传统方式通过 SessionFactory.getCurrentSession() 获取 Session,而使用 HibernateTemplate 时,需继承 HibernateDaoSupport 类,该类提供模板实例 hibernateTemplate,并自动管理 Session 的生命周期(包括事务和异常处理)。

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
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public class EmployeeDao extends HibernateDaoSupport { // 继承 HibernateDaoSupport

// 无需手动注入 SessionFactory,由父类通过 setSessionFactory 方法自动注入

public Employee findById(Long id) {
return getHibernateTemplate().get(Employee.class, id); // 直接使用模板方法
}

public void save(Employee employee) {
getHibernateTemplate().saveOrUpdate(employee); // 模板方法替代原生 Session
}

public void update(Employee employee) {
getHibernateTemplate().update(employee); // 等效于原生 Session.update()
}

public void delete(Employee employee) {
getHibernateTemplate().delete(employee); // 模板方法替代原生 Session
}

public List<Employee> findAll() {
return getHibernateTemplate().find("from Employee"); // 简化 HQL 查询
}

public List<Employee> findByLastName(String lastName) {
// 使用命名参数的简化写法
return getHibernateTemplate().findByNamedParam(
"from Employee where lastName = :lastName",
"lastName",
lastName
);
}
}
  1. 继承 HibernateDaoSupport
    • 父类提供 hibernateTemplate 模板实例,通过 getHibernateTemplate() 访问。
    • 自动处理 Session 的打开、关闭和异常转换(如将 Hibernate 异常转为 Spring DataAccessException)。
  2. 简化的 API 调用
    • getHibernateTemplate().get(...) 替代 sessionFactory.getCurrentSession().get(...)
    • findByNamedParam(...) 方法直接处理命名参数,无需手动创建 Query 对象。
  3. 无需手动管理事务
    • 事务由 Spring 的 @Transactional 注解统一管理(需在 Service 层或 DAO 层方法上声明)。

常用的API方法

方法 说明
get(Class entityClass, Serializable id) 根据 ID 获取实体(等效 Session.get())。
load(Class entityClass, Serializable id) 根据 ID 加载实体(支持延迟加载,等效 Session.load())。
save(Object entity) 保存新实体(等效 Session.save())。
update(Object entity) 更新现有实体(等效 Session.update())。
saveOrUpdate(Object entity) 自动判断保存或更新(等效 Session.saveOrUpdate())。
delete(Object entity) 删除实体(等效 Session.delete())。
find(String hql) 执行 HQL 查询,返回结果列表(等效 Session.createQuery(...).list())。
findByNamedParam(String hql, String paramName, Object value) 使用命名参数的 HQL 查询,防止 SQL 注入。
execute(HibernateCallback action) 执行自定义回调逻辑,支持原生 Session 操作。

使用回调接口(HibernateCallback)

一般情况下,使用模板类的简单代理方法就可以满足要求了,如果希望使用更多 Hibernate 底层的功能,则可以使用回调接口。Spring 定义了一个回调接口org.springframework.orm.hibernate5.HibernateCallback,该接口拥有唯一的方法,如下:

1
T dolnHibernate(org.hibernate.Session session) throws HibernateException,SQLException

该接口配合 HibernateTemplate 进行工作,它无须关心 HibernateSession 的打开/关闭等操作,仅需定义数据访问逻辑即可。可以通过该接口返回结果,结果可以是一个实体对象或一个实体对象的 List。回调接口中抛出的异常将传播到模板类中并被转换成 Spring DAO 异常体系的对应类。

HibernateTemplate 定义了两个使用 HibernateCallback 回调接口的方法。

  • <T> T execute(HibernateCallback action):一般使用该方法执行数据更新、新增等操作。
  • List executeFind(HibernateCallback<?> action):一般使用该方法执行数据查询操作,返回的结果是一个List。
1
2
3
4
5
6
7
8
9
10
11
12
13
public long getEmployeeNum() {
Long EmployeeNum = getHibernateTemplate().execute(
new HibernateCallback<Long>() {
public Long doInHibernate(Session session) throws HibernateException{
Object obj = session.createQuery("select count(e.EmployeeId) from Employee e")
.list()
.iterator()
.next();
return (Long) obj;
}
});
return EmployeeNum;
}

当需要执行模板类未封装的原生操作(如分页查询、存储过程调用)时,可通过 execute 方法传入 HibernateCallback 回调接口,在其中使用原生 Session API。

1
2
3
4
5
6
7
8
9
public List<Employee> findByPage(int pageNum, int pageSize) {
int firstResult = (pageNum - 1) * pageSize;
return getHibernateTemplate().execute(session -> {
Query<Employee> query = session.createQuery("from Employee", Employee.class);
query.setFirstResult(firstResult);
query.setMaxResults(pageSize);
return query.getResultList();
});
}

关键点

  • 回调接口中的 session 是当前事务绑定的 Session,无需手动关闭。
  • 可直接使用原生 Query 对象,实现复杂查询逻辑(如 setParameter()setLockMode() 等)。

在 Spring 中配置 DAO

在编写好基于 HibernateTemplate的DAO 类后,接下来要做的就是在 Spring 中进行具体配置,使该 DAO 生效

HibernateDaoSupport 注入 SessionFactory HibernateDaoSupport 父类需要 SessionFactory 来创建 HibernateTemplate,可通过 XML 中 `sessionFactory` 属性注入。

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
	<!-- 启用注解驱动和组件扫描 -->
<context:annotation-config/>
<context:component-scan base-package="com.yourpackage"/> <!-- 扫描 DAO、Service 等组件 -->

<!-- 配置 Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.yourpackage.model"/> <!-- 扫描实体类包 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop> <!-- 开发环境自动更新表结构 -->
</props>
</property>
</bean>

<!-- 配置 DAO:为 HibernateDaoSupport 注入 sessionFactory -->
<bean id="employeeDao" class="com.yourpackage.dao.EmployeeDao">
<property name="sessionFactory" ref="sessionFactory"/> <!-- 关键配置 -->
</bean>


</beans>
  • <bean id="employeeDao">中的 sessionFactory 属性: 必须为 HibernateDaoSupport 子类注入 SessionFactory,否则 hibernateTemplate 无法初始化。

  • 组件扫描context:component-scan 会自动扫描标注 @RepositoryEmployeeDao,无需手动注册 Bean(若 XML 中显式声明 Bean,则以 XML 配置为准)。

    这样 Dao 类的 @Repository@Autowired 注解就可以起作用,将 ForumHibernateDao 装配为 Spring 容器中的 Bean。

注解配置的详解

和 Spring 类似,Hibernate 不但可以使用 XML 提供 ORM 的配置信息,也可以直接在领域对象类中通过注解定义 ORM 映射信息。Hibernate 不但自已定义了一套注解,还支持 JPA 注解。这种方式比传统的 XML 映射文件更加直观、简洁,并且将映射信息直接与实体类关联在一起。

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
import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "employees")
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "first_name", nullable = false, length = 50)
private String firstName;

@Column(name = "last_name", nullable = false, length = 50)
private String lastName;

@Column(nullable = false, unique = true)
private String email;

@Column(name = "hire_date")
@Temporal(TemporalType.DATE) // @Temporal:定义日期类型的精度
private Date hireDate;

@ManyToOne(fetch = FetchType.LAZY) // 定义多对一关联,fetch = FetchType.LAZY:启用延迟加载
@JoinColumn(name = "department_id") // @JoinColumn:指定外键列名
private Department department;

// 构造函数、getter和setter
// ...
}

Hibemate 通过 AnnotationConfigurationaddAnnotatedClass()addPackage()方法加载使用 JPA 注解的实体类,获取映射的元数据信息,并在此基础上创建 SessionFactory 实例。

需要特别注意的是,使用 addPackage 并不是加载类包下所有标注了 ORM 注解的实体类,而是加载类包下 package-info.java 文件中定义的 Annotation,而该类包下的所有持久化类仍然需要通过 addAnnotatedClass() 方法加载。

Spring 专门提供了一个配套的 AnnotationSessionFactoryBean,用于创建基于 JPA 注解的 SessionFactory。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="sessionFactory" 
class="org.springframework.orm.hibernate5.annotation.AnnotationSessionFactoryBean" p:dataSourceref="dataSource">
<property name="annotatedClasses">
<list>
<value>com.smart.orm.domain.Forum</value>
</list>
</property>
<!--或-->
<property name="packagesToScan" value="com.yourpackage.model"/>

<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
<!-- 其他Hibernate配置 -->
</props>
</property>
</bean>

AnnotationSessionFactoryBean 扩展了 LocalSessionFactoryBean 类,增强的功能是:可以根据实体类的注解获取 ORM 的配置信息。也可以混合使用 XML 配置和注解配置对象关系映射,Hibernate 内部自动将这些元数据信息进行整合,并不会产生冲突。

annotatedCIasses 属性指定使用 JPA 注解的实体类名,如②处所示。如果实体类比较多,不要想当然地以为通过annotatedPackages 属性指定实体类所在包名就可以了,annotatedPackages 在内部通过调用 Hibernate 的 AnnotationConfigurationaddPackage() 方法加载包中package-info.java 文件定义的 Annotation,而非包中标注注解的实体类。

Spring 为了通过扫描方式加载带注解的实体类,提供了一个易用的 packagesToScan 属性,可以指定一系列包名,Spring 将扫描并加载这些包路径(包括子包)的所有带注解实体类。

packagesToScan 属性可接收多个类包路径,用逗号分隔即可,例如:

1
<property name="packagesToScan" value="com.yourpackage.model"/>

事务处理

Spring 的通用事务管理模型对 Hibernate 是完全适用的,包括编程式事务、基于 TransactionProxyFactoryBean、基于 aop/tx 及基于 @Transaction 注解的事务管理。在这里,仅给出基于 @Transaction 注解的事务管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
@Transactional
public class DepartmentService {

@Autowired
private DepartmentDao departmentDao;

@Autowired
private EmployeeDao employeeDao;

// 类级别的事务配置适用于所有方法
// 方法级别的事务配置会覆盖类级别的配置

@Transactional(readOnly = true)
public Department getDepartmentWithEmployees(Long deptId) {
...
}

@Transactional(rollbackFor = Exception.class)
public void transferEmployees(Long fromDeptId, Long toDeptId, List<Long> employeeIds) {
...
}
}

其次,在 Spring 配置文件中配置 Hibernate 事务管理器,并启用注解驱动事务。

1
2
3
4
5
6
7
8
9
10
 <!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<!--为事务管理器指定sessionFactory-->
<property name="sessionFactory" ref="sessionFactory"/>
<!--②默认查找名为transactionManager的事务管理器,因此可以不显示指定-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</bean>

<!-- 启用注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

Hibernate 的事务管理器需要注入一个 sessionFactory 实例,将其命名为 transactionManager 后,在 <tx:annotation-driven/> 中就无须通过 transaction-manager 默认显式指定了。不管 DepartmentServiceImpl所用的 DAO 是基于 HibernateTemplate 还是基于 Hibernate 原生的API,DepartmentServiceImpl 中的所有方法都具有事务性。