QPing's blog

在 SpringBoot 中使用 JPA:一对一、一对多和多对多

JPA是Java Persist Api的简称,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。使用他可以很方便的将数据保存至数据库,只需要维护Bean对象而不用关心具体的sql实现。两表之间的关系包含:一对一、一对多和多对多,记录下具体改如何维护Bean对象。

以班级管理系统为例,老师和班级是多对多的关系,班级和学生是一对多的关系,学生和学生扩展表是一对一的关系。老师和班级之间通过中间表 test_class_teacher 来关联。

1、 一对一

学生表 test_student 和扩展表 test_student_ext 是一对一的关系,通过外键student_id关联。因为外键是在学生扩展表中,所以学生扩展表是关系的维护端,而学生表是被维护端。维护端和被维护端的区别是:维护端需要指明怎么两表通过什么连接,即指定外键名称,而被维护端只需要指定 mappedBy 维护端的哪个属性。

以下代码中使用了 lombok 来省略 get、set 方法。
@Data
@Entity
@Table(name = "test_student", schema = "test")
public class TestStudent {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Basic
@Column(name = "name")
private String name;

@Basic
@Column(name = "gender")
private Integer gender;

// mappedBy指定在TestStudentExt中通过哪个属性维护关系
@OneToOne(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private TestStudentExt studentExt;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestStudent that = (TestStudent) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(gender, that.gender);
}

@Override
public int hashCode() {
return Objects.hash(id, name, gender);
}
}


@Data
@Entity
@Table(name = "test_student_ext", schema = "test")
public class TestStudentExt {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Basic
@Column(name = "hobby")
private String hobby;

@Basic
@Column(name = "father_name")
private String fatherName;

@OneToOne(cascade=CascadeType.ALL) // TestStudentExt 是关系的维护端
@JoinColumn(name="student_id") // 指定外键的名称
private TestStudent student;

@Override
public int hashCode() {
return Objects.hash(id, hobby, fatherName, student);
}


public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestStudentExt that = (TestStudentExt) o;
return Objects.equals(id, that.id) &&
Objects.equals(hobby, that.hobby) &&
Objects.equals(fatherName, that.fatherName) &&
Objects.equals(student, that.student);
}
}



创建 Dao 对象,Dao 对象很简单,只要继承 JpaRepository,以下省略 Dao 书写。

public interface TestStudentRepository extends JpaRepository<TestStudent, Integer> {
}


测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class MyTest {

@Autowired
TestStudentExtRepository testStudentExtRepository;

@Autowired
TestStudentRepository testStudentRepository;

@Test
public void testOneToOne() {

TestStudentExt ext = new TestStudentExt();
ext.setFatherName("小明爸爸");
ext.setHobby("弹钢琴");

TestStudent stu = new TestStudent();
stu.setName("小明");
stu.setGender(1);
stu.setStudentExt(ext);
// 保存被维护端对象时,需要在set一次 ext.setStudent(stu);

testStudentRepository.save(stu);
}
}


在控制台中可以看到两条insert语句,同时保存了学生表和学生扩展表。

2、 一对多

班级表 test_class 和 学生表 test_student 是一对多的关系。通过外键 class_id 关联。因为外键在 test_student中,所以学生对象是维护端。

@Data
@Entity
@Table(name = "test_class", schema = "test")
public class TestClass {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Basic
@Column(name = "class_name")
private String className;

@Basic
@Column(name = "class_no")
private Integer classNo;

@OneToMany(mappedBy = "testClass", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<TestStudent> students;

@Override
public int hashCode() {
return Objects.hash(id, className, classNo);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestClass testClass = (TestClass) o;
return Objects.equals(id, testClass.id) &&
Objects.equals(className, testClass.className) &&
Objects.equals(classNo, testClass.classNo);
}
}


学生和班级的关系是多对一,使用@ManyToOne注解。

public class TestStudent {

// ......同上略.....

@ManyToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="class_id")
private TestClass testClass;

}


测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class MyTest {

@Autowired
TestClassService testClassService;

@Autowired
TestClassRepository testClassRepository;

@Test
public void testOneToMany(){
TestClass testClass = new TestClass();
testClass.setClassName("一年一班");
testClass.setClassNo(1);

TestStudent stu1 = new TestStudent();
stu1.setName("小明");
stu1.setGender(1);
stu1.setTestClass(testClass);

TestStudent stu2 = new TestStudent();
stu2.setName("小李");
stu2.setGender(2);
stu2.setTestClass(testClass);


Set<TestStudent> students = Sets.newConcurrentHashSet();
students.add(stu1);
students.add(stu2);

testClass.setStudents(students);

testClassRepository.save(testClass);
} }


控制台显示保存成功

3、多对多

班级表 test_class 和教师表 test_teacher是多对多的关系,一个老师可以教多个班级,一个班级也有多个老师。他们之间通过中间表 test_class_teacher 关联。通过中间表关联的使用 @JoinTable 来维护关系。

@Data
@Entity
@Table(name = "test_teacher", schema = "test")
public class TestTeacher {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Basic
@Column(name = "name")
private String name;

@Basic
@Column(name = "age")
private Integer age;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "test_class_teacher", joinColumns = @JoinColumn(name="teacher_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "class_id",referencedColumnName = "id"))
private Set<TestClass> testClasses;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestTeacher that = (TestTeacher) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(age, that.age);
}

@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
}


public class TestClass {
    
    // ...同上略...

    @ManyToMany(mappedBy = "testClasses", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<TestTeacher> teachers;

}


@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class MyTest {

@Autowired
TestTeacherRepository testTeacherRepository;

@Test
public void testManyTOMany(){

TestClass classNo3 = new TestClass();
classNo3.setClassName("一年3班");
classNo3.setClassNo(3);

TestClass classNo4 = new TestClass();
classNo4.setClassName("一年4班");
classNo4.setClassNo(4);

// 史老师教的课
Set<TestClass> shiClasses = Sets.newConcurrentHashSet();
shiClasses.add(classNo3);
shiClasses.add(classNo4);

TestTeacher teacherShi = new TestTeacher();
teacherShi.setName("史老师");
teacherShi.setTestClasses(shiClasses);

testTeacherRepository.save(teacherShi);
}
}


控制台显示保存成功:


注意:如果中间表中除了外键还有其他字段需要保存,则需要增加一个中间表对象,通过 @OneToMany 和@ManyToOne 拆解,具体教程链接

4、 错误记录

保存多对多时,报了一个 GTID consistency的错误,百度上说的都是mysql在执行create后再执行select才会报这个错误。将hibernate自动更新表的配置:ddl_auto由update改为validate后,提示t_teacher_class表不存在,原来是我将中间表 test_class_teacher 表名写错了,写成了 t_class_teacher, hibernate发现表不存在就自动create,结果就出错了,debug时间1小时特此记录。



以下评论区域 0 Comments


  • 昵称:
  • 邮箱:
  • 内容: