This tutorial shows you how to use the @JavaType
and @JavaTypeRegistration
annotations introduced in Hibernate 6.
If you ever use Hibernate, you may already know if you create an entity field with a certain type, it requires a compatible type in the database. That's because some Java classes already have default mapping to JDBC types. You can read the standard mapping in the StandardBasicTypes
class.
Hibernate has a class named JavaType
, which is the descriptor for the Java side of a value mapping. It's always coupled with a JdbcType
, which is the descriptor for the SQL/JDBC side. For standard Java types, the mapping is already defined. Therefore, Hibernate can handle it and you don't have to define the JdbcType
or JavaType
to use, unless you need a custom behavior.
However, particular Java types cannot be mapped by default. If you have such fields, you can use the @JavaType
annotation to tell Hibernate which JavaType
to use which includes the corresponding JdbcType
.
Using @JavaType
Annotation
The annotation can be applied on a field, method, or annotation type. It has a required argument where you need to pass a class which defines the JavaType
for the annotated field, method, or annotation type. The class must be a subtype of the BasicJavaType
class.
To use the annotation, you have to import it from the org.hibernate.annotations
package.
import org.hibernate.annotations.JavaType;
Set Java Type of a Field
For example, there is an entity whose one of the fields is an Object
despite the values are always a String
. By default, Hibernate cannot determine the corresponding JDBC or SQL type for the Object
class. You'll get an error like the following.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory;
nested exception is org.hibernate.MappingException: Unable to determine SQL type name for column 'detail' of table 'items'
Changing the field type to String
can be a solution. But if you don't want to change the type, the alternative is to add the @JavaType
annotation to the field. The suitable Java type to use is StringJavaType
.
@Entity
@Table(name = "items")
public class Item {
@Id
@UuidGenerator
private UUID id;
@JavaType(StringJavaType.class)
private Object detail;
// other columns
}
In another example, the field is declared as Character[]
. The values are an array of characters whose elements cannot be null. Since Hibernate 6.2, there is a change in the mapping for that type that may cause an exception if you don't explicitly define how to handle that type.
Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory;
nested exception is org.hibernate.MappingException: The property com.woolha.hibernate6.example.model.Item#labels uses a wrapper type Byte[]/Character[]
which indicates an issue in your domain model.
While it's possible to change the type to char[]
, let's assume we cannot change it. To fix the error, add the @JavaType
annotation to the field by passing a suitable descriptor class as the argument. In this case, use the CharacterArrayJavaType
class.
@Entity
@Table(name = "items")
public class Item {
@Id
@UuidGenerator
private UUID id;
@JavaType(CharacterArrayJavaType.class)
private Character[] labels;
// other columns
}
Besides those two descriptor classes, there are a lot of built-in descriptors in the org.hibernate.type.descriptor.java
package. You can also create a custom one if necessary.
Set Java Type of a List
Instance Collection
JPA allows you to define an instance collection by using the @ElementCollection
annotation. If the annotated field type is a List
, there are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the element index.
If the field is annotated with the @JavaType
annotation, it will be applied on the element value column. For the element index column, the annotation to use is @ListIndexJavaType
.
public class Item {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@CollectionTable()
@JavaType(FloatJavaType.class)
@ListIndexJavaType(IntegerJavaType.class)
private List<Integer> purchasedStocks;
// other columns
}
In the example above, the Java type of the element value column is set to Float
. Meanwhile, the Java type of the element index column is set to Integer
.
Name | Java Type |
---|---|
item_id |
UUID |
purchased_stocks |
Float |
purchased_stocks_order |
Integer |
Set Java Type of a Map
Instance Collection
If the field annotated with @ElementCollection
is a List
, there are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the element key.
If the field is annotated with the @JavaType
annotation, it will be applied on the element value column. For the element key column, the annotation to use is @MapKeyJavaType
.
public class Item {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@JavaType(IntegerJavaType.class)
@MapKeyJavaType(StringJavaType.class)
private Map<Object, Object> ratings;
// other columns
}
In the example above, the Java type of the element value column is set to Integer
. Meanwhile, the Java type of the element key column is set to String
.
Name | Java Type |
---|---|
item_id |
UUID |
ratings |
Integer |
ratings_key |
String |
Set Java Type of a Bag Instance Collection
Another way to define an instance collection is by using a bag mapping which allows duplicate elements. There are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the bag ID.
If the field is annotated with the @JavaType
annotation, it will be applied on the element value column. For the bag ID column, the annotation to use is @CollectionIdJavaType
.
public class Item {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@CollectionId(column = @Column( name = "bag_id" ), generator = "increment")
@JavaType(StringJavaType.class)
@CollectionIdJavaType(IntegerJavaType.class)
private List<Object> tags;
// other columns
}
In the example above, the Java type of the element value column is set to String
. Meanwhile, the Java type of the bag ID is set to Integer
.
Name | Java Type |
---|---|
item_id |
UUID |
tags |
String |
bag_id |
Integer |
Set Java Type of an Instance Collection of Other Types
If you have an instance collection whose field type is neither List
nor Map
and not using a bag mapping, the @JavaType
annotation is used to specify the Java type of the element column.
public class Item {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@JavaType(StringJavaType.class)
private Set<Object> categories;
// other columns
}
In the example above, the Java type of the element value column is set to String
.
Name | Java Type |
---|---|
item_id |
UUID |
categories |
String |
Using @JavaTypeRegistration
Annotation
There's another annotation @JavaTypeRegistration
, which is used to set the default descriptor for a Java type. You can add the annotation to a package, type, or annotation type.
For example, we want to set the default Java type of Object
to use StringJavaType
. It can be done by adding @JavaTypeRegistration
annotation to the class. As a result, all fields whose type is Object
will use StringJavaType
descriptor by default. You are no longer required to add the @JavaType
annotation to each field.
@Entity
@Table(name = "items")
@JavaTypeRegistration(javaType = Object.class, descriptorClass = StringJavaType.class)
public class Item {
@Id
@UuidGenerator
private UUID id;
private Object detail;
}
However, if the field has a @JavaType
annotation with a different type descriptor, the one defined in the field (the more specific one) will be used.
Summary
The @JavaType
annotation can be used to set the Java type descriptor to use. If an instance collection field uses the annotation, it will be applied on the element value column. Hibernate also provides another annotation @JavaTypeRegistration
, which can be used to set the default descriptor for a Java type.
You can also read about: