您的位置:首页 > 教育 > 锐评 > 免费字体设计网站_如何推广网站完善火2星_鹤壁seo公司_百度seo排名优化助手

免费字体设计网站_如何推广网站完善火2星_鹤壁seo公司_百度seo排名优化助手

2025/11/5 3:34:59 来源:https://blog.csdn.net/weixin_42938619/article/details/146361978  浏览:    关键词:免费字体设计网站_如何推广网站完善火2星_鹤壁seo公司_百度seo排名优化助手
免费字体设计网站_如何推广网站完善火2星_鹤壁seo公司_百度seo排名优化助手

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

这篇笔记基于 [spring] Spring JPA - Hibernate 多表联查 1 之上进行的实现,主要的 skeleton 都在那里了,代码不一定会全部 cv

这篇主要实现的是 one-to-many 和 many-to-one 的关系。这个实现起来和上一篇里用 @OneToOne 以及 @OneToOne(mappedBy) 的方向是一致的,同样是一个表里设有 foreign key(一半在 many 的那个实例中),另一个没有 foreign key 的通过 mappedBy 去做 join 搜索

one to many & many to one

这次新增的实例为 course,这里的逻辑一个老师可以很多课——这里不考虑同一个课会被多个老师教,即不同 section 的条件

⚠️:不需要使用 cascading delete,即删除老师不需要联动删除课程,反之亦然

具体图如下:

instructor
course1
course2
course3

⚠️:这会是一个 bi-directional 的关系

更新数据库

这里也同样提供 sql:

DROP SCHEMA IF EXISTS `hb-03-one-to-many`;CREATE SCHEMA `hb-03-one-to-many`;use `hb-03-one-to-many`;SET FOREIGN_KEY_CHECKS = 0;DROP TABLE IF EXISTS `instructor_detail`;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;DROP TABLE IF EXISTS `instructor`;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;DROP TABLE IF EXISTS `course`;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;SET FOREIGN_KEY_CHECKS = 1;

运行后的结果应该如下:

在这里插入图片描述

⚠️:如果 spring boot 遇到了连接问题,可以查看 [spring] Spring JPA - Hibernate 多表联查 1 中的 reference 部分解决

代码设置

实现 Course Entity
package com.example.demo.entity;import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.Cascade;@Data
@NoArgsConstructor
@Entity
@Table(name = "course")
public class Course {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id")private int id;@Column(name = "title")private String title;@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE, CascadeType.REFRESH })@JoinColumn(name = "instructor_id")private Instructor instructor;public Course(String title) {this.title = title;}@Overridepublic String toString() {return "Course{" +"id=" + id +", title='" + title + '\'' +'}';}
}

⚠️:instructor-to-course 是 one-to-many 的关系,正常情况下是将 foreign key 放到 many 的这个部分——这个案例中也就是 course 里,所以这里才会用 @JoinColumn(name = "instructor_id")

更新 instructor entity
package com.example.demo.entity;import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.List;@Entity
@Table(name = "instructor")
@Data
@NoArgsConstructor
public class Instructor {@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;// set up mapping to InstructDetail@OneToOne(cascade = CascadeType.ALL)@JoinColumn(name = "instructor_detail_id")private InstructorDetail instructorDetail;@OneToMany(mappedBy = "instructor",cascade = { CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE, CascadeType.REFRESH })private List<Course> courses;public Instructor(String firstname, String lastname, String email) {this.firstname = firstname;this.lastname = lastname;this.email = email;}@Overridepublic String toString() {return "Instructor{" +"instructorDetail=" + instructorDetail +", email='" + email + '\'' +", lastname='" + lastname + '\'' +", firstname='" + firstname + '\'' +", id=" + id +'}';}// add convenience methods for bi-directional relationshippublic void add(Course course) {if (courses == null) {courses = new ArrayList<>();}courses.add(course);course.setInstructor(this);}
}

⚠️:这里主要更新的部分是 private List<Course> courses; 以及其对应的注解,在上面的图解中已经表明了,一个老师可以上好几门课,所以这里需要用一个 List 去存储对应的数据

👀:这里新增了一个 util 方法,即 public void add(Course course) ,主要是可以方便的添加课程

update properties file

主要是需要更新一下数据库相关的部分,也就是 datasource.url

spring.datasource.url=jdbc:mysql://localhost:3306/hb-01-one-to-one-uni

添加有课的老师

这里主要更新的是 cli 部分的代码:

	private void createInstructorWithCourses(AppDAO appDAO) {// create the instructorInstructor instructor = new Instructor("Peter", "Parker", "peter.p@gmail.com");InstructorDetail instructorDetail = new InstructorDetail("http://www.example.com", "Piano");Course course1 = new Course("Guitar");Course course2 = new Course("Paint ball");// associate the objectsinstructor.setInstructorDetail(instructorDetail);instructor.add(course1);instructor.add(course2);System.out.println("Saving instructor: " + instructor);System.out.println("Courses: " + instructor.getCourses());appDAO.save(instructor);System.out.println("Done!");}

效果如下:

在这里插入图片描述

在这里插入图片描述

fetch 类型

上篇有简单的题过 fetch 的类型分为 eager 和 lazy 两种,二者的区别如下:

  • eager 会获取所有的关联实例

    以当前案例来说,在获取 instructor 的数据后,它同时会获取所有关联的 courses

    当存在多个 instructors 的情况下,这就类似于执行了下面这个 query:

    SELECT * FROM instructor;SELECT * FROM course WHERE instructor_id = 1;
    SELECT * FROM course WHERE instructor_id = 2;
    SELECT * FROM course WHERE instructor_id = 3;
    

    这也就遇到了比较经典的 N+1 query 问题——即获取 1 次 instructors 数据,同时调用 instructors 长度(N)个 query 去获取关联的数据

    💡:这种情况下,要做优化的话可以通过 @Query("SELECT i FROM Instructor i JOIN FETCH i.courses") 去获取整个 instructor list,或者通过 @EntityGraph 去进行优化

  • lazy 默认情况下不会获取相关联的数据,只有在调用/访问对应的属性/getter 时,才会去重新通过 query 获取对应的数据

    ⚠️:@OneToMany 的默认 FetchType 是 lazy

    一般来说这种实现更好,不过它也会存在两个问题:

    1. 需要一个打开的 hibernate session

      这个情况下来说,发生在当前方法已经返回了 instructor,然后 hibernate session 关闭。其他的方法——在非 @Transactional 的方法中去访问 instructor 中的 courses,就会抛出异常

      触发方式如下:

      更新 main 中的代码:

      private void findInstructorWithCourses(AppDAO appDAO) {int id = 1;System.out.println("Finding instructor id: " + id);Instructor instructor = appDAO.findInstructorById(id);System.out.println("instructor: " + instructor);System.out.println("associated courses:" + instructor.getCourses());
      }
      

      这是在 main 方法内调用的,hibernate session 已经关闭。默认情况下是进行 lazy fetch,所以 instructor 相关联的 course 也不会被获取

      这个时候就会触发报错:

      在这里插入图片描述

      这个时候只要将 FetchType 改成 eager:

      @OneToMany(mappedBy = "instructor",fetch = FetchType.EAGER,cascade = { CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE, CascadeType.REFRESH })
      private List<Course> courses;
      

      那么数据就会正常渲染:

      在这里插入图片描述

    2. @Transactional 的方法中去循环访问每个 instructor 的 courses

      这个依旧是会造成 n+1 query 的问题

    一般来说,常见的处理方式是在 entity 中,使用 Lazy 注解,这样调用 findAll() 只会跑一条 query;同时也可以新增加一个方法,通过上面的 @Query 去实现 List<Instructor> findAllWithCourses(); 这样的方法,也能够有效地减缓数据库的调用

Lazy Fetch 的使用案例

这里会将 instructor 中的 course 获取方式设置成 lazy——即默认情况

@OneToMany(mappedBy = "instructor",fetch = FetchType.LAZY,cascade = { CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE, CascadeType.REFRESH })
private List<Course> courses;
直接通过 foreign key 获取

首先,因为 foreign key 设置在 course 上,所以可以直接通过 FK 去寻找一个 instructor 教的所有课,案例如下:

  1. 新增加一个方法获取 instructor

    public interface AppDAO {List<Course> findCoursesByInstructorId(int id);
    }
    @Override
    public List<Course> findCoursesByInstructorId(int id) {TypedQuery<Course> query = entityManager.createQuery("from Course where instructor.id = :data", Course.class);query.setParameter("data", id);return query.getResultList();
    }
    
  2. 使用新的方法

    private void findCoursesForInstructor(AppDAO appDAO) {int id = 1;System.out.println("finding instructor id: " + id);Instructor instructor = appDAO.findInstructorById(id);System.out.println("instructor: " + instructor);// retrieve courses for the instructorSystem.out.println("finding courses for instructor id: " + id);List<Course> courses = appDAO.findCoursesByInstructorId(id);instructor.setCourses(courses);System.out.println("associated courses: " + instructor.getCourses());System.out.println("Done");
    }
    
  3. 结果:

    在这里插入图片描述

join fetch

也就是上面提到的常见解决方法

这里新增加一个方法获取 instructor

    public Instructor findInstructorByIdJoinFetch(int id) {return null;}    @Overridepublic Instructor findInstructorByIdJoinFetch(int id) {TypedQuery<Instructor> query = entityManager.createQuery("select i from Instructor i "+ "JOIN FETCH i.courses "+ "where i.id = :data", Instructor.class);query.setParameter("data", id);return query.getSingleResult();}
    private void findInstructorWithCoursesJoinFetch(AppDAO appDAO) {int id = 1;System.out.println("finding instructor id: " + id);Instructor instructor = appDAO.findInstructorByIdJoinFetch(id);System.out.println(instructor);System.out.println("associated courses: " + instructor.getCourses());System.out.println("Done");}

调用结果如下:

在这里插入图片描述

更新实例(Update 操作)

更新 instructor

这里通过 merge 实现,代码修改如下:

    void update(Instructor instructor);
    @Override@Transactionalpublic void update(Instructor instructor) {entityManager.merge(instructor);}
	private void updateInstructor(AppDAO appDAO) {int id = 1;System.out.println("Finding instructor id: " + id);Instructor instructor = appDAO.findInstructorByIdJoinFetch(id);System.out.println("Updating instructor id: " + id);instructor.setLastname("TESTER");appDAO.update(instructor);System.out.println("Done");}

效果:

在这里插入图片描述

在这里插入图片描述在这里插入图片描述
更新 course

核心逻辑是一样的

    Course findCourseById(int id);void update(Course course);
    @Overridepublic Course findCourseById(int id) {return entityManager.find(Course.class, id);}@Override@Transactionalpublic void update(Course course) {entityManager.merge(course);}
	private void updateCourse(AppDAO appDAO) {int id = 10;System.out.println("Finding course id: " + id);Course course = appDAO.findCourseById(id);System.out.println("Updating course id: " + id);course.setTitle("New Course");appDAO.update(course);}

在这里插入图片描述

前后对比:

在这里插入图片描述在这里插入图片描述

复习一下 merge 的操作:

会将 detached entity 的变更数据 复制到当前 管理中的 entity。如果数据库中已有该 entity,则会 更新 现有记录;如果没有,则会 插入 新记录

更新删除实例

这里主要的操作就是将所有的 course 删除掉,否则会有 foreign key constraint 的问题

    @Override@Transactionalpublic void deleteInstructorById(int id) {Instructor instructor = this.findInstructorById(id);if (instructor != null) {List<Course> courses = instructor.getCourses();for (Course course : courses) {course.setInstructor(null);}entityManager.remove(instructor);}}

这里还是 n+1 query 的操作,可以通过手写一些 query 去提升效果

新增 uni-directional 的 Review 实例

这个基本上就是把东西重复一下,练一下手

主要 course --> review 是一个单方面的,one-to-many 的关系。当然,具体实现也是根据之前说的,在 many 的实例上设置 foreign key

数据库修改

sql 脚本如下:

DROP SCHEMA IF EXISTS `hb-04-one-to-many-uni`;CREATE SCHEMA `hb-04-one-to-many-uni`;use `hb-04-one-to-many-uni`;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;SET FOREIGN_KEY_CHECKS = 1;

在这里插入图片描述

实现 review 实例

代码基本上一致,

package com.example.demo.entity;import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@Entity
@Table(name = "review")
public class Review {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id")private int id;@Column(name = "comment")private String comment;public Review(String comment) {this.comment = comment;}@Overridepublic String toString() {return "Review{" +"id=" + id +", comment='" + comment + '\'' +'}';}
}

⚠️:这里 review 并没有和 course 建立任何关系,所以是 course --> review 的单方面联系

重构 course

如上面提到的,新增 one-to-many 的关系

public class Course {//...@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)@JoinColumn(name = "course_id")private List<Review> reviews;//...public void addReview(Review review) {if (reviews == null) {reviews = new ArrayList<>();}reviews.add(review);}
}
更新 DAO 和 main

这里就基本还是那几个变化了:

    @Override@Transactionalpublic void save(Course course) {entityManager.persist(course);}
	private void createCourseAndReviews(AppDAO appDAO) {Course course = new Course("Minesweeper");course.addReview(new Review("Finish advanced level in 100s!"));course.addReview(new Review("Great course!"));course.addReview(new Review("Love it!"));course.addReview(new Review("Average..."));System.out.println("Saving the course...");System.out.println(course);System.out.println(course.getReviews());appDAO.save(course);}

结果如下:

在这里插入图片描述

获取 course 和 review

这里也使用 join 去做搜索

    @Overridepublic Course findCourseAndReviewsByCourseId(int id) {TypedQuery<Course> query = entityManager.createQuery("select c from Course c "+ "JOIN FETCH c.reviews "+ "where c.id = :data",Course.class);query.setParameter("data", id);return query.getSingleResult();}
删除 course 和 review

这个实现更简单了,因为有 cascading delete,所以直接删即可

	private void deleteCourseAndReviews(AppDAO appDAO) {int id = 10;System.out.println("Deleting course id: " + id);appDAO.deleteCourseById(id);}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com