Hibernate入门第十一讲——Hibernate的多对多关联映射

在上一讲中,已经讲解过Hibernate中的一对多关联映射了,现在在其基础上,我们来讲解一下Hibernate中的多对多关联映射。

环境搭建

创建表

这里我们以用户(User)与角色(Role)为例来讲解Hibernate关联映射中的多对多关联关系,因此我们要在数据库下新建三张表——用户表、角色表以及中间表,这里笔者使用的数据库是MySQL。

CREATE TABLE `sys_user` (
	`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
	`user_code` varchar(32) COMMENT '用户账号',
	`user_name` varchar(64) COMMENT '用户名称',
	`user_password` varchar(32) COMMENT '用户密码',
	`user_state` char(1) COMMENT '1:正常,0:暂停',
	PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `sys_role` (
	`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
	`role_name` varchar(32) NOT NULL COMMENT '角色名称',
	`role_memo` varchar(128) DEFAULT NULL COMMENT '备注',
	PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `sys_user_role` (
	`role_id` bigint(32) NOT NULL COMMENT '角色id',
	`user_id` bigint(32) NOT NULL COMMENT '用户id',
	PRIMARY KEY (`role_id`,`user_id`),
	KEY `FK_user_role_user_id` (`user_id`),
	CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
	CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建实体

在src目录下的com.meimeixia.hibernate.domain包中创建两个实体类,如下:

  • 用户的实体

    package com.meimeixia.hibernate.domain;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * 用户的实体
     * @author liayun
     *
     */
    public class User {
    
    	private Long user_id;
    	private String user_code;
    	private String user_name;
    	private String user_password;
    	private String user_state;
    	
    	//如何设置多对多的关系,即表示一个用户可以选择多个角色?
    	//放置的是角色的集合
    	private Set<Role> roles = new HashSet<Role>();
    	
    	public Long getUser_id() {
    		return user_id;
    	}
    	public void setUser_id(Long user_id) {
    		this.user_id = user_id;
    	}
    	public String getUser_code() {
    		return user_code;
    	}
    	public void setUser_code(String user_code) {
    		this.user_code = user_code;
    	}
    	public String getUser_name() {
    		return user_name;
    	}
    	public void setUser_name(String user_name) {
    		this.user_name = user_name;
    	}
    	public String getUser_password() {
    		return user_password;
    	}
    	public void setUser_password(String user_password) {
    		this.user_password = user_password;
    	}
    	public String getUser_state() {
    		return user_state;
    	}
    	public void setUser_state(String user_state) {
    		this.user_state = user_state;
    	}
    	public Set<Role> getRoles() {
    		return roles;
    	}
    	public void setRoles(Set<Role> roles) {
    		this.roles = roles;
    	}
    	
    }
    
  • 角色的实体

    package com.meimeixia.hibernate.domain;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * 角色的实体
     * @author liayun
     *
     */
    public class Role {
    	
    	private Long role_id;
    	private String role_name;
    	private String role_memo;
    	
    	//如何设置多对多的关系,即表示一个角色被多个用户所选择?
    	//放置的是用户的集合
    	private Set<User> users = new HashSet<User>();
    	
    	public Long getRole_id() {
    		return role_id;
    	}
    	public void setRole_id(Long role_id) {
    		this.role_id = role_id;
    	}
    	public String getRole_name() {
    		return role_name;
    	}
    	public void setRole_name(String role_name) {
    		this.role_name = role_name;
    	}
    	public String getRole_memo() {
    		return role_memo;
    	}
    	public void setRole_memo(String role_memo) {
    		this.role_memo = role_memo;
    	}
    	public Set<User> getUsers() {
    		return users;
    	}
    	public void setUsers(Set<User> users) {
    		this.users = users;
    	}
    	
    }
    

创建映射配置文件

实体类创建完成之后,再在com.meimeixia.hibernate.domain包下分别创建这两个类的映射配置文件。

  • 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>
    	<class name="com.meimeixia.hibernate.domain.User" table="sys_user">
    		<!-- 建立OID与主键的映射 -->
    		<id name="user_id" column="user_id">
    			<generator class="native"></generator>
    		</id>
    		
    		<!-- 建立类中的普通属性与字段的映射 -->
    		<property name="user_code" column="user_code" />
    		<property name="user_name" column="user_name" />
    		<property name="user_password" column="user_password" />
    		<property name="user_state" column="user_state" />
    		
    		<!-- 建立与角色的多对多的映射关系 -->
    		<!--  
    			set标签:
    				1. name:对方的集合的属性名称
    				2. table:多对多的关系需要使用中间表,放置的是中间表的名称
    		-->
    		<set name="roles" table="sys_user_role">
    			<!--  
    				key标签:
    					1. column:放置的是当前的对象对应的中间表的外键名称
    			-->
    			<key column="user_id"></key>
    			<!--  
    				many-to-many标签:
    					1. class:对方的类的全路径
    					2. column:对方的对象在对应的中间表中的外键名称
    			-->
    			<many-to-many class="com.meimeixia.hibernate.domain.Role" column="role_id"></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>
    	<class name="com.meimeixia.hibernate.domain.Role" table="sys_role">
    		<!-- 建立OID与主键的映射 -->
    		<id name="role_id" column="role_id">
    			<generator class="native"></generator>
    		</id>
    		
    		<!-- 建立类中的普通属性与数据库表中字段的映射 -->
    		<property name="role_name" column="role_name" />
    		<property name="role_memo" column="role_memo" />
    		
    		<!-- 建立与用户的多对多的映射关系 -->
    		<!--  
    			set标签:
    				1. name:对方的集合的属性名称
    				2. table:多对多的关系需要使用中间表,放置的是中间表的名称
    		-->
    		<set name="users" table="sys_user_role">
    			<!--  
    				key标签:
    					1. column:放置的是当前的对象对应的中间表的外键名称
    			-->
    			<key column="role_id"></key>
    			<!--  
    				many-to-many标签:
    					1. class:对方的类的全路径
    					2. column:对方的对象在对应的中间表中的外键名称
    			-->
    			<many-to-many class="com.meimeixia.hibernate.domain.User" column="user_id"></many-to-many>
    		</set>
    	</class>
    </hibernate-mapping>
    

在核心配置文件中引入映射配置文件

在这里插入图片描述

编写测试类测试双向关联保存

现在我们来测试保存的操作,在src目录下创建一个com.meimeixia.hibernate.demo02包,并在该包下编写一个ManyToManyTest单元测试类,然后在该类中编写一个用于测试保存的操作,如下:

package com.meimeixia.hibernate.demo02;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Role;
import com.meimeixia.hibernate.domain.User;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * 测试Hibernate多对多的映射
 * @author liayun
 *
 */
public class MantToManyTest {

	/**
	 * 保存多条记录,即保存多个用户和角色
	 */
	@Test
	public void demo01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		//创建2个用户
		User user1 = new User();
		user1.setUser_name("酱爆");
		User user2 = new User();
		user2.setUser_name("大师兄");
		
		//创建3个角色
		Role role1 = new Role();
		role1.setRole_name("研发部");
		Role role2 = new Role();
		role2.setRole_name("市场部");
		Role role3 = new Role();
		role3.setRole_name("公关部");
		
		//设置双向的关联关系
		user1.getRoles().add(role1);
		user1.getRoles().add(role2);
		user2.getRoles().add(role2);
		user2.getRoles().add(role3);
		
		role1.getUsers().add(user1);
		role2.getUsers().add(user1);
		role2.getUsers().add(user2);
		role3.getUsers().add(user2);
		
		//执行保存操作:如果多对多建立了双向的关联关系,那么必须有一方放弃外键维护
		//那么通常让哪一方放弃呢?一般是让被动方去放弃外键的维护权
		session.save(user1);
		session.save(user2);
		session.save(role1);
		session.save(role2);
		session.save(role3);
		
		tx.commit();
	}
	
}

运行以上demo01方法,可以看到会报如下异常。
在这里插入图片描述
为啥呀!这是因为如果多对多建立了双向的关联关系,那么必须有一方放弃外键维护。问题又来了,通常让哪一方放弃呢?一般是让被动方去放弃外键的维护权。在这里,被动方到底是哪个呢?你可以认为是角色的一方。所以,我们应在角色的映射配置文件中进行设置。
在这里插入图片描述
这时,再次运行demo01方法,就能运行成功了。

Hibernate的多对多相关操作

多对多的关系,只保存一边是否可以?

在双向关联关系的情况下,我们只保存一边,也就是说你要么只保存用户,要么只保存角色,是否可行?可以预想到的是我们可能会这样写代码:

/**
 * 多对多的操作:
 * 		只保存一边是否可以?不可以,也会报瞬时对象异常
 */
@Test
public void demo02() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个用户
	User user1 = new User();
	user1.setUser_name("酱爆");
	
	//创建1个角色
	Role role1 = new Role();
	role1.setRole_name("研发部");
	
	//设置双向的关联关系
	user1.getRoles().add(role1);
	role1.getUsers().add(user1);
	
	//只保存用户
	session.save(user1);
	//session.save(role1);
	
	tx.commit();
}

