5

场景

假设有俩个实体用户和专业课(关系:多对多)

现需求用户可以新增编辑专业课,但在专业课模块中同样可以新增用户和编辑用户

实体

Course:

@Entity
public class Course implements YunzhiEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "courses")
    private List<User> users;
    
    public Course() {}
}

User:

@Entity

public class User implements YunzhiEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)

    private Long id;

    private String name;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @ManyToMany
    private List<Course> courses;

    public User() {}
}

最初实现

思路
新增:新增比较简单,直接操作中间表把数据保存到中间表
更新:更新变向的理解也就是新增,所以就先把原来的删除在新增

由于mappedBy映射关系是由一方维护,所以需要后台单独处理中间表中的数据

实现如下:
image.png

在更新中把中间表相关数据先移出,然后在重新建立关系,如下:

image.png

将新的数据,保存到中间表中。在此使专业课和用户重新建立新的关系

在新增时只需save时在调用updateUsersOfCourse(Course course)这个方法就可以

这种实现方式看起来比较笨拙,潘老师提示,Hibernate不会这么傻,需要这么麻烦,肯定有新的实现方式

新的实现

使用Hibernate的关联删除,去除手动操作中间表的代码,看起来更简洁,更新更容易理解

image.png

修改实体 Course实体与User实体,不使用由一方维护的mappedBy来映射关系,如下:
Course:

@Entity  
public class Course implements YunzhiEntity {  
  
     ......
  
    @ManyToMany(cascade ={CascadeType.REMOVE})  
    @JoinTable(name="User_Courses",joinColumns=@JoinColumn(name="Courses_id"),inverseJoinColumns=@JoinColumn(name="Users_id"))  
    @JsonView(users.class)  
    private List<User> users;  
    
    ......
    
 }

User:

@Entity  
public class User implements YunzhiEntity {

    ......
    
    @ManyToMany(cascade ={CascadeType.REMOVE})  
    @JoinTable(name="User_Courses",joinColumns=@JoinColumn(name="Users_id"),inverseJoinColumns=@JoinColumn(name="Courses_id"))  
    @JsonView(courses.class)  
    private List<Course> courses;
    
    ......
    
}

使用mappedBy来建立俩实体间的关系,它是一方来维护的,就像 ‘A中有B或B中有A’,是单向的。所以直接使用cascade ={CascadeType.REMOVE}级联删除,不好使;

向上述例子,‘A中有B,B中有A’,是双向的。“双向结构+双向维护外键”。这个双向维护外键,指无论是对course.users还是user.courses修改,保存后均会影响中间表的数据。

参考文档 stackoverflow

小测试

建立三个实体 A B C
A:B = ManyToMany(上述新方式)
A:C = ManyToMany(普通的方式mappedBy)

同样执行多对多的更新操作。对比两个更新最终生成的SQl语句有何不同。

A:

image.png

B:

image.png

C:

image.png

源码地址github

使用Postman向后台发送请求,执行更新操作,得到SQl语句,如下:

上述新方法生成的SQL语句:

image.png

普通的方式mappedBy生成的SQL语句:

image.png

发现普通的方式mappedBy生成的SQL语句比较多,多的是更新时操作中间表SQL,为数据库增加了无形的压力。

潘佳琦
894 声望34 粉丝

为 API 生,为框架死,为 debug 奋斗一辈子;