JPA 与 Hibernate 基础与 Hibernate 高级特性

共 43 题
📑 题目列表 43 题
#
★★★

1. @Transactional 与事务边界在 JPA 的协作

在 JPA 中,@Transactional 注解与事务边界是如何协作的?请说明事务边界、持久化上下文(Persistence Context)与数据库连接之间的关系?

  • 事务边界与持久化上下文生命周期
  • 事务提交与 flush 时机
  • 连接获取与释放的时机

@Transactional 声明了服务方法的事务边界。当方法进入时 Spring 开启事务并从连接池获取一个数据库连接,同时绑定一个持久化上下文(EntityManager);方法结束时提交或回滚事务。在 Spring 管理的事务型持久化上下文(transaction-scoped,JPA 默认类型)中,flush 默认在提交点之前自动执行(AUTO flush mode),将持久化实体的变更同步到数据库。查询、插入、更新、删除都通过这一条连接完成,真正体现了"一个事务一条连接"的边界。若方法抛异常,事务回滚,持久化上下文也会被标记为 rollback-only,后续操作会抛异常。

事务边界决定了持久化上下文的作用范围,也决定了数据库连接被占用的时间。事务期间持有连接,事务结束即释放连接,因此事务不能跨越远程调用或长耗时的非数据库操作,否则会长期占用连接。理解这个边界才能正确划分事务,避免连接池耗尽和一致性问题。

@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    Account a = accountRepository.findById(fromId).orElseThrow();
    Account b = accountRepository.findById(toId).orElseThrow();
    a.debit(amount);   // 变更进入持久化上下文
    b.credit(amount);
    // 方法返回时 Spring 提交事务,flush 将变更写入数据库
}
#
★★★

2. @Version 与乐观锁

请解释 @Version 字段在 JPA 乐观锁中的机制,以及乐观锁与悲观锁的区别?

  • @Version 字段的更新与校验流程
  • OptimisticLockException 的处理
  • 乐观锁与悲观锁的取舍

@Version 标注一个整型或时间戳字段用于乐观并发控制。每次提交更新时,JPA 会将 WHERE 条件附带 version = 当前版本,并把 version 加 1;若受影响行数为 0,说明版本已被其他事务修改,抛出 OptimisticLockException。这样避免了在无并发冲突时加锁的开销,只有更新时才检查冲突。乐观锁适合读多写少、冲突概率低的场景;悲观锁(SELECT ... FOR UPDATE)在读取时就加锁,适合写冲突频繁、冲突代价高的场景。

乐观锁的核心是"先读后校验",通过版本号 CAS 式的更新实现,代价低但可能抛异常重试;悲观锁提前锁定资源,代价高但不会因并发冲突失败。Hibernate 在 flush 时如果检测到版本不匹配会抛 OptimisticLockException(StaleObjectStateException),Spring 会将异常转换为对应运行时异常,业务层可捕获并重试。

@Entity
public class Account {
    @Id private Long id;
    @Version private Long version; // 乐观锁版本号
    private BigDecimal balance;
}
#
★★★

3. Hibernate 一级缓存(Session)与二级缓存(SessionFactory)

请说明 Hibernate 一级缓存(Session 级)与二级缓存(SessionFactory 级)的区别、作用域与失效策略?

  • 一级缓存的作用域与生命周期
  • 二级缓存的配置与共享
  • 缓存的失效与一致性问题

一级缓存又称事务级缓存,作用域是当前 Session(持久化上下文),Session 关闭即失效。它保证同一持久化上下文内重复查询同一实体不会重复执行 SQL,并维护实体快照用于脏检查。二级缓存是 SessionFactory 级别的进程级缓存,可跨 Session 共享,用于缓存实体、集合和查询结果,需显式配置(如 @Cache、EhCache/Caffeine),并配置缓存并发策略(read-only、read-write、nonstrict-read-write、transactional)。二级缓存能显著减少数据库访问,但带来与数据库一致性同步的问题,需要配合缓存失效策略。

一级缓存是 JPA 规范的默认行为,透明且稳定;二级缓存是可选优化,缓存命中率和管理成本高,不当使用会读到脏数据。分布式场景下二级缓存还需要考虑多实例间的缓存同步,通常采用分布式缓存或谨慎使用本地缓存。

#
★★★

4. Hibernate 的 SQL 日志(hibernate.show_sql/format_sql)与慢查询

请说明 Hibernate 的 SQL 日志配置(hibernate.show_sql、format_sql、use_sql_comments)以及如何结合慢查询定位性能问题?

  • show_sql/format_sql/use_sql_comments 配置含义
  • 日志开关与生产环境的关系
  • 慢查询定位与 N+1 分析

hibernate.show_sql=true 会把 Hibernate 生成的 SQL 打印到控制台,format_sql=true 进行格式化,use_sql_comments=true 会在 SQL 中附加注释指明来源(如实体、关联)。这些配置主要用于开发调试,生产环境应关闭或用日志框架将 SQL 输出到日志文件并按级别控制。慢查询治理需要结合数据库慢查询日志、Hibernate Stats 或 AOP 统计执行时间,重点关注批量查询、N+1 问题以及未命中的查询。

show_sql 是开发期便利配置,生产环境会影响性能与日志体积。真正的慢查询分析应基于数据库慢查询日志与连接池监控,Hibernate 的 FormatStyle 与 Statistics 机制可辅助定位耗时 SQL 与缓存的执行情况。

#
★★★

5. Hibernate 的二级缓存(JCache/EhCache/Caffeine)

请说明 Hibernate 二级缓存如何通过 JCache 标准接入 EhCache、Caffeine 等实现,以及缓存策略的选择?

  • JCache(JSR-107)标准与 RegionFactory
  • 缓存并发策略(read-only/read-write/nonstrict-read-write/transactional)
  • 缓存一致性

Hibernate 自 5.3 起通过 JCache(JSR-107)标准接入各种缓存实现,配置 hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory 以及对应 provider(如 EhCache、Caffeine)。实体、集合、查询结果可配置不同的缓存区域(Region)和并发策略。read-only 适合不修改的配置数据;read-write 用于频繁读写但可接受偶尔不一致的场景;nonstrict-read-write 适合很少更新的数据;transactional 需要 JTA 支持,保证缓存与数据库原子性。缓存策略的选择直接影响数据一致性风险。

