上一章简单介绍了Hibernate一对多关联的两个关键属性cascade和inverse(十一),如果没有看过,​​请观看上一章​​

一. Hibernate的多对多映射

Hibernate与正常的数据库一样,有一对一的映射,也有一对多的映射,自然也有多对多的映射。这一章主要讲多对多的映射。 在生活中,多对多的例子很常见,如商品和订单。一个订单上面可以有多个商品,同一件商品也可以卖多次,即有多个订单。 学生与课程表。当然,也有最经典的用户和角色。 常见的角色有: 超级管理员,机构管理员,系统管理员,一般用户等。 这里就用用户和角色来说明多对多的关系。

二.多对多关系需要创建第三个表。

多对多,与数据库一样,需要第三个表来进行相关联。 Hibernate形式的多对多形式,会自动生成第三张表。 这第三张表只保留第一张表的主键和第二张表的主键。 第三张表是复合主键。 其实,多对多的关系,就是两个一对多的关系。 如User和Role 实体类。 一个用户可以有多个角色,一个角色也属于多个用户。
表现在代码上就是用户User类中有一个Set集合,存储角色。 一个角色Role类中有一个Set集合,存储用户。下面,具体操作一下。

三.搭建多对多实例

三.一 User实体类

package com.yjl.pojo;
import java.util.HashSet;
import java.util.Set;

/**
@author:两个蝴蝶飞
@date: 2019年3月2日 下午6:18:43
@Description 用户组
*/
public class User {
/**
* @param id 用户编号
* @param name 用户名称
* @param sex 用户的性别
*/
private Integer id;
private String name;
private String sex;
public User() {
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
/**
* @param role 角色。 是一对多的关系
*/
private Set<Role> roles=new HashSet<Role>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}

三.二 Role 实体类

package com.yjl.pojo;

import java.util.HashSet;
import java.util.Set;

/**
@author:两个蝴蝶飞
@date: 2019年3月2日 下午6:18:51
@Description 角色组
*/
public class Role {
/**
* @param id 角色编号
* @param name 用户的名称
*/
private Integer id;
private String name;
public Role() {
}
public Role(String name) {
this.name = name;
}
/**
* @param users 用户 是一对多的关系
*/
private Set<User> users=new HashSet<User>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}

三.三 User.hbm.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入相应的约束 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.yjl.pojo">
<class name="User">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<property name="sex"></property>

<!-- 对角色的一对多设置 . 引入一个table,第三张表。-->
<set name="roles" table="user_role">
<key column="userId"></key>
<!-- 用的标签是多对多,many-to-many. -->
<many-to-many column="roleId" class="Role"> </many-to-many>
</set>
</class>
</hibernate-mapping>

三.四 Role.hbm.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入相应的约束 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.yjl.pojo">
<class name="Role">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!-- 对用户的一对多 -->
<set name="users" table="user_role">
<key column="roleId"></key>
<many-to-many column="userId" class="User"></many-to-many>
</set>
</class>
</hibernate-mapping>

两个配置文件,要保证table的表名称是一致的。 key的值和另外的一个表的column值要保持一致。

三.五 hibernate.cfg.xml文件中引入资源

<!-- 引入相应的约束文件  ctrl点击时可以正确进入-->
<mapping resource="com/yjl/pojo/Role.hbm.xml"/>
<mapping resource="com/yjl/pojo/User.hbm.xml"/>

三.六 进行相关的测试

@Test
public void createTest(){
Session session=HibernateUtil.getSession();
session.close(); //一定不要忘记关闭。
}

测试运行,执行顺序是:

