1. 概述
在使用 Hibernate 构建持久层时,我们经常需要自动生成或填充某些实体属性。这包括分配默认值、生成唯一标识符或应用自定义生成逻辑。
Hibernate 6.2 引入了两个新接口:BeforeExecutionGenerator 和 OnExecutionGenerator,允许我们自动生成实体属性值。这些接口取代了已弃用的 ValueGenerator 接口。
本教程将探讨如何使用这些新接口在 SQL 语句执行前后生成属性值。
2. 应用程序设置
在讨论如何使用新接口生成实体属性值之前,我们先搭建一个简单的应用程序作为演示基础。
2.1. 依赖项
首先,在项目的 pom.xml 文件中添加 Hibernate 依赖:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.2.Final</version>
</dependency>
2.2. 定义实体类和仓库层
现在定义实体类:
@Entity
@Table(name = "wizards")
class Wizard {
@Id
private UUID id;
private String name;
private String house;
// 标准的 setter 和 getter 方法
}
Wizard 类是示例的核心实体,后续将用它演示如何自动生成属性值。
定义实体类后,创建一个继承 JpaRepository 的仓库接口与数据库交互:
@Repository
interface WizardRepository extends JpaRepository<Wizard, UUID> {
}
3. BeforeExecutionGenerator 接口
*BeforeExecutionGenerator* 接口允许在 SQL 语句执行前生成属性值。它包含两个方法:generate() 和 *getEventTypes()*。
以自动为新插入的 Wizard 实体分配随机 house 属性为例,创建实现该接口的自定义生成器:
class SortingHatHouseGenerator implements BeforeExecutionGenerator {
private static final String[] HOUSES = { "Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin" };
private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current();
@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
int houseIndex = RANDOM.nextInt(HOUSES.length);
return HOUSES[houseIndex];
}
@Override
public EnumSet<EventType> getEventTypes() {
return EnumSet.of(EventType.INSERT);
}
}
在 generate() 方法中,随机选择一个霍格沃茨学院并返回。getEventTypes() 方法指定 EventType.INSERT,表示仅在创建新实体时应用此生成器。
要使用 SortingHatHouseGenerator,需创建一个自定义注解并用 @ValueGenerationType 进行元注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ValueGenerationType(generatedBy = SortingHatHouseGenerator.class)
@interface SortHouse {
}
现在将自定义 @SortHouse 注解应用到 Wizard 实体的 house 属性:
@SortHouse
private String house;
*保存新 Wizard 实体时,生成器会自动分配随机 house:*
Wizard wizard = new Wizard();
wizard.setId(UUID.randomUUID());
wizard.setName(RandomString.make());
Wizard savedWizard = wizardRepository.save(wizard);
assertThat(savedWizard.getHouse())
.isNotBlank()
.isIn("Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin");
⚠️ 如需在 generate() 方法中访问实体实例或当前字段值,可将 owner 和 currentValue 参数转换为所需类型。
4. OnExecutionGenerator 接口
*OnExecutionGenerator* 接口允许在 SQL 语句执行期间生成属性值。当需要数据库生成值时特别有用。
该接口比 BeforeExecutionGenerator 包含更多方法。
以添加 updatedAt 属性为例,在插入或更新 Wizard 实体时自动设置为当前时间戳:
class UpdatedAtGenerator implements OnExecutionGenerator {
@Override
public boolean referenceColumnsInSql(Dialect dialect) {
return true;
}
@Override
public boolean writePropertyValue() {
return false;
}
@Override
public String[] getReferencedColumnValues(Dialect dialect) {
return new String[] { dialect.currentTimestamp() };
}
@Override
public EnumSet<EventType> getEventTypes() {
return EnumSet.of(EventType.INSERT, EventType.UPDATE);
}
}
在 getReferencedColumnValues() 方法中,返回包含生成当前时间戳的内置 SQL 函数数组。这引用了 JPQL 的 current_timestamp 函数。
referenceColumnsInSql() 方法返回 true,表示 updatedAt 属性应包含在 SQL 语句的列列表中。
writePropertyValue() 方法设为 false,确保不传递 JDBC 参数值(因为由数据库生成)。
为 UpdatedAtGenerator 创建自定义注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ValueGenerationType(generatedBy = UpdatedAtGenerator.class)
@interface GenerateUpdatedAtTimestamp {
}
将 @GenerateUpdatedAtTimestamp 应用到 Wizard 实体的新属性:
@GenerateUpdatedAtTimestamp
private LocalDateTime updatedAt;
保存或更新 Wizard 实体时,数据库自动设置 updatedAt 为当前时间戳:
Wizard savedWizard = wizardRepository.save(wizard);
LocalDateTime initialUpdatedAtTimestamp = savedWizard.getUpdatedAt();
savedWizard.setName(RandomString.make());
Wizard updatedWizard = wizardRepository.save(savedWizard);
assertThat(updatedWizard.getUpdatedAt())
.isAfter(initialUpdatedAtTimestamp);
*OnExecutionGenerator* 也可基于自定义 SQL 表达式生成值。添加 spellPower 属性,根据创建日期计算其值:
class SpellPowerGenerator implements OnExecutionGenerator {
// ... 其他方法同上
@Override
public String[] getReferencedColumnValues(Dialect dialect) {
String sql = "50 + (EXTRACT(DAY FROM CURRENT_DATE) % 30) * 2";
return new String[] { sql };
}
}
为 SpellPowerGenerator 创建对应的 @GenerateSpellPower 注解并应用到新属性:
@GenerateSpellPower
private Integer spellPower;
创建新 Wizard 实体时,定义的 SQL 表达式自动设置 spellPower 值:
Wizard savedWizard = wizardRepository.save(wizard);
assertThat(savedWizard.getSpellPower())
.isNotNull()
.isGreaterThanOrEqualTo(50);
5. 总结
本文探讨了 Hibernate 中 BeforeExecutionGenerator 和 OnExecutionGenerator 接口,用于自动生成实体属性值。
✅ 使用场景对比:
- BeforeExecutionGenerator:在 SQL 执行前生成值,适合需要 Java 逻辑的场景(如随机分配学院)
- OnExecutionGenerator:在 SQL 执行期间生成值,适合利用数据库函数和 SQL 表达式(如时间戳和魔法值计算)
选择依据取决于生成逻辑更适合 Java 代码还是数据库操作。踩坑点:混淆两者可能导致生成时机不符合预期。
本文所有代码示例可在 GitHub 获取。