SpringBoot学习之旅(八)---JPA进阶篇之联表操作

阳光穿透心脏的1/2处 2022-03-06 03:03 395阅读 0赞

文章目录

        • 前言
        • 源码下载
        • 其他文章
        • 数据关联关系映射
        • 数据库创建
        • 数据库表映射对象
        • 操作及测试
          • 学生操作及测试
          • 其他功能测试
        • 常见问题
        • 总结

前言

前两节有说道JPA的基础操作及JPA自定义查询、修改、分页等操作,有兴趣的可以去看看,前面两章的所有操作都只是在操作一张表,但是真实的项目中,不可能所有的业务都只去做单标的操作,因为这不符合数据库表的设计法则,因此,各个表之间就总会发生那么点正当、或者不正当的关系;那么我们就得去理清各个要素之间的关系;

源码下载

点击开源中国下载源码

由于代码内容比较多多,建议优先下载代码,对着代码读更加快捷

其他文章

SpringBoot学习之旅(七)—JPA进阶篇之自定义查询、修改、分页


数据关联关系映射


























关系类型 Owning-Side Inverse-Side
one-to-one @OneToOne @OneToOne(mappedBy=“othersideName”)
one-to-many / many-to-one @ManyToOne @OneToMany(mappedBy=“xxx”)
many-to-many @ManyToMany @ManyToMany(mappedBy =“xxx”)

针对以上关系,举一下常见例子

  • 一对一
    一个学生只能对应一个档案编号及学号
  • 一对多/多对一
    一个班级可以容纳多个学生
  • 多对多
    一个学生可以选择多门学科,一个学科允许有多个学生学习

