从零开始 Spring Boot 52:@Embedded 和 @Embeddable
图源:简书 (jianshu.com)
这篇文章会介绍@Embedded
和@Embeddable
两个注解在 JPA 中的用法。
简单示例
先看一个示例:
@AllArgsConstructor
@Builder
@Data
@Entity
@Table(name = "user_student")
@Accessors(chain = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Student {@Id@GeneratedValue(strategy = GenerationType.AUTO)@EqualsAndHashCode.Includeprivate Long id;@Column(length = 25, nullable = false)private String name;@Column(length = 50, nullable = false)private String address;@Column(length = 25)private String contactsName;@Column(length = 50)private String contactsAddress;@Column(length = 15)private String contactsPhone;public Student() {}
}
这里使用了 Lombok 相关注解(比如
@Builder
)帮助构建实体类,详细内容可以阅读我的相关文章。
user_student
是一个学生表,其中的contacts_
开头的字段保存联系人信息,这体现在实体类中就是以contacts
开头的属性。
测试用例:
@Test
@SneakyThrows
void testAddNewStudent() {Student newStudent = Student.builder().address("宁安大街101号").name("icexmoon").contactsName("lalala").contactsAddress("北京大街100号").contactsPhone("123456789").build();studentRepository.save(newStudent);Assertions.assertNotNull(newStudent.getId());ObjectMapper om = new ObjectMapper();var json = om.writeValueAsString(newStudent);System.out.println(json);
}
这样做并没有什么问题,但Student
这个实体类并不具备良好的“结构化”,换言之我们很难将其中的联系人部分进行代码重用。
因此,接下来我们要想办法将Student
中的联系人部分信息提取出来单独作为一个类型存在,这可以借助 JPA 的@Embedded
和@Embeddable
注解完成。
@Embedded 和 @Embeddable
先定义一个联系人类:
@Embeddable
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Contacts {private String name;private String address;private String phone;
}
使用@Embedded
和@Embeddable
“改造”Student
类:
@AllArgsConstructor
@Builder
@Data
@Entity
@Table(name = "user_student2")
@Accessors(chain = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Student2 {@Id@GeneratedValue(strategy = GenerationType.AUTO)@EqualsAndHashCode.Includeprivate Long id;@Column(length = 25, nullable = false)private String name;@Column(length = 50, nullable = false)private String address;@Embedded@AttributeOverrides({@AttributeOverride(name = "name", column = @Column(name = "contacts_name", length = 25)),@AttributeOverride(name = "address", column = @Column(name = "contacts_address", length = 50)),@AttributeOverride(name = "phone", column = @Column(name = "contacts_phone", length = 15))})private Contacts contacts;public Student2() {}
}
这里的@Embeddable
注解表明该类可以被“嵌入”到一个实体类中,充当某些字段的映射。@Embedded
注解表明这里嵌入了一个用@Embeddable
标记的类。
就像以前学习 MyBastis 时在一个 MapperSet 中嵌入另一个 MapperSet 时需要指定字段映射关系,这里同样需要指定,这体现在 @AttributeOverrides
注解中包含的多条@AttributeOverride
注解。其name
属性表示的是被嵌入的类型的属性名称,column
属性表示的是对应的数据库表结构中的字段信息。
如果缺省
@AttributeOverrides
和@AttributeOverride
注解,默认会用被嵌入的类型(这里是Contacts
)的属性名称作为表结构字段名进行映射。但显然这里是行不通的,会报错(因为联系人的姓名与学生的姓名都会映射到同一个name
字段)。
现在实体类变得更加“结构化”,这点在测试用例中构建新对象时体现的很明显:
@Test
@SneakyThrows
void testAddNewStudent() {Student2 newStudent = Student2.builder().address("宁安大街101号").name("icexmoon").contacts(Contacts.builder().name("lalala").address("北京东路100号").phone("123456789").build()).build();student2Repository.save(newStudent);Assertions.assertNotNull(newStudent.getId());ObjectMapper om = new ObjectMapper();var json = om.writeValueAsString(newStudent);System.out.println(json);
}
输出的 JSON 串也能更清楚地观察到结构化的好处。
The End,谢谢阅读。
本文的完整示例代码可以从这里获取。
参考资料
- Jpa @Embedded and @Embeddable | Baeldung