二级缓存是进程内缓存,多实例部署时各实例缓存独立,存在不一致。通过 JCache 统一 API 可无缝切换实现,但缓存失效(如更新后主动 evict)与分布式同步仍是工程难点。写入频繁或一致性要求高的数据不应放入二级缓存。

#
★★★

6. 级联(cascade)与孤儿删除(orphanRemoval)的语义

请解释 JPA 中级联(cascade)类型与孤儿删除(orphanRemoval)的语义和应用场景?

  • CascadeType 各枚举(PERSIST/MERGE/REMOVE/REFRESH/DETACH/ALL)含义
  • orphanRemoval=true 的语义
  • 级联与孤儿删除的边界

cascade 定义了实体操作(persist、merge、remove、refresh、detach)在关联实体上的传播。例如 cascade=PERSIST 表示保存父实体时自动保存尚未持久化的子实体;cascade=REMOVE 表示删除父实体时级联删除子实体。orphanRemoval=true 表示当子实体从父实体的集合中被移除(孤儿化)时,自动删除该子实体,它与 cascade=REMOVE 不同:cascade 是删除父实体时级联,orphanRemoval 是将子实体从集合摘除时删除。两者都需谨慎使用,避免误删数据。

级联和孤儿删除本质是持久化语义的传播,合理使用能简化代码,但过度使用会在删除时产生大量看不到的 SQL,甚至误删关联数据。工程上通常只在明确拥有生命周期关系的聚合根上使用,避免在多对多或共享实体上滥用。

#
★★★

7. @Convert/AttributeConverter 的自定义类型转换

请说明 JPA 的 @Convert 与 AttributeConverter 如何实现自定义类型与数据库列之间的转换?

  • AttributeConverter 接口(convertToDatabaseColumn/convertToEntityAttribute)
  • @Convert 与 @Converter(autoApply) 的使用
  • 敏感字段加密等应用

AttributeConverter<X,Y> 定义实体类型 X 与数据库类型 Y 的映射,通过 convertToDatabaseColumn 将实体值转为列值、convertToEntityAttribute 反向转换。@Converter 标注的类可被 JPA 自动发现应用,autoApply=true 对该类型全局生效;@Convert 可在字段上指定特定的 converter。典型应用包括枚举与字符串互转、敏感字段加解密、货币、JSON 等复杂类型。

自定义转换将类型映射逻辑收敛到单一组件,避免在每个实体中重复处理。注意 AttributeConverter 是无状态的,转换过程中不能依赖上下文;对于加解密等场景,converter 每次读写都会执行,需注意性能与密钥管理。

@Converter(autoApply = true)
public class EncryptConverter implements AttributeConverter<String, String> {
    @Override
    public String convertToDatabaseColumn(String attr) { return encrypt(attr); }
    @Override
    public String convertToEntityAttribute(String col) { return decrypt(col); }
}
#
★★★

8. @Embeddable/@Embedded 的值对象

请说明 @Embeddable 与 @Embedded 在 JPA 中如何实现值对象的映射?

  • @Embeddable/@Embedded 的语义
  • 值对象与实体的区别
  • 嵌套与覆盖列名

@Embeddable 标注的类表示一个内嵌的值对象,它没有独立标识(Id),其字段直接映射到所属实体的列。父实体用 @Embedded 引用该值对象,@AttributeOverride 可覆盖默认列名。值对象强调 Value Object 概念(如地址、金额、时间段),它不独立存在、没有生命周期,随所属实体一起持久化和删除。相比实体,值对象不在一级缓存中作为独立身份管理,查询更底层。

值对象把一组内聚字段组合成强类型,提升可读性与复用性,避免大而全的实体。它与组合(composition)语义一致,生命周期完全由宿主实体控制。注意值对象不可共享给多个实体,且嵌套内嵌值对象需谨慎处理列名冲突。

@Embeddable
public class Address {
    private String city;
    private String street;
}
@Entity
public class User {
    @Id private Long id;
    @Embedded private Address address;
}
#
★★★

9. @Entity/@Table/@Column 在 Hibernate 7 中命名策略(PhysicalNamingStrategy)的演进

请说明 Hibernate 中实体命名策略(ImplicitNamingStrategy 与 PhysicalNamingStrategy)的演进,以及 Hibernate 7 中的默认行为?

  • ImplicitNamingStrategy 与 PhysicalNamingStrategy 的分工
  • 默认命名策略(camelCase 转 snake_case)
  • Hibernate 7 的演进

Hibernate 将命名策略分为两类:ImplicitNamingStrategy 决定未显式命名的实体属性、表、列、外键的默认名称;PhysicalNamingStrategy 在最终物理命名上做转换,Hibernate 默认的 CamelCaseToUnderscoresNamingStrategy 会把 camelCase 转成 snake_case。Hibernate 6/7 中,@Entity、@Table、@Column 的显式命名决定逻辑名称(不经过 ImplicitNamingStrategy),但 PhysicalNamingStrategy 仍会应用于最终物理名称(包括显式指定的名称)。理解显式命名与隐式命名的优先级,可避免设置了 @Column(name) 却发现被物理策略二次改写的问题。

命名策略的意义在于统一数据库命名规范(如统一下划线命名),避免手工在每处写 @Column(name=...)。理解显式命名与隐式命名的优先级以及物理策略的作用范围,可避免设置显式名称却被物理策略二次改写的意外。

#
★★★

10. @Entity/@Table/@Id/@GeneratedValue 的语义

请说明 @Entity、@Table、@Id、@GeneratedValue 注解的语义与使用要点?

  • @Entity 的实体标记与类要求
  • @Table 的映射
  • @Id 与 @GeneratedValue 的主键生成策略(IDENTITY/SEQUENCE/TABLE/AUTO)

