1. 概述

在这个教程中,我们将学习如何在Hibernate 6中使用添加的布尔类型转换器来映射域模型中的布尔属性。Hibernate 6对类型系统进行了全面更新,因此移除了之前用于表示布尔值的一些现有类。

2. 模型

为了展示如何在Hibernate 6中使用布尔转换器,我们将在测试中使用H2数据库。首先,我们通过SQL脚本创建一个数据库表:

CREATE TABLE Question (
    id UUID,
    content VARCHAR,
    correctAnswer CHAR,
    shouldBeAsked CHAR,
    isEasy TINYINT,
    wasAskedBefore CHAR,
    PRIMARY KEY (id)
)

然后,在Java中,我们将这个表映射到Question实体类:

@Entity
public class Question {
    @Id
    private UUID id;
    private String content;
    private Boolean correctAnswer;
    private Boolean shouldBeAsked;
    private Boolean isEasy;
    private Boolean wasAskedBefore;

    public Question() {
    }

    // standard setters and getters
}

3. 使用YesNoConverter映射YN

通常情况下,大写字母YN分别表示真和假的值。以前,我们可以通过在字段上注解@Type并指定YesNoBooleanType来实现这一点:

// the old way
@Type(type = "org.hibernate.type.YesNoBooleanType")
private Boolean someBoolean;

Hibernate 6将YesNoBooleanType替换为YesNoConverter由于它是一个转换器,我们需要使用@Convert而不是@Type来应用它。

让我们把这个新的转换器应用到QuestioncorrectAnswer字段上:

@Convert(converter = YesNoConverter.class)
private Boolean correctAnswer;

现在,让我们验证YN是否被正确映射:

@Test
void whenFieldAnnotatedWithYesNoConverter_ThenConversionWorks() {
    session.beginTransaction();
    UUID likeJavaQuestionId = UUID.randomUUID();
    UUID sydneyCapitalOfAustraliaQuestionId = UUID.randomUUID();
    session.persist(new QuestionBuilder().id(likeJavaQuestionId)
      .content("Do you like Java?")
      .correctAnswer(true)
      .build());
    session.persist(new QuestionBuilder().id(sydneyCapitalOfAustraliaQuestionId)
      .content("Is Sydney the capital of Australia?")
      .correctAnswer(false)
      .build());
    session.flush();

    char likeJavaQuestionCorrectAnswerDbValue = session.createNativeQuery(format("SELECT correctAnswer FROM Question WHERE id='%s'", likeJavaQuestionId), Character.class)
      .getSingleResult();
    char sydneyCapitalOfAustraliaQuestionCorrectAnswerDbValue = session.createNativeQuery(format("SELECT correctAnswer FROM Question WHERE id='%s'", sydneyCapitalOfAustraliaQuestionId), Character.class)
      .getSingleResult();
    session.close();

    assertEquals('Y', likeJavaQuestionCorrectAnswerDbValue);
    assertEquals('N', sydneyCapitalOfAustraliaQuestionCorrectAnswerDbValue);
}

4. 使用TrueFalseConverter映射TF

类似地,我们可以使用大写字母TF来表示布尔值。Hibernate 6去除了TrueFalseBooleanType

// old way
@Type(type = "org.hibernate.type.TrueFalseBooleanType")
private Boolean someBoolean;

作为替代,我们将使用TrueFalseConverter来指定这种映射。让我们为shouldBeAsked注册它:

@Convert(converter = TrueFalseConverter.class)
private Boolean shouldBeAsked;

让我们测试转换:

@Test
void whenFieldAnnotatedWithTrueFalseConverter_ThenConversionWorks() {
    session.beginTransaction();
    UUID codeTestedQuestionId = UUID.randomUUID();
    UUID earningsQuestionId = UUID.randomUUID();
    session.persist(new QuestionBuilder().id(codeTestedQuestionId)
      .content("Is this code tested?")
      .shouldBeAsked(true)
      .build());
    session.persist(new QuestionBuilder().id(earningsQuestionId)
      .content("How much do you earn?")
      .shouldBeAsked(false)
      .build());
    session.flush();

    char codeTestedQuestionShouldBeAskedDbValue = session.createNativeQuery(format("SELECT shouldBeAsked FROM Question WHERE id='%s'", codeTestedQuestionId), Character.class)
      .getSingleResult();
    char earningsQuestionsShouldBeAskedDbValue = session.createNativeQuery(format("SELECT shouldBeAsked FROM Question WHERE id='%s'", earningsQuestionId), Character.class)
      .getSingleResult();
    session.close();

    assertEquals('T', codeTestedQuestionShouldBeAskedDbValue);
    assertEquals('F', earningsQuestionsShouldBeAskedDbValue);
}

5. 使用NumericBooleanConverter映射01

最后,字母并不是表示布尔值的唯一方式。人们也经常用整数01来表示。过去,我们会使用NumericBooleanType来表示这种情况:

// old way
@Type(type = "org.hibernate.type.NumericBooleanType")
private Boolean someBoolean;

相反,我们应该使用新的NumericBooleanConverter我们将零或一的值映射到isEasy

@Convert(converter = NumericBooleanConverter.class)
private Boolean isEasy;

像往常一样,让我们检查它是否按预期工作:

