Custom Types in Hibernate and the @Type Annotation – Hibernate中的自定义类型和@类型注解

最后修改: 2018年 10月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Hibernate simplifies data handling between SQL and JDBC by mapping the Object-Oriented model in Java with the Relational model in Databases. Although mapping of basic Java classes is in-built in Hibernate, mapping of custom types is often complex.

Hibernate通过映射Java中的面向对象模型和数据库中的关系模型,简化了SQL和JDBC之间的数据处理。虽然Hibernate中内置了基本的Java类的映射,但自定义类型的映射往往很复杂。

In this tutorial, we’ll see how Hibernate allows us to extend the basic type mapping to custom Java classes. In addition to that, we’ll also see some common examples of custom types and implement them using Hibernate’s type mapping mechanism.

在本教程中,我们将看到Hibernate如何允许我们将基本的类型映射扩展到自定义Java类。除此之外,我们还将看到一些常见的自定义类型的例子,并使用Hibernate的类型映射机制实现它们。

2. Hibernate Mapping Types

2.Hibernate映射类型

Hibernate uses mapping types for converting Java objects into SQL queries for storing data. Similarly, it uses mapping types for converting SQL ResultSet into Java objects while retrieving data.

Hibernate使用映射类型将Java对象转换为SQL查询来存储数据。同样地,在检索数据时,它也使用映射类型将SQL ResultSet转换为Java对象。

Generally, Hibernate categorizes the types into Entity Types and Value Types. Specifically, Entity types are used to map domain-specific Java entities and hence, exist independently of other types in the application. In contrast, Value Types are used to map data objects instead and are almost always owned by the Entities.

一般来说,Hibernate将类型划分为实体类型和价值类型具体而言,Entity类型用于映射特定领域的Java实体,因此,在应用程序中独立于其他类型而存在。相比之下,价值类型则用于映射数据对象,并且几乎总是由实体拥有。

In this tutorial, we will focus on the mapping of Value types which are further classified into:

在本教程中,我们将重点讨论价值类型的映射,这些价值类型又被进一步分类为。

  • Basic Types – Mapping for basic Java types
  • Embeddable – Mapping for composite java types/POJO’s
  • Collections – Mapping for a collection of basic and composite java type

3. Maven Dependencies

3.Maven的依赖性

To create our custom Hibernate types, we’ll need the hibernate-core dependency:

要创建我们的自定义Hibernate类型,我们需要hibernate-core依赖性。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.7.Final</version>
</dependency>

4. Custom Types in Hibernate

4.Hibernate中的自定义类型

We can use Hibernate basic mapping types for most user domains. However, there are many use cases, where we need to implement a custom type.

我们可以为大多数用户领域使用Hibernate基本映射类型。然而,在很多用例中,我们需要实现一个自定义类型。

Hibernate makes it relatively easier to implement custom types. There are three approaches to implementing a custom type in Hibernate. Let’s discuss each of them in detail.

Hibernate让实现自定义类型变得相对容易。在Hibernate中,有三种方法来实现自定义类型。让我们来详细讨论每一种。

4.1. Implementing BasicType

4.1.实现BasicType

We can create a custom basic type by implementing Hibernate’s BasicType or one of its specific implementations, AbstractSingleColumnStandardBasicType.

我们可以通过实现Hibernate的BasicType或其特定实现之一AbstractSingleColumnStandardBasicType来创建一个自定义基本类型。

Before we implement our first custom type, let’s see a common use case for implementing a basic type. Suppose we have to work with a legacy database, that stores dates as VARCHAR. Normally, Hibernate would map this to String Java type. Thereby, making date validation harder for application developers. 

在我们实现第一个自定义类型之前,让我们看看实现基本类型的一个常见用例。假设我们必须使用一个传统的数据库,它将日期存储为VARCHAR。通常情况下,Hibernate会将其映射为StringJava类型。因此,对于应用程序开发人员来说,日期验证变得更加困难。

So let’s implement our LocalDateString type, which stores LocalDate Java type as VARCHAR:

所以让我们实现我们的LocalDateString类型,它将LocalDateJava类型存储为VARCHAR。

