关联映射 ---- Hibernate之多对多关系
叙:上一章详细的记录了关联映射中的“一对多|多对一”关系、级联操作、关系的维护等知识点,本章节轻风学习记录的是级联关系中的“多对多”关系;
Hibernate的“多对多”级联关系
1. 介绍
在生活中很多关系都是多对多的,比如一个人在公司是技术部的同时也是营销部的(只是个例子),所以,多对对关系是很普遍的,一个对象有多个角色,而一个角色又可以有多个对象,因此最正确的做法是在对象与角色之间创建一个新的表,用来保存对象和角色的主键,方便调用查看相应的信息;
2.学习目标
目标:根据创建的实体类以及映射文件,运行demo能自动创建出相应的两个实体类的数据库表以及这两个数据表的中间表;
解释:多对多关系中,想要表达多对多关系需要有一个中间表来存放两张数据表中数据的关联关系,模式如下图所示:
3.设计表
在hibernate中表的创建数据库表的操作中均是以实体类映射的方式进行创建的,因此,只要把实体类以及其映射文件配置好就可以了;
在这个操作中设计的表有两个,他们的实体类是对象以及角色,分别起名为User、Role,其对应的数据表表明分别是sys_user、sys_role,实体类分别如下:
User的实体类:
package com.java.domain; import java.util.HashSet; import java.util.Set; /* * 创建用户表sys_user */ public class User { /* * CREATE TABLE `sys_user` (`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id', * `user_code` varchar(32) NOT NULL COMMENT '用户账号', * `user_name` varchar(64) NOT NULL COMMENT '用户名称', * `user_password` varchar(32) NOT NULL COMMENT '用户密码', * `user_state` char(1) NOT NULL COMMENT '1:正常,0:暂停', * PRIMARY KEY (`user_id`) * ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; */ private Long user_id; private String user_code; private String user_name; private String user_password; private Character user_state; private Set<Role> roles = new HashSet<Role>(); }
Role的实体类:
package com.java.domain; import java.util.HashSet; import java.util.Set; /* * 创建角色表sys_role */ public class Role { /* * 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=6 DEFAULT CHARSET=utf8; */ private Long role_id; private String role_name; private String role_memo; private Set<User> users = new HashSet<User>(); //--------- //为方便阅读,省略类属性的set/get方法; }
从上面的User实体类中看到创建的有一个Role类集合对象,在Role实体类中同样创建有一个User类集合对象,学习过“一对多|多对一”关系后我们知道,这个是为了一个对象中的一条数据保存另外一个对象中的多条数据的集合,“一对多|多对一”关系中只有一个是“多”的一方,因此只有一个类的集合对象,而“多对多”关联映射中两个都是“多”的一方,因此就需要两个实体类中均创建另一个对象的集合对象;
4. 创建映射文件
在“多对多”关系中,实体类的创建并没有什么难点,重点是关联映射在映射文件的配置,因为都是“多”的一方,所以在配置上不会像“一对多|多对一”关联映射关系中那么复杂,”多对多“关系中的两个类使用的配置格式是一样的,只要将配置的数据进行相应替换就好(这里的配置是指关于关联映射的配置,当然,整个映射文件的套路也都是一样的),下面是User和Role实体类的映射文件配置;
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.java.domain"> <!-- 建立类与表的映射 --> <class name="User" table="sys_user"> <!-- 主键的配置 --> <id name="user_id" column="user_id"> <generator class="native" /> </id> <!-- 非主键属性的配置 --> <property name="user_code" column="user_code" ></property> <property name="user_name" column="user_name"></property> <property name="user_password" column="user_password"></property> <property name="user_state" column="user_state"></property> <!-- 集合:多对多关系在映射文件中设置 --> <set name="roles" table="sys_user_role"> <key column="user_id"></key> <many-to-many class="Role" column="role_id"/> </set> </class> </hibernate-mapping>
新知识点:
- 使用的标签分别是<set>、<key>、<many-to-many>,这些基本上和“一对多|多对一”关系中的一样,除了表达关系标签的<many-to-many>,但是也不会有什么理解困难;
- 标签中的各个属性及其属性值:
所在标签 | 属性名 | 属性值 | 解释 |
---|---|---|---|
set | name | roles | 在User实体类中创建的那个几何对象的名字 |
set | table | sys_user_role | 中间表的名字 |
key | column | user_id | 将User实体类中的那个属性放入中间表中 |
many-to-many | class | Role | 与User实体类进行关联映射的那个类的名称(全类名) |
many-to-many | column | role_id | 与User实体类进行关联映射的那个类放入中间表的属性名 |
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.java.domain"> <!-- 建立类与表的映射 --> <class name="Role" table="sys_role"> <!-- 主键的配置 --> <id name="role_id" column="role_id"> <generator class="native" /> </id> <!-- 非主键属性的配置 --> <property name="role_name" column="role_name" ></property> <property name="role_memo" column="role_memo"></property> <!-- 集合:多对多关系在映射文件中设置 --> <set name="users" table="sys_user_role" inverse="true"> <key column="role_id"></key> <many-to-many class="User" column="user_id"></many-to-many> </set> </class> </hibernate-mapping>
根据上面的讲述现在查看Role的映射文件是不是很容理解呢?
5. 在核心配置文件中引入ORM源路径
引入两个ORM源路径并没有什么需要进行补充的,老样子;
6. 测试
测试代码如下:
@Test public void demo1(){ Session session = HibernateUtils.openSession(); Transaction bt = session.beginTransaction(); //创建用户对象并写入数据 User u1 = new User(); u1.setUser_name("张飞"); u1.setUser_password("fj"); u1.setUser_code("fs"); u1.setUser_state('1'); User u2 = new User(); u2.setUser_name("关羽"); u2.setUser_password("sq"); u2.setUser_code("wj"); u2.setUser_state('1'); //创建角色对象并写入数据 Role r1 = new Role(); r1.setRole_name("战士"); r1.setRole_memo("高级"); Role r2 = new Role(); r2.setRole_name("刺客"); r2.setRole_memo("低级"); //用户关联角色(拉关系) u1.getRoles().add(r1); u1.getRoles().add(r2); u2.getRoles().add(r1); u2.getRoles().add(r2); //角色关联用户(拉关系) r1.getUsers().add(u1); r1.getUsers().add(u2); r2.getUsers().add(u1); r2.getUsers().add(u2); //保存数据 session.save(r1); session.save(r2); session.save(u1); session.save(u2); //提交事务 bt.commit(); session.close(); }
具体的也没什么好讲的,和“一对多|多对一”关系中的测试代码很像,不再赘述;
运行后查看数据库中会发现出现三张表sys_user、sys_role、sys_user_role三张表,并且表中有对应的数据(如果没有就说明失败了);
数据展示:
sys_user表中数据:
sys_role表中数据:
sys_user_role表中数据:
进阶操作
与“一对多|多对一”关系中的类似;
关系管理:inverse
在多对多关系中,两个关联对象的数据操作均在中间表上,即:两个对象均维护关联关系,这样在一张表中会出现两次的关系维护,而维护中会有主键重复的问题,违反了主键不可重复的规定,因此出现错误,所以最好是使用关系管理中的inverse属性进行配置,使一方放弃关系维护即可,而放弃关系维护的一方具体要看业务中的实际应用,比如:以恶员工的入职,首先要输入用户的基本信息,然后再选择这个人的工作角色,这样是以用户这个表来进行关系管理的,因此,可以选择让角色表来放弃关系维护;
测试代码:
@Test public void demo1(){ Session session = HibernateUtils.openSession(); Transaction bt = session.beginTransaction(); //1.创建用户 User u1 = new User(); u1.setUser_name("范帅"); u1.setUser_password("fs"); u1.setUser_code("fs"); u1.setUser_state('1'); User u2 = new User(); u2.setUser_name("王建"); u2.setUser_password("wj"); u2.setUser_code("wj"); u2.setUser_state('1'); //2.创建角色 Role r1 = new Role(); r1.setRole_name("保洁"); r1.setRole_memo("后勤"); Role r2 = new Role(); r2.setRole_name("门童"); r2.setRole_memo("接待"); //3.用户维护和角色的关系 u1.getRoles().add(r1); u1.getRoles().add(r2); u2.getRoles().add(r1); u2.getRoles().add(r2); //4.角色维护和用户的关系 r1.getUsers().add(u1); r1.getUsers().add(u2); r2.getUsers().add(u1); r2.getUsers().add(u2); //5.保存 session.save(r1); session.save(r2); session.save(u1); session.save(u2); bt.commit(); session.close(); }
在上面的3、4步均需要对一张表进行关系维护操作,两次的维护会有数据id值重复的问题,这样就会报错,最好的就是设置一方不进行关系维护,因此可以根据业务需要进行分析关闭哪一方的关系维护功能;
设置一方的就好,具体设置和一对多|多对一关系中的一样就不多赘述,设置如下图所示:
<set name="users" table="sys_user_role" inverse="true">
<key column="role_id"></key>
<many-to-many class="User" column="user_id"></many-to-many>
</set>
在Role的映射文件中设置inverse="true"
,既是放弃对关系的维护,在User的映射文件中并不需要在设置;
注意:inverse属性中,true是放弃关系管理;false(默认)才是需要关系管理的配置,不要搞混了;
级联操作:cascade
请注意:级联操作的目的就是为了减少代码编写量(不要把它想得太高大上);
测试代码:
/* * 为已存在的用户进行增加新角色 */ @Test public void demo2(){ Session session = HibernateUtils.openSession(); Transaction bt = session.beginTransaction(); //获得所要操作的已存在的用户 User user = session.get(User.class, 1l); //创建新的角色 Role r3 = new Role(); r3.setRole_name("厨师"); r3.setRole_memo("后勤"); //添加角色给用户 user.getRoles().add(r3); r3.getUsers().add(user); //将创建的瞬时态的角色信息转换为持久态 session.save(r3); bt.commit(); session.close(); }
查看数据库中是否存在id为1的用户有一个id代表厨师的角色,若有既是成功;在此中可以使用级联保存的优化操作,在user.hbm.xml中配置级联保存,因为是已存在用户保存新角色,即:用户为主导,因此级联保存设置在用户的映射文件中,配置和一对多多对一的一样,具体配置如下:
<set name="roles" table="sys_user_role" cascade="save-update">
<key column="user_id"></key>
<many-to-many class="Role" column="role_id"/>
</set>
只要配置一方的cascade="save-update"
就好了;配置好后的测试代码如下:
@Test public void demo2(){ Session session = HibernateUtils.openSession(); Transaction bt = session.beginTransaction(); //获得所要操作的客户 User user = session.get(User.class, 2l); //创建新角色 Role r3 = new Role(); r3.setRole_name("服务生"); r3.setRole_memo("接待"); //添加角色给用户 user.getRoles().add(r3); //r3.getUsers().add(user);配置好后此行就不用再写了 //创建持久态对象 session.save(r3); bt.commit(); session.close(); }
区别点就在于r3.getUsers().add(user);
再配置好后不用再重复写,一行两行的可能看不出来,但是多行之后就会出现区别;
pass:此章完后hibernate的主要使用的关联映射知识点就完了;