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 > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.3.20</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-orm</artifactId > <version > 5.3.20</version > </dependency > <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-core</artifactId > <version > 5.6.10.Final</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.29</version > </dependency > <dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 5.0.1</version > </dependency > <dependency > <groupId > javax.transaction</groupId > <artifactId > javax.transaction-api</artifactId > <version > 1.3</version > </dependency > </dependencies >
配置 Spring 配置文件
创建Spring配置文件applicationContext.xml
,application.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 /> <context:component-scan base-package ="com.yourpackage" /> <bean id ="dataSource" class ="com.zaxxer.hikari.HikariDataSource" destroy-method ="close" > <property name ="driverClassName" value ="com.mysql.cj.jdbc.Driver" /> <property name ="jdbcUrl" value ="jdbc:mysql://localhost:3306/your_db?useSSL=false& serverTimezone=UTC" /> <property name ="username" value ="your_username" /> <property name ="password" value ="your_password" /> <property name ="maximumPoolSize" value ="10" /> </bean > <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 > <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 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" ); 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;@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) private Date hireDate; @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;@Repository @Transactional public class EmployeeDao { @Autowired private SessionFactory sessionFactory; protected Session getCurrentSession () { return sessionFactory.getCurrentSession(); } public Employee findById (Long id) { return getCurrentSession().get(Employee.class, id); } public void save (Employee employee) { getCurrentSession().saveOrUpdate(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) { return getCurrentSession() .createQuery("from Employee where lastName = :lastName" , Employee.class) .setParameter("lastName" , lastName) .list(); } }
主要注解解析
@Repository
作用
声明该类为 Spring 的数据访问组件(DAO),是 @Component
的特殊化版本。
自动将 DAO 类注册为 Spring Bean,并支持 Spring
的数据访问异常转换(将 Hibernate/JPA 异常转换为 Spring 的
DataAccessException
体系)。
@Transactional
作用
声明该类的所有公共方法都在事务管理下执行。
默认配置下:
事务传播行为为
PROPAGATION_REQUIRED
(若当前无事务,则创建新事务;否则加入当前事务)。
事务隔离级别为数据库默认级别。
所有 RuntimeException
会触发事务回滚,受检异常(如
IOException
)不会触发回滚。
@Autowired
作用
通过类型自动注入依赖的 Bean(此处注入
SessionFactory
)。
Spring 会在容器中查找 SessionFactory
类型的 Bean
并注入。
方法解析
getCurrentSession()
功能 :获取当前线程绑定的
Session
。
关键点
在 @Transactional
注解的事务上下文中,Session
由 Spring
自动管理,无需手动关闭。
若不在事务中调用此方法,会抛出异常。
findById(Long id)
功能 :根据 ID 查询员工。
Hibernate 方法
Session.get(Class, id)
:立即查询数据库,若记录不存在返回
null
。
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); } }
主要注解解析
@Service
作用
声明该类为 Spring 的服务层组件,是 @Component
的特殊化版本。
用于标识业务逻辑层,提高代码可读性和可维护性。
Spring 容器行为
@Autowired
作用
通过类型自动注入依赖的 Bean(此处注入
EmployeeDao
)。
依赖注入方式
字段注入:简洁但不利于单元测试(建议使用构造器注入,Spring 4.3+
支持单参构造器省略 @Autowired
)。
@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); } }
主要注解解析
@Service
作用
声明该类为 Spring 的服务层组件,负责业务逻辑处理。
Spring 容器行为
@Transactional
(类级别)
作用
默认配置
传播行为:PROPAGATION_REQUIRED
(必要时创建新事务)。
隔离级别:使用数据库默认级别。
回滚规则:仅对 RuntimeException
和 Error
回滚。
@Transactional(readOnly = true)
(方法级别)
作用
优化点
Hibernate 会优化查询执行(如禁用脏检查)。
数据库可优化只读事务的锁策略。
@Transactional(rollbackFor = Exception.class)
(方法级别)
作用
覆盖类级别的回滚规则,指定所有异常(包括受检异常)都触发事务回滚。
适用场景
确保业务异常(如
ValidationException
)也能触发回滚。
方法解析
getDepartmentWithEmployees(Long deptId)
功能 :获取部门及其所有员工。
Hibernate 集成点
延迟加载处理
departmentDao.findById(deptId)
返回的
Department
对象中,employees
集合是 Hibernate
代理对象(未初始化)。
Hibernate.initialize(dept.getEmployees())
强制触发 SQL
查询,在事务未关闭前加载员工数据。
Session 生命周期
方法在事务内执行,Session 保持打开状态,允许延迟加载初始化。
transferEmployees(...)
功能 :批量转移员工到新部门。
Hibernate 集成点
实体状态管理
通过 DAO 获取的 Department
和 Employee
对象处于持久化状态。
修改持久化对象的关联关系(如
removeEmployee
、addEmployee
)会被 Hibernate
自动跟踪。
级联操作
employeeDao.save(emp)
可能无需调用(取决于实体类的
cascade
配置)。
若 Department
实体配置了
cascade = CascadeType.ALL
,则直接保存部门即可同步员工关联。
事务一致性
整个方法在单个事务中执行,确保数据一致性(要么全部转移成功,要么全部失败)。
Hibernate 集成关键点
延迟加载(Lazy Loading)处理 :
通过 Hibernate.initialize()
强制初始化延迟集合,避免在事务外访问时抛出
LazyInitializationException
。
依赖 @Transactional
保持 Session 打开状态。
实体状态管理 :
Hibernate 跟踪持久化对象的状态变化,事务提交时自动生成 SQL。
示例中通过 removeEmployee
和 addEmployee
修改实体关系,无需手动编写 SQL。
级联操作 :
实体类的 @OneToMany
或 @ManyToOne
注解可能配置了 cascade
属性(如
CascadeType.PERSIST
、CascadeType.MERGE
),决定关联对象的自动持久化行为。
Session 生命周期 :
Spring 的 @Transactional
管理 Hibernate Session
的打开、关闭和刷新。
方法执行期间 Session 保持打开,允许多次数据库操作。
注意事项
N+1 查询问题 :
getDepartmentWithEmployees
方法通过
Hibernate.initialize()
解决了 N+1
查询问题(先查部门,再查所有员工)。
更优方案:使用 JOIN FETCH
优化查询(如
SELECT d FROM Department d JOIN FETCH d.employees WHERE d.id = :id
)。
事务边界 :
transferEmployees
方法必须在单个事务内执行,否则可能导致数据不一致(如部分员工转移成功,部分失败)。
异常处理 :
rollbackFor = Exception.class
确保所有异常都触发回滚,但需谨慎使用,避免掩盖业务问题。
实体关系维护 :
示例假设 Department
实体的 removeEmployee
和 addEmployee
方法同时维护双向关联(即更新
Employee
的 department
字段)。
若未正确维护双向关联,可能导致数据库与对象状态不一致。
高级配置
二级缓存配置
为什么配置二级缓存,什么时候配置
减少数据库访问压力
原理 :将常用数据存储在应用服务器内存或分布式缓存中(如
Ehcache),避免重复查询数据库。
场景 :高并发读场景(如商品详情、字典表),减少数据库
I/O 负载,提升系统吞吐量。
提升查询性能
原理 :直接从缓存中读取数据,响应速度远快于数据库查询(内存访问速度比磁盘快
10 万倍以上)。
场景 :频繁查询但不常更新的数据(如用户档案、配置信息),降低延迟,改善用户体验。
减轻应用层压力
原理 :缓存分担了应用层的数据处理压力,尤其在复杂关联查询(如Department
关联Employee
)时,避免重复执行
Hibernate 的对象关系映射(ORM)操作。
降低资源消耗
原理 :减少数据库连接的创建和释放频率,降低 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 { }
延迟加载与缓存
1 2 3 4 5 public Department getDepartmentWithEmployees (Long deptId) { Department dept = departmentDao.findById(deptId); Hibernate.initialize(dept.getEmployees()); return dept; }
若Department
的employees
集合使用二级缓存,初始化时会直接从缓存加载数据,避免执行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" > <context:property-placeholder location ="classpath:jdbc.properties" /> <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}" /> <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 > <property name ="hibernateProperties" > <props > <prop key ="hibernate.dialect" > org.hibernate.dialect.MySQLDialect </prop > <prop key ="hibernate.show_sql" > true </prop > <prop key ="hibernate.format_sql" > true</prop > <prop key ="hibernate.use_sql_comments" > true</prop > <prop key ="hibernate.connection.autocommit" > false</prop > </props > </property > </bean > <bean id ="transactionManager" class ="org.springframework.orm.hibernate4.HibernateTransactionManager" > <property name ="sessionFactory" ref ="sessionFactory" /> </bean > <tx:annotation-driven transaction-manager ="transactionManager" /> <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 > <value > classpath*:/com/smart/orm/domain/**/*.hbm.xml</value > <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" /> <property name ="packagesToScan" > <list > <value > com.smart.orm.domain</value > <value > com.other.module.entity</value > </list > </property > <property name ="hibernateProperties" > <props > <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 () { 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) { 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 { public Employee findById (Long id) { return getHibernateTemplate().get(Employee.class, id); } public void save (Employee employee) { getHibernateTemplate().saveOrUpdate(employee); } public void update (Employee employee) { getHibernateTemplate().update(employee); } public void delete (Employee employee) { getHibernateTemplate().delete(employee); } public List<Employee> findAll () { return getHibernateTemplate().find("from Employee" ); } public List<Employee> findByLastName (String lastName) { return getHibernateTemplate().findByNamedParam( "from Employee where lastName = :lastName" , "lastName" , lastName ); } }
继承 HibernateDaoSupport
:
父类提供 hibernateTemplate
模板实例,通过
getHibernateTemplate()
访问。
自动处理 Session
的打开、关闭和异常转换(如将 Hibernate
异常转为 Spring DataAccessException
)。
简化的 API 调用 :
getHibernateTemplate().get(...)
替代
sessionFactory.getCurrentSession().get(...)
。
findByNamedParam(...)
方法直接处理命名参数,无需手动创建 Query
对象。
无需手动管理事务 :
事务由 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" /> <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 > <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
会自动扫描标注 @Repository
的
EmployeeDao
,无需手动注册 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) private Date hireDate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "department_id") private Department department; }
Hibemate 通过 AnnotationConfiguration
的
addAnnotatedClass()
或 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:dataSource —ref ="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 > </props > </property > </bean >
AnnotationSessionFactoryBean 扩展了 LocalSessionFactoryBean
类,增强的功能是:可以根据实体类的注解获取 ORM
的配置信息。也可以混合使用 XML 配置和注解配置对象关系映射,Hibernate
内部自动将这些元数据信息进行整合,并不会产生冲突。
annotatedCIasses
属性指定使用 JPA
注解的实体类名,如②处所示。如果实体类比较多,不要想当然地以为通过annotatedPackages
属性指定实体类所在包名就可以了,annotatedPackages
在内部通过调用 Hibernate 的 AnnotationConfiguration
的
addPackage()
方法加载包中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" > <property name ="sessionFactory" ref ="sessionFactory" /> <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
中的所有方法都具有事务性。