Skip to main content

Embedded Properties

Embedded properties (Composite properties) merge multiple columns in the database into an integral whole, map them to a non-entity custom type, and then use this custom type to declare a property for the entity.

As Ordinary Field

Basic Usage

First, define a composite type:

FullName.java
@Embeddable
public interface FullName {

String firstName();

String lastName();
}
caution

Types defined by @Embeddable are not entity types. They must not declare id properties or association properties, otherwise compilation errors will occur.

Then we can use it in another entity:

Author.java
@Entity
public interface Author {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

FullName name();
}
info

Unlike JPA/Hibernate, when using composite types in entities, @Embedded is not required. In fact, Jimmer does not provide the @Embedded annotation at all.

The table structure corresponding to Author is:

create table author(
id bigint unsigned not null auto_increment primary key,
/* highlight-next-line */
first_name varchar(20) not null,
/* highlight-next-line */
last_name varchar(20) not null
) engine=innodb;

Override Column Names

org.babyfish.jimmer.sql.PropOverride can be used to override column names of composite type properties:

  • Define composite type Point:

    Point.java
    @Embeddable
    public interface Point {

    int x();

    int y();
    }
  • Define composite type Rect and override column names of Point type:

    Rect.java
    @Embeddable
    public interface Rect {

    @PropOverride(prop = "x", columnName = "`LEFT`")
    @PropOverride(prop = "y", columnName = "`TOP`")
    Point leftTop();

    @PropOverride(prop = "x", columnName = "`RIGHT`")
    @PropOverride(prop = "y", columnName = "BOTTOM")
    Point rightBottom();
    }
  • Define entity type Transition and override column names of Rect type:

    Transition.java
    @Entity
    public interface Transition {

    @Id
    long id();

    int millis();

    Rect source();

    @PropOverride(prop = "leftTop.x", columnName = "TARGET_LEFT")
    @PropOverride(prop = "leftTop.y", columnName = "TARGET_TOP")
    @PropOverride(prop = "rightBottom.x", columnName = "TARGET_RIGHT")
    @PropOverride(prop = "rightBottom.y", columnName = "TARGET_BOTTOM")
    Rect target();
    }

The table structure corresponding to Transition is:

create table transition(
id bigint unsigned not null auto_increment primary key,
millis int not null,
`LEFT` int not null,
`TOP` int not null,
`RIGHT` int not null,
bottom int not null,
target_left int not null,
target_top int not null,
target_right int not null,
target_bottom int not null
) engine=innodb;

As Primary/Foreign Keys

warning

Using composite types as primary/foreign keys, that is, primary keys and foreign keys are composed of multiple columns, makes the system more complex without much benefit.

Therefore, unless compatibility with legacy database design is required, the system should avoid such usage to keep primary/foreign keys simple.

Let's first define a composite type:

UniqueId.java
@Embeddable
public interface UniqueId {

@columnName("UNIQUE_ID_DAY_NO")
int dayNo();

@columnName("UNIQUE_ID_SEQ_NO")
int sequenceNo();
}

As Primary Key

Book.java
@Entity 
public interface Book {

@Id
UniqueId id();

...Omit other code...
}

Since Book.id does not override column names of UniqueId type using @PropOverride, the column names configured in UniqueId are used. The corresponding DDL is:

create table book(
unique_id_day_no int not null,
unique_id_seq_no int not null,
...Omit other columns...
) engine=innodb;

alter table book
add constraint pk_book
primary key(unique_id_day_no, unique_id_seq_no);

Referenced by @JoinColumn

Chapter.java
@Entity
public interface Chapter {

@ManyToOne
@JoinColumn(
name = "BOOK_ID_DAY_NO",
referencedColumnName = "UNIQUE_ID_DAY_NO"
)
@JoinColumn(
name = "BOOK_ID_SEQ_NO",
referencedColumnName = "UNIQUE_ID_SEQ_NO"
)
Book book();

...Omit other code...
}
caution

Different from all the examples we have covered before, here the referencedColumnName of the @JoinColumn annotation is specified.

When the foreign key consists of multiple columns, multiple @JoinColumn annotations must be used, where each @JoinColumn must specify referencedColumnName.

If the foreign key is real, the corresponding constraint is:

alter table chapter
add constraint fk_chapter__book
foreign key(
book_id_day_no,
book_id_seq_id
) references book(
unique_id_day_no,
unique_id_seq_no
);

Referenced by @JoinTable

Book.java
@Entity
public interface Book {

@Id
UniqueId id();

@ManyToMany
@JoinTable(
joinColumns = {
@JoinColumn(
name = "BOOK_ID_DAY_NO",
referencedColumnName = "UNIQUE_ID_DAY_NO"
),
@JoinColumn(
name = "BOOK_ID_SEQ_NO",
referencedColumnName = "UNIQUE_ID_SEQ_NO"
)
}
)
List<Author> authors();

...Omit other code...
}

If the foreign key is real, the DDL of the join table is:

create table book_author_mapping(
book_id_day_no int not null,
book_id_seq_id int not null,
author_id bigint not null
) engine=innodb;

alter table book_author_mapping
add constraint pk_book_author_mapping
primary key(
book_id_day_no,
book_id_seq_id,
author_id
);

alter table book_author_mapping
add constraint fk_book_author_mapping__book
foreign key(
book_id_day_no,
book_id_seq_id
) references book(
unique_id_day_no,
unique_id_seq_no
);

alter table book_author_mapping
add constraint fk_book_author_mapping__author
foreign key(author_id)
references author(id);