第一章:Hibernate 框架概述
1.1 什么是 ORM
| 概念名称 | 说明 | 注意事项 |
|---|
| ORM(Object-Relational Mapping) | 对象关系映射,是一种将关系型数据库中的表结构映射为 Java 对象的技术。通过 ORM,开发者可以使用面向对象的方式操作数据库,无需编写原生 SQL 语句。 | ORM 并非完全屏蔽 SQL,复杂查询仍可能需要手动优化或使用原生 SQL。 |
| 实体类(Entity) | 对应数据库中的一张表,类的每个实例对应表中的一行记录。 | 类名通常与表名对应,属性与字段对应。 |
| 属性映射 | 将类的字段(Field)或属性(Property)映射到数据库表的列(Column)。 | 支持基本类型、包装类型、日期、大对象等类型映射。 |
| 主键映射 | 标识实体类中唯一标识记录的属性,对应数据库表的主键列。 | 必须配置主键,否则无法进行持久化操作。 |
1.2 Hibernate 核心架构简介
| 组件名称 | 说明 | 注意事项 |
|---|
| Hibernate API | 提供核心接口如 Session、SessionFactory、Transaction 等,用于与数据库交互。 | 开发者主要通过这些接口进行数据操作。 |
| JDBC / JTA | Hibernate 底层依赖 JDBC 进行数据库连接,也可通过 JTA 支持分布式事务。 | 在配置文件中指定连接方式和事务策略。 |
| Dialect(方言) | 数据库方言,用于生成针对特定数据库的 SQL 语句。 | 必须根据使用的数据库选择正确的方言,如 MySQLDialect、OracleDialect。 |
| Connection Pool | 连接池(如 C3P0、HikariCP)管理数据库连接,提升性能。 | 建议生产环境使用第三方连接池。 |
| Mapping Metadata | 映射元数据,定义实体类与数据库表之间的映射关系,可通过 XML 或注解配置。 | 元数据是 Hibernate 实现 ORM 的核心依据。 |
1.3 Hibernate 与 JDBC 的对比
| 对比项 | JDBC | Hibernate | 注意事项 |
|---|
| SQL 编写 | 需手动编写所有 SQL 语句 | 大部分操作无需手写 SQL,由框架自动生成 | Hibernate 仍支持原生 SQL 查询 |
| 结果集处理 | 需手动处理 ResultSet,逐行映射为对象 | 自动将结果映射为实体对象 | 减少样板代码 |
| 数据库移植性 | SQL 语句依赖具体数据库,移植性差 | 通过方言(Dialect)自动适配不同数据库 | 更易实现跨数据库支持 |
| 对象关系管理 | 无内置机制管理对象状态 | 提供完整的对象生命周期管理(瞬时、持久、脱管) | 简化持久化逻辑 |
| 开发效率 | 较低,需处理大量底层细节 | 较高,专注于业务逻辑 | 适合中大型项目 |
| 性能控制 | 完全可控,可精细优化 SQL | 自动生成 SQL,复杂场景需调优 | 需注意 N+1 查询等问题 |
1.4 Hibernate 的核心组件与工作流程
| 组件/流程 | 说明 | 注意事项 |
|---|
| Configuration | 读取 hibernate.cfg.xml 或 hibernate.properties 配置文件,构建 SessionFactory | 是启动 Hibernate 的第一步 |
| SessionFactory | 线程安全的工厂对象,用于创建 Session 实例,一个应用通常只有一个实例 | 应全局唯一,避免频繁创建销毁 |
| Session | 单线程对象,代表与数据库的一次会话,用于执行 CRUD 操作 | 非线程安全,使用后应及时关闭 |
| Transaction | 事务管理对象,控制数据库事务的提交与回滚 | 推荐显式管理事务,尤其是在增删改操作中 |
| 工作流程 | 1. 加载配置 → 2. 创建 SessionFactory → 3. 打开 Session → 4. 开启 Transaction → 5. 执行数据库操作 → 6. 提交事务 → 7. 关闭 Session | 典型的”会话-事务”编程模型 |
第二章:开发环境搭建与第一个 Hibernate 程序
2.1 所需依赖与 JAR 包说明
| 依赖名称/作用 | 说明 | 注意事项 |
|---|
| hibernate-core | Hibernate 核心库,包含所有核心 API 和功能 | 必需 |
| hibernate-c3p0(可选) | 集成 C3P0 连接池 | 生产环境推荐使用连接池 |
| mysql-connector-java | MySQL 数据库驱动 | 根据实际数据库选择对应驱动 |
| javassist 或 byte-buddy | 动态代理和字节码操作库,用于延迟加载等功能 | 通常由 hibernate-core 传递依赖引入 |
| slf4j-api + 日志实现(如 logback) | 日志接口与实现,用于输出 Hibernate 执行日志 | 推荐配置日志以便调试 |
| jboss-logging | Hibernate 使用的日志门面 | 一般自动引入 |
示例(Maven 依赖片段):
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.15.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
2.2 配置文件 hibernate.cfg.xml 详解
| 配置项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| dialect | <property name="hibernate.dialect"> | 指定数据库方言,用于生成适配的 SQL | org.hibernate.dialect.MySQL8Dialect | 必须正确匹配数据库版本 |
| connection.driver_class | <property name="hibernate.connection.driver_class"> | 数据库驱动类 | com.mysql.cj.jdbc.Driver | 驱动类名随 JDBC 版本变化 |
| connection.url | <property name="hibernate.connection.url"> | 数据库连接 URL | jdbc:mysql://localhost:3306/testdb | 确保数据库已启动且可访问 |
| connection.username | <property name="hibernate.connection.username"> | 数据库用户名 | root | 建议使用配置文件或环境变量管理敏感信息 |
| connection.password | <property name="hibernate.connection.password"> | 数据库密码 | password | 同上 |
| hbm2ddl.auto | <property name="hibernate.hbm2ddl.auto"> | 自动建表策略 | update | 值可为 create、create-drop、update、validate;生产环境建议使用 validate 或手动管理 DDL |
| show_sql | <property name="hibernate.show_sql"> | 是否在控制台输出 SQL 语句 | true | 调试时开启,生产环境关闭 |
| format_sql | <property name="hibernate.format_sql"> | 是否格式化输出的 SQL | true | 提高 SQL 可读性 |
| use_sql_comments | <property name="hibernate.use_sql_comments"> | 是否在 SQL 中添加注释 | true | 有助于调试和理解生成的 SQL |
| mapping resource | <mapping resource="..."/> | 注册实体映射文件(.hbm.xml) | — | 每个实体映射文件需单独注册 |
2.3 实体类与映射文件(.hbm.xml)编写
| 概念/元素 | 说明 | 注意事项 |
|---|
| 实体类(POJO) | 普通 Java 类,包含属性、getter/setter、无参构造函数 | 建议实现 Serializable 接口 |
<class> 标签 | 定义一个实体类与数据库表的映射,name 属性为类全名,table 为表名 | — |
<id> 标签 | 映射主键属性 | 必须存在,column 可省略(默认同属性名) |
<generator> 子标签 | 定义主键生成策略 | class 属性指定策略,如 native、uuid、assigned |
<property> 标签 | 映射普通属性,name 为属性名,column 为列名,type 可选(自动推断) | — |
| 主键生成策略(generator) | 常见值:native(自增)、uuid、assigned(手动赋值)、increment 等 | 根据数据库和业务需求选择 |
示例:User.hbm.xml
<hibernate-mapping>
<class name="com.example.User" table="t_user">
<id name="id" column="user_id">
<generator class="native"/>
</id>
<property name="name" column="user_name" length="50"/>
<property name="email" column="email" not-null="true"/>
</class>
</hibernate-mapping>
2.4 编写第一个 Hibernate 应用:保存实体对象
| 步骤 | 说明 | 代码示例 | 注意事项 |
|---|
| 1. 创建 Configuration | 加载配置文件 | Configuration cfg = new Configuration().configure(); | configure() 默认加载 classpath 下的 hibernate.cfg.xml |
| 2. 构建 SessionFactory | 通过 Configuration 构建 | SessionFactory sf = cfg.buildSessionFactory(); | 应使用单例模式管理 |
| 3. 打开 Session | 获取数据库会话 | Session session = sf.openSession(); | 每次操作使用新 Session,用后关闭 |
| 4. 开启事务 | 开始事务 | Transaction tx = session.beginTransaction(); | 写操作必须在事务中 |
| 5. 创建并保存实体 | 构造对象并保存 | User user = new User(); user.setName("张三"); user.setEmail("zhangsan@example.com"); session.save(user); | save() 返回主键值(可选接收) |
| 6. 提交事务 | 提交更改 | tx.commit(); | 必须显式提交,否则不生效 |
| 7. 关闭 Session | 释放资源 | session.close(); | 防止内存泄漏 |
完整代码示例:
Session session = sf.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
User user = new User();
user.setName("李四");
user.setEmail("lisi@example.com");
session.save(user);
tx.commit();
} catch (Exception e) {
if (tx != null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
2.5 SessionFactory 与 Session 的基本使用
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| buildSessionFactory() | configuration.buildSessionFactory() | 从 Configuration 构建 SessionFactory | SessionFactory sf = cfg.buildSessionFactory(); | 重量级对象,应用启动时创建一次 |
| openSession() | sessionFactory.openSession() | 打开一个新的 Session | Session session = sf.openSession(); | 每次调用返回新实例,需手动管理关闭 |
| getCurrentSession() | sessionFactory.getCurrentSession() | 获取当前线程绑定的 Session | Session session = sf.getCurrentSession(); | 需在配置中启用 hibernate.current_session_context_class=thread |
| close()(SessionFactory) | sessionFactory.close() | 关闭 SessionFactory,释放资源 | sf.close(); | 应用关闭时调用 |
| save() | session.save(entity) | 保存实体,返回主键 | Long id = (Long) session.save(user); | 瞬时对象变为持久化状态 |
| get() | session.get(Class, id) | 根据主键立即加载对象 | User user = session.get(User.class, 1L); | 立即发送 SQL 查询,查不到返回 null |
| load() | session.load(Class, id) | 根据主键返回代理对象(延迟加载) | User user = session.load(User.class, 1L); | 返回代理,真正使用时才查询,查不到抛异常 |
| update() | session.update(entity) | 更新脱管状态的对象 | session.update(user); | 对象必须存在主键 |
| delete() | session.delete(entity) | 删除实体 | session.delete(user); | 支持传入实体或 HQL |
| close()(Session) | session.close() | 关闭 Session,释放连接 | session.close(); | 必须调用,避免资源泄漏 |
| isOpen() | session.isOpen() | 检查 Session 是否打开 | if (session.isOpen()) { ... } | 用于状态判断 |
| getTransaction() | session.getTransaction() | 获取当前事务对象 | Transaction tx = session.getTransaction(); | 用于事务控制 |
第三章:核心接口与生命周期管理
3.1 Configuration 接口
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| configure() | new Configuration().configure() | 加载默认配置文件 hibernate.cfg.xml | Configuration cfg = new Configuration().configure(); | 默认加载 classpath 根路径下的 hibernate.cfg.xml |
| configure(String) | new Configuration().configure(String configFileName) | 加载指定名称的配置文件 | Configuration cfg = new Configuration().configure("hibernate-test.cfg.xml"); | 文件需在 classpath 中 |
| addResource() | configuration.addResource(String resourceName) | 手动添加 .hbm.xml 映射文件 | cfg.addResource("com/example/User.hbm.xml"); | 适用于未在 cfg.xml 中注册的映射文件 |
| setProperty() | configuration.setProperty(String key, String value) | 动态设置配置属性 | cfg.setProperty("hibernate.show_sql", "true"); | 优先级高于配置文件中的设置 |
| buildSessionFactory() | configuration.buildSessionFactory() | 构建 SessionFactory 实例 | SessionFactory sf = cfg.buildSessionFactory(); | 触发元数据解析和连接池初始化 |
3.2 SessionFactory 接口
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| openSession() | sessionFactory.openSession() | 打开一个新 Session | Session session = sf.openSession(); | 返回的 Session 不属于任何事务上下文,需手动管理事务 |
| getCurrentSession() | sessionFactory.getCurrentSession() | 获取当前线程绑定的 Session | Session session = sf.getCurrentSession(); | 需配置 hibernate.current_session_context_class=thread,事务提交后自动关闭 |
| close() | sessionFactory.close() | 关闭 SessionFactory,释放所有资源 | sf.close(); | 应用关闭时调用,避免资源泄漏 |
| isOpen() | sessionFactory.isOpen() | 检查 SessionFactory 是否处于打开状态 | if (sf.isOpen()) { ... } | 关闭后不可再使用 |
| getStatistics() | sessionFactory.getStatistics() | 获取会话工厂的运行时统计信息 | Statistics stats = sf.getStatistics(); | 用于性能监控和调试 |
3.3 Session 接口
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| save() | session.save(entity) | 保存瞬时对象,返回主键 | Long id = (Long) session.save(user); | 实体进入持久化状态,事务提交后写入数据库 |
| persist() | session.persist(entity) | 持久化对象,不保证立即返回主键 | session.persist(user); | JPA 标准方法,与 save 行为略有不同 |
| get() | session.get(Class, id) | 立即加载对象,查不到返回 null | User user = session.get(User.class, 1L); | 立即执行 SQL 查询,可能返回 null |
| load() | session.load(Class, id) | 延迟加载对象,返回代理 | User user = session.load(User.class, 1L); | 仅当访问属性时才查询,查不到抛 ObjectNotFoundException |
| update() | session.update(entity) | 更新脱管状态的对象 | session.update(user); | 对象必须存在主键,且数据库中存在对应记录 |
| saveOrUpdate() | session.saveOrUpdate(entity) | 根据主键判断执行 save 或 update | session.saveOrUpdate(user); | 若主键为 null 执行 save,否则执行 update |
| merge() | session.merge(entity) | 将脱管对象状态合并到持久化对象 | User merged = session.merge(user); | 返回新的持久化实例,原对象仍为脱管 |
| delete() | session.delete(entity) | 删除持久化或脱管对象 | session.delete(user); | 支持传入实体或 HQL 条件 |
| flush() | session.flush() | 强制同步持久化上下文到数据库 | session.flush(); | 触发 SQL 执行,但不提交事务 |
| clear() | session.clear() | 清除 Session 缓存中所有对象 | session.clear(); | 所有持久化对象变为脱管状态 |
| evict() | session.evict(entity) | 从缓存中移除指定对象 | session.evict(user); | 该对象变为脱管状态 |
| contains() | session.contains(entity) | 判断对象是否在当前 Session 缓存中 | boolean isIn = session.contains(user); | 用于判断对象的持久化状态 |
| close() | session.close() | 关闭 Session,释放连接 | session.close(); | 必须调用,否则连接不释放 |
3.4 Transaction 接口
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| begin() | transaction.begin() | 开始事务 | tx.begin(); | 通常由 session.beginTransaction() 返回 |
| commit() | transaction.commit() | 提交事务 | tx.commit(); | 成功完成操作后调用 |
| rollback() | transaction.rollback() | 回滚事务 | tx.rollback(); | 异常时调用,撤销所有更改 |
| isActive() | transaction.isActive() | 检查事务是否处于活动状态 | if (tx.isActive()) { ... } | 提交或回滚后返回 false |
| setRollbackOnly() | transaction.setRollbackOnly() | 标记事务为只回滚 | tx.setRollbackOnly(); | 用于嵌套事务或跨操作回滚控制 |
| getRollbackOnly() | transaction.getRollbackOnly() | 检查事务是否被标记为回滚 | boolean only = tx.getRollbackOnly(); | 配合 setRollbackOnly 使用 |
3.5 对象的持久化生命周期(瞬时、持久、脱管、删除)
| 状态名称 | 说明 | 注意事项 |
|---|
| 瞬时状态(Transient) | 对象未与 Session 关联,无主键值或主键为 null | 此时对对象的修改不会影响数据库 |
| 持久状态(Persistent) | 对象与 Session 关联,存在于数据库中(可能未提交) | 在 Session 范围内,任何属性修改在 flush 或 commit 时自动同步到数据库 |
| 脱管状态(Detached) | 对象曾与 Session 关联,但 Session 已关闭 | 需通过 update()、saveOrUpdate() 或 merge() 重新关联 |
| 删除状态(Removed) | 对象已被 delete() 方法标记删除,但尚未提交 | 在事务提交前仍可回滚,提交后从数据库移除 |
状态转换规则:
- 瞬时 → 持久:
save()、persist()、saveOrUpdate()
- 持久 → 瞬时:
close() Session 后对象未被引用
- 持久 → 脱管:
evict()、clear()、close() Session
- 脱管 → 持久:
update()、saveOrUpdate()、merge()
- 任意 → 删除:
delete()
第四章:映射基础
4.1 基本类型属性映射
| Java 类型 | 对应数据库类型(常见) | 映射方式(.hbm.xml) | 注解方式 | 注意事项 |
|---|
| String | VARCHAR / TEXT | <property name="name" type="string"/> | @Column(name = "name") | 默认映射为 VARCHAR(255) |
| Integer / int | INTEGER | <property name="age" type="integer"/> | @Column(name = "age") | 包装类型可为 null |
| Long / long | BIGINT | <property name="id" type="long"/> | @Column(name = "user_id") | 常用于主键 |
| Boolean / boolean | BIT / TINYINT(1) | <property name="isActive" type="boolean"/> | @Column(name = "is_active") | MySQL 中常用 TINYINT(1) 表示 |
| Date / java.util.Date | DATETIME / TIMESTAMP | <property name="createTime" type="timestamp"/> | @Temporal(TemporalType.TIMESTAMP) | 需指定 TemporalType |
| java.sql.Date | DATE | <property name="birthDate" type="date"/> | @Temporal(TemporalType.DATE) | 只包含日期部分 |
| float / Float | FLOAT | <property name="salary" type="float"/> | @Column(name = "salary") | 精度较低 |
| double / Double | DOUBLE | <property name="price" type="double"/> | @Column(name = "price") | 默认浮点类型 |
| BigDecimal | DECIMAL / NUMERIC | <property name="amount" type="big_decimal"/> | @Column(precision=10, scale=2) | 高精度数值,适合金额 |
注意: type 属性可省略,Hibernate 可自动推断;注解方式中 @Temporal 用于日期类型。
4.2 主键映射策略(@Id 与 generator)
| 生成策略(generator) | 说明 | .hbm.xml 配置 | 注解配置 | 注意事项 |
|---|
| assigned | 主键由程序手动赋值 | <generator class="assigned"/> | @GeneratedValue(strategy = GenerationType.IDENTITY) | 需在保存前设置主键值 |
| native | 根据数据库选择 identity 或 sequence | <generator class="native"/> | @GeneratedValue(strategy = GenerationType.AUTO) | 通用策略,推荐使用 |
| identity | 主键由数据库自增(如 MySQL AUTO_INCREMENT) | <generator class="identity"/> | @GeneratedValue(strategy = GenerationType.IDENTITY) | 仅支持支持自增的数据库 |
| sequence | 使用数据库序列(如 Oracle) | <generator class="sequence"><param name="sequence">seq_name</param></generator> | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_gen") @SequenceGenerator(name = "seq_gen", sequenceName = "seq_name") | Oracle 推荐方式 |
| uuid | 生成 128 位 UUID 字符串 | <generator class="uuid"/> | @GeneratedValue(strategy = GenerationType.UUID) | 全局唯一,适合分布式系统 |
| increment | Hibernate 内部递增(不推荐) | <generator class="increment"/> | 不常用 | 多实例环境下不安全 |
4.3 单表实体映射实践
| 映射元素 | 说明 | 代码示例(.hbm.xml) | 代码示例(注解) | 注意事项 |
|---|
<class> / @Entity | 实体类与表映射 | <class name="User" table="t_user"> | @Entity @Table(name = "t_user") | 必须定义 |
<id> / @Id | 主键映射 | <id name="id"><generator class="native"/></id> | @Id @GeneratedValue | 必须存在 |
<property> / @Column | 普通属性映射 | <property name="name" column="user_name"/> | @Column(name = "user_name") | 可省略 column 默认同名 |
| length | 字段长度 | <property name="name" length="50"/> | @Column(length = 50) | 用于字符串类型 |
| not-null | 非空约束 | <property name="email" not-null="true"/> | @Column(nullable = false) | 生成 DDL 时生效 |
| unique | 唯一约束 | <property name="email" unique="true"/> | @Column(unique = true) | 用于唯一索引 |
实践建议: 使用注解更简洁,避免 XML 配置冗余。
4.4 属性访问策略(字段 vs 属性)
| 访问策略 | 说明 | 配置方式 | 代码示例 | 注意事项 |
|---|
| 字段访问(field) | 直接访问类的字段(成员变量) | 默认策略,或使用 @Access(AccessType.FIELD) | private String name; // 直接访问 | 无需 getter/setter,但破坏封装 |
| 属性访问(property) | 通过 getter/setter 方法访问 | 使用 @Access(AccessType.PROPERTY) | private String name; public String getName(){...} | 符合 JavaBean 规范,推荐使用 |
| 混合访问 | 部分字段、部分属性 | 在不同属性上分别标注 | @Access(AccessType.FIELD) private Long id; @Access(AccessType.PROPERTY) private String getName() | 复杂场景使用,需谨慎 |
注意: 若使用属性访问,必须提供符合规范的 getter/setter;Hibernate 通过反射调用。
第五章:关联映射
5.1 一对一关联映射(单向与双向)
| 映射方式 | 说明 | .hbm.xml 配置 | 注解配置 | 注意事项 |
|---|
| 单向一对一(基于外键) | 主表持有从表的外键,从表无引用 | <many-to-one unique="true"/> | @OneToOne @JoinColumn(name = "profile_id") | 外键字段加唯一约束 |
| 单向一对一(主键关联) | 从表主键同时作为外键引用主表主键 | <generator class="foreign"/> | @OneToOne @PrimaryKeyJoinColumn | 两表主键值相同 |
| 双向一对一 | 双方互相持有对方引用 | 主方:<one-to-one name="profile" cascade="all"/> 从方:<one-to-one name="user" constrained="true"/> | 主方:@OneToOne(mappedBy = "...") 从方:@OneToOne | mappedBy 指定关系维护端 |
| mappedBy 属性 | 指定由哪一方维护外键 | 不适用(XML 中通过 key 指定) | @OneToOne(mappedBy = "user") | 被维护方必须使用 mappedBy |
示例(注解):
// User.java
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id")
private Profile profile;
// Profile.java
@OneToOne(mappedBy = "profile")
private User user;
5.2 一对多与多对一关联映射
| 映射方式 | 说明 | .hbm.xml 配置 | 注解配置 | 注意事项 |
|---|
| 单向一对多 | 一的一方持有集合引用多的一方 | <set name="orders"><key column="user_id"/><one-to-many class="Order"/></set> | @OneToMany @JoinColumn(name = "user_id") | 外键在”多”表中 |
| 双向一对多 | 双方互持引用,“多”方维护外键 | 一的一方:<set name="orders" inverse="true"><key column="user_id"/><one-to-many class="Order"/></set> 多的一方:<many-to-one name="user" column="user_id"/> | 一的一方:@OneToMany(mappedBy = "user") 多的一方:@ManyToOne | mappedBy 在 @OneToMany 端指定 |
| @JoinColumn | 指定外键列名 | <key column="user_id"/> | @JoinColumn(name = "user_id") | 默认命名规则为 关联类名_主键 |
| cascade | 级联操作 | cascade="all" | @OneToMany(cascade = CascadeType.ALL) | 常用于级联保存/删除 |
| fetch type | 加载策略 | lazy="true" / lazy="false" | @OneToMany(fetch = FetchType.LAZY) | 推荐一对多使用 LAZY |
示例(双向一对多):
// User.java
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Order> orders = new HashSet<>();
// Order.java
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
5.3 多对多关联映射
| 映射方式 | 说明 | .hbm.xml 配置 | 注解配置 | 注意事项 |
|---|
| 单向多对多 | 一方持有集合引用另一方 | <set name="roles" table="user_role"><key column="user_id"/><many-to-many class="Role" column="role_id"/></set> | @ManyToMany @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) | 必须指定中间表 |
| 双向多对多 | 双方互持集合引用 | 同上,反向也配置 | 一端:@ManyToMany(mappedBy = "users") 另一端:@ManyToMany | mappedBy 指定非维护端 |
| @JoinTable | 指定中间表信息 | 使用 <set table="..."/> 和 <key/> / <many-to-many/> | @JoinTable(name = "middle_table", joinColumns = ..., inverseJoinColumns = ...) | joinColumns 是本方外键,inverseJoinColumns 是对方外键 |
| 中间表主键 | 通常为联合主键(user_id, role_id) | 自动由外键构成 | 无需额外配置 | 建议添加索引提升查询性能 |
示例(注解):
// User.java
@ManyToMany
@JoinTable(name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
5.4 级联操作(cascade)配置
| 级联类型 | 说明 | 注解值 | 代码示例 | 注意事项 |
|---|
| save-update | 级联保存或更新 | CascadeType.SAVE_UPDATE | cascade = {CascadeType.SAVE_UPDATE} | 当父对象 save/update 时,子对象也执行相同操作 |
| persist | 级联持久化 | CascadeType.PERSIST | cascade = CascadeType.PERSIST | JPA 标准,仅 save 时触发 |
| merge | 级联合并 | CascadeType.MERGE | cascade = CascadeType.MERGE | 将脱管对象状态合并到持久化对象 |
| delete | 级联删除 | CascadeType.DELETE | cascade = CascadeType.DELETE | 删除父对象时删除所有子对象 |
| delete-orphan | 删除孤儿记录 | CascadeType.DELETE_ORPHAN | cascade = CascadeType.DELETE_ORPHAN | 子对象从集合移除后自动删除(仅集合关系) |
| all | 包含所有级联操作 | CascadeType.ALL | cascade = CascadeType.ALL | 常用于强聚合关系 |
注意: 级联操作应谨慎使用,避免误删数据;DELETE_ORPHAN 仅适用于 @OneToMany 和 @ManyToMany。
5.5 懒加载与急加载(fetch type)
| 加载策略 | 说明 | 注解配置 | .hbm.xml 配置 | 注意事项 |
|---|
| FetchType.LAZY | 懒加载,访问时才查询 | @OneToMany(fetch = FetchType.LAZY) | lazy="true" | 默认策略,避免 N+1 查询问题 |
| FetchType.EAGER | 急加载,立即关联查询 | @ManyToOne(fetch = FetchType.EAGER) | lazy="false" | 可能导致性能问题,慎用 |
| 一对一默认策略 | 默认 EAGER | @OneToOne 默认 EAGER | 无显式 lazy 属性时为 eager | 可能造成过度加载 |
| 一对多/多对多默认策略 | 默认 LAZY | @OneToMany, @ManyToMany 默认 LAZY | lazy="true" | 推荐保持懒加载 |
| 多对一默认策略 | 默认 EAGER | @ManyToOne 默认 EAGER | many-to-one 默认立即加载 | 建议改为 LAZY 优化性能 |
注意事项:
- 懒加载需保证 Session 未关闭,否则抛
LazyInitializationException。
- 可通过
JOIN FETCH 在 HQL 中显式预加载关联数据。
- 急加载适用于数据量小、必用的关联。
第六章:Hibernate 查询语言(HQL)
6.1 HQL 基本语法与特点
| 特性 | 说明 | 注意事项 |
|---|
| 面向对象 | 操作实体类和属性,而非数据库表和字段 | from User 等价于 select * from t_user |
| 大小写敏感 | 实体类名和属性名区分大小写 | User 与 user 不同 |
| 别名使用 | 可使用 as 或空格定义别名 | from User as u 或 from User u |
| 支持继承 | 可查询父类,返回所有子类实例 | 多态查询 |
| 不支持 INSERT | HQL 不支持 INSERT INTO ... SELECT ... 形式 | 但支持 INSERT INTO ... VALUES ... |
6.2 SELECT 查询
| 方法/语法 | 用途 | 代码示例 | 注意事项 |
|---|
select * | 查询所有字段 | from User | 返回 Object[] 或实体对象 |
select 属性 | 查询指定属性 | select u.name, u.email from User u | 返回 Object[] 数组 |
select new 构造器 | 投影到 DTO | select new com.example.UserDTO(u.name, u.email) from User u | DTO 必须有对应构造函数 |
distinct | 去重查询 | select distinct u.department from User u | 避免重复结果 |
6.3 WHERE 条件查询
| 运算符 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
= | property = value | 等值比较 | from User u where u.age = 25 | 字符串需加引号 |
<> 或 != | property <> value | 不等比较 | where u.status != 'inactive' | — |
>, <, >=, <= | 数值比较 | 范围判断 | where u.salary >= 5000 | — |
between | property between x and y | 范围查询 | where u.age between 18 and 60 | 闭区间 |
in | property in (list) | 集合匹配 | where u.role in ('admin','user') | 支持子查询 |
not in | property not in (list) | 集合排除 | where u.id not in (1,2,3) | — |
is null / is not null | 判断空值 | 空值检查 | where u.email is not null | 不能用 = null |
like | property like pattern | 模糊匹配 | where u.name like '张%' | 支持 % 和 _ |
and / or / not | 逻辑运算 | 组合条件 | where u.age > 20 and u.active = true | 注意优先级 |
6.4 参数绑定(命名参数、位置参数)
| 参数类型 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 命名参数 | :paramName | 使用名称绑定参数 | Query q = session.createQuery("from User u where u.name = :name"); q.setParameter("name", "张三"); | 推荐使用,可读性强 |
| 位置参数 | ?index | 按位置绑定参数 | Query q = session.createQuery("from User u where u.name = ?1"); q.setParameter(1, "张三"); | 从 1 开始编号 |
| setParameter() | 通用方法 | 设置任意类型参数 | q.setParameter("id", 100L); | 自动处理类型转换 |
| setString(), setLong() 等 | 类型专用方法 | 设置特定类型参数 | q.setLong("id", 100L); | 已过时,推荐使用 setParameter |
注意: 命名参数更安全,支持重复使用;避免字符串拼接防止 SQL 注入。
6.5 聚合函数与分组(GROUP BY, HAVING)
| 函数/子句 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| count() | count(property) | 计数 | select count(*) from User | 支持 count(*), count(prop) |
| max(), min() | max(property) | 最大最小值 | select max(salary) from Employee | — |
| avg() | avg(property) | 平均值 | select avg(age) from User | 返回 Double |
| sum() | sum(property) | 求和 | select sum(price) from Order | — |
| group by | group by property | 分组统计 | select u.dept, count(*) from User u group by u.dept | select 中非聚合字段必须出现在 group by |
| having | having condition | 分组后过滤 | having count(*) > 5 | 类似 SQL,用于过滤分组结果 |
6.6 分页查询(setFirstResult, setMaxResults)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| setFirstResult() | query.setFirstResult(int) | 设置起始行索引(从 0 开始) | query.setFirstResult(10); | 第 11 条开始 |
| setMaxResults() | query.setMaxResults(int) | 设置最大返回行数 | query.setMaxResults(20); | 最多 20 条 |
| 组合使用 | 共同实现分页 | 获取第 2 页,每页 10 条 | query.setFirstResult(10).setMaxResults(10); | 常用于大数据集分页 |
示例:
Query query = session.createQuery("from User");
query.setFirstResult((pageNo - 1) * pageSize);
query.setMaxResults(pageSize);
List<User> list = query.list();
6.7 多表查询与连接(JOIN)
| 连接类型 | HQL 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| inner join | join | 内连接,只返回匹配行 | from User u join u.orders o | 默认行为 |
| left outer join | left join 或 left outer join | 左外连接,返回左表全部 | from User u left join u.profile p | 常用于可选关联 |
| right outer join | right join 或 right outer join | 右外连接,返回右表全部 | from Profile p right join p.user u | 较少使用 |
| full join | full join | 全连接 | from A a full join B b on a.id = b.a_id | 支持有限 |
| join fetch | join fetch | 连接并立即加载(解决懒加载) | from User u left join fetch u.orders | 避免 N+1 查询问题 |
注意:
- 使用关联属性名进行连接,如
u.orders。
- fetch join 会将关联数据一同加载,Session 关闭后仍可访问。
6.8 投影查询与构造器返回
| 投影方式 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 多列投影 | select prop1, prop2 | 返回 Object[] | select u.name, u.email from User u | 结果为 Object[] 列表 |
| 单列投影 | select prop | 返回单一类型列表 | select u.name from User u | 返回 List<String> |
| 构造器投影 | select new Class(...) | 返回自定义对象 | select new UserDTO(u.name, u.email) from User u | DTO 必须有对应构造函数 |
| 别名使用 | 使用 as 定义别名 | 提高可读性 | select new UserDTO(u.name as name, ...) | 构造函数参数顺序必须匹配 |
示例:
public class UserDTO {
public UserDTO(String name, String email) { ... }
}
Query q = session.createQuery(
"select new com.example.UserDTO(u.name, u.email) from User u");
List<UserDTO> dtos = q.list();
第七章:原生 SQL 查询
7.1 使用 SQLQuery 执行原生 SQL
| 方法/语法 | 用途 | 代码示例 | 注意事项 |
|---|
| createSQLQuery() | 创建原生 SQL 查询对象 | Query query = session.createSQLQuery("SELECT * FROM t_user"); | 方法已过时,在新版本中推荐使用 createNativeQuery() |
| createNativeQuery() | JPA 标准方式创建原生查询 | Query query = session.createNativeQuery("SELECT * FROM t_user"); | 推荐使用,符合 JPA 规范 |
| list() | 执行查询并返回结果列表 | List<Object[]> results = query.list(); | 原生 SQL 默认返回 Object[] 数组 |
| uniqueResult() | 返回唯一结果,若无结果返回 null,多于一个抛异常 | Object result = query.uniqueResult(); | 适用于主键查询或唯一约束查询 |
| executeUpdate() | 执行更新/删除语句,返回影响行数 | int rows = session.createNativeQuery("UPDATE t_user SET name = ?").executeUpdate(); | 用于 INSERT, UPDATE, DELETE |
注意: createSQLQuery() 在 Hibernate 5+ 中已被标记为过时,应优先使用 createNativeQuery()。
7.2 结果集映射(addEntity, addScalar)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| addEntity(Class) | query.addEntity(User.class) | 将结果映射为指定实体类 | SQLQuery query = session.createSQLQuery("SELECT * FROM t_user"); query.addEntity(User.class); | 要求字段名与实体属性匹配(或通过映射配置) |
| addEntity(String, Class) | query.addEntity("alias", EntityClass.class) | 为带别名的表指定实体映射 | query.addEntity("u", User.class); | 用于多表连接查询 |
| addScalar(String) | query.addScalar("columnName") | 指定查询的标量字段(基本类型) | query.addScalar("name"); query.addScalar("age"); | 明确字段类型,避免类型推断错误 |
| addScalar(String, Type) | query.addScalar("price", StandardBasicTypes.DOUBLE) | 指定字段及其 Hibernate 类型 | query.addScalar("salary", StandardBasicTypes.BIG_DECIMAL); | 控制类型映射精度,如金额使用 BigDecimal |
| setResultTransformer() | query.setResultTransformer(Transformers.aliasToBean(DTO.class)) | 将结果转换为 DTO 对象 | query.setResultTransformer(new AliasToEntityMapResultTransformer()); | 需引入 org.hibernate.transform 包 |
示例:混合映射
SQLQuery query = session.createSQLQuery(
"SELECT u.id, u.name, r.role_name FROM t_user u JOIN t_role r ON u.role_id = r.id");
query.addScalar("id").addScalar("name").addScalar("role_name");
query.setResultTransformer(Transformers.aliasToBean(UserRoleDTO.class));
List<UserRoleDTO> dtos = query.list();
7.3 命名 SQL 查询(@NamedNativeQuery)
| 注解 | 用途 | 代码示例 | 注意事项 |
|---|
| @NamedNativeQuery | 定义命名的原生 SQL 查询 | @NamedNativeQuery(name = "findUserByEmail", query = "SELECT * FROM t_user WHERE email = ?1", resultClass = User.class) | 必须定义在实体类上 |
| name | 查询的唯一标识符 | name = "findActiveUsers" | 用于在代码中引用该查询 |
| query | 原生 SQL 语句 | query = "SELECT * FROM t_user WHERE status = 'ACTIVE'" | 可包含参数占位符 ?1, ?2 或 :param |
| resultClass | 映射结果类型(实体类) | resultClass = User.class | 返回实体对象列表 |
| resultSetMapping | 引用自定义结果集映射 | resultSetMapping = "UserSummaryMapping" | 用于复杂映射场景 |
| @SqlResultSetMapping | 定义自定义结果集结构 | @SqlResultSetMapping(name = "UserSummaryMapping", entities = @EntityResult(entityClass = User.class), columns = @ColumnResult(name = "total")) | 支持实体和标量混合返回 |
使用命名查询:
Query query = session.getNamedQuery("findUserByEmail");
query.setParameter(1, "user@example.com");
List<User> users = query.list();
优点: 将 SQL 与代码分离,便于维护;支持缓存;可在启动时验证 SQL 语法。
第八章:Criteria API 查询
8.1 Criteria API 概述(旧版 Criteria)
| 概念 | 说明 | 注意事项 |
|---|
| Criteria API | 面向对象的查询 API,用于构建类型安全的查询 | 旧版(Hibernate 5.2 之前)基于 org.hibernate.Criteria |
| 创建方式 | 通过 Session 创建 Criteria 实例 | Criteria criteria = session.createCriteria(User.class); |
| 类型安全 | 相比 HQL 字符串拼接,更安全,编译期检查 | 但旧版 Criteria 并非完全类型安全(使用字符串属性名) |
| 可读性 | 条件构建清晰,适合动态查询 | 代码较冗长 |
| 过时状态 | 自 Hibernate 5.2 起被标记为 deprecated | 官方推荐使用 JPA 2.1+ 的 javax.persistence.criteria.CriteriaQuery |
替代方案: 新项目应使用 JPA Criteria API 或 QueryDSL。
8.2 Restrictions 条件构建
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Restrictions.eq() | eq("property", value) | 等值比较 | criteria.add(Restrictions.eq("name", "张三")); | — |
| Restrictions.ne() | ne("property", value) | 不等比较 | criteria.add(Restrictions.ne("status", "INACTIVE")); | — |
| Restrictions.gt(), ge() | gt("age", 18) | 大于、大于等于 | criteria.add(Restrictions.ge("age", 18)); | — |
| Restrictions.lt(), le() | lt("salary", 5000) | 小于、小于等于 | — | — |
| Restrictions.between() | between("age", 18, 60) | 范围查询 | criteria.add(Restrictions.between("age", 18, 60)); | 闭区间 |
| Restrictions.like() | like("name", "李%") | 模糊匹配 | criteria.add(Restrictions.like("name", "李%")); | 支持 % 和 _ |
| Restrictions.in() | in("role", list) | 集合匹配 | criteria.add(Restrictions.in("role", Arrays.asList("admin","user"))); | — |
| Restrictions.isNull(), isNotNull() | isNull("email") | 空值判断 | criteria.add(Restrictions.isNotNull("email")); | 不能使用 == null |
| Restrictions.and(), or(), not() | and(crit1, crit2) | 逻辑组合 | criteria.add(Restrictions.and(Restrictions.eq("a",1), Restrictions.eq("b",2))); | — |
| 复合条件 | 多个 add() 调用 | 默认为 AND 关系 | criteria.add(...).add(...); | 多个条件自动组合为 AND |
8.3 Projections 聚合操作
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Projections.rowCount() | Projections.rowCount() | 计数 | criteria.setProjection(Projections.rowCount()); | 返回 Long 类型 |
| Projections.max(), min() | Projections.max("salary") | 最大最小值 | criteria.setProjection(Projections.max("age")); | — |
| Projections.avg() | Projections.avg("price") | 平均值 | criteria.setProjection(Projections.avg("score")); | — |
| Projections.sum() | Projections.sum("amount") | 求和 | criteria.setProjection(Projections.sum("total")); | — |
| Projections.groupProperty() | Projections.groupProperty("dept") | 分组字段 | criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("dept")) .add(Projections.count("id"))); | 必须与聚合函数结合使用 |
| ProjectionList | 组合多个投影 | ProjectionList list = Projections.projectionList(); | 使用 Projections.projectionList() 创建 | 可组合多个聚合与分组字段 |
示例:分组统计
Criteria criteria = session.createCriteria(User.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.groupProperty("department"));
projList.add(Projections.count("id"));
projList.add(Projections.avg("salary"));
criteria.setProjection(projList);
List<Object[]> results = criteria.list();
8.4 Order 排序控制
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Order.asc() | Order.asc("property") | 升序排列 | criteria.addOrder(Order.asc("name")); | — |
| Order.desc() | Order.desc("property") | 降序排列 | criteria.addOrder(Order.desc("createTime")); | — |
| 多重排序 | 连续添加 Order | 先按 A 排,再按 B 排 | criteria.addOrder(Order.asc("dept")).addOrder(Order.desc("salary")); | 执行顺序即优先级 |
8.5 关联查询与别名(createAlias)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| createAlias() | createAlias("association", "alias") | 创建关联别名,用于连接查询 | criteria.createAlias("orders", "o"); criteria.add(Restrictions.gt("o.amount", 1000)); | 执行 INNER JOIN |
| createCriteria() | createCriteria("association") | 创建子 Criteria(已过时) | criteria.createCriteria("orders").add(...); | 不推荐,应使用 createAlias + Restrictions |
| 别名用途 | 在 Restrictions 中引用关联属性 | Restrictions.eq("o.status", "PAID") | 必须先 createAlias 才能使用别名 | — |
| 外连接支持 | createAlias("prop", "alias", JoinType.LEFT_OUTER_JOIN) | 指定连接类型 | criteria.createAlias("profile", "p", JoinType.LEFT_OUTER_JOIN); | 需导入 org.hibernate.sql.JoinType |
示例:查询有高价值订单的用户
Criteria criteria = session.createCriteria(User.class);
criteria.createAlias("orders", "o");
criteria.add(Restrictions.gt("o.total", 5000));
criteria.addOrder(Order.desc("o.total"));
List<User> users = criteria.list();
注意: createAlias 是实现关联查询的关键,避免了 N+1 查询问题(当 fetch 策略为 LAZY 时)。
第九章:注解映射(Annotation Mapping)
9.1 @Entity 与 @Table
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @Entity | 标记一个类为持久化实体,对应数据库表 | @Entity | @Entity public class User { ... } | 必须标注在类上;需有无参构造函数 |
| @Table | 指定实体映射的数据库表名及表级配置 | @Table(name="table_name") | @Entity @Table(name = "t_user") public class User { ... } | 可设置表名、schema、catalog、唯一约束等;若不指定,默认表名为类名 |
| name 属性 | 指定数据库表名 | name = "custom_table" | @Table(name = "users") | 区分大小写,取决于数据库 |
| schema 属性 | 指定数据库 schema | schema = "public" | @Table(schema = "admin") | 用于多 schema 环境 |
| uniqueConstraints | 定义表级唯一约束 | uniqueConstraints = @UniqueConstraint(columnNames={"col1","col2"}) | @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"email"})) | 生成 DDL 时生效 |
9.2 @Id 与主键生成策略(@GeneratedValue)
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @Id | 标识主键属性 | @Id | @Id private Long id; | 每个实体必须有一个 @Id |
| @GeneratedValue | 定义主键生成策略 | @GeneratedValue(strategy = GenerationType.XXX) | @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; | 需与 @Id 一起使用 |
| strategy 属性 | 指定生成策略 | strategy = GenerationType.IDENTITY | 见下表 | — |
| generator 属性 | 引用自定义生成器 | generator = "my_gen" | 配合 @GenericGenerator 使用 | — |
主键生成策略:
| 策略 | 说明 | 适用场景 |
|---|
| GenerationType.IDENTITY | 数据库自增(如 MySQL AUTO_INCREMENT) | MySQL, SQL Server |
| GenerationType.SEQUENCE | 使用数据库序列(如 Oracle) | Oracle, PostgreSQL |
| GenerationType.TABLE | 使用单独表模拟序列 | 跨数据库兼容 |
| GenerationType.AUTO | 由 Hibernate 自动选择(IDENTITY/SEQUENCE) | 通用,推荐 |
| GenerationType.UUID | 生成 UUID 字符串 | 分布式系统 |
9.3 @Column 映射
| 注解属性 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| name | 指定数据库列名 | name = "col_name" | @Column(name = "user_name") | 默认为属性名 |
| length | 字符串字段长度 | length = 50 | @Column(length = 100) | 默认 255 |
| nullable | 是否可为空 | nullable = false | @Column(nullable = false) | 对应 NOT NULL 约束 |
| unique | 是否唯一 | unique = true | @Column(unique = true) | 创建唯一索引 |
| insertable | 是否参与 INSERT | insertable = false | 用于只读字段(如创建时间) | — |
| updatable | 是否参与 UPDATE | updatable = false | 用于不可修改字段(如创建时间) | — |
| precision, scale | 数值精度与小数位 | precision=10, scale=2 | @Column(precision = 10, scale = 2) | 用于 BigDecimal,金额字段 |
9.4 @OneToOne 映射
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @OneToOne | 定义一对一关联 | @OneToOne | @OneToOne private Profile profile; | 默认使用主键关联 |
| mappedBy | 指定关系由对方维护(双向) | mappedBy = "user" | @OneToOne(mappedBy = "profile") private User user; | 用于被维护端 |
| cascade | 级联操作 | cascade = CascadeType.ALL | @OneToOne(cascade = CascadeType.ALL) | 常用于聚合关系 |
| fetch | 加载策略 | fetch = FetchType.LAZY | @OneToOne(fetch = FetchType.LAZY) | 注意:@OneToOne 默认 EAGER,建议显式设为 LAZY |
| optional | 关联是否可为空 | optional = false | @OneToOne(optional = false) | 对应外键是否允许 NULL |
外键位置: 在未使用 mappedBy 的一方添加外键。
9.5 @OneToMany 与 @ManyToOne 映射
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @OneToMany | 一的一方持有多个 | @OneToMany | @OneToMany(mappedBy = "user") private Set<Order> orders; | 必须使用 mappedBy 指向 @ManyToOne 端 |
| mappedBy | 指定由 @ManyToOne 端维护 | mappedBy = "user" | 同上 | @OneToMany 端为只读 |
| @ManyToOne | 多的一方引用一的一方 | @ManyToOne | @ManyToOne private User user; | 关系维护端,外键在此方 |
| fetch | 加载策略 | fetch = FetchType.LAZY | @OneToMany(fetch = FetchType.LAZY) | @OneToMany 默认 LAZY,推荐 |
| cascade | 级联操作 | cascade = CascadeType.ALL | @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") | 常用于级联保存/删除子对象 |
最佳实践:
- 使用
Set 而非 List 避免性能问题。
- 初始化集合:
private Set<Order> orders = new HashSet<>();
9.6 @ManyToMany 映射
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @ManyToMany | 定义多对多关联 | @ManyToMany | @ManyToMany private Set<Role> roles; | 需配合 @JoinTable |
| mappedBy | 指定非维护端 | mappedBy = "users" | @ManyToMany(mappedBy = "roles") private Set<User> users; | 维护端定义中间表 |
| @JoinTable | 指定中间表信息 | @JoinTable(...) | 见 9.7 节 | 维护端必须使用 |
9.7 @JoinColumn 与 @JoinTable
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @JoinColumn | 指定外键列 | @JoinColumn(name = "user_id") | @ManyToOne @JoinColumn(name = "user_id") | 用于 @OneToOne, @ManyToOne, @OneToMany(单向) |
| name | 外键列名 | name = "fk_col" | @JoinColumn(name = "author_id") | 默认命名:关联实体名_主键 |
| @JoinTable | 定义中间表 | @JoinTable(name="user_role", ...) | @ManyToMany @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) | 用于 @ManyToMany |
| joinColumns | 当前实体在中间表的外键 | joinColumns = @JoinColumn(...) | 指向本方主键 | — |
| inverseJoinColumns | 关联实体在中间表的外键 | inverseJoinColumns = @JoinColumn(...) | 指向对方主键 | — |
9.8 其他常用注解(@Transient, @Temporal 等)
| 注解 | 用途 | 语法 | 代码示例 | 注意事项 |
|---|
| @Transient | 标记非持久化字段 | @Transient | @Transient private String tempData; | 不映射到数据库,如临时计算字段 |
| @Temporal | 指定日期时间类型映射 | @Temporal(TemporalType.DATE/TIME/TIMESTAMP) | @Temporal(TemporalType.TIMESTAMP) private Date createTime; | Java 8 之后推荐使用 java.time 类型(如 LocalDateTime),无需此注解 |
| @Lob | 大对象类型(CLOB/BLOB) | @Lob | @Lob private String content; | 用于长文本或二进制数据 |
| @Enumerated | 枚举类型映射 | @Enumerated(EnumType.STRING/ORDINAL) | @Enumerated(EnumType.STRING) private Status status; | STRING 更安全,ORDINAL 为序号 |
| @CreationTimestamp | 自动填充创建时间 | @CreationTimestamp | @CreationTimestamp private LocalDateTime createTime; | 需启用 hibernate.jpa.compliance.entity-graph 或使用监听器 |
| @UpdateTimestamp | 自动填充更新时间 | @UpdateTimestamp | @UpdateTimestamp private LocalDateTime updateTime; | 同上 |
注意: @CreationTimestamp 和 @UpdateTimestamp 是 Hibernate 特有注解,非 JPA 标准。
第十章:事务管理与并发控制
10.1 事务的基本概念(ACID)
| 特性 | 说明 | Hibernate 中的体现 |
|---|
| A - 原子性 (Atomicity) | 事务是不可分割的工作单位,要么全部成功,要么全部失败 | Hibernate 通过 Transaction 确保所有操作在 commit 前不会持久化 |
| C - 一致性 (Consistency) | 事务执行前后,数据库从一个一致状态转换到另一个一致状态 | Hibernate 映射约束(如非空、唯一)帮助维护一致性 |
| I - 隔离性 (Isolation) | 多个事务并发执行时,彼此隔离,互不干扰 | Hibernate 通过数据库隔离级别和锁机制实现 |
| D - 持久性 (Durability) | 事务一旦提交,其结果永久保存在数据库中 | 提交后数据写入磁盘,即使系统崩溃也不会丢失 |
10.2 Hibernate 中的事务 API(Transaction)
| 方法 | 用途 | 代码示例 | 注意事项 |
|---|
| beginTransaction() | 开始新事务 | Transaction tx = session.beginTransaction(); | 返回 Transaction 对象 |
| commit() | 提交事务 | tx.commit(); | 成功完成操作后调用,数据持久化 |
| rollback() | 回滚事务 | tx.rollback(); | 异常时调用,撤销所有更改 |
| isActive() | 检查事务是否活跃 | if (tx.isActive()) { ... } | 提交或回滚后返回 false |
| setRollbackOnly() | 标记事务为只回滚 | tx.setRollbackOnly(); | 用于跨方法调用时通知回滚 |
基本模式:
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
// 执行数据库操作
session.save(user);
tx.commit();
} catch (Exception e) {
if (tx != null) tx.rollback();
throw e;
} finally {
session.close();
}
10.3 事务边界控制(begin/commit/rollback)
| 原则 | 说明 | 实践建议 |
|---|
| 短事务 | 事务应尽可能短,减少锁持有时间 | 避免在事务中执行耗时操作(如网络调用) |
| 原子性操作 | 一个事务应包含一组逻辑上不可分割的操作 | 如转账:扣款与入账应在同一事务 |
| 异常处理 | 必须捕获异常并回滚 | try-catch-finally 中确保 rollback() 调用 |
| 资源管理 | 及时关闭 Session | 使用 try-with-resources(若 Session 实现 AutoCloseable)或 finally 块 |
| 传播行为 | 在 Spring 等框架中需配置事务传播 | 如 REQUIRED, REQUIRES_NEW |
10.4 并发策略与锁机制(乐观锁 @Version)
| 机制 | 说明 | 配置方式 | 注意事项 |
|---|
| 乐观锁 (Optimistic Locking) | 假设并发冲突少,通过版本号检测冲突 | @Version private Integer version; | 字段类型可为 int, long, Date;更新时检查版本 |
| 悲观锁 (Pessimistic Locking) | 假设冲突频繁,直接加锁阻止并发访问 | session.get(User.class, id, LockMode.PESSIMISTIC_WRITE); | 性能开销大,慎用 |
| @Version 注解 | 实现乐观锁 | @Version private Long version; | 字段在每次更新时自动递增 |
| 工作流程 | 1. 读取数据(含版本号)2. 修改数据 3. 更新时检查版本是否匹配 | UPDATE t_user SET name=?, version=version+1 WHERE id=? AND version=? | 若版本不匹配,抛 StaleObjectStateException |
| 适用场景 | 读多写少,冲突概率低 | Web 应用、高并发系统 | 推荐作为默认并发控制策略 |
示例:
@Entity
public class User {
@Id
private Long id;
private String name;
@Version
private Integer version; // 乐观锁字段
// getters and setters
}
当两个事务同时读取同一用户并尝试更新时,后提交的事务会因版本号不匹配而失败,需由应用层处理重试或提示用户。
第十一章:缓存机制
11.1 一级缓存(Session 级缓存)
| 特性 | 说明 | 注意事项 |
|---|
| 作用范围 | 绑定到 Session 实例,生命周期与 Session 一致 | 每个 Session 拥有独立的一级缓存 |
| 自动启用 | 默认开启,无需配置 | Hibernate 自动管理 |
| 缓存内容 | 持久化状态的对象(通过 get(), load(), HQL, Criteria 查询加载的对象) | 包括实体、集合、代理对象 |
| 主要作用 | - 避免重复 SQL 查询 - 保证事务内对象一致性 - 支持脏检查(Dirty Checking) | 在同一个 Session 中多次查询同一主键对象,仅第一次访问数据库 |
| 清除时机 | - session.clear() - session.evict(entity) - session.close() - 事务提交后(Session 通常关闭) | flush() 会将更改同步到数据库,但不清理缓存 |
| 示例 | User u1 = session.get(User.class, 1L); User u2 = session.get(User.class, 1L); // u1 == u2(同一实例) | 第二次 get() 不会发送 SQL |
注意: 一级缓存是强制性的,无法关闭。
11.2 二级缓存(SessionFactory 级缓存)
| 特性 | 说明 | 配置方式 | 注意事项 |
|---|
| 作用范围 | 跨 Session,由 SessionFactory 管理 | 所有 Session 共享二级缓存 | — |
| 可选启用 | 需手动配置并开启 | hibernate.cache.use_second_level_cache=true | 默认关闭 |
| 缓存粒度 | 按实体类或集合缓存 | 需为每个实体显式声明可缓存 | — |
| 缓存提供者 | 需集成第三方缓存实现(如 Ehcache, Redis) | 通过 hibernate.cache.region.factory_class 指定 | 见 11.4 |
| 配置实体可缓存 | 使用 @Cacheable 和 @Cache 注解 | @Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) | 或在 .hbm.xml 中使用 <cache usage="read-write"/> |
| 并发策略 | 控制并发访问行为 | READ_ONLY:只读,适合静态数据 / READ_WRITE:读写,基于时间戳 / NONSTRICT_READ_WRITE:非严格读写,可能脏读 / TRANSACTIONAL:支持事务,开销大 | 推荐 READ_WRITE |
| 命中条件 | 仅对通过主键查询(get(), load())有效 | HQL/SQL 查询默认不使用二级缓存 | 需配合查询缓存(11.3) |
示例配置(pom.xml 引入 Ehcache):
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.6.15.Final</version>
</dependency>
hibernate.cfg.xml:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
11.3 查询缓存
| 特性 | 说明 | 配置方式 | 注意事项 |
|---|
| 作用 | 缓存 HQL 或 Criteria 查询的结果集 ID 列表 | 配合二级缓存使用,避免重复执行查询 | — |
| 启用 | 需开启并配置 | hibernate.cache.use_query_cache=true | 默认关闭 |
| 使用方式 | 在查询对象上调用 setCacheable(true) | query.setCacheable(true); | 可选设置缓存区域 setCacheRegion("query.user") |
| 工作原理 | 1. 缓存查询条件 + 参数 → 结果集 ID 列表 2. 根据 ID 从二级缓存获取实体对象 | 若二级缓存中无实体,仍需访问数据库 | — |
| 失效机制 | 当查询涉及的任何表发生增删改时,所有相关查询缓存失效 | 高频更新的表会使查询缓存失效频繁,降低效率 | — |
| 适用场景 | 查询条件固定、结果集不大、数据更新不频繁 | 如分页查询、字典查询 | — |
| 不适用场景 | 频繁更新的表、大结果集、使用 ORDER BY RAND() 等非确定性查询 | — | — |
示例:
Query query = session.createQuery("FROM User u WHERE u.status = :status");
query.setParameter("status", "ACTIVE");
query.setCacheable(true); // 启用查询缓存
query.setCacheRegion("userQueries"); // 指定缓存区域
List<User> users = query.list();
11.4 常用缓存提供者(Ehcache, Redis 集成)
| 缓存提供者 | 特点 | 集成方式 | 注意事项 |
|---|
| Ehcache | 本地内存缓存、轻量级,部署简单、支持磁盘溢出 | 1. 添加 hibernate-ehcache 依赖 2. 配置 ehcache.xml 3. 在 hibernate.cfg.xml 中指定 EhCacheRegionFactory | 适合单机应用;集群环境下需使用 Terracotta 实现分布式 |
| Redis | 分布式内存数据库、支持持久化、高可用、适合集群环境 | 1. 添加 hibernate-redis 依赖(如 hibernate-redis:2.7.0)2. 配置 hibernate.cache.region.factory_class=org.hibernate.cache.redis.hibernate52.SingletonRedisRegionFactory 3. 设置 Redis 连接信息(host, port) | 需独立部署 Redis 服务;网络延迟可能影响性能;确保序列化兼容 |
| Infinispan | JBoss 出品,功能强大、支持本地/分布式模式、与 WildFly 集成好 | 配置 InfinispanRegionFactory | 适合企业级应用 |
| Caffeine | 高性能本地缓存、替代 Guava Cache | 需通过自定义 RegionFactory 集成 | Hibernate 官方不直接支持,需额外开发 |
Redis 配置示例(hibernate.cfg.xml):
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.redis.hibernate52.SingletonRedisRegionFactory
</property>
<property name="hibernate.cache.redis.host">127.0.0.1</property>
<property name="hibernate.cache.redis.port">6379</property>
<property name="hibernate.cache.redis.expiration">3600</property>
第十二章:性能优化与最佳实践
12.1 N+1 查询问题与解决方案
| 问题描述 | 解决方案 | 实现方式 | 示例 |
|---|
| N+1 问题:执行 1 次主查询 + N 次关联查询(如查用户列表,每用户查订单) | 1. JOIN FETCH | HQL: from User u left join fetch u.orders | from User u join fetch u.profile |
| 2. 批量抓取 (Batch Fetching) | 注解: @BatchSize(size=10) | @Entity @BatchSize(size=20) class User {...} |
| 3. 子查询抓取 (Subselect Fetching) | 注解: @Fetch(FetchMode.SUBSELECT) | — |
| 后果 | 性能急剧下降,数据库交互次数过多 | — | — |
| 检测 | 启用 SQL 日志,观察重复的 SELECT 语句 | hibernate.show_sql=true hibernate.format_sql=true | — |
最佳实践:
- 优先使用 JOIN FETCH 解决已知的 N+1 问题。
- 对集合关联使用 @BatchSize 减少查询次数。
- 避免在循环中调用
session.get()。
12.2 批量操作(batch processing)
| 方法 | 用途 | 代码示例 | 注意事项 |
|---|
| JDBC 批量 | 提高大批量增删改性能 | hibernate.jdbc.batch_size=20 | 在 hibernate.cfg.xml 中配置 |
| 手动分批提交 | 避免内存溢出和长事务 | for (int i = 0; i < 10000; i++) { session.save(entity); if (i % 50 == 0) { session.flush(); session.clear(); } } | flush() 同步到数据库;clear() 清理一级缓存;避免 OutOfMemoryError |
| StatelessSession | 无状态批量操作(见 12.4) | StatelessSession statelessSession = sessionFactory.openStatelessSession(); | 不使用缓存和持久化上下文 |
配置:
hibernate.jdbc.batch_size=20
hibernate.order_inserts=true # 批量优化
hibernate.order_updates=true
hibernate.batch_versioned_data=true # 批量更新版本化数据
12.3 fetch 策略优化
| 关联类型 | 推荐 Fetch 策略 | 原因 |
|---|
| @ManyToOne | FetchType.LAZY | 默认 EAGER,易导致过度加载 |
| @OneToOne | FetchType.LAZY | 默认 EAGER,常造成性能问题 |
| @OneToMany | FetchType.LAZY | 默认 LAZY,保持 |
| @ManyToMany | FetchType.LAZY | 默认 LAZY,保持 |
优化建议:
- 将 @ManyToOne 和 @OneToOne 显式设为 LAZY。
- 在 HQL 中使用 JOIN FETCH 按需预加载。
- 避免在实体中定义 EAGER 关联,除非 100% 确定每次都需要。
12.4 StatelessSession 的使用场景
| 特性 | 说明 | 适用场景 | 注意事项 |
|---|
| 无一级缓存 | 不缓存任何对象 | 批量导入、导出数据 | 无法利用缓存 |
| 无脏检查 | 不跟踪对象状态变化 | 高性能 ETL 作业 | 必须显式调用 insert(), update(), delete() |
| 无级联操作 | 不支持 cascade | 简单的 CRUD 批量操作 | 需手动管理关联 |
| 低内存占用 | 不维护持久化上下文 | 处理海量数据(> 10万条) | 避免 OutOfMemoryError |
| 方法 | insert(), update(), delete(), get(), query() | statelessSession.insert(user); | 不支持 saveOrUpdate() |
示例:
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
for (User user : users) {
session.insert(user); // 必须显式插入
}
tx.commit();
session.close();
12.5 性能监控与日志配置
| 工具/配置 | 用途 | 配置方式 | 注意事项 |
|---|
| SQL 日志 | 查看执行的 SQL 语句 | hibernate.show_sql=true hibernate.format_sql=true | 开发环境开启,生产环境关闭 |
| 慢查询日志 | 识别执行时间长的查询 | 数据库层面配置(如 MySQL slow_query_log) | 结合 APM 工具分析 |
| 统计信息 | 监控缓存命中率、查询次数等 | hibernate.generate_statistics=true 通过 SessionFactory.getStatistics() 获取 | 性能开销小,生产可用 |
| APM 工具 | 应用性能监控(如 SkyWalking, Prometheus) | 集成探针或埋点 | 全链路监控,定位瓶颈 |
| Hibernate Metrics | 暴露指标到 Micrometer | 使用 hibernate-micrometer 模块 | 与 Spring Boot Actuator 集成 |
推荐日志配置(logback.xml):
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/> <!-- 参数绑定 -->
<logger name="org.hibernate.cache" level="DEBUG"/> <!-- 缓存日志 -->
第十三章:Spring 集成(可选)
13.1 Spring 与 Hibernate 整合原理
| 核心思想 | 说明 | 优势 |
|---|
| 容器管理 | Spring 容器负责创建和管理 SessionFactory、Session 和 Transaction | 解耦配置与使用,便于维护 |
| 依赖注入 | Hibernate 的 SessionFactory 通过 DI 注入到 DAO 或 Service 层 | 符合 IoC 原则,提升可测试性 |
| 事务抽象 | Spring 提供统一的事务管理 API,屏蔽底层差异(Hibernate、JDBC 等) | 支持声明式事务,简化事务控制 |
| 资源管理 | Spring 自动处理 Session 的开启、关闭和绑定到线程(通过 ThreadLocal) | 避免资源泄漏,保证线程安全 |
| 异常转换 | 将 Hibernate 的 Checked Exception 转换为 Spring 的 DataAccessException(Runtime Exception) | 简化异常处理,无需显式捕获 |
整合方式:
- XML 配置(传统方式)
- Java Config(推荐)
- Spring Boot 自动配置(最简便)
13.2 配置 LocalSessionFactoryBean
| 配置方式 | 说明 | 代码示例 |
|---|
| XML 配置 | 在 Spring 配置文件中定义 LocalSessionFactoryBean | <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="packagesToScan" value="com.example.entity"/><property name="hibernateProperties"><props><prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop><prop key="hibernate.hbm2ddl.auto">update</prop><prop key="hibernate.show_sql">true</prop></props></property></bean> |
| Java Config | 使用 @Configuration 类配置 | 见下方代码示例 |
| 关键属性 | dataSource:数据源;packagesToScan:实体类包路径;hibernateProperties:Hibernate 配置属性 | — |
Java Config 示例:
@Configuration
@EnableTransactionManagement
public class HibernateConfig {
@Autowired
private DataSource dataSource;
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setPackagesToScan("com.example.entity");
Properties hibernateProperties = new Properties();
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
hibernateProperties.put("hibernate.hbm2ddl.auto", "update");
sessionFactory.setHibernateProperties(hibernateProperties);
return sessionFactory;
}
}
注意:
LocalSessionFactoryBean 是 Spring 提供的工厂 Bean,用于创建 Hibernate SessionFactory。
packagesToScan 替代了传统的 mappingResources,自动扫描带 @Entity 的类。
13.3 声明式事务管理(@Transactional)
| 注解 | 用途 | 配置方式 | 注意事项 |
|---|
| @Transactional | 声明方法或类具有事务性 | 在方法或类上添加注解 | 需启用 <tx:annotation-driven/> 或 @EnableTransactionManagement |
| 事务传播 | 控制事务的边界行为 | @Transactional(propagation = Propagation.REQUIRED) | 默认 REQUIRED |
| 隔离级别 | 设置事务隔离 | @Transactional(isolation = Isolation.READ_COMMITTED) | 默认数据库默认级别 |
| 只读事务 | 提示数据库优化 | @Transactional(readOnly = true) | 用于查询方法,提升性能 |
| 回滚规则 | 指定异常时是否回滚 | @Transactional(rollbackFor = Exception.class) | 默认仅对 RuntimeException 和 Error 回滚 |
| 超时设置 | 事务最大执行时间 | @Transactional(timeout = 30) | 单位:秒 |
工作原理: Spring 使用 AOP 动态代理,在目标方法执行前后开启和提交/回滚事务。
示例:
@Service
public class UserService {
@Autowired
private SessionFactory sessionFactory;
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Session session = sessionFactory.getCurrentSession();
User from = session.get(User.class, fromId);
User to = session.get(User.class, toId);
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
session.update(from);
session.update(to);
// 方法结束,事务自动提交
}
}
注意:
@Transactional 仅对 public 方法有效。
- 自调用(同一个类中方法调用)不会触发事务代理。
13.4 使用 HibernateTemplate(已过时)或直接使用 SessionFactory
| 方式 | 说明 | 代码示例 | 状态与建议 |
|---|
| HibernateTemplate | Spring 提供的模板类,封装了 Session 获取、异常转换等 | hibernateTemplate.get(User.class, id) | 已过时(从 Spring 4.3+ 不再推荐)。原因:侵入性代码,限制了 Hibernate 新特性使用 |
| 直接使用 SessionFactory | 通过 getCurrentSession() 获取绑定到事务的 Session | sessionFactory.getCurrentSession().get(User.class, id) | 推荐方式。更简洁,直接使用原生 API,与 Spring 事务无缝集成 |
getCurrentSession() 特点:
- 返回当前线程绑定的 Session。
- 由 Spring 管理生命周期,无需手动关闭。
- 要求已配置
hibernate.current_session_context_class=thread(Spring 已自动处理)。
示例:
@Repository
public class UserDao {
@Autowired
private SessionFactory sessionFactory;
public User findById(Long id) {
return sessionFactory.getCurrentSession().get(User.class, id);
}
public void save(User user) {
sessionFactory.getCurrentSession().save(user);
}
}
总结: 现代 Spring + Hibernate 开发应:
- 使用
LocalSessionFactoryBean 或 Java Config 配置 SessionFactory。
- 直接注入 SessionFactory 并调用
getCurrentSession()。
- 使用
@Transactional 管理事务。
- 避免使用 HibernateTemplate。