  1. 创建Role表。
  2. 创建User表。
  3. 创建user_role表。
  4. 修改user_role的复合主键是Role的外键
  5. 修改user_role的复合主键是Role的外键

其中发现,生成的user_role表是这样的:

Hibernte的多对多映射(十二)_多对多映射


角色在前,用户在后。 不太习惯。 原因就是在引入的时候的顺序。 现在是将Role.hbm.xml放在前面,所以创建表和修改外键时,role均在前面。 换一下位置即可.

Hibernte的多对多映射(十二)_hibernate_02


注意:在引入资源时,要注意顺序。

四.测试方法

四.一 插入方法

保存的时候,设置一下级联的保存。以用户为主。故User.hbm.xml中:

Hibernte的多对多映射(十二)_xml_03


具体的测试代码是:

@Test
public void saveTest(){
Session session=HibernateUtil.getSession();
//不要忘记开启事务
Transaction transaction=session.beginTransaction();
transaction.begin();
User user1=new User("两个蝴蝶飞","男");
User user2=new User("风流少爷","男");
User user3=new User("精灵妹","女");
/*2. 设置多个角色*/
Role role1=new Role("超级管理员");
Role role2=new Role("系统管理员");
Role role3=new Role("一般用户");

/*Role role1=session.get(Role.class,1);
Role role2=session.get(Role.class,2);
Role role3=session.get(Role.class,3);*/

/*3.设置之间的关系 需要多个设置设置即可*/
user1.getRoles().add(role1);
user1.getRoles().add(role3);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
user3.getRoles().add(role3);
/*4.进行保存*/
session.save(user1);
session.save(user2);
session.save(user3);
transaction.commit();
}

Hibernte的多对多映射(十二)_多对多映射_04


Hibernte的多对多映射(十二)_多对多映射_05


Hibernte的多对多映射(十二)_第三张表添加其他字段_06

在代码中,我设置的是单向的关联。 即只用用户去找角色。 是成功的。 我又设置了一下双向的关联。 即:

user1.getRoles().add(role1);
user1.getRoles().add(role3);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
user3.getRoles().add(role3);
role1.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user1);
role3.getUsers().add(user2);
role3.getUsers().add(user3);

这个时候,运行出来,却是错误的。 报的是重复的键。 也就是说,这种关系是设置了两遍。 查了一下,原因是这样的: 现在inverse=false, 默认的形式。 用户是这样的,角色也是这样的。 外键关系由双方进行关联。 这是不对的. 有两种解决的方式:

  1. 就是像代码中说的那样,只是从用户的角度去设置。 并不是从角色的角度去设置。 当然,也可以从角色的角度去设置。
  2. 在User.hbm.xml 中设置 inverse=“true” .即用户放弃外键维护。 这个时候,代码中要设置两种角度。 用户的角度和角色的角度。 即第二种代码的形式。 当然,也可以Role.hbm.xml中设置inverse=“true”. 即角色放弃外键维护。

其中,如果两个都设置inverse=true, 这个时候,双方都不维护外键,那么此时,无论代码中如何设置,都不会向第三张表插入值。 要特别注意这一点。

四.二 查询方法的测试

@Test
public void searchTest(){
Session session=HibernateUtil.getSession();
User user=session.get(User.class,1);
System.out.println("用户的名称:"+user.getName());
//1.根据用户去查它下面的角色。
System.out.println("该用户所拥有的角色是:");
Set<Role> roles=user.getRoles();
for (Role role : roles) {
System.out.println(role.getName()+",");
}
//2.找到其中的一个角色,然后根据这个角色去查一下用户。
for (Role role : roles) {
if(role.getId()==1){
System.out.println("该角色的所属用户是:");
Set<User> users=role.getUsers();
for (User user2 : users) {
System.out.println(user2.getName());
}
}
}

}

Hibernte的多对多映射(十二)_hibernate_07


Hibernte的多对多映射(十二)_第三张表添加其他字段_08


其中插入的顺序,即role表的值:

Hibernte的多对多映射(十二)_第三张表添加其他字段_09


并不是我们代码中设置的顺序。 原因是因为Set是无序的。

四.三 修改的测试