@Entity 将一个类标记为 JPA 实体,要求必须有无参构造、非 final 类、字段可通过属性或字段访问。@Table 指定实体映射到的数据库表(name、schema、uniqueConstraints 等)。@Id 标识主键字段,@GeneratedValue 指定主键生成策略:IDENTITY 依赖数据库自增(MySQL 常用,但插入需立即执行 SET 获取主键);SEQUENCE 使用数据库序列(Oracle/PostgreSQL 推荐,配合 allocationSize 批量预取);TABLE 用单独表模拟(性能差,少用);AUTO 由 JPA 提供方自行选择。主键策略影响批量插入性能与数据库适配。

@Entity 是 ORM 的基石,@Id 决定实体身份,@GeneratedValue 决定主键如何产生。选择策略需考虑数据库类型与插入性能:MySQL 常用 IDENTITY,高并发批量插入用 SEQUENCE 更优;Hibernate 的 SEQUENCE 还可通过 sequence generator 配置优化。

#
★★★

11. @EntityGraph 与抓取策略(FetchType)

请说明 @EntityGraph 与 FetchType(LAZY/EAGER)在关联加载中的区别与使用?

  • FetchType.LAZY/EAGER 语义
  • @NamedEntityGraph/@EntityGraph 的 fetch 图
  • N+1 问题的治理

FetchType 决定关联默认的加载策略:LAZY 表示关联在访问时才加载(通过代理或延迟加载),EAGER 表示加载实体时立即加载关联。EAGER 容易导致不必要的关联加载和 N+1 问题,LAZY 默认按需加载,但访问未初始化关联时可能抛 LazyInitializationException(Session 已关闭)。@EntityGraph 提供查询级的抓取图,通过 join fetch 或后续 select 一次性加载指定关联,在查询时按需覆盖默认的 FetchType,是治理 N+1 的推荐方式。

FetchType 是默认策略,EntityGraph 是查询级覆盖。实际工程中应尽量用 LAZY + 查询时 EntityGraph/join fetch 精确控制加载,既避免 N+1 又避免过度加载。两者结合是平衡性能与复杂度的关键。

@EntityGraph(attributePaths = {"orders", "orders.items"})
@Query("select u from User u where u.id = :id")
Optional<User> findWithOrders(@Param("id") Long id);
#
★★★

12. @Inheritance 三种策略(SINGLE_TABLE/JOINED/TABLE_PER_CLASS)

请说明 JPA 继承映射的三种策略(SINGLE_TABLE、JOINED、TABLE_PER_CLASS)的原理与适用场景?

  • 单表策略(单表 + discriminator)
  • 连接表策略(每类一张表 + 外键共用主键)
  • 每类一表策略

@Inheritance(strategy=...) 定义继承层次在数据库中的映射。SINGLE_TABLE 将所有子类数据放在一张表,通过 discriminator 列区分类型,查询简单、无 JOIN,但存在大量空列且约束难以加;JOINED 为父类和每个子类各建一张表,子类表通过外键关联父类表主键,规范化好、冗余少,但查询需 JOIN、性能略低;TABLE_PER_CLASS 每个具体类一张表,无共享表,但多态查询需 UNION ALL,且 JPA 对多态关联支持有限。选择需权衡查询性能、规范化与扩展性。

三种策略是"空间换性能"与"规范化换灵活性"的权衡。SINGLE_TABLE 适合类型少、差异小的场景且查询最快;JOINED 适合类层次复杂、各子类属性差异大的场景;TABLE_PER_CLASS 较少用,多态查询开销大。策略选择还应考虑对数据库约束、索引和迁移的影响。

#
★★

13. @JoinColumn 与 mappedBy 的所有权方向

请说明 @JoinColumn 与 mappedBy 在双向关联中的所有权方向(owning side)语义?

  • 关联所有权的概念
  • mappedBy 与 @JoinColumn 的关系
  • 维护方的职责

在双向关联中,一方是维护方(owning side),另一方用 mappedBy 声明被维护方(inverse side)。维护方持有外键列并负责更新关联,通常用 @JoinColumn 指定外键列名;inverse 侧通过 mappedBy 关联到对方的属性名,仅用于从反向读取,不参与外键维护。例如一对多中,多的一方(ManyToOne 侧)通常是维护方,一的一方用 mappedBy 指向该属性。若只在 inverse 侧维护集合则不会持久化关联。

所有权决定外键的归属与更新方向。理解 owning side 才能正确配置双向关联,避免外键不更新或重复维护。mappedBy 的值必须与对端实体中 ManyToOne 的属性名一致,否则映射失败。

@Entity
public class Order {
    @OneToMany(mappedBy = "order")  // inverse side
    private List<OrderItem> items;
}
@Entity
public class OrderItem {
    @ManyToOne
    @JoinColumn(name = "order_id")  // owning side
    private Order order;
}
#
★★

14. @OneToOne/@OneToMany/@ManyToMany 的关系映射

请说明 @OneToOne、@OneToMany、@ManyToMany 三种关系映射的配置与特点?

  • 一对多(外键在多方)
  • 多对多(中间表)
  • 一对一(外键或共享主键)

@OneToOne 表示一对一,可在一侧用 @JoinColumn 保存外键,或用共享主键(@PrimaryKeyJoinColumn);@OneToMany 表示一对多,通常由多方的 @ManyToOne 持有外键,One 侧用 mappedBy 指向它;@ManyToMany 表示多对多,需要中间表,通过 @JoinTable 指定连接表名及关联外键列。这些关系都需注意 FetchType 与级联设置,避免 N+1 和误删。

关系映射是 ORM 的核心。@OneToMany 的集合默认懒加载,访问时可能触发额外查询;@ManyToMany 中间表可额外承载业务字段时更适合拆成实体。理解关系方向与 JoinTable 才能正确建模。

#
★★

15. @Query/@Modifying 与 clearAutomatically

请说明 @Query、@Modifying 与 clearAutomatically 在 Spring Data JPA 中的使用与语义?

  • @Query 自定义查询
  • @Modifying 的 UPDATE/DELETE
  • clearAutomatically 与 flushAutomatically

@Query 允许在 Repository 方法上定义 JPQL 或原生 SQL 查询。@Modifying 标注 UPDATE/DELETE 类修改操作,它必须配合 @Transactional 使用。clearAutomatically=true 表示执行修改后自动调用 EntityManager.clear(),清空持久化上下文,避免陈旧实体与后续操作冲突;flushAutomatically=true 表示执行前先 flush 挂起的变更,保证修改基于最新数据。这两个开关用于管理持久化上下文与批量修改的一致性。

@Modifying 查询绕过实体生命周期直接操作数据库,可能使持久化上下文中的实体变为陈旧状态,clearAutomatically 用于清理;flushAutomatically 确保先同步本上下文变更。合理组合可避免一致性问题。

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update Account a set a.balance = a.balance + :delta where a.id = :id")
int addBalance(@Param("id") Long id, @Param("delta") BigDecimal delta);
#
★★

16. @Where/@Filter 与软删除

请说明 Hibernate 的 @Where、@Filter 以及软删除的实现方式?

  • @Where 的全局过滤条件
  • @Filter 的动态过滤
  • 软删除(逻辑删除)的实现

@Where 在实体上指定一个固定的 SQL 条件(如 where deleted = 0),该条件会被附加到所有针对该实体的查询,实现软删除的全局过滤。@Filter 定义动态过滤条件,可在运行时通过 Session.enableFilter 传入参数动态启用,适合多租户、数据权限等场景。Hibernate 6 还提供 @SQLRestriction 替代 @Where。软删除是逻辑删除,通过标记字段而非物理 DELETE 实现,便于审计与恢复。

@Where/@Filter 把过滤逻辑下沉到映射层,避免在每个查询中重复写条件,但也要注意它会在所有查询上生效,可能影响 Count 与关联查询,且对原生 SQL 不生效。软删除需注意唯一约束与级联删除的处理。

@SQLRestriction("deleted = false")
@Entity
public class User {
    private boolean deleted;
}
#
★★

17. EntityManager 与 EntityManagerFactory 的生命周期

请说明 EntityManager 与 EntityManagerFactory 的生命周期及线程安全特性?

  • EntityManagerFactory 的创建与关闭
  • EntityManager 的持久化上下文生命周期
  • 线程安全

EntityManagerFactory 是重量级、线程安全的对象,一个应用通常只创建一个,由 Spring 容器管理,应用关闭时随之销毁。EntityManager 是轻量级、线程不安全对象,代表一个持久化上下文,每个事务通常创建一个,事务结束即关闭。Spring Data JPA 通过 @PersistenceContext 注入的 EntityManager 实际是代理,绑定到当前事务的 EntityManager,保证线程内与事务内一致。生命周期管理不当(如 EntityManager 未关闭)会导致资源泄漏。

理解 factory 与应用同生命周期、entityManager 与事务同生命周期,是避免共享 EntityManager 导致线程安全问题与资源泄漏的关键。Spring 通过事务绑定 EntityManager 实现了线程安全与作用域控制。

#
★★

18. Hibernate 6.x 的 @TenantId 与多租户

请说明 Hibernate 6.x 的 @TenantId 与多租户(Multi-tenancy)支持?

  • @TenantId 注解
  • 多租户的三种隔离方式(SCHEMA/DATABASE/DISCRIMINATOR)
  • CurrentTenantIdentifierResolver

Hibernate 6.x 引入 @TenantId 注解,用于标记实体中标识租户的字段,在带租户的实体上自动注入租户标识。多租户支持的隔离方式包括 SCHEMA(每个租户一个 schema)、DATABASE(每个租户一个数据库)、DISCRIMINATOR(共享表,用租户列区分)。通过 MultiTenantConnectionProvider 提供按租户的连接,CurrentTenantIdentifierResolver 解析当前租户标识。Hibernate Reactive 也支持多租户。

多租户的关键是租户标识的解析与连接/查询的隔离。@TenantId 简化了共享表模型下租户字段的维护,自动在插入与查询时填充/过滤租户,避免漏填导致数据串租户。

#
★★

19. Hibernate Statistics(SessionFactory.getStatistics)的应用

请说明 Hibernate Statistics 在性能监控中的应用?

  • SessionFactory.getStatistics
  • 缓存命中率、查询计数
  • 开启 Statistics 的开关

Hibernate 通过 SessionFactory.getStatistics() 暴露统计信息,包括实体加载数、查询执行数、缓存的命中/未命中次数、二级缓存命中率、flush 次数、连接获取数等。需先设置 hibernate.generate_statistics=true 开启。统计可用于分析 N+1 问题(查询次数异常)、二级缓存命中率、批量操作效率等,并可通过 JMX 或日志定期输出。生产环境可开启统计并以低频率采样,辅助性能调优。

Statistics 是量化 Hibernate 行为的工具,帮助定位查询次数暴涨、缓存失效等性能问题。结合缓存命中率与实体加载数,可判断是否需要优化关联加载或缓存策略。

#
★★

20. Hibernate 与 Spring Data 的 JpaRepository 抽象

请说明 Hibernate 与 Spring Data JPA 的 JpaRepository 抽象之间的关系?

  • JpaRepository 与 CrudRepository/PagingAndSortingRepository
  • 方法命名派生查询
  • JpaRepository 委托给 EntityManager

JpaRepository 是 Spring Data JPA 提供的接口,继承自 CrudRepository 与 PagingAndSortingRepository,提供 CRUD、分页、排序等通用方法,并扩展了 JPA 特有方法(如 flush、saveAndFlush、getReferenceById)。方法名派生查询(如 findByUsernameAndAgeGreaterThan)由 Spring Data 解析为 JPQL 查询,底层最终委托给 Hibernate 的 EntityManager 执行。JpaRepository 抽象屏蔽了 JPA 细节,简化开发,同时保留 @Query 等自定义能力。

JpaRepository 是"约定优于配置"的体现,通过方法名自动生成查询,减少样板代码。底层仍由 Hibernate 执行,因此 JPA 的行为(事务、缓存、懒加载)依然适用。理解这一抽象关系有助于合理使用 Repository 与自定义查询。

#
★★

21. Hibernate 的 @DynamicInsert/@DynamicUpdate

请说明 Hibernate 的 @DynamicInsert 与 @DynamicUpdate 的作用与性能影响?

  • @DynamicUpdate 只更新变更字段
  • @DynamicInsert 只插入非空字段
  • 与脏检查的关系

@DynamicInsert 让 INSERT 只包含非空的字段,避免插入大量 NULL;@DynamicUpdate 让 UPDATE 只包含实际发生变化的字段,而不是全部字段。默认 Hibernate 会生成包含所有字段的 UPDATE(只在有变更时),@DynamicUpdate 会基于快照比较生成只含变更字段的动态 SQL。优点是可减少 SQL 体积、利于数据库默认值生效、减少不必要字段更新;缺点是每次动态生成 SQL 增加解析开销,且与批量更新、乐观锁等配合需注意。

动态 SQL 以少量解析开销换取 SQL 精简与数据库默认值/触发器生效。@DynamicUpdate 尤其适合宽表、字段多的场景,但需权衡 SQL 解析成本。它默认关闭,需显式开启。

#
★★

22. Hibernate 的 @Formula/@Generated(计算列)

请说明 Hibernate 的 @Formula 与 @Generated 注解(计算列)的使用?

  • @Formula 只读计算字段
  • @Generated 与数据库默认值/触发器
  • 只读属性

@Formula 在实体上定义一个只读字段,其值由 SQL 表达式计算(如 select 子查询、聚合),该字段不参与持久化,只在查询时通过 SQL 计算填充。@Generated 用于声明字段由数据库生成(如默认值、触发器生成),配合 GenerationTime(如 INSERT/ALWAYS,Hibernate 6.2+ 亦可用 EventType),Hibernate 在 insert/update 后重新查询回填该字段;注意与 @CreationTimestamp/@UpdateTimestamp 等注解区分。两者都用于只读/数据库生成字段,避免在 Java 侧重复计算。

@Formula 是"映射计算列"的便捷方式,但表达式与数据库方言耦合,且对只读字段不能写入。@Generated 适合数据库默认值、时间戳、触发器生成字段,需配置在 insert/update 后重新加载。注意此类字段在实体生命周期中可能为 null,需在查询后读取。

#
★★

23. Hibernate 的 @Source/@Target 与软引用映射

请说明 Hibernate 的 @Source/@Target 注解与软引用映射的用途?

  • @Source/@Target 的映射语义
  • 软引用(SoftReference)映射
  • 应用场景

@Source 用于指定 @CreationTimestamp/@UpdateTimestamp 等自动生成字段值的来源(SourceType.VM 表示取 JVM 当前时间,SourceType.DB 表示由数据库生成当前时间);@Target 用于指定属性的实际目标类型,例如属性声明为接口或抽象类时,指定懒加载时 Hibernate 应实例化的具体类。二者都与注解处理器/元模型生成无关。软引用映射(SoftReference)在 Hibernate 里用于将缓存值以软引用持有,避免强引用导致内存无法回收,主要出现在二级缓存与查询缓存的设计中。二者用途不同。

@Source 指定时间戳等自动生成值的来源(JVM 或数据库),@Target 指定接口/抽象属性懒加载时的具体目标类型;软引用映射属于内存管理优化,软引用在内存紧张时可被 GC 回收,适合缓存大量可重建对象。理解二者区分可避免混淆。

#
★★

24. Hibernate 的 @Subselect 不可变只读映射

请说明 Hibernate 的 @Subselect 注解及其不可变只读映射的用途?

  • @Subselect 定义子查询为实体来源
  • @Immutable 只读
  • 视图/报表场景

@Subselect 将实体映射到一个子查询(SQL SELECT)而非物理表,配合 @Synchronize 与 @Immutable 使用。@Immutable 表示实体不可变,Hibernate 不会对其实施更新或删除,也跳过脏检查。典型场景是关联多表、聚合查询的只读视图或报表实体,用子查询虚拟出一张表,避免手动建数据库视图。因为不可变,此类实体不会进入持久化上下文的脏检查,性能更好。

@Subselect 实现"代码内定义视图",适合只读报表模型,避免 DB 视图对象管理。@Immutable 保证只读不可修改,防止误更新。注意子查询结果没有主键唯一性保证时需谨慎设计标识。

#
★★

25. Hibernate 的 EventListener 与生命周期事件

请说明 Hibernate 的 EventListener 与实体生命周期事件机制?

  • 生命周期回调(@PrePersist/@PostPersist 等)
  • EventListener 机制
  • 应用场景

JPA 提供实体生命周期回调注解(@PrePersist、@PostPersist、@PreUpdate、@PostUpdate、@PreRemove、@PostRemove、@PostLoad),在实体状态变化时触发。Hibernate 还提供更底层的 EventListener 机制(如 PersistEventListener、FlushEventListener、PreInsertEventListener 等),通过 EventListenerRegistry 注册监听器,可对会话级事件进行拦截与扩展。生命周期回调适合填充审计字段、记录日志;EventListener 适合实现全局审计、防盗链、软删除等横切逻辑。

回调注解简单、绑定实体,适合单实体;EventListener 粒度更细、可全局注册,适合跨实体横切逻辑。注意回调中抛异常会影响事务,且回调不应执行耗时操作或额外数据库访问。

#
★★

26. Hibernate 的 HQL 与 JPQL 的演进差异

请说明 Hibernate HQL 与标准 JPQL 之间的演进差异?

  • HQL 与 JPQL 的关系
  • HQL 特有的扩展(Native 函数、hint)
  • 演进方向

HQL(Hibernate Query Language)与 JPQL(Java Persistence Query Language)语法高度相似,HQL 是 Hibernate 对 JPQL 的实现与超集。JPQL 是 JPA 标准,面向实体对象查询;HQL 在 JPQL 基础上扩展了 Hibernate 特有功能,如原生函数调用、SQL 片段、hint 支持、更灵活的查询构造。Hibernate 6 重构了 HQL 解析器,统一了 JPQL 与 HQL,支持更多 SQL 特性(如 with 子句、窗口函数通过 HQL 函数映射)。现代 Hibernate 中基本可视作 JPQL 的增强版。

理解 HQL 与 JPQL 的关系有助于编写可移植的查询。跨 JPA 实现(如 EclipseLink)应使用标准 JPQL,若要使用 Hibernate 特有扩展则用 HQL,以换取与特定实现绑定的代价。