@Test
void whenFieldAnnotatedWithNumericBooleanConverter_ThenConversionWorks() {
    session.beginTransaction();
    UUID earthFlatQuestionId = UUID.randomUUID();
    UUID shouldLearnProgrammingQuestionId = UUID.randomUUID();
    session.persist(new QuestionBuilder().id(earthFlatQuestionId)
      .content("Is the Earth flat?")
      .isEasy(true)
      .build());
    session.persist(new QuestionBuilder().id(shouldLearnProgrammingQuestionId)
      .content("Should one learn programming")
      .isEasy(false)
      .build());
    session.flush();

    int earthFlatQuestionIsEasyDbValue = session.createNativeQuery(format("SELECT isEasy FROM Question WHERE id='%s'", earthFlatQuestionId), Integer.class)
      .getSingleResult();
    int shouldLearnProgrammingQuestionIsEasyDbValue = session.createNativeQuery(format("SELECT isEasy FROM Question WHERE id='%s'", shouldLearnProgrammingQuestionId), Integer.class)
      .getSingleResult();
    session.close();

    assertEquals(1, earthFlatQuestionIsEasyDbValue);
    assertEquals(0, shouldLearnProgrammingQuestionIsEasyDbValue);
}

6. 使用@ConverterRegistration指定默认转换器

为每个布尔字段都添加转换器既繁琐又容易出错。在大多数情况下,我们在数据库中只有一种方式表示布尔值。

不出所料,Hibernate提供的布尔转换器默认不会自动应用。然而,Hibernate 6.1引入了@ConverterRegistration,我们可以使用它来自动应用现有的转换器。

为此,让我们在包含Question类的包中添加一个package-info.java文件,并使YesNoConverter默认应用:

@ConverterRegistration(converter = YesNoConverter.class)
package com.baeldung.hibernate.booleanconverters.model;

import org.hibernate.annotations.ConverterRegistration;
import org.hibernate.type.YesNoConverter;

我们没有设置@ConverterRegistrationautoApply,因为它默认为trueQuestion实体包含布尔字段wasAskedBefore,因为我们没有为其标注任何转换器,所以Hibernate会使用YesNoConverter来映射它:

@Test
void givenConverterRegisteredToAutoApply_whenFieldIsNotAnnotated_ThenConversionWorks() {
    session.beginTransaction();
    UUID likeJavaQuestionId = UUID.randomUUID();
    UUID likeKotlinQuestionId = UUID.randomUUID();
    session.persist(new QuestionBuilder().id(likeJavaQuestionId)
      .content("Do you like Java?")
      .wasAskedBefore(true)
      .build());
    session.persist(new QuestionBuilder().id(likeKotlinQuestionId)
      .content("Do you like Kotlin?")
      .wasAskedBefore(false)
      .build());
    session.flush();

    char likeJavaQuestionWasAskedBeforeDbValue = session.createNativeQuery(format("SELECT wasAskedBefore FROM Question WHERE id='%s'", likeJavaQuestionId), Character.class)
      .getSingleResult();
    char likeKotlinQuestionWasAskedBeforeDbValue = session.createNativeQuery(format("SELECT wasAskedBefore FROM Question WHERE id='%s'", likeKotlinQuestionId), Character.class)
      .getSingleResult();
    session.close();

    assertEquals('Y', likeJavaQuestionWasAskedBeforeDbValue);
    assertEquals('N', likeKotlinQuestionWasAskedBeforeDbValue);
}

7. 注意事项

当我们开始使用这些新转换器时,可能会发现Hibernate无法正确填充我们的布尔字段。这可能是因为**YesNoConverterTrueFalseConverter只支持大写字母。小写字母会被无声地映射为null**:

@Test
void givenFieldAnnotatedWithYesNoConverter_WhenDbValueIsLowercase_ThenDomainModelValueNull() {
    session.beginTransaction();
    UUID mappedToNullQuestionId = UUID.randomUUID();
    UUID behaviorIntuitiveQuestionId = UUID.randomUUID();
    session.createNativeMutationQuery(format("INSERT INTO Question (id, content, correctAnswer) VALUES ('%s', 'Will correctAnswer be mapped to null?', 'y')", mappedToNullQuestionId))
      .executeUpdate();
    session.createNativeMutationQuery(format("INSERT INTO Question (id, content, correctAnswer) VALUES ('%s', 'Is this behavior intuitive?', 'n')", behaviorIntuitiveQuestionId))
      .executeUpdate();

    Question behaviorIntuitiveQuestion = session.get(Question.class, behaviorIntuitiveQuestionId);
    Question mappedToNullQuestion = session.get(Question.class, mappedToNullQuestionId);
    session.close();

    assertNull(behaviorIntuitiveQuestion.getCorrectAnswer());
    assertNull(mappedToNullQuestion.getCorrectAnswer());
}

8. 使用JPA AttributeConverter映射不同的表示方式

在数据库中,我们可能会遇到各种各样的布尔表示方式。如果默认的Hibernate转换器都不能满足我们的需求,我们需要自己定义映射逻辑。我们可以通过创建一个JPA属性转换器轻松做到这一点。

9. 总结

在这篇文章中,我们学习了如何在Hibernate 6中处理布尔值。

标准的布尔类型被转换器取代。因此,我们需要使用@Convert而不是@Type

此外,我们需要注意的是**YesNoConverterTrueFalseConverter不支持小写字母。为了减少重复代码,我们应该使用@ConverterRegistration来自动应用转换器**。

我们看到的Hibernate转换器都是JPA AttributeConverter的实现。如果它们都不适合我们的需求,我们可以编写自己的实现,并以相同的方式使用它。

如往常一样,本文的所有完整代码示例可在GitHub上找到。