运行以上方法,会发现报了一个瞬时态对象异常,即持久态对象关联了一个瞬时态对象。
在这里插入图片描述
出现问题,就要着手解决,那又该怎么解决呢?这就引出了下面我要讲的多对多的级联操作。

多对多的级联操作

级联保存或更新

级联是有方向性的,现在我们要做的是保存用户,然后去级联保存角色。由于现在操作的主体对象是用户对象,所以就需要在用户的映射文件中进行配置。
在这里插入图片描述
这时,运行以下测试方法,就能在保存用户时顺带自动保存角色。

/**
 * 多对多的级联保存
 * 		保存用户,同时去级联保存角色。在用户的映射配置文件中去配置
 * 		在User.hbm.xml中的set上去配置cascade="save-update"
 */
@Test
public void demo03() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个用户
	User user1 = new User();
	user1.setUser_name("酱爆");
	
	//创建1个角色
	Role role1 = new Role();
	role1.setRole_name("研发部");
	
	//设置双向的关联关系
	user1.getRoles().add(role1);
	role1.getUsers().add(user1);
	
	//只保存用户
	session.save(user1);
	
	tx.commit();
}

我们又想要在保存角色时,保存用户,那又该怎么做呢?答案是不言而喻的,用屁股想都能知道,现在操作的主体对象是角色对象,所以就需要在角色的映射文件中进行配置。
在这里插入图片描述
温馨提示:由于现在外键是由角色这方来维护的,所以用户那方要放弃外键的维护权,也就说用户的映射文件要这样配置。
在这里插入图片描述
这时,运行以下测试方法,就能在保存角色时顺带自动保存用户。

