当前位置: 首页 > news >正文

[spring] Spring JPA - Hibernate 多表联查 3

[spring] Spring JPA - Hibernate 多表联查 3

这篇笔记基于:

  • [spring] Spring JPA - Hibernate 多表联查 1
  • [spring] Spring JPA - Hibernate 多表联查 2

进行实现

这是目前对于 Hibernate 学习的最后一步了,主要学习一下 many-to-many 的关系

之前的内容都覆盖的差不多了,整体来说这篇笔记会比较短,也没这么多代码

Many to Many

many-to-many 应该是日常生活中遇到的最多的情况,如 course <-> student,但是在这种情况下,如果需要使用外链去存储对应的关系,那么就会让数据变得非常难以管理,并且搜索的效率也会非常的低下。

目前比较常见的一个处理方法是使用 join table,去单独存储二者之间的 mapping

更新数据库

DROP SCHEMA IF EXISTS `hb-05-many-to-many`;

CREATE SCHEMA `hb-05-many-to-many`;

use `hb-05-many-to-many`;

SET FOREIGN_KEY_CHECKS = 0;

CREATE TABLE `instructor_detail` (
  `id` int NOT NULL AUTO_INCREMENT,
  `youtube_channel` varchar(128) DEFAULT NULL,
  `hobby` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;


CREATE TABLE `instructor` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) DEFAULT NULL,
  `last_name` varchar(45) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  `instructor_detail_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_DETAIL_idx` (`instructor_detail_id`),
  CONSTRAINT `FK_DETAIL` FOREIGN KEY (`instructor_detail_id`)
  REFERENCES `instructor_detail` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;


CREATE TABLE `course` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(128) DEFAULT NULL,
  `instructor_id` int DEFAULT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `TITLE_UNIQUE` (`title`),

  KEY `FK_INSTRUCTOR_idx` (`instructor_id`),

  CONSTRAINT `FK_INSTRUCTOR`
  FOREIGN KEY (`instructor_id`)
  REFERENCES `instructor` (`id`)

  ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;


CREATE TABLE `review` (
  `id` int NOT NULL AUTO_INCREMENT,
  `comment` varchar(256) DEFAULT NULL,
  `course_id` int DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `FK_COURSE_ID_idx` (`course_id`),

  CONSTRAINT `FK_COURSE`
  FOREIGN KEY (`course_id`)
  REFERENCES `course` (`id`)

  ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;


CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) DEFAULT NULL,
  `last_name` varchar(45) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;