#
★★

27. Hibernate 的 HQL 命名参数与位置参数

请说明 HQL 中命名参数与位置参数的区别与使用?

  • 命名参数(:name)
  • 位置参数(?1)
  • 参数绑定与预编译

HQL 支持命名参数(:paramName)和位置参数(?1 或 ?)。命名参数通过 setParameter("name", value) 绑定,可读性好、避免位置错乱;位置参数通过 setParameter(1, value) 按位置绑定。Hibernate 6 中位置参数统一为 1-based 从 1 开始。命名参数更适合可读性与复用,位置参数更简洁。两者都使用 PreparedStatement 预编译与参数绑定,避免 SQL 注入。

参数绑定是防注入的关键,无论命名还是位置参数都走参数化查询。命名参数在复杂查询中可读性更强,且可重复使用同一个参数名。工程上推荐命名参数。

Query q = em.createQuery("select u from User u where u.name = :name and u.age > :age");
q.setParameter("name", "tom");
q.setParameter("age", 18);
#
★★

28. Hibernate 的 Interceptor/StatementInspector

请说明 Hibernate 的 Interceptor 与 StatementInspector 的作用?

  • Interceptor 接口(onLoad/onSave/onFlushDirty)
  • StatementInspector 的 SQL 改写
  • 区别与场景

Interceptor 是 Hibernate 的回调接口,可在实体加载、保存、更新、删除等生命周期点上插入逻辑(如审计、虚拟字段填值、软删除设置)。StatementInspector 提供了对即将执行的 SQL 进行改写/校验的钩子(inspect 方法返回新 SQL),通常用于权限过滤、多租户 SQL 注入、在 SQL 层追加条件。两者区别:Interceptor 面向实体生命周期回调,StatementInspector 面向底层 SQL 语句改写。

Interceptor 适合实体层面的横切逻辑,StatementInspector 适合 SQL 层面的改写(如强制追加租户/权限过滤条件)。注意 StatementInspector 改写 SQL 需谨慎,避免破坏预编译与可移植性。

#
★★

29. Hibernate 的 MultiLoad 批量加载

请说明 Hibernate 的 MultiLoad 批量加载机制?

  • MultiLoad 与按主键批量加载
  • 与 find 逐个加载的区别
  • 参数配置

Hibernate 的 MultiLoad(Session 的 byMultipleIds 方法)允许按主键批量加载多个实体。相比逐个 find,MultiLoad 会根据实体与批量大小配置,将多个主键合并成一次 SELECT 或用 in 子句批量加载,减少 SQL 次数,并提升一级缓存命中与批量获取效率。它受 hibernate.batch_fetch_size 等相关配置影响,配合二级缓存可显著减少数据库访问。

MultiLoad 是治理"按主键批量查询"场景的性能优化手段,将 N 次查询合并为少数几次。适合批量 ID 查询(如聚合多个查询结果后回表)。注意主键顺序与缓存命中。

#
★★

30. Hibernate 的 NamingStrategy 与物理命名

请说明 Hibernate 的 NamingStrategy 与物理命名(PhysicalNamingStrategy)的关系?

  • ImplicitNamingStrategy 与 PhysicalNamingStrategy
  • 物理命名的转换
  • 命名规范

Hibernate 的命名分两层:ImplicitNamingStrategy 负责在未显式命名时生成默认名称(基于属性名、类名等),PhysicalNamingStrategy 负责最终的物理命名转换(如将 camelCase 转 snake_case、加前缀)。显式指定的 @Table/@Column 名称不经过 ImplicitNamingStrategy,但会经过 PhysicalNamingStrategy 的二次处理。Hibernate 6 默认的 CamelCaseToUnderscoresNamingStrategy 统一转为下划线命名。

物理命名策略统一了数据库命名规范,避免在每个实体上手工写列名。理解显式/隐式命名与物理策略的先后关系,可避免命名被意外改写。自定义策略可实现统一前缀、统一大小写等。

#
★★

31. Hibernate 的 Session.doWork 与 JDBC 直接调用

请说明 Hibernate 的 Session.doWork 用于直接 JDBC 调用的场景?

  • doWork 与 Connection 的使用
  • 与 Hibernate 托管操作的区别
  • 应用场景

Session.doWork(Work) 允许在 Hibernate 会话内获取底层 JDBC Connection,执行 Hibernate 不便表达的原生 SQL、批量操作、临时表、存储过程等。通过 doWork 获取的连接与当前事务绑定,可参与同样的事务边界。相比通过 createNativeQuery 执行,doWork 更底层面,适合需要 Connection 级别的操作(如设置会话变量、调用特殊 JDBC API)。注意 doWork 获取的连接不能被 Hibernate 的持久化上下文感知,需自行管理其操作。

doWork 是 Hibernate 与 JDBC 的桥接,用于数据库依赖性强、标准 API 无法覆盖的场景。它复用当前事务连接,保证一致性,但绕过了 Hibernate 的缓存与脏检查,需谨慎使用。

session.doWork(connection -> {
    try (Statement st = connection.createStatement()) {
        st.execute("UPDATE t SET flag = 1");
    }
});
#
★★

32. Hibernate 的反应式 API(Stage.Session)

请说明 Hibernate Reactive 的 Stage.Session 反应式 API?

  • Stage.Session 与 Mutiny API
  • 反应式事务与 @WithSession/@WithTransaction
  • 与阻塞式 Session 的区别

Hibernate Reactive 提供反应式 API,其中 Stage.Session 是基于 CompletionStage 的 API,适用于编程式反应式调用;另一套是 Mutiny API(Mutiny.Session),基于 Mutiny Uni/Multi。反应式 API 不阻塞线程,通过回调驱动。Stage.Session 提供 find、persist、createQuery 等方法的 CompletionStage 版本,事务通过 withTransaction 或 Hibernate Reactive 的 @WithSession/@WithTransaction 注解管理。反应式 API 与阻塞式 Session 的差异在于不占用线程等待数据库,适合高并发 IO 密集场景。

Stage.Session 是 Hibernate Reactive 的编程式入口之一,与 Mutiny 版本并存。选择取决于项目是否用 Mutiny(Quarkus/Spring WebFlux)。反应式 ORM 需要反应式驱动(如 PostgreSQL reactive driver),且不提供阻塞式 API。

