1. 概述

在使用 Hibernate 构建持久层时,我们经常需要自动生成或填充某些实体属性。这包括分配默认值、生成唯一标识符或应用自定义生成逻辑。

Hibernate 6.2 引入了两个新接口BeforeExecutionGeneratorOnExecutionGenerator,允许我们自动生成实体属性值。这些接口取代了已弃用的 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() 方法中访问实体实例或当前字段值,可将 ownercurrentValue 参数转换为所需类型。

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 函数数组。这引用了 JPQLcurrent_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 中 BeforeExecutionGeneratorOnExecutionGenerator 接口,用于自动生成实体属性值。

使用场景对比:

  • BeforeExecutionGenerator:在 SQL 执行前生成值,适合需要 Java 逻辑的场景(如随机分配学院)
  • OnExecutionGenerator:在 SQL 执行期间生成值,适合利用数据库函数和 SQL 表达式(如时间戳和魔法值计算)

选择依据取决于生成逻辑更适合 Java 代码还是数据库操作。踩坑点:混淆两者可能导致生成时机不符合预期。

本文所有代码示例可在 GitHub 获取。


原始标题:Generate Values for Entity Attributes in Hibernate | Baeldung