/**
 * 多对多的级联保存
 * 		保存角色,同时去级联保存用户。要在角色的映射配置文件中去配置
 * 		暂时这样设置,外键得交给它来维护,在Role.hbm.xml中的set上去配置cascade="save-update":
 * 			<set name="users" table="sys_user_role" cascade="save-update">
 * 
 * 		User.hbm.xml:
 * 			<set name="roles" table="sys_user_role" cascade="save-update" inverse="true">
 * 		
 */
@Test
public void demo04() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//创建1个用户
	User user1 = new User();
	user1.setUser_name("阿星");
	
	//创建1个角色
	Role role1 = new Role();
	role1.setRole_name("公关部");
	
	//设置双向的关联关系
	user1.getRoles().add(role1);
	role1.getUsers().add(user1);
	
	//只保存角色
	session.save(role1);
	
	tx.commit();
}

级联删除(基本上用不上)

现在我们有这样一个需求:当我们删除一个用户时,应该将用户关联的角色也删除掉。由于删除的主体对象是用户对象,所以就需要在用户的映射文件中进行配置。
在这里插入图片描述
此时,运行以下测试方法,就能在删除用户时,然后去级联删除角色。

/**
 * 先运行demo01方法,让数据库表中有些数据。
 * 
 * 多对多的级联删除
 * 		删除用户,同时去级联删除角色。要在用户的映射配置文件中去配置
 * 		在User.hbm.xml中的set上去配置cascade="delete":
 * 			<set name="roles" table="sys_user_role" cascade="save-update,delete">
 * 		
 * 		Role.hbm.xml:
 * 			<set name="roles" table="sys_user_role" cascade="save-update" inverse="true">
 * 
 */