@Test
public void updateTest(){
Session session=HibernateUtil.getSession();
Transaction transaction=session.getTransaction();
transaction.begin();
User user=session.get(User.class,1);
System.out.println("用户的名称:"+user.getName());
Set<Role> roles=user.getRoles();
Iterator<Role> iterator=roles.iterator();
while(iterator.hasNext()){
Role role=iterator.next();
//1. 去除第一个角色是1的值,即去除普通用户这个角色。
if(role.getId()==1){
iterator.remove();
}else if (role.getId()==2){ //是超级管理员的角色的话
//将精灵妹放入到到超级管理员里面
role.getUsers().add(session.get(User.class,3));
}
}
//已经设置了是用户级联了。
session.update(user);
transaction.commit();
}

最后执行的结果是:

Hibernte的多对多映射(十二)_hibernate_10

四.四 删除的测试

在用户User.hbm.xml中设置级联cascade=“delete” .查看此时的级联删除:

Hibernte的多对多映射(十二)_多对多映射_11

代码为:

@Test
public void deleteTest(){
Session session=HibernateUtil.getSession();
Transaction transaction=session.getTransaction();
transaction.begin();
User user=session.get(User.class,1);
session.delete(user);
transaction.commit();
}

主要的执行顺序是:

  1. 根据员工编号去找那个员工的信息
  2. 将user表与user_role表进行关联,根据员工的编号查询出他所拥有的角色编号
  3. 从user_role 表中删除那个用户的信息
  4. 从user_role表中删除那个角色的信息
  5. 从role表中删除那个角色
  6. 从user表中删除那个用户。

本来是想删除那个用户,没有想到,把那个角色和在user_role表中那个角色所对应的记录给删除了。 这就是级联删除的危害。 所以,要慎用这一个级联 cascade=“delete” 。
注意一点: 并不会由这个角色即系统管理员去找那些人,然后删除那些人,删除之前找那些人所拥有的角色,去删除那些角色,删除角色之前,再找人。 并不会下去。 不然,很可能到最近,user,role,user_role表中都没有任何记录。 只是到删除用户角色表中关于那个角色的配置用户的记录而已。

五. 替换多对多

一般来说,上面那个多对多,并不太好。 因为第三张表,几乎只能有那两个字段。并不会添加其他字段之类的。 如,插入的操作人,插入的时间等。 一般来说,第三张表是单独设置的,然后分别引用第一张表和第二张表的外键而已。 即,创建一个实实在在的User_Role表和实体。 这个时候,用户与角色将不会存在直接的关系了。 全在User_Role实体中。这个实体与用户是多对一,这个实体与角色是多对一。

五.一 UserRole实体类

package com.yjl.pojo;

import java.io.Serializable;
import java.util.Date;

/**
@author:两个蝴蝶飞
@date: 2019年3月2日 下午9:52:31
@Description 用户角色表,真实存在
*/
public class UserRole implements Serializable{
private static final long serialVersionUID = -1109238867051166408L;
/**
* @param id 编号
* @param user 与用户表是多对一的关系
* @param role 与用户表是多对一的关系
* @param operateDate 操作日期
* @param operateUser 操作人
*/
private Integer id;
private User user;
private Role role;
private Date operateDate;
private String operateUser;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public Date getOperateDate() {
return operateDate;
}
public void setOperateDate(Date operateDate) {
this.operateDate = operateDate;
}
public String getOperateUser() {
return operateUser;
}
public void setOperateUser(String operateUser) {
this.operateUser = operateUser;
}

}

五.二 User实体类

package com.yjl.pojo;
import java.util.HashSet;
import java.util.Set;

