对象关系
# 对象关系
主要有一对一@OneToOne、多对多、一对多、多对一四种关系, 关系可以单向,也可以双向。
关系的维护方与被维护方(mappedBy
)。 中间表@JoinTable
的定义(表名称、维护方外键属性名称joinColumns
、被维护方外键属性名称inverseJoinColumns
)
# 一对一@OneToOne
JPA使用@OneToOne来标注一对一的关系。
实体City:城市。 实体Mayor:市长。 City和Mayor是一对一的关系。
这里用两种方式描述JPA的一对一关系。
一种是通过外键的方式(一个实体通过外键关联到另一个实体的主键);
另外一种是通过一张关联表来保存两个实体一对一的关系,会多出来一张表。
# 1、通过关联表的方式来保存一对一的关系
@Entity(name="city")
public class City {
@Id
@GeneratedValue
private Long id;
/**城市名称*/
@Column(length=32)
private String name;
/**城市的市长*/
@OneToOne(cascade=CascadeType.ALL) //City是关系的维护端
@JoinTable(name="city_mayor",joinColumns=@JoinColumn(name="city_id"),inverseJoinColumns=@JoinColumn(name="mayor_id"))
//通过关联表来保存一对一的关系。定义了一张叫"city_mayor"的表,
//joinColumns定义一个外键叫"city_id",指向关系维护端City的主键
//inverseJoinColumns定义了一个外键叫"mayor_id",指向关系被维护端Mayor的主键
private Mayor mayor;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Table(name="mayor")
public class Mayor {
@Id
@GeneratedValue
private Long id;
/**市长姓名*/
@Column(length=32)
private String name;
/**管理的城市*/
@OneToOne(mappedBy="mayor",cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)
//mappedBy="mayor"表明Mayor是关系被维护端,"mayor"是City实体中的属性
private City city;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方式二通过外键的方式
Cityp配置如下, Mayor.java还是原注解不变。
@Entity
@Table(name="city")
public class City {
@Id
@GeneratedValue
private Long id;
/**城市名称*/
@Column(length=32)
private String name;
/**城市的市长*/
@OneToOne(cascade=CascadeType.ALL)//City是关系的维护端
@JoinColumn(name="mayor_id")//指定外键的名称
@Fetch(FetchMode.JOIN)//会使用left join查询,只产生一条语句
//@Fetch(FetchMode.JOIN) 会使用left join查询,只产生一条sql语句(默认使用)
//@Fetch(FetchMode.SELECT) 会产生N+1条sql语句
//@Fetch(FetchMode.SUBSELECT) 产生两条sql语句 第二条语句使用id in (…..)查询出所有关联的数据
private Mayor mayor;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
参考文章:JPA的一对一映射 - CN.programmer.Luxh - 博客园 (cnblogs.com) (opens new window)
# 多对多
用户User与角色Role为多对多, 关系由用户去维护中间表数据;
一个老师教许多学生,一个学生被许多老师教,一个学生有好多书,同一种书被许多同学拥有,要查询教拥有书"a"的学生的老师!
Hql语句:SELECT t FROM Teacher t join t.students s join s.books b where b.name = 'a'
解释:t.students s中s并不是集合的意思,而是t的students对象的表别名,join t.students s这个hql,hibernate会翻译成两个表的内连接关系
错误写法:SELECT t FROM teacher t where t.students.books.name = 'a'
其实道理是很显然的,t.students是一个Set,那么这个Set怎么可能有books这样的属性呢?同理books.name也不对,所以使用表间连接并给出别名t.students s,此时的对象s才有books属性,所以可以写s.books b,最后使用b.name限制查询b.name = 'a'.
另外一种写法:SELECT t FROM Teacher t,Student s,Book b where s.id in elements(t.students) and b.id in elements(s.books) 这种方法没有出错!不过这种方式要用子查询!
@Entity(name="t_user")
@Data @AllArgsConstructor @NoArgsConstructor @ApiModel("权限用户")
public class User implements UserDetails {
/**
* User为多对多关系维护方 默认LAZY懒加载
*/
@ManyToMany
@JoinTable(name = "t_user_role",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")})
private List<Role> roles;
... ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity(name = "t_role")
@Data @AllArgsConstructor @NoArgsConstructor
public class Role {
/**
* mappedBy表示关系交由User维护, 默认LAZY懒加载
*/
@ManyToMany(mappedBy = "roles")
private List<User> users;
......
}
2
3
4
5
6
7
8
9
10
11
保存: 单表各保存自己的,关系维护方保存的时候,会自动往中间表更新数据
@Test
void contextLoads() {
Role r1 = new Role();
r1.setName("admin");
r1.setCname("管理员");
Role r2 = new Role("user", "普通用户");
Role r3 = new Role("test", "测试用户");
roleService.save(r1);
roleService.save(r2);
roleService.save(r3);
User u1 = new User();
u1.setUsername("admin");
u1.setPassword("123");
u1.setAccountNonExpired(true);
u1.setAccountNonLocked(true);
u1.setCredentialsNonExpired(true);
u1.setEnabled(true);
u1.setRoles(Arrays.asList(r1));
userService.save(u1);
User u2 = new User();
u2.setUsername("user");
u2.setPassword("123");
u2.setAccountNonExpired(true);
u2.setAccountNonLocked(true);
u2.setCredentialsNonExpired(true);
u2.setEnabled(true);
u2.setRoles(Arrays.asList(r2));
userService.save(u2);
User u3 = new User();
u3.setUsername("test");
u3.setPassword("123");
u3.setAccountNonExpired(true);
u3.setAccountNonLocked(true);
u3.setCredentialsNonExpired(true);
u3.setEnabled(true);
u3.setRoles(Arrays.asList(r3));
// 关系的维护方会更新中间表的数据
userService.save(u3);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 多对多model写法
可以弄一个中间表对应的model, 也可以不要, 多对多,需要明确关系维护方,
关系维护方
//粉丝标签的多对多, 维护中间表wx_fans_tags, 两表的关联字段wxfans_id wxfans_tag_id
@ManyToMany
@JoinTable( name="wx_fans_tags"
, joinColumns = @JoinColumn(name = "wxfans_id"),
inverseJoinColumns = @JoinColumn(name = "wxfans_tag_id"))
@JsonIgnore
private Set<WxFansTag> wxFansTags=new HashSet<WxFansTag>(0);
2
3
4
5
6
7
被维护方
@JsonIgnore
@ManyToMany(mappedBy="wxFansTags")
private Set<WxFans> wxFanses=new HashSet<WxFans>(0);//粉丝参与活动时,与推荐人之间的一对多关系
2
3
# repo写法
见WxFansTagRepository
, 原生写法不能进行缓存, hql写法可以进行缓存,join默认为内联接(inner join),如果要外联接LEFT join ()
public interface WxFansTagRepository extends ZxpRepository<WxFansTag,Long> {
// 不能用QueryHint,因为不会清理缓存 中间表wx_fans_tags, 标签表:wx_fans_tag 粉丝表 wx_fans
@Query(value = "SELECT t.* FROM wx_fans_tag t, wx_fans_tags ft, wx_fans f WHERE " +
"ft.wxfans_id = f.id and ft.wxfans_tag_id = t.id AND f.id = ?1",nativeQuery = true)
List<WxFansTag> findByWxFansId(Long wxFansId);
// hql方式多对多查询
@QueryHints({@QueryHint(name = Cache.QHINT, value ="true") })
@Query(value = "SELECT t FROM WxFansTag t JOIN t.wxFanses f WHERE f.id = :wxFansId ")
List<WxFansTag> findHqlByWxFansId(@Param(value = "wxFansId") Long wxFansId);
}
2
3
4
5
6
7
8
9
10
11
12
# 多对一与一对多
实体Company:公司。 实体Employee:雇员。 Company和Employee是一对多关系。那么在JPA中,如何表示一对多的双向关联呢?
JPA使用@OneToMany和@ManyToOne来标识一对多的双向关联。一端(Company)使用@OneToMany,多端(Employee)使用@ManyToOne。
在JPA规范中,一对多的双向关系由多端(Employee)来维护。就是说多端(Employee)为关系维护端,负责关系的增删改查。一端(Company)则为关系被维护端,不能维护关系。
一端(Company)使用@OneToMany注释的mappedBy="company"属性表明Company是关系被维护端。
多端(Employee)使用@ManyToOne和@JoinColumn来注释属性company,@ManyToOne表明Employee是多端,@JoinColumn设置在employee表中的关联字段(外键)。
@Entity
@Table(name="company")
public class Company {
@Id
@GeneratedValue
private Long id;
/**公司名称*/
@Column(name="name",length=32)
private String name;
/**拥有的员工*/
@OneToMany(mappedBy="company",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
//拥有mappedBy注解的实体类为关系被维护端
//mappedBy="company"中的company是Employee中的company属性
private Set<Employee> employees = new HashSet<Employee>();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@Table(name="employee")
public class Employee {
@Id
@GeneratedValue
private Long id;
/**雇员姓名*/
@Column(name="name")
private String name;
/**所属公司*/
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)//可选属性optional=false,表示company不能为空
@JoinColumn(name="company_id")//设置在employee表中的关联字段(外键)
private Company company;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17