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