CREATE TABLE `course_student` (
  `course_id` int NOT NULL,
  `student_id` int NOT NULL,

  PRIMARY KEY (`course_id`,`student_id`),

  KEY `FK_STUDENT_idx` (`student_id`),

  CONSTRAINT `FK_COURSE_05` FOREIGN KEY (`course_id`)
  REFERENCES `course` (`id`)
  ON DELETE NO ACTION ON UPDATE NO ACTION,

  CONSTRAINT `FK_STUDENT` FOREIGN KEY (`student_id`)
  REFERENCES `student` (`id`)
  ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

SET FOREIGN_KEY_CHECKS = 1;

跑完后的结果应该是这样的:

在这里插入图片描述

下面一排和之前的案例是一样的,这里就是新增了两张表格

实现学生实例 & 更新课程实例

简单的代码,写了蛮多遍了:

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

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

    public Student(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

下面是实现 many-to-many 的部分

更新课程 entity 获取学生
    @JoinTable(
            name = "course_student",
            joinColumns = @JoinColumn(name = "course_id"),
            inverseJoinColumns = @JoinColumn(name = "student_id")
    )
    private List<Student> students;

这个部分理解起来还是比较简单的:

  • JoinTable - 顾名思义,联表
  • joinColumns - 指的是当前 entity 在表中对应的 id
  • inverseJoinColumns - 目标 entity 的 id
更新学生 entity 获取课程
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = { CascadeType.PERSIST, CascadeType.DETACH,
                    CascadeType.MERGE, CascadeType.REFRESH })
    @JoinTable(
            name = "course_student",
            joinColumns = @JoinColumn(name = "student_id"),
            inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses;

student 和 course 的实现基本是一致的,但是因为 entity 的不同,所以 joinColumnsinverseJoinColumns 所 mapping 的值需要进行对调

更新 main
	private void createCourseAndStudents(AppDAO appDAO) {
		Course course = new Course("Table Tennis");
		Student student1 = new Student("John", "Doe", "john_doe@gmail.com");
		Student student2 = new Student("Mary", "Jane", "m_jane@gmail.com");

		course.addStudent(student1);
		course.addStudent(student2);

		System.out.println("Saving the course: " + course);
		System.out.println("associated students: " + course.getStudents());

		appDAO.save(course);
	}

这个写的应该也是蛮多了,没什么特别大的差异,主要就是跑一下证明一下代码没写错

效果如下:

在这里插入图片描述

寻找上 A 课的学生

这里最关键的是 courseId,找的是所有注册 :course_id 的课

DAOImpl 的实现:

    @Override
    public Course findCourseAndStudentByCourseId(int id) {
        TypedQuery<Course> query = entityManager.createQuery(
                "select c from Course c "
                        + "JOIN FETCH c.students "
                        + "where c.id = :data",
                Course.class
        );

        query.setParameter("data", id);

        return query.getSingleResult();
    }

main 的更新:

	private void findCourseAndStudents(AppDAO appDAO) {
		int id = 10;
		Course course = appDAO.findCourseAndStudentByCourseId(id);

		System.out.println(course);
	}

效果如下:

在这里插入图片描述

找 B 学生要上的所有课

这里最关键的是 studentId,找的是所有注册 :student_id 的课

DAOImpl 的实现如下:

    @Override
    public Student findStudentAndCoursesByStudentId(int id) {
        TypedQuery<Student> query = entityManager.createQuery(
                "select s from Student s "
                        + "JOIN FETCH s.courses "
                        + "where s.id = :data",
                Student.class
        );

        query.setParameter("data", id);

        return query.getSingleResult();
    }

main 的更新如下:

	private void findStudentAndCourses(AppDAO appDAO) {
		int id = 1;
		Student student = appDAO.findStudentAndCoursesByStudentId(id);

		System.out.println(student);
		System.out.println(student.getCourses());
	}

效果截图:

在这里插入图片描述

删除课程

这部分的代码其实没必要变动,一样可以跑:

在这里插入图片描述

主要还是因为 cascade type 里面没有什么 delete 的关系

删除学生

    @Override
    @Transactional
    public void deleteStydentById(int id) {
        Student student = entityManager.find(Student.class, id);

        if (student != null) {
            entityManager.remove(student);
        }
    }

remove 之前调用一下 clear 也可以手动断开所有的 foreign key 关联。一般来说 map 的比较好的,JPA 可以自动进行清理——也就是 demo app 跑的

但是如果是要用在 production 上的话,还是加一个 student.getCourses().clear(); 比较好

相关文章:

  • 人形机器人科普
  • Unity Shader 的编程流程和结构
  • 现代前端质量保障与效能提升实战指南
  • Noteexpress插入参考文献无法对齐
  • Linux生产者消费者模型
  • 快速求出质数
  • 【算法训练】单向链表
  • pandas中新增的case_when()方法
  • c++ 命名空间 namespace
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的数据验证:使用 Hibernate Validator
  • 数据建模流程: 概念模型>>逻辑模型>>物理模型
  • NSSCTF(MISC)——[NSSRound#4 SWPU]Type Message
  • 网络爬虫-2:基础与理论
  • 论文阅读笔记:Denoising Diffusion Probabilistic Models (3)
  • C语言中*a与a的区别和联系
  • 数据结构——B树、B+树、哈夫曼树
  • 安全测试理论
  • JavaScript 性能优化实战
  • 【云馨AI-大模型】自动化部署Dify 1.1.2,无需科学上网,Linux环境轻松实现,附Docker离线安装等
  • 【C++教程】setw()函数的使用方法
  • 广州一季度GDP为7532.51亿元,同比增长3%
  • 伊朗港口爆炸已造成25人死亡,灭火行动已近尾声
  • 王羲之《丧乱帖》在日本流传了1300年,将在大阪展23天
  • 第二十届华表奖提名名单公布,张译、王一博、马丽、郭帆等入围
  • A股三大股指涨跌互现,电力股走强,地产股冲高回落
  • 中国铝业首季“开门红”:净利润超35亿元,同比增加近六成