#
★★

33. Hibernate 的批量抓取(@BatchSize)与 N+1 治理

请说明 Hibernate 的 @BatchSize 批量抓取与 N+1 问题的治理?

  • @BatchSize 的批量加载
  • N+1 问题的成因
  • 治理手段(join fetch、EntityGraph、BatchSize)

N+1 问题是指查询 1 个主实体后,访问其关联集合时对每个关联再发起 1 次查询,共产生 N+1 次 SQL。@BatchSize 可以让 Hibernate 在加载一批实体的关联时合并为批量 in 查询,如 @BatchSize(size=20) 表示一次加载 20 个实体的关联。治理 N+1 的手段包括:查询时用 join fetch/@EntityGraph 一次关联加载、二级缓存、@BatchSize 批量预取、以及显式批量查询。@BatchSize 属于"按需批量"策略,能显著减少查询次数且不改变懒加载语义。

N+1 是 ORM 最常见的性能问题。@BatchSize 是副作用较小的优化,适合懒加载集合场景;join fetch 适合确定要加载关联的场景。综合使用三者可有效治理 N+1。

@Entity
public class Order {
    @OneToMany(mappedBy = "order")
    @BatchSize(size = 20)
    private List<OrderItem> items;
}
#
★★

34. Hibernate 的批量操作(hibernate.jdbc.batch_size)

请说明 Hibernate 的 hibernate.jdbc.batch_size 批量操作配置?

  • batch_size 配置与 JDBC 批处理
  • 批量插入/更新
  • 与 order_inserts/order_updates 的配合

hibernate.jdbc.batch_size 设置 Hibernate 在 flush 时批量提交 SQL 的粒度,将多条 INSERT/UPDATE 合并为 JDBC 批量执行,减少往返次数、提升吞吐。配合 hibernate.order_inserts=true 和 hibernate.order_updates=true 可让 Hibernate 按类型分组排序,最大化批处理效率。批量操作需注意 IDENTITY 主键(MySQL 下会影响批处理,多行插入受限)、乐观锁版本号更新、以及一次 flush 的批次控制。批量删除/更新大量数据更推荐直接使用 JPQL/SQL 批量语句。

批处理以"合并往返"换取性能提升,是写入密集场景的关键调优参数。需注意与主键策略、缓存的兼容性,以及避免大批次导致内存与锁开销。真正的大批量更新应绕过持久化上下文,使用批量 DML。

#
★★

35. Hibernate 的自定义类型(UserType)

请说明 Hibernate 的自定义类型 UserType 的实现与用途?

  • UserType 接口
  • 自定义类型与 AttributeConverter 的区别
  • 应用场景

Hibernate 的 UserType 接口允许自定义实体属性与数据库列之间的映射,可控制 JDBC 读写、null 处理、可变性、equals/hashCode、实际 SQL 类型等。相比 JPA 的 AttributeConverter,UserType 更底层、更强大,能实现更复杂的类型(如数组、JSON 特殊结构、自定义 SQL 类型、组合列)。应用场景包括复杂数据结构(JSON、数组、位图)、自定义 SQL 类型、需要特殊 SQL 生成的类型。Hibernate 6 也推荐优先用 AttributeConverter/JavaType/HibernateType 扩展,UserType 用于高级定制。

UserType 提供对映射全过程的精细控制,但实现复杂、需注意线程安全与可变性。对大多数场景 AttributeConverter 更简便;只有在需要定制 SQL 类型、处理复杂结构时才考虑 UserType。

#

36. JPA 3.x 在 Jakarta EE 的演进

请说明 JPA 3.x 在 Jakarta EE 中的演进?

  • javax.persistence 到 jakarta.persistence
  • JPA 3.0/3.1 的变更
  • 与 Hibernate 6 的对应

JPA 3.0 是 Jakarta Persistence 的首次发布,将包名从 javax.persistence 改为 jakarta.persistence,使 Jakarta EE 与 Java EE 解耦。JPA 3.1 增加了 java.util.UUID 与 GenerationType.UUID 主键支持,以及 LOCAL DATE/DATETIME/TIME、EXTRACT、CEILING/EXP/FLOOR/LN/POWER/ROUND/SIGN 等 JPQL 函数与 Criteria API 增强(EntityManager/EntityManagerFactory 也实现了 AutoCloseable)。Hibernate 6.x 是 JPA 3.x 的参考实现,支持 Jakarta Persistence 3.x。JPA 3.x 演进的核心是命名空间迁移与 API 微调,围绕 Jakarta EE 语义化版本策略。

JPA 3.x 的演进是 Jakarta EE 生态的一部分,包名变更影响依赖升级。理解 Hibernate 6 对应 JPA 3.x 有助于在 Spring Boot 3.x(Jakarta)下正确配置。

#

37. JPA 与 Hibernate 的关系、ORM 核心概念

请说明 JPA 与 Hibernate 的关系以及 ORM 的核心概念?

  • JPA 规范与 Hibernate 实现
  • 实体、映射、持久化上下文
  • ORM 的透明持久化

JPA(Jakarta Persistence API)是一套标准规范,定义实体映射、EntityManager、JPQL、事务等接口;Hibernate 是 JPA 最流行的参考实现,同时提供 JPA 之外的扩展能力。ORM 的核心概念包括:实体(Entity)与数据库行的映射、持久化上下文(Persistence Context)管理实体生命周期、脏检查与自动 flush、关系映射、缓存与查询。ORM 的目标是让 Java 对象透明地持久化到关系数据库,屏蔽 SQL 差异。

理解 JPA(规范)与 Hibernate(实现)的关系,是合理选型与迁移基础。JPA 保证可移植性,Hibernate 扩展提供更多性能与功能选项。ORM 的核心是对象-关系映射与生命周期管理。

#

38. JPQL 与原生 SQL 的取舍

请说明 JPQL 与原生 SQL 的取舍?

  • JPQL 的面向对象与可移植性
  • 原生 SQL 的性能与灵活性
  • 选择依据

JPQL 面向实体对象查询,语法与 SQL 类似但操作实体属性,屏蔽数据库差异、可移植性好,并与 ORM 的缓存、一级缓存、懒加载等集成。原生 SQL 直接操作数据库,可充分利用数据库特有语法(窗口函数、方言函数、复杂优化)、性能可控,但失去可移植性且绕过 ORM 的某些映射便利。取舍依据:简单 CRUD 与关联查询用 JPQL;复杂统计、性能敏感、数据库特有功能用原生 SQL。JPQL 无法覆盖的场景可退化为原生 SQL。

可移植性与性能是一对矛盾。JPQL 保证跨数据库一致性,原生 SQL 换取性能与方言能力。工程上通常默认 JPQL,在 SQL 复杂或性能瓶颈时用原生 SQL(配合 @Query nativeQuery)。

#

39. 实体状态(Transient/Persistent/Detached/Removed)

请说明 JPA 的四种实体状态(Transient/Persistent/Detached/Removed)?

  • Transient(瞬时)
  • Persistent(持久化)
  • Detached(游离)

JPA 实体有四种状态:Transient 是 new 出来但未被持久化、没有数据库标识的实体;Persistent 是与持久化上下文关联、有数据库标识的实体,其变更会被脏检查跟踪;Detached 是持久化上下文关闭后脱离管理的实体,其变更不再被跟踪,重新 merge 可回到 Persistent;Removed 是标记删除、在事务提交时执行 DELETE 的实体。状态转换由 EntityManager 的 persist、merge、remove、detach 等操作驱动。

理解实体状态是掌握 JPA 生命周期的基础。状态决定脏检查是否生效、标识是否存在、以及连接到持久化上下文的行为。Detached 实体往往见于跨层传递或 DTO-DO 转换场景。

#

40. Hibernate 6.x 的 SqlExceptionTranslator 与异常体系

请说明 Hibernate 6.x 的 SqlExceptionTranslator 与异常体系?

  • SqlExceptionTranslator 的作用
  • 异常体系(DataAccessException)
  • Spring 的异常转换

SqlExceptionTranslator(Spring 的接口)将 SQLException 转换为 Spring 的 DataAccessException 体系,屏蔽数据库差异。Hibernate 6.x 通过 Spring 的 HibernateJpaDialect 或 SessionFactory 集成,将 Hibernate 的 JDBCException 等转换为 Spring 的 DataAccessException(如 DataIntegrityViolationException、ObjectOptimisticLockingFailureException 等)。Hibernate 自身也有异常体系(如 HibernateException 及其子类),由 spring 统一的 DataAccessException 提供更友好的异常层次,便于上层统一捕获处理。

异常翻译让上层不依赖具体数据库或 ORM 的异常类型。理解 SqlExceptionTranslator 与 Hibernate 异常转换,有助于编写统一的异常处理(如把唯一约束冲突转成业务异常)。

#

41. Hibernate 7 对 JDK 25 虚拟线程与 AOT(JEP 483/514/515)

请说明 Hibernate 7 对 JDK 25 虚拟线程与 AOT(JEP 483/514/515)的支持?

  • 虚拟线程与阻塞式 ORM
  • AOT 编译对 ORM 的挑战
  • Hibernate 7 的原生编译优化

虚拟线程(Virtual Threads,JDK 21 引入,JEP 444)允许阻塞式 JDBC 调用在线程阻塞时释放载体线程,从而提升并发吞吐。Hibernate 7 与虚拟线程协作,使传统阻塞式 ORM 也能在高并发下高效运行,无需改写为反应式。AOT(Ahead-of-Time)编译(JEP 483/514/515,native image 与静态初始化增强)要求 Hibernate 在编译期完成元模型与字节码增强的静态化,减少运行时反射,支持 GraalVM Native Image。Hibernate 7 通过增强型字节码处理与静态元模型支持 AOT 与原生启动。

虚拟线程"扩并发"与 AOT"降启动"是 JDK 演进对 ORM 的两大影响。Hibernate 7 适配虚拟线程以发挥阻塞式 IO 在高并发下的优势,同时通过 AOT 优化适配原生镜像,减少启动时间与内存。

#

42. Hibernate 在 GraalVM Native Image 的边界

请说明 Hibernate 在 GraalVM Native Image 中的边界与限制?

  • 反射与动态代理的限制
  • 字节码增强与静态元模型
  • 需配置的项

GraalVM Native Image 将应用编译为原生可执行文件,默认禁止反射、动态代理、JNI 等运行时动态能力,这对依赖反射与字节码增强的 Hibernate 构成挑战。需要提前配置反射元数据(reflect-config.json)、代理(proxy-config.json)、资源与序列化元数据,或使用 Hibernate 的静态元模型与字节码增强(compile-time enhancement)来减少运行时反射。Hibernate 6/7 提供配合 GraalVM 的配置与支持,但部分功能(如运行时动态 SQL 生成、动态代理)在原生镜像中受限,需在编译期确定元模型。

原生镜像的"封闭世界"假设与 ORM 的动态特性冲突。解决路径是编译期字节码增强与显式元数据配置,代价是灵活性与启动期动态配置的降低。理解边界有助于评估是否采用 Native Image。

#

43. Hibernate 的 ResultTransformer(已弃用)

请说明 Hibernate 的 ResultTransformer 及其弃用原因?

  • ResultTransformer 的用途
  • 弃用原因与替代
  • JPA 的替代方案

ResultTransformer 是 Hibernate 用于将查询结果集转换为自定义对象结构的接口(如 AliasToBeanResultTransformer 将结果转为 DTO)。Hibernate 6 已弃用 ResultTransformer,原因是其处理查询结果与实体的机制复杂、易混淆,且与原生查询结果的类型映射不直观。替代方案:使用 JPQL 的构造器表达式(new DTO(...))、DTO 投影、以及 JPA 的 Tuple 或 @ConstructorResult(SqlResultSetMapping)。Hibernate 6 鼓励使用标准 JPA 投影方式。

ResultTransformer 曾用于 DTO 映射,但弃用后应迁移到 JPA 投影或 Spring Data 的 Projection。理解弃用原因有助于选择当代推荐的 DTO 映射方式。