This tutorial shows you how to use the @Struct
annotation in Hibernate for mapping struct or composite types.
Beside basic column types, some databases allow us to define struct or composite types. It allows a column to have a more complex data type with several fields that comply with a defined structure. That kind of data type is stricter than JSON and usually you need to define it in the database. If you use Hibernate version 6.2 or above, there is an easy way to tell Hibernate which struct type should be used for a field. You can do it by using the @Struct
annotation
Using @Struct
Annotation
For example, we want to define a struct or composite type named location_coordinate
. The type has three attributes whose names in order are altitude
, latitude
, and longitude
. Below are the queries for creating the type and a table that has a column using the type. I'm going to use PostgreSQL database for this tutorial. You can use other databases as long as it's supported by Hibernate.
CREATE TYPE location_coordinate AS (
altitude int,
latitude double precision,
longitude double precision
);
CREATE TABLE locations (
id uuid NOT NULL,
address text NOT NULL,
coordinate location_coordinate NOT NULL,
CONSTRAINT "pk_locations" PRIMARY KEY (id)
);
Here is the Java class that represents the type.
@Embeddable
public class Coordinate {
private Integer altitude;
private Double longitude;
private Double latitude;
// Consturctor, getters, and setters
}
Besides using a class, it's also possible to use a record instead.
Just like using other column types, the column has to be defined as a field in the entity class. However, if you only do that, Hibernate won't be able to know that it's a column that uses a struct type. As a solution, annotate the field with the @Struct
annotation.
The annotation can be put on types (classes), fields, and methods. It requires you to pass the struct name. Optionally, you can also define the order of the attributes if necessary.
Set Struct Name
When adding this annotation to a field, it's required to set the value of the name
parameter. It has to match the name of the struct or composite type defined in the database. Below is an example that uses the location_coordinate
type. You also need to ensure that the class or record used as the field type has the same attribute names as the actual type's attribute defined on the database. Not only the names, the types of each attribute must be compatible as well.
@Entity
@Table(name = "locations")
public class Location {
@Id
@UuidGenerator
private UUID id;
private String address;
@Struct(name = "location_coordinate")
private Coordinate coordinate;
// Constructor, getters, and setters
}
Next, let's test it by running a code for inserting a new row to the table.
@Transactional
public void create(CreateLocationRequestDto requestDto) {
Coordinate coordinate = Coordinate.builder()
.altitude(requestDto.getAltitude())
.latitude(requestDto.getLatitude())
.longitude(requestDto.getLongitude())
.build();
Location location = Location.builder()
.address(requestDto.getAddress())
.coordinate(coordinate)
.coordinateV2(coordinate)
.build();
this.entityManager.persist(location);
}
Hibernate logs:
insert into locations (address,coordinate,id) values (?,?,?)
binding parameter [1] as [VARCHAR] - [My address]
binding parameter [2] as [STRUCT] - [com.woolha.hibernate.model.Coordinate@4ef09166]
binding parameter [3] as [UUID] - [78a8ac10-893b-4455-a8b2-02f0ab9be2a4]
PostgreSQL logs:
insert into locations (address,coordinate,id) values ($1,$2,$3)
parameters: $1 = 'My address', $2 = '(10,-0.7893,113.9213)', $3 = '78a8ac10-893b-4455-a8b2-02f0ab9be2a4'
Below is another example showing that you can use the attribute of the struct type as a query criteria.
public void getSouthernHemisphereLocations() {
String sql = "SELECT l FROM Location l WHERE l.coordinate.latitude < 0";
List<Location> locations = this.entityManager.createQuery(sql)
.getResultList();
for (Location location : locations) {
System.out.println(location.getAddress());
}
}
Hibernate logs:
select l1_0.id,l1_0.address,(l1_0.coordinate).altitude,(l1_0.coordinate).latitude,(l1_0.coordinate).longitude from locations l1_0 where (l1_0.coordinate).latitude<0
PostgreSQL logs:
select l1_0.id,l1_0.address,(l1_0.coordinate).altitude,(l1_0.coordinate).latitude,(l1_0.coordinate).longitude from locations l1_0 where (l1_0.coordinate).latitude<0
Set Attributes Order
Another thing that you need to know is regarding the attributes order. In the database, the value of a column with struct or composite type is stored as a list of values. Therefore, Hibernate needs to know the order of the attributes.
The default order depends on the embeddable. If you use a class, the default order is the fields in alphabetical order. If you use a record, the default order depends on the record definition. In the example above, the attributes of the composite type are in alphabetical order. As a result, we could get the expected behavior.
In case you need to use a different order but don't want to modify the class or record, the solution is to set the attributes
value of the annotation.
For example, we have another type called location_coordinate_v2
which is similar to location_coordinate
, but the altitude
attribute is put as the last one.
CREATE TYPE location_coordinate_v2 AS (
latitude double precision,
longitude double precision,
altitude int
);
ALTER TABLE locations ADD COLUMN coordinate_v2 location_coordinate_v2;
Below is an example that passes the attributes
value to the annotation.
public class Location {
@Column(name = "coordinate_v2")
@Struct(name = "location_coordinate_v2", attributes = {"latitude", "longitude", "altitude"})
private Coordinate coordinateV2;
// Constructor, getters, setters, and other fields
}
Summary
Hibernate's @Struct
annotation can be used to easily map a field to a struct or composite type in the database. You only need to annotate the field by passing the database's type name as the value of the name
parameter. The order of the attributes matters. You may need to pass the value of the attributes
parameter for defining the order.
You can also read about: