跳到主要内容

复合字段

复合字段是将数据库的多个列合并为一个整体,将其映射为一个非实体的自定义类型,然后利用此自定义类型为实体声明一个属性。

作为普通字段

简单使用

首先,定义一个复合类型

FullName.java
@Embeddable
public interface FullName {

String firstName();

String lastName();
}
警告

@Embeddable所定义的类型不是实体类型,其中不得声明id属性和关联属性。否则,会导致编译错误。

接下来我们就可以在另外一个实体中使用它

Author.java
@Entity
public interface Author {

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

FullName name();
}
信息

和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

    Point.java
    @Embeddable
    public interface Point {

    int x();

    int y();
    }
  • 定义复合类型Rect,覆盖Point类型的列名

    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();
    }
  • 定义实体类型Transition,覆盖Rect类型的列名

    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对应的表结构如下

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;

作为主外键

注意

用复合类型作为主外键,即,主键和外键都由多个列组合而成,除了让系统变得复杂外,没有什么好处。

因此,除非必须兼容遗留系统中已有的数据库设计,系统应该避免如此使用,保证主外键的简单性。

让我们先定义一个复合类型

UniqueId.java
@Embeddable
public interface UniqueId {

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

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

作为主键

Book.java
@Entity
public interface Book {

@Id
UniqueId id();

...省略其他代码...
}

由于Book.id未使用@PropOverrideUniqueId类型中列名配置进行覆盖,所以采用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引用

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();

...省略其他代码...
}
警告

和我们之前讲解过的所有例子不同,这里@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
);

被@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();

...省略其他代码...
}

如果外键是真的,中间表的DDL如下

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);