This tutorial explains how to use the @JdbcTypeCode
annotation introduced in Hibernate 6.
With JPA, you can define an entity class which represents a database entity or table. It can have some fields or columns. Hibernate already has a default mapping between Java field types and JDBC types. However, sometimes it's necessary to map a specific field to use a different JDBC type. Hibernate 6 brought a new annotation called @JdbcTypeCode
, which makes it easier to do that. Below I'm going to give you some usage examples of the annotation.
Using @JdbcTypeCode
Annotation
Hibernate has a class named JdbcType
, which defines a descriptor for the SQL/JDBC side of a value mapping that always has a corresponding JavaType
value. It defines how values are read from and written to JDBC. For some Java types, such as Integer
, String
, and Boolean
, Hibernate already has a default mapping which can be seen in the StandardBasicTypes
class.
In some cases, you may need to explicitly define the JDBC type to use for a field. For example, if you want to use a different one from the default mapping. Another case is when Hibernate cannot determine the JDBC type of a Java type.
The usage of the annotation is quite simple. Just annotate a field, a method, or an annotation interface with it. It has a required argument whose type is an integer that represents the JDBC type code to be used.
The value of a JDBC type code is an integer. Each type has a unique code value. Fortunately, you don't have to remember or search for the code for the type that you want to use. You can use the constants defined in the java.sql.Types
class. With Hibernate, you can also use the org.hibernate.type.SqlTypes
class, which is the extension of the java.sql.Types
class.
To use the annotation, you have to import it from the org.hibernate.annotations
package.
import org.hibernate.annotations.JdbcTypeCode;
Set JDBC Type of a Field
As I've written above, Hibernate has a default mapping between JDBC types and Java types. For example, if the field's Java type is Integer
, the default JDBC type is INTEGER
. As a result, the database column type must be compatible with the JDBC type (INTEGER
) if the field type is Integer
.
Let's say we want to define a field with Integer
as the type because the value cannot have any decimal point. The database column uses a FLOAT
type and it cannot be changed for some reasons. If the JDBC type and the database column mismatches, you may get an exception like the following when starting the application (assuming the DDL validation is on).
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [price] in table [products]; found [float8 (Types#DOUBLE)], but expecting [integer (Types#INTEGER)]
The solution is quite simple, just annotate the field with the @JdbcTypeCode
annotation by passing the type code for FLOAT
.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@JdbcTypeCode(SqlTypes.FLOAT)
private int price;
// other columns
}
Since an integer value can be converted to a double value, you should get the expected behavior when persisting or fetching a row.
Name | Type |
---|---|
product_id |
UUID |
price |
FLOAT |
If the conversion between the Java type and JDBC type cannot be performed, you may get a HibernateException
. For example, the field type is Integer
but the JDBC type is BOOLEAN
. The application can be started successfully by annotating the field with @JdbcTypeCode(SqlTypes.BOOLEAN)
. However, when fetching or persisting a row, Hibernate will throw an exception.
org.hibernate.HibernateException: Unknown unwrap conversion requested: java.lang.Integer to java.lang.Boolean : `org.hibernate.type.descriptor.java.IntegerJavaType` (java.lang.Integer)
org.hibernate.HibernateException: Unknown wrap conversion requested: java.lang.Boolean to java.lang.Integer : `org.hibernate.type.descriptor.java.IntegerJavaType` (java.lang.Integer)
In that case, you may need to use a custom converter or a custom type.
Set JDBC Type of a JSON Field
Another usage is for declaring a field whose type in the database is JSON. Before this annotation, you have to define the JSON type using a @TypeDef
annotation by providing a class that handles the mapping. You can either create your own class or use a library such as hibernate-types-52
. Then, you need to annotate the field using @Type
annotation.
For example, in a Spring application with a PostgreSQL database, we want to define a field whose database column type is JSONB. Below is the code where the class is annotated with @TypeDef
and the field is annotated with @Type
.
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@Type(type = "jsonb")
private Map<String, Object> details;
// other columns
}
With the @JdbcTypeCode
annotation, it becomes much simpler. You don't need to create your own class or use any library anymore, unless you need a custom implementation. Just add the @JdbcTypeCode
annotation on the field by passing the type code for JSON as the value.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@JdbcTypeCode(SqlTypes.JSON)
private Map<String, Object> details;
// other columns
}
Below are the column types of the entity above.
Name | Type |
---|---|
product_id |
UUID |
details |
JSON |
Set JDBC Type of a List
Instance Collection
You can define a collection of instances by creating a List
field annotated with @ElementCollection
. That means there is another table that stores the collection which has a relationship with the primary table. The collection table is required to have three columns which include the foreign key to the primary table, the element value, and the element index.
If you add the @JdbcTypeCode
annotation, it will be applied to the element value column. To set the JDBC type of the element index column, use the @ListIndexJdbcTypeCode
annotation.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@JdbcTypeCode(SqlTypes.FLOAT)
@ListIndexJdbcTypeCode(SqlTypes.TINYINT)
private List<Integer> addedStocks;
// other columns
}
In the example above, the JDBC type of the element value is set to FLOAT
despite the element type of the field is Integer
. Meanwhile, the JDBC type of the element index is set to TINYINT
.
Name | Type |
---|---|
product_id |
UUID |
added_stocks |
FLOAT |
added_stocks_order |
TINYINT |
Set JDBC Type of a Map
Instance Collection
Another way to define a collection of instances is using a Map
field. With this way, the collection table must have three columns which include the foreign key to the primary table, the element key, and the element value.
If you add the @JdbcTypeCode
annotation, it will be applied to the element value column. To set the type of the element key column, use the @MapKeyJdbcTypeCode
annotation.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@JdbcTypeCode(SqlTypes.FLOAT)
@MapKeyJdbcTypeCode(SqlTypes.VARCHAR)
private Map<Integer, Integer> ratings;
// other columns
}
In the example above, the JDBC type of the element value is set to FLOAT
despite the element type of the field is Integer
. Meanwhile, the JDBC type of the element key is set to VARCHAR
.
Name | Type |
---|---|
product_id |
UUID |
ratings_key |
VARCHAR |
ratings |
FLOAT |
Set JDBC Type of a Bag Instance Collection
It's also possible to define an instance collection using bag mapping. The stored instances are unordered and allow duplicate elements. There must be three columns in the collection table which include the foreign key to the primary table, the bag ID, and the element value.
If you add the @JdbcTypeCode
annotation, it will be applied to the element value column. To set the JDBC type of the bag ID, use the @CollectionIdJdbcTypeCode
annotation.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@CollectionId(column = @Column( name = "bag_id" ), generator = "increment")
@CollectionIdJdbcTypeCode(Types.SMALLINT)
@JdbcTypeCode(SqlTypes.VARCHAR)
private List<String> keywords;
// other columns
}
In the example above, the JDBC type of the element value is set to VARCHAR
(actually not necessary because the String
is mapped to VARCHAR
by default). Meanwhile, the JDBC type of the bag ID is set to SMALLINT
.
Name | Type |
---|---|
product_id |
UUID |
bag_id |
SMALLINT |
keywords |
VARCHAR |
Set JDBC Type of an Instance Collection of Other Types
For other collections types whose field type is neither List
nor Map
and not using bag mapping, the @JdbcTypeCode
is used to specify the JDBC type of the element column.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@ElementCollection
@JdbcTypeCode(SqlTypes.VARCHAR)
private Set<LocalDate> restockDates;
// other columns
}
In the example above, the collection table has a column named restock_dates
whose JDBC type is set to VARCHAR
.
Name | Type |
---|---|
product_id |
UUID |
restock_dates |
VARCHAR |
Set JDBC Type of a Discriminated Association Mappings
If you define an association mapping using @Any
or @ManyToAny
, the discriminator type can be set by using the @JdbcTypeCode
annotation.
@Entity
@Table(name = "products")
public class Product {
@Id
@UuidGenerator
private UUID id;
@Any
@AnyDiscriminator(DiscriminatorType.INTEGER)
@AnyDiscriminatorValue(discriminator = "1", entity = CreditPayment.class)
@AnyDiscriminatorValue(discriminator = "2", entity = CashPayment.class)
@AnyKeyJavaClass(UUID.class)
@Column(name = "payment_type")
@JoinColumn(name = "payment_id")
@JdbcTypeCode(SqlTypes.INTEGER)
private Payment payment;
// other columns
}
In the example above, the products
table has a discriminator column named payment_type
. The JDBC type of which is set to INTEGER
by using the annotation.
Name | Type |
---|---|
payment_type |
INTEGER |
Summary
The @JdbcTypeCode
annotation can be used to set the JDBC type of a column. When using the annotation, you have to specify the type code to use. Usually, the code is already defined in the Hibernate's SqlTypes
class. If an instance collection field uses the annotation, it will be applied on the element value column. The annotation can be applied on the discriminator column of an association mapping as well.
You can also read about: