JPA 表关联注解的实验

2019-12-29  本文已影响0人  DimonHo

我们都知道数据库表与表之间有如下四种关系

1:1(一对一,相应的注解叫@OneToOne)
1:n(一对多,相应的注解叫@OneToMany)
n:1(多对一,相应的注解叫@ManyToOne)
n:n(多对多,相应的注解叫@ManyToMany)


环境说明:

首先我们在讲解之前,我们先约定几个表关系

image.png
然后我在代码中定义了一个抽象的entity父类AbstractEntity ,所有的entity都继承于它
@Data
@Accessors(chain = true)
@MappedSuperclass
@JsonIgnoreProperties(value = {"handler","hibernateLazyInitializer","fieldHandler"})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") //防止entity互相引用导致json解析进入死循环
public abstract class AbstractEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // 自增主键
    Long id;

    @Temporal(TemporalType.TIMESTAMP)  // 时间格式:YYYY-MM-dd HH:mm:ss
    @Column(name = "gmt_create", columnDefinition = "timestamp DEFAULT CURRENT_TIMESTAMP comment '创建时间'")
    Date gmtCreate;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "gmt_modified", columnDefinition = "timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '最后修改时间'")
    Date gmtModified;
}

在讲解这四个注解之前,我们还需要先了解一下@JoinColumn这个注解
@JoinColumn用于注释表中的字段,与@Column不同的是它要保存表与表之间关系的字段;


下面我们分别来对四个注解来进行讲解

一. @OneToOne

表 employees 和 address 是一对一的关系
在jpa中是这样定义的:

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
public class Address extends AbstractEntity {
     // ...其它字段

    @OneToOne
    @JoinColumn(name = "emp_id")
    private Employee employee;
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "employees")
public class Employee extends AbstractEntity {
    @OneToOne
    @JoinColumn(name = "addr_id")
    private Address address;

    // ...
}

@OneToOne有如下几个可选参数

targetEntity=void.class,关联的实体类。
cascade={},可选值有CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.DETACH
fetch=FetchType.EAGER,可选值有FetchType.EAGER, FetchType.LAZY
optional=true,可选值有true, false
mappedBy=""
orphanRemoval=false,可选值有falsetrue

我们对以上几个参数做下详细的说明:

在上面我们创建entity的时候,没有指定由谁管理外键,双方都有对方的外键字段,也就是employee中有外键addr_id,address中也有外键emp_id。


表employee的外键
表address的外键

如果此时我们不指定cascade,也就是cascade默认为{}的时候,我们插入一条数据

Employee employee = new Employee()
                .setFirstName("Tom")
                .setLastName("Welliam")
                .setBirthDate(new Date())
                .setHireDate(new Date())
                .setGender(Employee.Gender.F)
                .setAddress(new Address().setHome("保利国际B1栋4703"));
        employeeRepository.save(employee);

我们插入一条员工数据,并且set了员工的地址信息,发现插入报错,如下:


因为address在数据库中还不存在,无法保存员工地址信息。

如果我们在Address的employee字段上加上cascade = CascadeType.PERSIST会出现什么结果呢?

public class Address extends AbstractEntity {

    @OneToOne(cascade = CascadeType.PERSIST)
    private Employee employee;
}

同样运行上面的保存员工信息的代码,发现还是报同样的错误,为什么呢?因为我们save的是Employee,在Address 中的 cascade 对save Employee是无效的。 如果要想看到效果,那么我们可以把上面的代码修改一下,改为保存Address:

Address address = new Address().setHome("保利国际B1栋4703")
                .setEmployee(new Employee().setFirstName("Tom")
                        .setLastName("Welliam")
                        .setBirthDate(new Date())
                        .setHireDate(new Date())
                        .setGender(Employee.Gender.F));
        addressRepository.save(address);

可以看到,当我们save address的时候,jpa执行了两条SQL语句,先插入employee,然后再插入address。


address
employee

这里我们先来明确一个概念:父表和字表。父表和子表的概念我们也可以理解为主、副之分,比如此处员工和地址,一般我们认为,某地址是属于某个员工的,那么我们说员工表应该为父表(主表),地址表为子表(副表)。所以我们应该把cascade = CascadeType.PERSIST作用在父表 Employee 的 address 字段上,表示由employee来管理address。所以员工和地址的一对一关系应该改成下面这样

