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
映射Y
和N
通常情况下,大写字母Y
和N
分别表示真和假的值。以前,我们可以通过在字段上注解@Type
并指定YesNoBooleanType
来实现这一点:
// the old way
@Type(type = "org.hibernate.type.YesNoBooleanType")
private Boolean someBoolean;
Hibernate 6将YesNoBooleanType
替换为YesNoConverter
。由于它是一个转换器,我们需要使用@Convert
而不是@Type
来应用它。
让我们把这个新的转换器应用到Question
的correctAnswer
字段上:
@Convert(converter = YesNoConverter.class)
private Boolean correctAnswer;
现在,让我们验证Y
和N
是否被正确映射:
@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
映射T
和F
类似地,我们可以使用大写字母T
和F
来表示布尔值。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
映射0
和1
最后,字母并不是表示布尔值的唯一方式。人们也经常用整数0
和1
来表示。过去,我们会使用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;
我们没有设置@ConverterRegistration
的autoApply
,因为它默认为true
。Question
实体包含布尔字段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无法正确填充我们的布尔字段。这可能是因为**YesNoConverter
和TrueFalseConverter
只支持大写字母。小写字母会被无声地映射为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
。
此外,我们需要注意的是**YesNoConverter
和TrueFalseConverter
不支持小写字母。为了减少重复代码,我们应该使用@ConverterRegistration
来自动应用转换器**。
我们看到的Hibernate转换器都是JPA AttributeConverter
的实现。如果它们都不适合我们的需求,我们可以编写自己的实现,并以相同的方式使用它。
如往常一样,本文的所有完整代码示例可在GitHub上找到。