JPA之间实体关系

   JPA在生成数据表的时候,实体之间的主要关系主要有三种,分别是:

  • @ManyToOne (多对一)
  • @OneToMany (一对多关系)
  • @OneToOne (一对一关系)
  • @ManyToMany (多对多关系)

其中一对多是比较常用的。下面对一对多进行演示说明。

实体与实体之间一对多关系配置

  有两张表,分别是banner和banner_item,两者之间的关系是一对多。

  • Banner.java(一对多中,“一”的一方)
@Entity
//@Table(name = "banner")
public class Banner {
    @Id
    private long id;

    @Column(length = 20)
    private String name;

    private String description;

    @Transient // 该注解表示不生成该字段到数据库
    private String img;

    @Column(length = 12)
    private String title;

    @OneToMany
    private List<BannerItem> items;
}
  • BannerItem.java(“多”的一方)
@Entity
public class BannerItem {
    @Id
    private long id;

    private String img;

    private String keyword;

    private short type;

    private String name;
}



那么这里有一个问题,正常情况下,只有是多对多的关系,才会生成中间关系表,这里为什么会生成呢?因为在banner_item中是必须要指明属于那个banner,但是这里并没有指明外键关系,所以JPA也不知道两个表之间的关系,因此就只好生成了一个中间表,在第三种表进行维护两者之间的关系。



注意,在Banner中我们已经维护有一个List的属性,但时这个并不是指明两者之间的关系,那么不要生成第三张表的办法就是,在banner_item表中维护外键。

如何指定外键呢?

1. 在 BannerItem 中添加 private long bannerId
2. 在 Banner 中添加 @JoinColumn(name = “bannerId”)

修改代码如下:

  • Banner
@Entity
//@Table(name = "banner")
public class Banner {
    @Id
    private long id;

    @Column(length = 20)
    private String name;

    private String description;

    @Transient // 该注解表示不生成该字段到数据库
    private String img;

    @Column(length = 12)
    private String title;

    @OneToMany
    @JoinColumn(name = "bannerId")
    //在主表banner中添加了BannerItem表中的外键,也就是banner的主键
    private List<BannerItem> items;
}
  • BannerItem
@Entity
public class BannerItem {
    @Id
    private long id;

    private String img;

    private String keyword;

    private short type;

    private String name;

    // 用于标明和Banner之间的关系
    //即banner的主键,这个字段就是BannerItem的外键
    private long bannerId;

}

再生成数据库表后,在banner_item中就会有一个 banner_id的外键字段

实体关联中注解参数详解

举栗子在老师和学生关系中(多对多)

  • Teacher.java
@Entity 
public class Teacher { 
     private Integer id; 
     private String name; 
     private Set<Student> students=new HashSet<Student>(); 
  
     @Id @GeneratedValue       //id作为实体标识符   并且采用数据库的id自增长方式生成主键值 
     public Integer getId() { 
         return id; 
     } 
     public void setId(Integer id) { 
         this.id = id; 
     } 
  
     @Column(length = 10, nullable = false) 
     public String getName() { 
         return name; 
     } 
     public void setName(String name) { 
         this.name = name; 
     } 
  
     @ManyToMany(cascade=CascadeType.REFRESH,mappedBy="teachers") 
     public Set<Student> getStudents() { 
         return students; 
     } 

  
     public void setStudents(Set<Student> students) { 
        this.students = students; 
     } 
 }

参数说明

     在注解中,我们可以设置cascade(级联关系),fetch(加载策略),mappedBy(声明关系的维护方)等属性。
看例子:

  • CascadeType.PERSIST
//学生实体
public class Student {

    @ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.LAZY)
    //学生和课程的关系
    private Set<Course> courses = new HashSet<>();
    
}

可以看到,我们在上面的代码中给了Student对Course进行级联保存(cascade=CascadeType.PERSIST)的权限。此时,若Student实体持有的Course实体在数据库中不存在时,保存该Student时,系统将自动在Course实体对应的数据库中保存这条Course数据。而如果没有这个权限,则无法保存该Course数据。



Cascade级联参数属性说明:

  • CascadeType.REMOVE
    Cascade remove operation,级联删除操作。
    删除当前实体时,与它有映射关系的实体也会跟着被删除。
  • CascadeType.MERGE
    Cascade merge operation,级联更新(合并)操作。
    当Student中的数据改变,会相应地更新Course中的数据。
  • CascadeType.DETACH
    Cascade detach operation,级联脱管/游离操作。
    如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联
  • CascadeType.REFRESH
    Cascade refresh operation,级联刷新操作。
    假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。
  • CascadeType.ALL
    Cascade all operations,拥有以上所有级联操作权限。

mappedBy参数说明:

    mappedBy声明于关系的被维护方,声明的值为关系的维护方的关系对象属性名。
在实例中,mappedBy被声明于Course类中,其值为Student类中的Set对象"courses"。即,Student为关系维护方,Course为被维护方。

FetchType参数说明:

  • FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载。
  • FetchType.EAGER:急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。

举个例子:

    比如User类有两个属性,name跟address,就像百度知道,登录后用户名是需要显示出来的,此属性用到的几率极大,要马上到数据库查,用急加载;而用户地址大多数情况下不需要显示出来,只有在查看用户资料是才需要显示,需要用了才查数据库,用懒加载就好了。所以,并不是一登录就把用户的所有资料都加载到对象中,于是有了这两种加载模式。