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:
- Java
- Kotlin
@Embeddable
public interface FullName {
String firstName();
String lastName();
}
@Embeddable
interface FullName {
val firstName: String
val lastName: String
}
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:
- Java
- Kotlin
@Entity
public interface Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();
FullName name();
}
@Entity
interface Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long
val name: FullName
}
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
:- Java
- Kotlin
Point.java@Embeddable
public interface Point {
int x();
int y();
}Point.kt@Embeddable
interface Point {
val x: Int
val y: Int
} -
Define composite type
Rect
and override column names ofPoint
type:- Java
- Kotlin
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();
}Rect.kt@Embeddable
interface Rect {
@PropOverride(prop = "x", columnName = "`LEFT`")
@PropOverride(prop = "y", columnName = "`TOP`")
val leftTop: Point
@PropOverride(prop = "x", columnName = "`RIGHT`")
@PropOverride(prop = "y", columnName = "BOTTOM")
val rightBottom: Point
} -
Define entity type
Transition
and override column names ofRect
type:- Java
- Kotlin
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();
}Transition.kt@Entity
interface Transition {
@Id
val id: Long
val millis: Int
val source: Rect
@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")
val target: Rect
}
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
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:
- Java
- Kotlin
@Embeddable
public interface UniqueId {
@columnName("UNIQUE_ID_DAY_NO")
int dayNo();
@columnName("UNIQUE_ID_SEQ_NO")
int sequenceNo();
}
@Embeddable
interface UniqueId {
@columnName("UNIQUE_ID_DAY_NO")
val dayNo: Int
@columnName("UNIQUE_ID_SEQ_NO")
val sequenceNo: Int
}
As Primary Key
- Java
- Kotlin
@Entity
public interface Book {
@Id
UniqueId id();
...Omit other code...
}
@Entity
interface Book {
@Id
val id: UniqueId
...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
- Java
- Kotlin
@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...
}
@Entity
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"
)
val book: Book
...Omit other code...
}
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
- Java
- Kotlin
@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...
}
@Entity
interface Book {
@Id
val id: UniqueId
@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"
)
]
)
val authors: List<Author>
...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);