本文介绍 Spring Boot JPA @ManyToOne
的使用方法。
目录
- 开发环境
- 基础示例
- 总结
开发环境
- 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 '姓名',
`class_id` BIGINT UNSIGNED NOT NULL COMMENT '班级',
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
UNIQUE INDEX `number_UNIQUE` (`number` ASC) VISIBLE)
COMMENT = '学生表';
CREATE TABLE `class` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(30) NOT NULL COMMENT '班级名称',
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE)
COMMENT = '班级表';
如何创建 Spring Boot JPA 工程请参考:https://www.jianshu.com/p/e2b64d5c6107
创建 PO(Persistence Object) 对象。
package tutorial.spring.boot.domain;
import javax.persistence.*;
@Entity
@Table(name = "class")
public class ClassPO {
/**
* 自增主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 姓名
*/
private String name;
// 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;
/**
* 所属班级
*/
@ManyToOne(cascade = {CascadeType.PERSIST})
@JoinColumn(name = "class_id")
private ClassPO theClass;
// Getter、Setter 和 toString 方法略
}
- 创建继承 JpaRepository 的 Repository 接口类。
package tutorial.spring.boot.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import tutorial.spring.boot.domain.ClassPO;
@Repository
public interface ClassRepository extends JpaRepository {
}
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 {
}
- 编写单元测试。
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 org.springframework.dao.InvalidDataAccessApiUsageException;
import tutorial.spring.boot.domain.ClassPO;
import tutorial.spring.boot.domain.StudentPO;
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class StudentRepositoryTest {
@Autowired
private ClassRepository classRepository;
@Autowired
private StudentRepository studentRepository;
@Test
@Order(1)
void testInsert() {
// class 和 student 表中都没有记录
Assertions.assertThat(classRepository.count()).isEqualTo(0);
Assertions.assertThat(studentRepository.count()).isEqualTo(0);
// 创建 class 和 student 实体对象并将其绑定起来
ClassPO theClass = new ClassPO();
theClass.setName(RandomStringUtils.randomAlphanumeric(1, 10));
StudentPO student1 = new StudentPO();
student1.setNumber(RandomStringUtils.randomAlphanumeric(10));
student1.setName(RandomStringUtils.randomAlphanumeric(2, 20));
student1.setTheClass(theClass);
// 执行保存 student 实体对象的操作
studentRepository.save(student1);
// class 和 student 表中各新增一条记录
Assertions.assertThat(classRepository.count()).isEqualTo(1);
Assertions.assertThat(studentRepository.count()).isEqualTo(1);
// 再次创建一个 student 实体对象
StudentPO student2 = new StudentPO();
student2.setNumber(RandomStringUtils.randomAlphanumeric(9));
student2.setName(RandomStringUtils.randomAlphanumeric(2, 20));
student2.setTheClass(classRepository.findAll().get(0));
// 执行保存操作,因为使用 CascadeType.PERSIST 级联类型,因此级联保存会抛异常
Assertions.assertThatThrownBy(() -> studentRepository.save(student2))
.isInstanceOf(InvalidDataAccessApiUsageException.class);
}
@Test
@Order(2)
void testUpdate() {
// 依赖于之前的单元测试,此时 class 和 student 表中应各有一条记录
Assertions.assertThat(classRepository.count()).isEqualTo(1);
Assertions.assertThat(studentRepository.count()).isEqualTo(1);
ClassPO theClass = classRepository.findAll().get(0);
StudentPO student = studentRepository.findAll().get(0);
Assertions.assertThat(student.getTheClass().getId()).isEqualTo(theClass.getId());
Assertions.assertThat(student.getTheClass().getName()).isEqualTo(theClass.getName());
// 修改 student 数据
student.setNumber(student.getNumber().substring(1));
student.setName(RandomStringUtils.randomAlphanumeric(2, 20));
// 修改关联的 class 数据
student.getTheClass().setName(student.getTheClass().getName() + RandomStringUtils.randomAlphabetic(1));
// 保存修改后的数据
studentRepository.save(student);
ClassPO changedClass = classRepository.findAll().get(0);
StudentPO changedStudent = studentRepository.findAll().get(0);
// student 数据已改变
Assertions.assertThat(changedStudent.getNumber()).isEqualTo(student.getNumber());
Assertions.assertThat(changedStudent.getName()).isEqualTo(student.getName());
// 因为使用 CascadeType.PERSIST 级联类型,因此关联的 class 数据未改变
Assertions.assertThat(changedStudent.getTheClass().getName())
.isEqualTo(changedClass.getName())
.isEqualTo(theClass.getName())
.isNotEqualTo(student.getClass().getName());
}
@Test
@Order(3)
void testDelete() {
// 依赖于之前的单元测试,此时 class 和 student 表中应各有一条记录
Assertions.assertThat(classRepository.count()).isEqualTo(1);
Assertions.assertThat(studentRepository.count()).isEqualTo(1);
// 执行删除操作
StudentPO student = studentRepository.findAll().get(0);
studentRepository.delete(student);
// 因为使用 CascadeType.PERSIST 级联类型,因此关联的 class 数据未被级联删除
Assertions.assertThat(studentRepository.count()).isEqualTo(0);
Assertions.assertThat(classRepository.count()).isEqualTo(1);
}
}
总结
- 本示例中只建立了单向映射,即只在 many 方使用了
@ManyToOne
注解,没有在 one 方使用@OneToMany
注解配合; - 本实例中使用了【外键关联】的表结构设计策略,针对一对多和多对一的实体关系而言,还可以使用【表关联】的表结构设计策略;
- 单向映射
@OneToMany
的写法与本例类似。