/**
@author:两个蝴蝶飞
@date: 2019年3月2日 下午6:18:43
@Description 用户组
*/
public class User {
/**
* @param id 用户编号
* @param name 用户名称
* @param sex 用户的性别
*/
private Integer id;
private String name;
private String sex;
public User() {
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
/**
* @param userRoles1 角色。 是一对多的关系
*/
private Set<UserRole> userRoles1=new HashSet<UserRole>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Set<UserRole> getUserRoles1() {
return userRoles1;
}
public void setUserRoles1(Set<UserRole> userRoles1) {
this.userRoles1 = userRoles1;
}
}

五.三 Role实体类

package com.yjl.pojo;

import java.util.HashSet;
import java.util.Set;

/**
@author:两个蝴蝶飞
@date: 2019年3月2日 下午6:18:51
@Description 角色组
*/
public class Role {
/**
* @param id 角色编号
* @param name 用户的名称
*/
private Integer id;
private String name;
public Role() {
}
public Role(String name) {
this.name = name;
}
/**
* @param userRoles 用户 是一对多的关系
*/
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
}

五.四 User.hbm.xml文件

<hibernate-mapping>
<class name="com.yjl.pojo.User">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<property name="sex"></property>

<!-- 对角色的一对多设置 .-->
<set name="userRoles1" cascade="save-update" inverse="true">
<key column="userId"></key>
<!-- 用的标签是多对多,many-to-many. -->
<one-to-many class="com.yjl.pojo.UserRole"/>
</set>
</class>
</hibernate-mapping>

五.五 Role.hbm.xml配置文件

<hibernate-mapping>
<class name="com.yjl.pojo.Role">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!-- 对用户的一对多 -->
<!-- 对角色的一对多设置 .-->
<set name="userRoles" cascade="save-update" inverse="true">
<key column="roleId"></key>
<!-- 用的标签是多对多,many-to-many. -->
<one-to-many class="com.yjl.pojo.UserRole"/>
</set>
</class>
</hibernate-mapping>

五.六 UserRole.hbm.xml配置文件

<hibernate-mapping >
<class name="com.yjl.pojo.UserRole">
<id name="id">
<generator class="native"></generator>
</id>
<many-to-one name="user" column="userId" class="com.yjl.pojo.User"></many-to-one>
<many-to-one name="role" column="roleId" class="com.yjl.pojo.Role"></many-to-one>
<property name="operateDate" column="operateDate" type="timestamp"></property>
<property name="operateUser" column="operateUser" type="java.lang.String"></property>
</class>
</hibernate-mapping>

五.七 hibernate.cfg.xml引入资源

Hibernte的多对多映射(十二)_hibernate_12

五.八 测试创建方法

@Test
public void createTest(){
Session session=HibernateUtil.getSession();
session.close();
}

运行创建的方法,达到正常的效果.

Hibernte的多对多映射(十二)_xml_13

五.九 插入测试

@Test
public void saveTest(){
Session session=HibernateUtil.getSession();
Transaction transaction=session.beginTransaction();
/*1.设置多个用户*/
User user1=new User("两个蝴蝶飞","男");
User user2=new User("风流少爷","男");
User user3=new User("精灵妹","女");
/*2. 设置多个角色*/
Role role1=new Role("超级管理员");
Role role2=new Role("系统管理员");
Role role3=new Role("一般用户");
/*3.进行设置关系. 以多的那一方进行设置了. 均是多的一方为UserRole.
所以,要以UserRole 为主角*/

UserRole ur1=new UserRole();
ur1.setUser(user1);
ur1.setRole(role1);
ur1.setOperateDate(new Date());
ur1.setOperateUser("110");

/*上面就是设置了,第一条的记录。 接下来,设置第二条,第三条的记录。*/

UserRole ur2=new UserRole();
ur2.setUser(user1);
ur2.setRole(role3);
ur2.setOperateDate(new Date());
ur2.setOperateUser("111");

UserRole ur3=new UserRole();
ur3.setUser(user2);
ur3.setRole(role1);
ur3.setOperateDate(new Date());
ur3.setOperateUser("112");

/*4.进行保存*/
session.save(user1);
session.save(user2);
session.save(user3);

session.save(role1);
session.save(role2);
session.save(role3);

session.save(ur1); //必须要添加上。
session.save(ur2);
session.save(ur3);

transaction.commit();
}

运行之后是:

Hibernte的多对多映射(十二)_第三张表添加其他字段_14

此时,虽然设置了级联保存,但是userrole这张表并不是设置的唯一外键。 不但要用user表,还要用role表。 所以,级联是不好用的。 要单独进行save()方法进行插入数据。 有个疑问,要不要设置成复合主键? 将userRole类中的id去除掉? 后来,想想,还是算了。 添加一个标识id必须好一些。

谢谢!!!