本文介绍 Spring Boot JPA @OneToOne 的使用方法。
目录
- 开发环境
- 基础示例
开发环境
- JDK 8
- MySQL 8
基础示例
- 创建数据表。
CREATE SCHEMA `test` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
USE `test`;
CREATE TABLE `student` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`number` CHAR(10) NOT NULL COMMENT '学号',
`name` VARCHAR(30) NOT NULL COMMENT '姓名',
`family_id` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
UNIQUE INDEX `number_UNIQUE` (`number` ASC) VISIBLE)
COMMENT = '学生表';
CREATE TABLE `family` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`father` VARCHAR(30) NOT NULL COMMENT '父亲姓名',
`mother` VARCHAR(30) NOT NULL COMMENT '母亲姓名',
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE);
-
如何创建 Spring Boot JPA 工程请参考:https://www.jianshu.com/p/e2b64d5c6107
-
创建 PO(Persistence Object) 对象。
package tutorial.spring.boot.domain;
import javax.persistence.*;
@Entity
@Table(name = "family")
public class FamilyPO {
/**
* 自增主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String father;
private String mother;
// Getter、Setter、toString 方法略
}
package tutorial.spring.boot.domain;
import javax.persistence.*;
@Entity
@Table(name = "student")
public class StudentPO {
/**
* 自增主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 学号
*/
private String number;
/**
* 姓名
*/
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "family_id")
private FamilyPO family;
// Getter、Setter、toString 方法略
}
注意:@JoinColumn(name = "family_id")
可以省略。
- 创建继承
JpaRepository
的 Repository 接口类。
package tutorial.spring.boot.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import tutorial.spring.boot.domain.FamilyPO;
@Repository
public interface FamilyRepository extends JpaRepository<FamilyPO, Long> {
}
package tutorial.spring.boot.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import tutorial.spring.boot.domain.StudentPO;
@Repository
public interface StudentRepository extends JpaRepository<StudentPO, Long> {
}
- 编写单元测试。
package tutorial.spring.boot.dao;
import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tutorial.spring.boot.domain.FamilyPO;
import tutorial.spring.boot.domain.StudentPO;
import java.util.List;
import java.util.Objects;
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class StudentRepositoryTest {
@Autowired
private FamilyRepository familyRepository;
@Autowired
private StudentRepository studentRepository;
/**
* 测试 [CascadeType.ALL] 条件下的级联新增
*/
@Test
@Order(1)
void testInsert() {
// family 和 student 表中都没有记录
Assertions.assertThat(familyRepository.count()).isEqualTo(0);
Assertions.assertThat(studentRepository.count()).isEqualTo(0);
// 创建 family 和 student 实体对象并将其绑定起来
StudentPO student = new StudentPO();
student.setNumber(RandomStringUtils.randomAlphanumeric(10));
student.setName(RandomStringUtils.randomAlphanumeric(2, 20));
FamilyPO family = new FamilyPO();
family.setFather(RandomStringUtils.randomAlphanumeric(2, 20));
family.setMother(RandomStringUtils.randomAlphanumeric(2, 20));
student.setFamily(family);
// 执行保存 student 实体对象的操作
studentRepository.save(student);
// family 和 student 表中各新增一条记录,且新增记录信息与保存的 student 实体对象信息一致
List<FamilyPO> families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(1);
List<StudentPO> students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
Assertions.assertThat(students.get(0).getNumber()).isEqualTo(student.getNumber());
Assertions.assertThat(students.get(0).getName()).isEqualTo(student.getName());
Assertions.assertThat(students.get(0).getFamily().getFather()).isEqualTo(families.get(0).getFather());
Assertions.assertThat(students.get(0).getFamily().getMother()).isEqualTo(families.get(0).getMother());
Assertions.assertThat(students.get(0).getFamily().getId()).isEqualTo(families.get(0).getId());
}
/**
* 测试 [CascadeType.ALL] 条件下的级联更新
* 只更新级联关系被持有方属性
*/
@Test
@Order(2)
void testUpdate1() {
// 依赖于之前的单元测试,此时 student 和 family 表中应各有一条记录
List<FamilyPO> families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(1);
List<StudentPO> students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
StudentPO student = students.get(0);
FamilyPO family = families.get(0);
// 修改 student 信息及关联的 family 信息后保存
String originalStudentNumber = student.getNumber();
student.setNumber(originalStudentNumber.substring(1));
String originalStudentName = student.getName();
student.setName(originalStudentName + RandomStringUtils.randomAlphabetic(1));
student.getFamily().setFather(student.getFamily().getFather() + RandomStringUtils.randomAlphabetic(1));
student.getFamily().setMother(student.getFamily().getMother() + RandomStringUtils.randomAlphabetic(2));
studentRepository.save(student);
// 保存后 student 和 family 表中还应只有一条记录
families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(1);
students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
// 校验 student 及关联的 family 信息已更新且与原来不同
FamilyPO findFamilyResult = families.get(0);
StudentPO findStudentResult = students.get(0);
Assertions.assertThat(findStudentResult.getNumber())
.isEqualTo(student.getNumber())
.isNotEqualTo(originalStudentNumber);
Assertions.assertThat(findStudentResult.getName())
.isEqualTo(student.getName())
.isNotEqualTo(originalStudentName);
Assertions.assertThat(findStudentResult.getFamily().getFather())
.isEqualTo(student.getFamily().getFather())
.isEqualTo(findFamilyResult.getFather())
.isNotEqualTo(family.getFather());
Assertions.assertThat(findStudentResult.getFamily().getMother())
.isEqualTo(student.getFamily().getMother())
.isEqualTo(findFamilyResult.getMother())
.isNotEqualTo(family.getMother());
}
/**
* 测试 [CascadeType.ALL] 条件下的级联更新
* 重新绑定级联关系被持有方
*/
@Test
@Order(3)
void testUpdate2() {
// 依赖于之前的单元测试,此时 student 和 family 表中应各有一条记录
List<FamilyPO> families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(1);
List<StudentPO> students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
StudentPO student = students.get(0);
FamilyPO family = families.get(0);
// 为 student 重新绑定 family 并保存
FamilyPO newFamily = new FamilyPO();
newFamily.setFather(family.getFather() + RandomStringUtils.randomAlphabetic(2));
newFamily.setMother(family.getMother() + RandomStringUtils.randomAlphabetic(1));
student.setFamily(newFamily);
studentRepository.save(student);
// family 表应该保留之前绑定的 family 信息并新增新绑定的 family 信息
families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(2);
students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
StudentPO findStudentResult = students.get(0);
FamilyPO findOldFamilyResult;
FamilyPO findNewFamilyResult;
if (Objects.equals(families.get(0).getId(), findStudentResult.getFamily().getId())) {
findNewFamilyResult = families.get(0);
findOldFamilyResult = families.get(1);
} else {
findNewFamilyResult = families.get(1);
findOldFamilyResult = families.get(0);
}
Assertions.assertThat(findOldFamilyResult.getId()).isEqualTo(family.getId());
Assertions.assertThat(findOldFamilyResult.getFather()).isEqualTo(family.getFather());
Assertions.assertThat(findOldFamilyResult.getMother()).isEqualTo(family.getMother());
Assertions.assertThat(findNewFamilyResult.getId()).isEqualTo(findStudentResult.getFamily().getId());
Assertions.assertThat(findNewFamilyResult.getFather()).isEqualTo(findStudentResult.getFamily().getFather());
Assertions.assertThat(findNewFamilyResult.getMother()).isEqualTo(findStudentResult.getFamily().getMother());
}
@Test
@Order(4)
void testDelete() {
// 依赖于之前的单元测试,此时 student 表中应有一条记录,family 表中应有两条记录
List<FamilyPO> families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(2);
List<StudentPO> students = studentRepository.findAll();
Assertions.assertThat(students.size()).isEqualTo(1);
FamilyPO familyNoUsed;
if (Objects.equals(families.get(0).getId(), students.get(0).getFamily().getId())) {
familyNoUsed = families.get(1);
} else {
familyNoUsed = families.get(0);
}
// 执行删除 student 操作
studentRepository.delete(students.get(0));
Assertions.assertThat(studentRepository.count()).isEqualTo(0);
families = familyRepository.findAll();
Assertions.assertThat(families.size()).isEqualTo(1);
Assertions.assertThat(families.get(0).getId()).isEqualTo(familyNoUsed.getId());
Assertions.assertThat(families.get(0).getFather()).isEqualTo(familyNoUsed.getFather());
Assertions.assertThat(families.get(0).getMother()).isEqualTo(familyNoUsed.getMother());
}
}
说明:
因为使用了 CascadeType.ALL
,级联关系持有方拥有被持有方的所有权限,本例中 StudentPO
是级联关系持有方,FamilyPO
是级联关系的被持有方,因此 StudentPO
拥有 FamilyPO
的所有权限,测试用例中创建的两个 PO 对象本来都不存在于数据库中,因为 StudentPO
拥有 FamilyPO
的所有权限,所以保存 StudentPO
实例时也同时保存了 FamilyPO
实例,有关级联更新和删除详情请参考测试用例。
网友评论