在上一讲中,已经讲解过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
<hibernate-mapping>
<class name="com.meimeixia.hibernate.domain.User" table="sys_user">
<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 name="roles" table="sys_user_role">
<key column="user_id">key>
<many-to-many class="com.meimeixia.hibernate.domain.Role" column="role_id">many-to-many>
set>
class>
hibernate-mapping>
Role.hbm.xml
<hibernate-mapping>
<class name="com.meimeixia.hibernate.domain.Role" table="sys_role">
<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 name="users" table="sys_user_role">
<key column="role_id">key>
<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方法,就能运行成功了。
在双向关联关系的情况下,我们只保存一边,也就是说你要么只保存用户,要么只保存角色,是否可行?可以预想到的是我们可能会这样写代码:
/**
* 多对多的操作:
* 只保存一边是否可以?不可以,也会报瞬时对象异常
*/
@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":
*
*
* User.hbm.xml:
*
*
*/
@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":
*
*
* Role.hbm.xml:
*
*
*/
@Test
public void demo05() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询1号用户
User user = session.get(User.class, 1l);
session.delete(user);//update
tx.commit();
}
又有这样一个需求:当我们删除一个角色时,应该将角色关联的用户也删除掉。由于现在删除的主体对象是角色对象,那么就需要在角色的映射文件中进行配置。
温馨提示:如果你现在也在用户的映射文件中设置级联删除,就像下面这样,
就是说现在双方都设置了级联删除,那么就很可能会出现严重的后果。我现在是要删除1号角色的,照理来说它就会把1号角色所关联的用户(即1号用户)删除掉,但是由于我在双方都设置了级联删除,尼玛的,全JB删除光了,这是咋鸡儿回事呢?因为现在删除了1号角色,所以它会连带地删除1号角色所关联的用户,1号角色所关联的用户又关联了其他的角色,就又会删除掉所关联的角色,所关联的角色又JB关联了其他用户,就又会删除掉其他用户…感觉没完没了啊!
所以,现在为了要测试在删除角色时,然后去级联删除用户,最好是只在角色的映射文件设置级联删除,不要在用户的映射文件中也设置级联删除了。这个时候,再运行以下测试方法,就能在删除角色时,然后去级联删除用户。
/**
* 先运行demo01方法,让数据库表中有些数据。
*
* 多对多的级联删除
* 删除角色,同时去级联删除用户。要在角色的映射配置文件中去配置
* 在Role.hbm.xml中的set上去配置cascade="delete":
*
*
* User.hbm.xml:
*
*
*/
@Test
public void demo06() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询1号角色
Role role = session.get(Role.class, 1l);
session.delete(role);//update
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号角色
//update
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);
//update
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);
//update
tx.commit();
}
运行以上测试方法,就能给2号用户删除1号角色。