复合字段
复合字段是将数据库的多个列合并为一个整体,将其映射为一个非实体的自定义类型,然后利用此自定义类型为实体声明一个属性。
作为普通字段
简单使用
首先,定义一个复合类型
- Java
- Kotlin
FullName.java
@Embeddable
public interface FullName {
String firstName();
String lastName();
}
FullName.kt
@Embeddable
interface FullName {
val firstName: String
val lastName: String
}
警告
@Embeddable所定义的类型不是实体类型,其中不得声明id属性和关联属性。否则,会导致编译错误。
接下来我们就可以在另外一个实体中使用它
- Java
- Kotlin
Author.java
@Entity
public interface Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();
FullName name();
}
Author.kt
@Entity
interface Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long
val name: FullName
}
信息
和JPA/Hibernate不同,在实体中使用复合类型时无需使用@Embedded
。事实上,Jimmer也不提供@Embedded
注解。
Author
对应的表结构如下
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;
覆盖列名
可以使用org.babyfish.jimmer.sql.PropOverride
覆盖复合类型字段的列名
-
定义复合类型
Point
- Java
- Kotlin
Point.java@Embeddable
public interface Point {
int x();
int y();
}Point.kt@Embeddable
interface Point {
val x: Int
val y: Int
} -
定义复合类型
Rect
,覆盖Point
类型的列名- 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
} -
定义实体类型
Transition
,覆盖Rect
类型的列名- 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
}
Transition
对应的表结构如下
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;
作为主外键
注意
用复合类型作为主外键,即,主键和外键都由多个列组合而成,除了让系统变得复杂外,没有什么好处。
因此,除非必须兼容遗留系统中已有的数据库设计,系统应该避免如此使用,保证主外键的简单性。
让我们先定义一个复合类型
- Java
- Kotlin
UniqueId.java
@Embeddable
public interface UniqueId {
@columnName("UNIQUE_ID_DAY_NO")
int dayNo();
@columnName("UNIQUE_ID_SEQ_NO")
int sequenceNo();
}
UniqueId.kt
@Embeddable
interface UniqueId {
@columnName("UNIQUE_ID_DAY_NO")
val dayNo: Int
@columnName("UNIQUE_ID_SEQ_NO")
val sequenceNo: Int
}
作为主键
- Java
- Kotlin
Book.java
@Entity
public interface Book {
@Id
UniqueId id();
...省略其他代码...
}
Book.kt
@Entity
interface Book {
@Id
val id: UniqueId
...省略其他代码...
}
由于Book.id
未使用@PropOverride
对UniqueId
类型中列名配置进行覆盖,所以采用UniqueId
中配置的列名,对应的DDL为
create table book(
unique_id_day_no int not null,
unique_id_seq_no int not null,
...省略其他字段...
) engine=innodb;
alter table book
add constraint pk_book
primary key(unique_id_day_no, unique_id_seq_no);
被@JoinColumn引用
- Java
- Kotlin
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();
...省略其他代码...
}
Chapter.kt
@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
...省略其他代码...
}
警告
和我们之前讲解过的所有例子不同,这里@JoinColumn注解的referencedColumnName
被指定了。
当外键由多列构成时,就需要使用多个@JoinColumn
注解,其中,每个@JoinColumn
必须指定referencedColumnName
。
如果外键是真的,对应的约束为
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
);