public class Employee extends AbstractEntity {

    @OneToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "addr_id")
    private Address address;
}
public class Address extends AbstractEntity {

    @OneToOne
    @JoinColumn(name = "emp_id")
    private Employee employee;
}

至于cascade 的不同取值说明如下:

CascadeType.PERSIST: 关联持久化(插入)
CascadeType.MERGE:级联合并(更新)
CascadeType.REMOVE:关联删除
CascadeType.REFRESH: 关联刷新(查询)
CascadeType.DETACH:脱离关联,也就是关闭外键检查,放在父表时,那么就可以单独删除父表数据,而不影响子表数据。放在子表时删除子表数据无效。
CascadeType.ALL:包含以上所有关联操作逻辑

public class Address extends AbstractEntity {

    @OneToOne(mappedBy = "address")
    @JoinColumn(name = "emp_id")
    private Employee employee;
}
public class Employee extends AbstractEntity {

    @OneToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "addr_id")
    private Address address; // mappedBy 要指定的名称
}
此时,在Address表中就不会出现emp_id这个外键字段了

二. @OneToMany 和 @ManyToOne

employees 和 salaries 是一对多的关系
在jpa中是这样定义的:

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "Salaries")
public class Salary extends AbstractEntity {

    @ManyToOne
    private Employee employee;
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "employees")
public class Employee extends AbstractEntity {

    @OneToMany(mappedBy = "employee")
    private Set<Salary> salaries;
}

@OneToMany 的可选参数

targetEntity=void.class
cascade={}
fetch=FetchType.LAZY
mappedBy=""
orphanRemoval=false

@ManyToOne 只有四个可选参数

targetEntity=void.class
cascade={}
fetch=FetchType.EAGER
optional=true

他们作用在一对一的关系中已经讲的很清楚了,他们只是默认取值不一样而已。
可以看到,在 @OneToMany 中有 mappedBy ,而 @ManyToOne 中没有 mappedBy ,如果你理解了我上面说的 mappedBy 的作用就应该很清楚了,在一对多的关系中,外键只可能存在于多的那一方,所以,在 @ManyToOne 这个注解中不可能存在 mappedBy 这个参数就好理解了。既然如此,那为什么 @OneToMany 中还需要 mappedBy 这个参数呢?默认 mappedBy 为多的一方不久好了吗?那是因为在一对多的关系中,通常我们是不会再需要一个中间表去关联的,只会在多的一方添加一个外键字段即可。这时候我们就需要手动指定 mappedBy 参数,如果不指定它为多的一方,那么默认JPA会帮我们自动生成一个中间表,这可能不是我们想看到的。当然,除此之外,我们也可以用 @JoinColumn 注解达到同样的效果。

特别说明一下在 @OneToMany 和 @ManyToOne 中,mappedBy 参数不能和@JoinTable 以及 @JoinColumn 同时出现,在 @OneToOne 中却是可以的。

三. @ManyToMany

employees 和 departments 是多对多的关系
在jpa中是这样定义的:

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "departments")
public class Department extends AbstractEntity{

    @ManyToMany
    @JoinTable(name="dept_emp",
            joinColumns={@JoinColumn(name="dept_id")},
            inverseJoinColumns={@JoinColumn(name="emp_id")})
    private Set<Employee> employees;
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "employees")
public class Employee extends AbstractEntity {

    @ManyToMany
    @JoinTable(name="dept_emp",
            joinColumns={@JoinColumn(name="emp_id")},
            inverseJoinColumns={@JoinColumn(name="dept_id")})
    private Set<Department> departments;
}
自动生成的表结构

@ManyToMany 也是只四个可选参数

targetEntity=void.class
cascade={}
fetch=FetchType.LAZY
mappedBy=""

在这里我们出现了一个新的 @JoinTable 注解,这个注解定义了中间表。在 @ManyToMany 的关系中,必定会出现一个中间表,如果不用 @JoinTable 注解,默认JPA生成的中间表表名为 employees_departments 或 departments_employees。


image.png

至于这个其中谁在前,谁在后由 mappedBy 决定。如果不指定mappedBy,又没有 @JoinTable 注解来指定中间表名称,那么JPA自动给我们生成的表会是这样:


image.png
上一篇 下一篇

猜你喜欢

热点阅读