|

How to map composite column types with Hibernate


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


When most developers design their table and entity models, they create tables that only use basic column types and map these to basic entity attributes. Even though these are the most commonly used types, they are not the only ones supported by modern relational databases and Hibernate. You can also use composite, JSON, and XML types. I already showed you how to map a JSON column using different Hibernate versions. In this article, I want to focus on mapping composite column types. Since Hibernate 6.2, this got incredibly simple if you’re using an Oracle, PostgreSQL, or DB2 database.

A composite type represents a structure that consists of multiple fields. Each field has a name and can store a value. Here you can see an example of such a type definition for a PostgreSQL database.

create type my_struct as (longProp bigint, stringProp varchar(255))

I will use the my_struct type in the examples of this article. It contains the longProp field, which stores a numerical value, and the stringProp field, which stores text. When creating your database table, you can use this type like any other column type.

create table MyEntity (
	id bigint not null,
	structProperty my_struct,
	primary key (id)
)

Starting with Hibernate 6.2, you can easily map a composite type column to an embeddable, which you can then use as your entity attribute type.

Mapping composite types with Hibernate 6.2

A composite type consists of multiple fields. When mapping it to an entity attribute, you obviously want to map it to an attribute type that handles each field separately. That makes an embeddable an obvious choice.

As I showed in a recent article, you can model an embeddable as a Java class or record. You can map both of them to a composite type. But how you define your embeddable has a small influence on Hibernate’s mapping.

Mapping an embeddable class or record to a composite type

The easiest way to map an embeddable to a composite type is to annotate your class with a @Struct annotation and reference the name of the composite type. This works in the same way for embeddable implemented as a class or as a record.

@Embeddable
@Struct(name = "my_struct")
public class MyStructure {

	private String stringProp;
	
	private Long longProp;

	...
}
@Embeddable
@Struct(name = "my_struct")
public record MyStructureRecord(String stringProp, Long longProp) {}

Since version 6.2, Hibernate supports records as embeddable by default. If you’re using Hibernate 6.0 or 6.1, you have to implement an EmbeddableInstantiator, which tells Hibernate how to instantiate the record.

In the following examples, I will use the embeddable class MyStructure. But you could use the embeddable record MyStructureRecord in the same way.

After implementing your embeddable and defining the mapping to a composite type, you can use it like any other embeddable.

@Entity
public class MyEntity {

	@Id
	@GeneratedValue
	private Long id;

	@Embedded
	private MyStructure structProperty;
	
	...
}

You only see a difference when you check your table model or the executed SQL statements. Instead of mapping each attribute of your embeddable to a separate database column, Hibernate maps each attribute to a field of the referenced composite type.

MyStructure p = new MyStructure();
p.setLongProp(123L);
p.setStringProp("abc");

MyEntity e = new MyEntity();
e.setStructProperty(p);
em.persist(e);
11:43:52,494 DEBUG [org.hibernate.SQL] - 
    select
        nextval('MyEntity_SEQ')
11:43:52,510 DEBUG [org.hibernate.SQL] - 
    insert 
    into
        MyEntity
        (structProperty,id) 
    values
        (?,?)
11:43:52,510 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [STRUCT] - [com.thorben.janssen.model.MyStructure@7c7e73c5]
11:43:52,514 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [BIGINT] - [1]

You can even use the embeddable in your JPQL queries. Like a normal embeddable, you use the path operator “.” to navigate from your entity to the embeddable and from there to a specific attribute.

MyEntity e = em.createQuery("""
								SELECT e 
								FROM MyEntity e 
								WHERE e.structProperty.longProp = 456""", 
							MyEntity.class)
				.getSingleResult();

Hibernate translates this into the SQL dialect of your database. I’m using a PostgreSQL database for this example, and Hibernate generates the following statement.

16:06:43,917 DEBUG [org.hibernate.SQL] - 
    select
        m1_0.id,
        (m1_0.structProperty).longProp,
        (m1_0.structProperty).stringProp 
    from
        MyEntity m1_0 
    where
        (
            m1_0.structProperty
        ).longProp=456

Adjusting the order of the composite’s fields

When you use the default mapping, you might run into the problem that the order in which Hibernate maps the attributes of your embeddable doesn’t match the structure of your composite type. The reason for that problem becomes obvious when you look at the data structure stored in a composite type. It’s a simple list of values. The order of the list’s elements has to match the order in which the fields of the composite type are defined.

So, for the following definition of the my_struct type, Hibernate needs to provide the value of the stringProp attribute as the 1st and the value of the longProp attribute as the 2nd value in that list.

create type my_struct as (stringProp varchar(255), longProp bigint)

Hibernate’s default order depends on how you implement your embeddable. If you implement an embeddable class, Hibernate maps the attributes in the alphabetical order of their names. If you implement it as a record, Hibernate maps the fields in the order listed in the record definition.

You can adjust the default mapping by specifying the order in which Hibernate shall map the attributes. You do that by providing an array of attribute names to the @Struct annotation. Hibernate then maps the attributes of your embeddable class or record in the order defined by the attributes array.

@Embeddable
@Struct(name = "my_struct", attributes = {"stringProp", "longProp"})
public class MyStructure {

	private String stringProp;
	
	private Long longProp;

	...
}

Conclusion

The @Struct annotation introduced in Hibernate 6.2 enables you to map an embeddable to a composite column type. As you saw in the examples, the mapping is straightforward. But you need to ensure that Hibernate maps the elements of the embeddable in the same order as the composite type defined its fields.

After you have implemented your embeddable and defined the mapping to the composite type, you can use it in the same way as any other embeddable. This even includes your JPQL queries. You can use the path operator “.” to navigate from an entity object via the embeddable attribute to a specific field.