在我们的角色管理系统中,一个用户可以有多种角色,一种角色可以赋予多个用户,显然用户和角色就是典型的多对多关系。又或者博客网站上,用户与文章点赞记录也是一个多对多关系,即一个用户可以点赞多篇文章,一篇文章可以给多个用户点赞等,这时候,我们往往需要附加一些信息,比如授权时间、点赞时间等。在上面两个实例中,都可对应于hibernate多对多映射关系的两种方式,在多对多映射中,我们往往使用中间表来建立关联关系,而且会是双向关联,确保任意一方添加或删除,都可以对中间表进行操作来维护关联关系。
下面我们来看多对多映射的第一种实现:
我们先看一个错误的配置:
/****************用户类***************/
@Entity
@Table(name = "t_user3")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String userName;
@ManyToMany(cascade = CascadeType.ALL)
private Set<Role> roles;
//忽略get 和set方法
}
/****************角色类***************/
@Entity
@Table(name = "t_role3")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String roleName;
@ManyToMany(cascade = CascadeType.ALL)
private Set<User> users;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((roleName == null) ? 0 : roleName.hashCode());
return result;
}
//忽略get 和set方法
}
在这里我们简单的通过注解ManyToMany建立了两边关联关系,并且级联所有操作,这时候,调用我们的测试方法:
@Test
public void test1(){
User user = new User();
user.setUserName("userName");
Role role = new Role();
role.setRoleName("roleName");
Set<Role> roles = new HashSet<Role>();
roles.add(role);
user.setRoles(roles);//建立关联关系
session.save(user);
}
执行方法,我们会看到数据记录如下图
在数据库中,hibernate帮我们生成了4张表,其中2张是中间表,是因为User和Role都是关联关系主控方,会以自己为主建立一张中间表,通过级联插入操作我们会发现,我们添加User,即使级联添加了Role,但维护关系在由User主控的中间表t_user3_t_role3中,这样,如果我尝试从Role方对这条记录进行级联操作,因为在Role放的主控表t_role3_t_user3中找不到维护关系,则会导致级联操作失败!
比如我们执行如下操作:
Role role = session.get(Role.class, 2);//获取刚刚插入的记录
System.out.println(role.getUsers().size());//结果打印0,即从Role端找不到关联的User
session.delete(role);//尝试删除操作,看能否级联删除
这是查看记录
发现确实只有Role表的记录被删除了。当然,如果数据库存在外键关联的话,这个删除操作是会失败的,因为中间表t_user3_t_role3的记录roles_id=2会进行约束
接下来,我们先恢复role端的记录:
再执行如下操作:
User user = session.get(User.class, 1);
System.out.println(user.getRoles().size());//打印1
session.delete(user);
我们会看到控制台记录hibernate使用了如下sql语句:
Hibernate: delete from t_user3_t_role3 where User_id=?
Hibernate: delete from t_role3 where id=?
Hibernate: delete from t_user3 where id=?
这里主要想说明的是,如果我们设置了级联删除,不仅中间表的关系维护记录会被清除,通过另一方的记录也会被删除。可在实际应用中,我们往往不希望存在这种级联删除,这好比我要除去一条没有意义的角色,不小心把关联的用户也删除了,这不是我们想要的结果,因此要控制好级联关系。
下面我们来看一个正确关联关系配置:
/********************用户方**************/
@ManyToMany
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)//使用hibernate注解级联保存和更新
@JoinTable(name = "t_user_role",
joinColumns = {@JoinColumn(name = "user_id")},//JoinColumns定义本方在中间表的主键映射
inverseJoinColumns = {@JoinColumn(name = "role_id")})//inverseJoinColumns定义另一在中间表的主键映射
private Set<Role> roles;
/********************角色方**************/
@ManyToMany
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)//使用hibernate注解级联保存和更新
@JoinTable(name = "t_user_role",
joinColumns = {@JoinColumn(name = "role_id")},//JoinColumns定义本方在中间表的主键映射
inverseJoinColumns = {@JoinColumn(name = "user_id")})//inverseJoinColumns定义另一在中间表的主键映射
private Set<User> users;
在这里,我们将两边都设为主控方,这样两边都可以对关联关系进行维护。假如我们只需要User方维护关联关系,可以将角色方改成如下配置:
@ManyToMany(mapperBy = "roles")
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)//使用hibernate注解级联保存和更新
private Set<User> users;
上面是第一种建立多对多关系,但这样配置的缺点是,我们无法知道两者关联关系的上下文属性,比如授权时间等,针对这一需求,我们可以单独建立一张中间表,然后让用户表和角色表分别和中间表建立一对多关联关系,这样,我们可以在中间表中添加一些附加信息如授权开始时间、授权结束时间等,这在实际开发中是很有意义的。
关于这种方法的实现这里不再举例。关于一对多关联关系的配置实现可参考我前面的文章。