图源:
在之前的文章中我们讨论了 ,实际上存在一种特殊的一对一关系,即将一个实体映射到多张表,本文会讨论这种关系。
我之前提过,有时候会因为性能上的考量将一张表拆分成多张表,虽然拆分后也可以用一对一关系来表示和实现,但这样并不是特别合适,因为一对一关系中一边的关系是可以为null
的,比如说一个学生对应一个电子邮件地址,也可能有的学生没有电子邮件地址,此时email
表可能就没有一条对应的数据。但如果是从同一张表上拆分出的两张表,必然存在一对一的关系,即使另一张表中的数据都是null
字段。
下面我们将说明如何实现这种关系。
示例
假设我们有一张表,保存学生的所有信息,这个表可以用下面的实体类表示:
name = "student")
(public class Student {
strategy = GenerationType.IDENTITY)
( private Long id;
max = 45)
( private String name;
private Boolean loveMusic = false;
private Boolean loveDraw = false;
private String address;
private String email;
}
Hibernate 生成的表结构如下:
CREATE TABLE `student` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`address` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`love_draw` bit(1) NOT NULL,
`love_music` bit(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
假如这个表有性能瓶颈,我们需要将其中的爱好相关字段拆分出来,但我们又不希望改变对这个实体的使用习惯,即我们依然希望通过这个实体的属性访问相关数据。
@SecondaryTable
为了实现这个目的,可以按下面的方式修改实体类:
name = "Student2")
(name = "student2")
(name = "student_hobbies2",
( pkJoinColumns = (name = "student_id"))
public class Student {
strategy = GenerationType.IDENTITY)
( private Long id;
max = 45)
( private String name;
table = "student_hobbies2")
( private Boolean loveMusic = false;
table = "student_hobbies2")
( private Boolean loveDraw = false;
private String address;
private String email;
}
这里用@SecondaryTable
注解来表示要拆分出去的副表,name
表示副表的名称,pkJoinColumns
表示用于关联两张表的主键(外键)。
此外,还需要将拆分出去的字段相关的属性的@Column
注解标注为副表字段(table=xxx
)。
现在 Hibernate 生成的表结构为:
CREATE TABLE `student2` (
`id` bigint NOT NULL AUTO_INCREMENT,
`address` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `student_hobbies2` (
`love_draw` bit(1) NOT NULL,
`love_music` bit(1) NOT NULL,
`student_id` bigint NOT NULL,
PRIMARY KEY (`student_id`),
CONSTRAINT `FKp37nmekoeubhhvwpsw0euvps3` FOREIGN KEY (`student_id`) REFERENCES `student2` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
但对于实体的调用方来说,不会有任何影响,依然可以用之前的方式使用实体类,甚至不会“察觉”这个实体中的字段已经拆分成了两张表,这就是实体抽象层的好处。
@SecondaryTables
可以用多个@SecondaryTable
让一个实体对应多张表:
// ...
name = "student_hobbies3",
( pkJoinColumns = (name = "student_id"))
name = "student_detail3",
( pkJoinColumns = (name = "student_id"))
public class Student {
// ...
table = "student_hobbies3")
( private Boolean loveMusic = false;
Default
. table = "student_hobbies3")
( private Boolean loveDraw = false;
table = "student_detail3")
( private String address;
table = "student_detail3")
( private String email;
}
JDK8 之前的版本不支持重复注解,可以这样:
({ name = "student_hobbies3",
( pkJoinColumns = (name = "student_id")),
name = "student_detail3",
( pkJoinColumns = (name = "student_id"))})
public class Student {
// ...
}
@Embeddable 和 @Embedded
之前我们介绍过的用途,映射到副表的属性同样可以用类似的方式进行分组:
name = "Student4")
(name = "student4")
(name = "student_hobbies4",
( pkJoinColumns = (name = "student_id"))
name = "student_detail4",
( pkJoinColumns = (name = "student_id"))
public class Student {
public static class Hobbies {
Default
. table = "student_hobbies4")
( private Boolean loveMusic = false;
Default
. table = "student_hobbies4")
( private Boolean loveDraw = false;
}
public static class Details {
table = "student_detail4")
( private String address;
table = "student_detail4")
( private String email;
}
strategy = GenerationType.IDENTITY)
( private Long id;
max = 45)
( unique = true)
( private String name;
Default
. private Hobbies hobbies = new Hobbies();
Default
. private Details details = new Details();
}
如果@Embeddable
标记的类需要重用,也可以使用@AttributeOverrides
注解,具体可以看。
The End,谢谢阅读。
本文的完整示例可以从获取。
文章评论