public class LocalDateStringType 
  extends AbstractSingleColumnStandardBasicType<LocalDate> {

    public static final LocalDateStringType INSTANCE = new LocalDateStringType();

    public LocalDateStringType() {
        super(VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
        return "LocalDateString";
    }
}

The most important thing in this code is the constructor parameters. First, is an instance of SqlTypeDescriptor, which is Hibernate’s SQL type representation, which is VARCHAR for our example. And, the second argument is an instance of JavaTypeDescriptor which represents Java type.

这段代码中最重要的是构造函数的参数。首先,是一个SqlTypeDescriptor的实例,它是Hibernate的SQL类型表示,在我们的例子中是VARCHAR。第二个参数是JavaTypeDescriptor的一个实例,代表Java类型。

Now, we can implement a LocalDateStringJavaDescriptor for storing and retrieving LocalDate as VARCHAR:

现在,我们可以实现一个LocalDateStringJavaDescriptor来存储和检索LocalDate为VARCHAR:

public class LocalDateStringJavaDescriptor extends AbstractTypeDescriptor<LocalDate> {

    public static final LocalDateStringJavaDescriptor INSTANCE = 
      new LocalDateStringJavaDescriptor();

    public LocalDateStringJavaDescriptor() {
        super(LocalDate.class, ImmutableMutabilityPlan.INSTANCE);
    }
	
    // other methods
}

Next, we need to override wrap and unwrap methods for converting the Java type into SQL. Let’s start with the unwrap:

接下来,我们需要覆盖wrapunwrap方法,将Java类型转换为SQL。让我们从unwrap:开始吧

@Override
public <X> X unwrap(LocalDate value, Class<X> type, WrapperOptions options) {

    if (value == null)
        return null;

    if (String.class.isAssignableFrom(type))
        return (X) LocalDateType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

Next, the wrap method:

接下来是wrap方法。

@Override
public <X> LocalDate wrap(X value, WrapperOptions options) {
    if (value == null)
        return null;

    if(String.class.isInstance(value))
        return LocalDate.from(LocalDateType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

unwrap() is called during PreparedStatement binding to convert LocalDate to a String type, which is mapped to VARCHAR. Likewise, wrap() is called during ResultSet retrieval to convert String to a Java LocalDate.

unwrap() PreparedStatement 绑定期间被调用,以将LocalDate 转换为一个字符串类型,它被映射为VARCHAR。同样地,wrap() ResultSet 检索期间被调用,以将String 转换为Java LocalDate

Finally, we can use our custom type in our Entity class:

最后,我们可以在实体类中使用我们的自定义类型。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Column
    @Type(type = "com.baeldung.hibernate.customtypes.LocalDateStringType")
    private LocalDate dateOfJoining;

    // other fields and methods
}

Later, we’ll see how we can register this type in Hibernate. And as a result, refer to this type using the registration key instead of the fully qualified class name.

稍后,我们将看到我们如何在Hibernate中注册这个类型。因此,使用注册密钥而不是完全合格的类名来指代这个类型。

4.2. Implementing UserType

4.2.实现UserType

With the variety of basic types in Hibernate, it is very rare that we need to implement a custom basic type. In contrast, a more typical use case is to map a complex Java domain object to the database. Such domain objects are generally stored in multiple database columns.

由于Hibernate中基本类型的多样性,我们很少需要实现一个自定义的基本类型。相比之下,更典型的用例是将一个复杂的Java领域对象映射到数据库中。这样的领域对象通常被存储在多个数据库列中。

So let’s implement a complex PhoneNumber object by implementing UserType:

因此,让我们通过实现UserType:来实现一个复杂的PhoneNumber对象。

public class PhoneNumberType implements UserType {
    @Override
    public int[] sqlTypes() {
        return new int[]{Types.INTEGER, Types.INTEGER, Types.INTEGER};
    }

    @Override
    public Class returnedClass() {
        return PhoneNumber.class;
    }

    // other methods
}	

Here, the overridden sqlTypes method returns the SQL types of fields, in the same order as they are declared in our PhoneNumber class. Similarly, the returnedClass method returns our PhoneNumber Java type.

在这里,被重载的sqlTypes方法返回字段的SQL类型,其顺序与在我们的PhoneNumber类中声明的相同。同样地,returnedClass方法返回我们的PhoneNumberJava类型。

The only thing left to do is to implement the methods to convert between Java type and SQL type, as we did for our BasicType.

唯一要做的是实现Java类型和SQL类型之间的转换方法,就像我们为BasicType做的那样。

First, the nullSafeGet method:

首先是nullSafeGet方法。

@Override
public Object nullSafeGet(ResultSet rs, String[] names, 
  SharedSessionContractImplementor session, Object owner) 
  throws HibernateException, SQLException {
    int countryCode = rs.getInt(names[0]);

    if (rs.wasNull())
        return null;

    int cityCode = rs.getInt(names[1]);
    int number = rs.getInt(names[2]);
    PhoneNumber employeeNumber = new PhoneNumber(countryCode, cityCode, number);

    return employeeNumber;
}

Next, the nullSafeSet method:

接下来是nullSafeSet方法。

@Override
public void nullSafeSet(PreparedStatement st, Object value, 
  int index, SharedSessionContractImplementor session) 
  throws HibernateException, SQLException {

    if (Objects.isNull(value)) {
        st.setNull(index, Types.INTEGER);
        st.setNull(index + 1, Types.INTEGER);
        st.setNull(index + 2, Types.INTEGER);
    } else {
        PhoneNumber employeeNumber = (PhoneNumber) value;
        st.setInt(index,employeeNumber.getCountryCode());
        st.setInt(index+1,employeeNumber.getCityCode());
        st.setInt(index+2,employeeNumber.getNumber());
    }
}

Finally, we can declare our custom PhoneNumberType in our OfficeEmployee entity class:

最后,我们可以在OfficeEmployee实体类中声明我们的自定义PhoneNumberType

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Columns(columns = { @Column(name = "country_code"), 
      @Column(name = "city_code"), @Column(name = "number") })
    @Type(type = "com.baeldung.hibernate.customtypes.PhoneNumberType")
    private PhoneNumber employeeNumber;
	
    // other fields and methods
}

4.3. Implementing CompositeUserType

4.3.实现CompositeUserType

Implementing UserType works well for straightforward types. However, mapping complex Java types (with Collections and Cascaded composite types) need more sophistication. Hibernate allows us to map such types by implementing the CompositeUserType interface.

实现UserType对于简单的类型来说效果不错。然而,映射复杂的 Java 类型(使用集合和级联复合类型)需要更多的复杂性。Hibernate允许我们通过实现CompositeUserType 接口来映射此类类型。

So, let’s see this in action by implementing an AddressType for the OfficeEmployee entity we used earlier:

所以,让我们通过为我们之前使用的OfficeEmployee实体实现一个AddressType来看看这个操作。

public class AddressType implements CompositeUserType {

    @Override
    public String[] getPropertyNames() {
        return new String[] { "addressLine1", "addressLine2", 
          "city", "country", "zipcode" };
    }

    @Override
    public Type[] getPropertyTypes() {
        return new Type[] { StringType.INSTANCE, 
          StringType.INSTANCE, 
          StringType.INSTANCE, 
          StringType.INSTANCE, 
          IntegerType.INSTANCE };
    }

    // other methods
}

Contrary to UserTypes, which maps the index of the type properties, CompositeType maps property names of our Address class. More importantly, the getPropertyType method returns the mapping types for each property.

与映射类型属性索引的UserTypes相反,CompositeType映射了我们的Address类的属性名称。更重要的是,getPropertyType方法返回每个属性的映射类型。

Additionally, we also need to implement getPropertyValue and setPropertyValue methods for mapping PreparedStatement and ResultSet indexes to type property. As an example, consider getPropertyValue for our AddressType:

此外,我们还需要实现getPropertyValuesetPropertyValue方法,以将PreparedStatementResultSet索引映射到类型属性。作为一个例子,考虑为我们的AddressType的getPropertyValue

@Override
public Object getPropertyValue(Object component, int property) throws HibernateException {

    Address empAdd = (Address) component;

    switch (property) {
    case 0:
        return empAdd.getAddressLine1();
    case 1:
        return empAdd.getAddressLine2();
    case 2:
        return empAdd.getCity();
    case 3:
        return empAdd.getCountry();
    case 4:
        return Integer.valueOf(empAdd.getZipCode());
    }

    throw new IllegalArgumentException(property + " is an invalid property index for class type "
      + component.getClass().getName());
}

Finally, we would need to implement nullSafeGet and nullSafeSet methods for conversion between Java and SQL types. This is similar to what we did earlier in our PhoneNumberType.

最后,我们需要实现nullSafeGetnullSafeSet方法来实现Java和SQL类型之间的转换。这与我们之前在PhoneNumberType.中所做的相似。

Please note that CompositeType‘s are generally implemented as an alternative mapping mechanism to Embeddable types.

请注意,CompositeType‘s 通常是作为Embeddable types 的替代映射机制来实现的。

4.4. Type Parameterization

4.4.类型参数化

Besides creating custom types, Hibernate also allows us to alter the behavior of types based on parameters.

除了创建自定义类型,Hibernate还允许我们根据参数来改变类型的行为。

For instance, suppose that we need to store the Salary of our OfficeEmployee. More importantly, the application must convert the salary amount into geographical local currency amount.

例如,假设我们需要存储我们的OfficeEmployee的Salary更重要的是,应用程序必须将工资数额转换成地理上的当地货币数额。

So, let’s implement our parameterized SalaryType which accepts currency as a parameter:

所以,让我们实现我们的参数化SalaryType ,它接受currency 作为参数。

public class SalaryType implements CompositeUserType, DynamicParameterizedType {

    private String localCurrency;
	
    @Override
    public void setParameterValues(Properties parameters) {
        this.localCurrency = parameters.getProperty("currency");
    }
	
    // other method implementations from CompositeUserType
}

Please note that we have skipped the CompositeUserType methods from our example to focus on parameterization. Here, we simply implemented Hibernate’s DynamicParameterizedType, and overridden the setParameterValues() method. Now, the SalaryType accepts a currency parameter and will convert any amount before storing it.

请注意,我们跳过了例子中的CompositeUserType方法,以专注于参数化。在这里,我们简单地实现了 Hibernate 的 DynamicParameterizedType,并覆盖了 setParameterValues() 方法。现在,SalaryType接受了一个currency参数,并将在存储之前转换任何金额。

We’ll pass the currency as a parameter while declaring the Salary:

我们将在声明工资:时,将货币作为一个参数传递。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Type(type = "com.baeldung.hibernate.customtypes.SalaryType", 
      parameters = { @Parameter(name = "currency", value = "USD") })
    @Columns(columns = { @Column(name = "amount"), @Column(name = "currency") })
    private Salary salary;

    // other fields and methods
}

5. Basic Type Registry

5.基本类型注册表

Hibernate maintains the mapping of all in-built basic types in the BasicTypeRegistry. Thus, eliminating the need to annotate mapping information for such types.

Hibernate在BasicTypeRegistry中维护所有内置的基本类型的映射。因此,不需要为这些类型注释映射信息了。

Additionally, Hibernate allows us to register custom types, just like basic types, in the BasicTypeRegistry. Normally, applications would register custom types while bootstrapping the SessionFactory. Let’s understand this by registering the LocalDateString type we implemented earlier:

此外,Hibernate允许我们在BasicTypeRegistry中注册自定义类型,就像基本类型一样。通常情况下,应用程序会在引导SessionFactory的时候注册自定义类型。让我们通过注册我们之前实现的LocalDateString类型来理解这一点。

private static SessionFactory makeSessionFactory() {
    ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
      .applySettings(getProperties()).build();
														  
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
      .addAnnotatedClass(OfficeEmployee.class)
      .getMetadataBuilder()
      .applyBasicType(LocalDateStringType.INSTANCE)
      .build();
														  
    return metadata.buildSessionFactory()
}

private static Properties getProperties() {
    // return hibernate properties
}

Thus, it takes away the limitation of using the fully qualified class name in Type mapping:

因此,它消除了在类型映射中使用完全限定类名的限制:

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Column
    @Type(type = "LocalDateString")
    private LocalDate dateOfJoining;
	
    // other methods
}

Here, LocalDateString is the key to which the LocalDateStringType is mapped.

这里,LocalDateStringLocalDateStringType被映射到的键。

Alternatively, we can skip Type registration by defining TypeDefs:

另外,我们可以通过定义TypeDefs:跳过类型注册。

@TypeDef(name = "PhoneNumber", typeClass = PhoneNumberType.class, 
  defaultForType = PhoneNumber.class)
@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Columns(columns = {@Column(name = "country_code"),
    @Column(name = "city_code"),
    @Column(name = "number")})
    private PhoneNumber employeeNumber;
	
    // other methods
}

6. Conclusion

6.结语

In this tutorial, we discussed multiple approaches for defining a custom type in Hibernate. Additionally, we implemented a few custom types for our entity class based on some common use cases where a new custom type can come in handy.

在本教程中,我们讨论了在Hibernate中定义自定义类型的多种方法。此外,我们根据一些新的自定义类型可以派上用场的常见使用案例,为我们的实体类实现了一些自定义类型。

As always the code samples are available over on GitHub.

像往常一样,代码样本可在GitHub上获得。