@Test
public void demo05() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//查询1号用户
	User user = session.get(User.class, 1l);
	session.delete(user);//<property name="hibernate.hbm2ddl.auto">update</property>
	
	tx.commit();
}

又有这样一个需求:当我们删除一个角色时,应该将角色关联的用户也删除掉。由于现在删除的主体对象是角色对象,那么就需要在角色的映射文件中进行配置。
在这里插入图片描述
温馨提示:如果你现在也在用户的映射文件中设置级联删除,就像下面这样,
在这里插入图片描述
就是说现在双方都设置了级联删除,那么就很可能会出现严重的后果。我现在是要删除1号角色的,照理来说它就会把1号角色所关联的用户(即1号用户)删除掉,但是由于我在双方都设置了级联删除,尼玛的,全JB删除光了,这是咋鸡儿回事呢?因为现在删除了1号角色,所以它会连带地删除1号角色所关联的用户,1号角色所关联的用户又关联了其他的角色,就又会删除掉所关联的角色,所关联的角色又JB关联了其他用户,就又会删除掉其他用户…感觉没完没了啊!
在这里插入图片描述
所以,现在为了要测试在删除角色时,然后去级联删除用户,最好是只在角色的映射文件设置级联删除,不要在用户的映射文件中也设置级联删除了。这个时候,再运行以下测试方法,就能在删除角色时,然后去级联删除用户。

/**
 * 先运行demo01方法,让数据库表中有些数据。
 * 
 * 多对多的级联删除
 * 		删除角色,同时去级联删除用户。要在角色的映射配置文件中去配置
 * 		在Role.hbm.xml中的set上去配置cascade="delete":
 * 			<set name="users" table="sys_user_role" cascade="save-update,delete" inverse="true">
 * 		
 * 		User.hbm.xml:
 * 			<set name="roles" table="sys_user_role" cascade="save-update">
 * 
 */
@Test
public void demo06() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//查询1号角色
	Role role = session.get(Role.class, 1l);
	session.delete(role);//<property name="hibernate.hbm2ddl.auto">update</property>
	
	tx.commit();
}

多对多的其他的操作

首先做下准备工作,运行demo01方法,让数据库表中初始化一些数据,如下图所示。
在这里插入图片描述

给用户选择角色

例,给1号用户多选择一个3号角色。

/**
 * 先运行demo01方法,让数据库表中初始化些数据。
 * 
 * 给用户选择角色
 */
@Test
public void demo07() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//给1号用户多选择一个3号角色
	//查询1号用户
	User user = session.get(User.class, 1l);
	
	//查询3号角色
	Role role = session.get(Role.class, 3l);
	
	user.getRoles().add(role);//给1号用户多选择一个3号角色
	
	//<property name="hibernate.hbm2ddl.auto">update</property>
	
	tx.commit();
}

运行以上测试方法,就能给1号用户多选择一个3号角色。

给用户改选角色

例,给2号用户将原有的3号角色改为1号角色。

/**
 * 给用户改选角色
 */
@Test
public void demo08() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//给2号用户将原有的3号角色改为1号角色
	//查询2号用户
	User user = session.get(User.class, 2l);
	
	//查询1、3号角色
	Role role1 = session.get(Role.class, 1l);
	Role role3 = session.get(Role.class, 3l);
	
	user.getRoles().remove(role3);
	user.getRoles().add(role1);
	
	//<property name="hibernate.hbm2ddl.auto">update</property>
	
	tx.commit();
}

运行以上测试方法,就能给2号用户将原有的3号角色改为1号角色。

给用户删除角色

例,给2号用户删除1号角色。

/**
 * 给用户删除角色
 */
@Test
public void demo09() {
	Session session = HibernateUtils.getCurrentSession();
	Transaction tx = session.beginTransaction();
	
	//给2号用户删除1号角色
	//查询2号用户
	User user = session.get(User.class, 2l);
	
	//查询1号角色
	Role role = session.get(Role.class, 1l);
	
	user.getRoles().remove(role);
	
	//<property name="hibernate.hbm2ddl.auto">update</property>
	
	tx.commit();
}

运行以上测试方法,就能给2号用户删除1号角色。

相关推荐
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页