数据库创建

  • 表结构

    1. -- 学生信息表
    2. DROP TABLE IF EXISTS `student_info`;
    3. CREATE TABLE `student_info` (
    4. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '索引ID',
    5. `stu_name` varchar(20) NOT NULL COMMENT '学生姓名',
    6. `stu_sex` int(2) NOT NULL COMMENT '学生性别',
    7. `stu_age` int(3) NOT NULL COMMENT '学生年龄',
    8. `stu_class_id` int(5) NOT NULL COMMENT '学生班级ID',
    9. `stu_file_id` int(5) NOT NULL COMMENT '学生档案ID',
    10. PRIMARY KEY (`id`),
    11. KEY `student_info_class_id` (`stu_class_id`),
    12. KEY `student_info_file_id` (`stu_file_id`),
    13. CONSTRAINT `student_info_class_id` FOREIGN KEY (`stu_class_id`) REFERENCES `class_info` (`id`),
    14. CONSTRAINT `student_info_file_id` FOREIGN KEY (`stu_file_id`) REFERENCES `file_info` (`id`)
    15. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    16. -- ----------------------------
    17. -- Records of student_info
    18. -- ----------------------------
    19. INSERT INTO `student_info` VALUES ('1', '张三', '0', '7', '1', '1');
    20. INSERT INTO `student_info` VALUES ('2', '李四', '1', '7', '1', '2');
    21. INSERT INTO `student_info` VALUES ('3', '王五', '2', '7', '2', '3');
    22. -- 班级表
    23. DROP TABLE IF EXISTS `class_info`;
    24. CREATE TABLE `class_info` (
    25. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '班级ID',
    26. `class_name` varchar(255) DEFAULT NULL COMMENT '班级名称',
    27. PRIMARY KEY (`id`)
    28. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
    29. -- ----------------------------
    30. -- Records of class_info
    31. -- ----------------------------
    32. INSERT INTO `class_info` VALUES ('1', '一年级一班');
    33. INSERT INTO `class_info` VALUES ('2', '一年级二班');
    34. INSERT INTO `class_info` VALUES ('3', '一年级三班');
    35. INSERT INTO `class_info` VALUES ('4', '二年级一班');
    36. --档案表
    37. DROP TABLE IF EXISTS `file_info`;
    38. CREATE TABLE `file_info` (
    39. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '索引ID',
    40. `file_id` int(11) DEFAULT NULL COMMENT '档案编号',
    41. PRIMARY KEY (`id`)
    42. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    43. -- ----------------------------
    44. -- Records of file_info
    45. -- ----------------------------
    46. INSERT INTO `file_info` VALUES ('1', '100001');
    47. INSERT INTO `file_info` VALUES ('2', '100002');
    48. INSERT INTO `file_info` VALUES ('3', '100003');
    49. -- 科目表
    50. DROP TABLE IF EXISTS `subject_info`;
    51. CREATE TABLE `subject_info` (
    52. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '索引ID',
    53. `subject_name` varchar(50) DEFAULT NULL COMMENT '科目名称',
    54. PRIMARY KEY (`id`)
    55. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
    56. -- ----------------------------
    57. -- Records of subject_info
    58. -- ----------------------------
    59. INSERT INTO `subject_info` VALUES ('1', '语文');
    60. INSERT INTO `subject_info` VALUES ('2', '数学');
    61. INSERT INTO `subject_info` VALUES ('3', '英语');
    62. INSERT INTO `subject_info` VALUES ('4', '体育');
    63. INSERT INTO `subject_info` VALUES ('5', '音乐');
    64. -- 学生与科目关联表
    65. DROP TABLE IF EXISTS `subject_selection_info`;
    66. CREATE TABLE `subject_selection_info` (
    67. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '索引ID',
    68. `student_id` int(11) NOT NULL COMMENT '学生ID',
    69. `subject_id` int(11) NOT NULL COMMENT '科目ID',
    70. PRIMARY KEY (`id`),
    71. KEY `subject_selection_info_stu_id` (`student_id`),
    72. KEY `subject_selection_info_sub_id` (`subject_id`),
    73. CONSTRAINT `subject_selection_info_stu_id` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`),
    74. CONSTRAINT `subject_selection_info_sub_id` FOREIGN KEY (`subject_id`) REFERENCES `subject_info` (`id`)
    75. ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
    76. -- ----------------------------
    77. -- Records of subject_selection_info
    78. -- ----------------------------
    79. INSERT INTO `subject_selection_info` VALUES ('1', '1', '1');
    80. INSERT INTO `subject_selection_info` VALUES ('2', '1', '2');
    81. INSERT INTO `subject_selection_info` VALUES ('3', '1', '3');
    82. INSERT INTO `subject_selection_info` VALUES ('4', '1', '4');
    83. INSERT INTO `subject_selection_info` VALUES ('5', '2', '1');
    84. INSERT INTO `subject_selection_info` VALUES ('6', '2', '2');
    85. INSERT INTO `subject_selection_info` VALUES ('7', '2', '3');
    86. INSERT INTO `subject_selection_info` VALUES ('8', '2', '5');
    87. INSERT INTO `subject_selection_info` VALUES ('9', '3', '1');
    88. INSERT INTO `subject_selection_info` VALUES ('10', '3', '2');
    89. INSERT INTO `subject_selection_info` VALUES ('11', '3', '3');
  • 数据库ER图表
    watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1cGVuZ2ZlaTEwMDk_size_16_color_FFFFFF_t_70

数据库表映射对象

本文的所有对象均使用Lombok生成对象get set方法

  • 学生对象

    1. package com.lupf.springbootjpa.dbobject;
    2. import lombok.Data;
    3. import lombok.ToString;
    4. import javax.persistence.*;
    5. import java.io.Serializable;
    6. import java.util.List;
    7. @Data
    8. @Entity
    9. public class StudentInfo implements Serializable {
    10. @Id
    11. @GeneratedValue(strategy = GenerationType.IDENTITY)
    12. private Integer id;
    13. private String stuName;
    14. private Integer stuSex;
    15. private Integer stuAge;
    16. //---------以下配置是为了方便查询学生信息的时候一并查询出其关联的信息
    17. //由于学号及档案信息是一对一的关系,因此这里是一对一的
    18. @OneToOne(cascade = CascadeType.ALL)
    19. //name为当前表中列的名称 referencedColumnName标表示外键关联了对方的表内容
    20. @JoinColumn(name = "stu_file_id", referencedColumnName = "id")
    21. private FileInfo fileInfo;
    22. //对于学生来说,是多个学生对应一个班级,因此这里是一对多的关系
    23. //设置为CascadeType.MERGE时,当添加数据的时候,会自动插入class表中的数据
    24. @ManyToOne(cascade = CascadeType.MERGE)
    25. //name在表中的列
    26. @JoinColumn(name = "stu_class_id", referencedColumnName = "id")
    27. private ClassInfo classInfo;
    28. //这里的fetch要使用EAGER(全部抓取) LAZY懒加载会报错
    29. //由于一个学生可以选择多个科目 因此这里相对与科目来说是一对多的关系
    30. //设置为CascadeType.MERGE时,当添加数据的时候,会自动插入subject_selection_info表中的数据
    31. @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
    32. //由于学生表和所选的科目表是使用subject_selection_info关联的 因此这里需要设置他们的关联关系
    33. @JoinTable(name = "subject_selection_info", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "subject_id"))
    34. private List<SubjectInfo> subjectInfos;
    35. }
  • 科目对象

    1. package com.lupf.springbootjpa.dbobject;
    2. import com.alibaba.fastjson.annotation.JSONField;
    3. import lombok.Data;
    4. import javax.persistence.*;
    5. import java.io.Serializable;
    6. import java.util.List;
    7. @Data
    8. @Entity
    9. public class SubjectInfo implements Serializable {
    10. @Id
    11. @GeneratedValue(strategy = GenerationType.IDENTITY)
    12. private Integer id;
    13. private String subjectName;
    14. //这里需要加上反序列化忽略,因为StudentInfo关联了SubjectInfo 这里又关联了StudentInfo
    15. // 就出现了你中有我,我中有你的问题,从未导致反序列话的时候堆栈溢出(死循环)的问题
    16. @JSONField(serialize = false)
    17. //这里关联是为了满足,删除这里面的数据的时候,自动删除subject_selection_info并在学生与科目之间的关系
    18. @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
    19. @JoinTable(name = "subject_selection_info", joinColumns = @JoinColumn(name = "subject_id"), inverseJoinColumns = @JoinColumn(name = "student_id"))
    20. private List<StudentInfo> studentInfos;
    21. }
  • 班级对象

    1. package com.lupf.springbootjpa.dbobject;
    2. import lombok.Data;
    3. import javax.persistence.*;
    4. import java.util.List;
    5. import java.util.Objects;
    6. import java.util.Set;
    7. @Data
    8. @Entity
    9. public class ClassInfo {
    10. @Id
    11. @GeneratedValue(strategy = GenerationType.IDENTITY)
    12. private Integer id;
    13. @Column(insertable = false, updatable = false)
    14. private String className;
    15. // @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    16. // @JoinColumn(name = "id")
    17. // private List<StudentInfo> studentInfos;
    18. }
  • 档案对象

    1. package com.lupf.springbootjpa.dbobject;
    2. import lombok.Data;
    3. import javax.persistence.*;
    4. import java.util.Objects;
    5. @Data
    6. @Entity
    7. public class FileInfo {
    8. @Id
    9. @GeneratedValue(strategy = GenerationType.IDENTITY)
    10. private Integer id;
    11. private Integer fileId;
    12. }
  • 学生及科目关系映射对象

    1. package com.lupf.springbootjpa.dbobject;
    2. import lombok.Data;
    3. import javax.persistence.*;
    4. @Data
    5. @Entity
    6. public class SubjectSelectionInfo {
    7. @Id
    8. @GeneratedValue(strategy = GenerationType.IDENTITY)
    9. private Integer id;
    10. @Column(name = "student_id")
    11. private Integer studentId;
    12. @Column(name = "subject_id")
    13. private Integer subjectId;
    14. @ManyToOne(cascade = CascadeType.ALL)
    15. //name在表中的列
    16. @JoinColumn(name = "student_id", referencedColumnName = "id", insertable = false, updatable = false)
    17. private StudentInfo studentInfo;
    18. @ManyToOne(cascade = CascadeType.ALL)
    19. //name在表中的列
    20. @JoinColumn(name = "subject_id", referencedColumnName = "id", insertable = false, updatable = false)
    21. private SubjectInfo subjectInfo;
    22. }

操作及测试

学生操作及测试
  • StudentRepository

    1. @Repository
    2. public interface StudentRepository extends JpaRepository<StudentInfo, Integer> {
    3. //根据班级ID查询学生信息
    4. @Query(value = "select a.* from student_info as a where stu_class_id = ?1", nativeQuery = true)
    5. List<StudentInfo> findByStuClassId(Integer classId);
    6. }
  • StudentService

    1. package com.lupf.springbootjpa.service;
    2. import com.lupf.springbootjpa.dbobject.StudentInfo;
    3. import java.util.List;
    4. /**
    5. * 学生的Service
    6. */
    7. public interface StudentService {
    8. //添加学生 并添加学生的档案信息、分班信息以及学生选择的科目
    9. StudentInfo save(StudentInfo studentInfo);
    10. //查询某个学生的信息
    11. StudentInfo findById(Integer id);
    12. //删除学生信息
    13. void deleteById(Integer id);
    14. //修改信息
    15. StudentInfo update(StudentInfo studentInfo);
    16. //根据班级名称查询学生信息
    17. List<StudentInfo> findByStuClassId(Integer classId);
    18. }
  • StudentServiceImpl

    1. package com.lupf.springbootjpa.service.impl;
    2. import com.lupf.springbootjpa.dbobject.StudentInfo;
    3. import com.lupf.springbootjpa.repository.StudentRepository;
    4. import com.lupf.springbootjpa.service.StudentService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Service;
    7. import java.util.List;
    8. import java.util.Optional;
    9. @Service
    10. public class StudentServiceImpl implements StudentService {
    11. @Autowired
    12. StudentRepository studentRepository;
    13. @Override
    14. public StudentInfo findById(Integer id) {
    15. Optional<StudentInfo> studentInfoOptional = studentRepository.findById(id);
    16. if (null != studentInfoOptional && studentInfoOptional.isPresent())
    17. return studentInfoOptional.get();
    18. return null;
    19. }
    20. @Override
    21. public void deleteById(Integer id) {
    22. studentRepository.deleteById(id);
    23. }
    24. @Override
    25. public StudentInfo update(StudentInfo studentInfo) {
    26. return studentRepository.save(studentInfo);
    27. }
    28. @Override
    29. public List<StudentInfo> findByStuClassId(Integer classId) {
    30. List<StudentInfo> studentInfos = studentRepository.findByStuClassId(classId);
    31. return studentInfos;
    32. }
    33. @Override
    34. public StudentInfo save(StudentInfo studentInfo) {
    35. StudentInfo saveStudentInfo = studentRepository.save(studentInfo);
    36. return saveStudentInfo;
    37. }
    38. }
  • 学生测试用例

    1. @RunWith(SpringRunner.class)
    2. @SpringBootTest
    3. @Slf4j
    4. public class StudentServiceImplTest {
    5. @Autowired
    6. StudentService studentService;
    7. @Test
    8. public void findById() {
    9. //查询学生信息会一并查处用户所属的班级信息及选择的科目信息
    10. StudentInfo studentInfo = studentService.findById(1);
    11. log.info(JSON.toJSONString(studentInfo));
    12. }
  1. @Test
  2. public void deleteById() {
  3. //用户时会级联删除学生信息 学生档案信息 学生科目选择信息
  4. studentService.deleteById(16);
  5. }
  6. @Test
  7. public void update() {
  8. StudentInfo studentInfo = studentService.findById(20);
  9. studentInfo.setStuAge(8);
  10. ClassInfo classInfo = new ClassInfo();
  11. //这个classID必须在class_info表中存在,否则这里会报错
  12. classInfo.setId(2);
  13. studentInfo.setClassInfo(classInfo);
  14. studentService.update(studentInfo);
  15. }
  16. @Test
  17. public void save() {
  18. StudentInfo studentInfo = new StudentInfo();
  19. studentInfo.setStuAge(8);
  20. studentInfo.setStuName("唐七");
  21. studentInfo.setStuSex(1);
  22. //级联更新 这里设置的id属性在添加学生信息的时候会一并将这部分数据添加到class_info表
  23. ClassInfo classInfo = new ClassInfo();
  24. //这个classID必须在class_info表中存在,否则这里会报错
  25. classInfo.setId(2);
  26. studentInfo.setClassInfo(classInfo);
  27. //绑定并插入学生的用户信息
  28. //这里是级联插入 添加的时候自动插入相关的档案信息
  29. FileInfo fileInfo = new FileInfo();
  30. fileInfo.setFileId(100004);
  31. studentInfo.setFileInfo(fileInfo);
  32. //级联更新学生选择的科目信息
  33. //语文
  34. SubjectInfo subjectInfo = new SubjectInfo();
  35. //这里的ID必须在subject_info表中存在,否则这里会报错
  36. subjectInfo.setId(1);
  37. //数学
  38. SubjectInfo subjectInfo2 = new SubjectInfo();
  39. //这里的ID必须在subject_info表中存在,否则这里会报错
  40. subjectInfo2.setId(2);
  41. //英语
  42. SubjectInfo subjectInfo3 = new SubjectInfo();
  43. //这里的ID必须在subject_info表中存在,否则这里会报错
  44. subjectInfo3.setId(3);
  45. List<SubjectInfo> subjectInfos = new ArrayList<>();
  46. subjectInfos.add(subjectInfo);
  47. subjectInfos.add(subjectInfo2);
  48. subjectInfos.add(subjectInfo3);
  49. studentInfo.setSubjectInfos(subjectInfos);
  50. studentService.save(studentInfo);
  51. }
  52. @Test
  53. public void findByStuClassId() {
  54. List<StudentInfo> s = studentService.findByStuClassId(2);
  55. String va = JSON.toJSONString(s, SerializerFeature.DisableCircularReferenceDetect);
  56. log.info(va);
  57. }
  58. }
  • 测试详情

    • 根据ID查询用户
      watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1cGVuZ2ZlaTEwMDk_size_16_color_FFFFFF_t_70 1
    • 插入测试
      下图可见,插入学生信息的时候,同步插入了档案信息、班级信息及选择科目的关联关系watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1cGVuZ2ZlaTEwMDk_size_16_color_FFFFFF_t_70 2
      20190319153642420.
      20190319153724670.
      20190319154030301.
  • 修改测试
    watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1cGVuZ2ZlaTEwMDk_size_16_color_FFFFFF_t_70 3
    20190319160204742.
  • 学生信息删除测试
    删除学生的时候会同步删除档案信息及学生管理的科目信息
    watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1cGVuZ2ZlaTEwMDk_size_16_color_FFFFFF_t_70 4
其他功能测试

其他对象为一些基础性的操作,没有什么特殊性,因此,这里就不占用篇幅,需要了解的可以下载源码查看。

常见问题

  • could not initialize proxy - no Session
    20190319160858885.
    由于对象中管理了其他的对象,因此这里使用懒加载会出现未找到session的问题,需要将LAZY改为EAGER
    20190319161025122.
  • java.lang.StackOverflowError
    为Json反序列化导致的堆栈溢出
    一般出现ManyToMany下,因为两个对象是你中有我,我中有你的关系,从而反序列化时导致递归堆栈溢出;
    解决方案:可以使用@JSONField(serialize = false)注解忽略对应对象的反序列化
    20190319161647563.
  • Multiple representations of the same entity […] are being merged. Detached: […]; Detached: […]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity […] are being merged. Detached: […]; Detached: […]
    更新修改的时候,合并对象的问题
    20190319162159804.
    以上示例为将用户从其他班调整到id为3的班级,可能会报以上的错
    解决方案:
    在application.yml中添加以下配置

    1. spring:
    2. jpa:
    3. show-sql: true
    4. properties:
    5. hibernate:
    6. event:
    7. merge:
    8. entity_copy_observer: allow

总结

到此,使用JPA操作关联表已经完了,可以看出,使用JPA,我们只需要关注于业务流程,几乎不用、或者很少去关注数据库那边的操作,从手动档直接换成了自动档,从而让我们有更多的精力去写业务层那一块儿的代码。

发表评论

表情:
评论列表 (有 0 条评论,395人围观)

还没有评论,来说两句吧...

相关阅读