Hibernate 的一对多关联映射
之前在学习 Hibernate 的时候,其实都是单表的操作。在实际的开发当中,比如做一个商城,就需要好多张数据库表,表与表之间是有关系的。之前些做一些关联查询或者是一些基本的查询操作的时候,都是写 SQL 语句去关联两张表,这样子稍微有一点麻烦。那么有了 Hibernate 之后,它就可以解决这类问题了。在这之前,先了解一些数据库 表之间的关系,它是有三种关系的。
1、数据库表与表之间的关系
也就是说我们的一个系统,抽取出来的那些实体,因为表是根据实体来的 ,那么它会根据实体分成 这三类的关系。也就是说你所有的系统里边,所有抽取出来的实体,也只有这三种关系。
① 一对多的关系
什么关系属于一对多?
一个部门对于多个员工,一个员工只能属于一个部门。
一个客户可以对应多个联系人,一个联系人只能属于某一个客户(个人认为是错误的)。
一对多的创表原则
② 多对多的关系
什么样的关系属于多对多?
一个学生可以选择多门课程,一门课程也可以被多个学生选择。
一个用户可以选择多个角色,一个角色也可以被多个用户选择。
多对多建表原则
③ 一对一的关系(了解,实际开发过程中应用很少)
在实际开发过程中,一对一的关系可以建成一张表,除非 是有特殊的需求需要将表分开。
唯一外键对应
在一张表添加外键字段,并作唯一约束。
主键对应
需要将两张表关联。
一对多怎么表示?一个客户应该有多个联系人,一个联系人只能属于一个客户。
现在要通过 ORM 的方式表示:一个联系人只能属于某一个客户。开发语言是面向对象的 ,怎么表示一个联系人属于一个客户呢?放置一个客户的对象,所有以后创表的时候会创建外键,但是创建实体的时候要放的是“一”的一方的一个对象。所以以后碰到外键的时候不要写外键的名称了,把外键改成“一”的一个对象,并生成相应的get & set。
还是通过 ORM 的方式表示 :一个客户对应多个联系人。放置的是“多”的一方的集合。需要注意的是,hibernate 默认使用的是 set 集合,不是 list 集合。当然,它里面也可以配置 list/map,list 集合应为在 hibernate 中需要对 list 数据进行有序排列。如果配置 list 集合,hibernate 需要在数据库表中多建一列,这一列就是用于 hibernate 排序的,它会用0、1、2、3、4、5...列这个顺序,那么表会多出来一列。所以一般是使用的都是 set 集合。
现在做的是双向关联,就是从客户这边查联系人可以查,从联系人这边查客户也可以查。如果只做了单向关联,只在联系人那边放客户对象了,客户这边没有放联系人的集合,那你在查询的时候,只能从联系人查到客户,就不能从客户查联系人了。当然,两边都做好了,想从那边去查都没问题(也可以做出单项的)。
Hibernate 的一对多关联映射
(一)、Hibernate 的一对多关联映射
1、创建一个项目,引入相应 jar 包
2、创建数据库和表
CREATE TABLE `cst_customer` (`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',`cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',`cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源',`cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业',`cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别',`cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话',`cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话',PRIMARY KEY (`cust_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE `cst_linkman` (`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',`lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名',`lkm_cust_id` bigint(32) DEFAULT NULL COMMENT '客户id',`lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别',`lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话',`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机',`lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱',`lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq',`lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位',`lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注',PRIMARY KEY (`lkm_id`),KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
在 MySQL 数据库中,将两张表拖拽到“架构设计器”选项卡就可以看到两张表之间的关系。
3、创建实体
① “一”的一方的实体
② “多的一方的实体”
4、创建映射文件
① 多的一方的映射的创建(联系人表的 hibernate 配置文件 LinkMan.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.itheima.hibernate.domain.LinkMan" table="cst_linkman"><!-- 建立OID与主键映射 --><id name="lkm_id" column="lkm_id"><generator class="native"/></id><!-- 建立普通属性与表字段映射 --><property name="lkm_name"/><property name="lkm_gender"/><property name="lkm_phone"/><property name="lkm_mobile"/><property name="lkm_email"/><property name="lkm_qq"/><property name="lkm_position"/><property name="lkm_memo"/><!-- 配置多对一的关系:放置的是一的一方的对象 --><!-- many-to-one标签* name :一的一方的对象的属性名称。* class :一的一方的类的全路径。* column :在多的一方的表的外键的名称。
操作 column 就相当与操作数据库中表的外键--><many-to-one name="customer" class="com.itheima.hibernate.domain.Customer" column="lkm_cust_id"/></class> </hibernate-mapping>
② “一”的一方的映射的创建(客户表的 hibernate 配置文件 Customer.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.itheima.hibernate.domain.Customer" table="cst_customer"><!-- 建立OID与主键映射 --><id name="cust_id" column="cust_id"><generator class="native"/></id><!-- 建立普通属性与数据库表字段映射 --><property name="cust_name" column="cust_name" /><property name="cust_source" column="cust_source"/><property name="cust_industry" column="cust_industry"/><property name="cust_level" column="cust_level"/><property name="cust_phone" column="cust_phone"/><property name="cust_mobile" column="cust_mobile"/><!-- 配置一对多的映射:放置的多的一方的集合 --><!-- set标签 :* name :多的一方的对象集合的属性名称。* cascade:级联* inverse:放弃外键维护权。--><set name="linkMans"><!--key标签* column:多的一方的外键的名称。--><key column="lkm_cust_id"/><!-- one-to-many标签* class :多的一方的类的全路径--><one-to-many class="com.itheima.hibernate.domain.LinkMan"/></set></class> </hibernate-mapping>
5、创建核心配置文件(hibernate.cfg.xml)
将要连接的数据库名称改一下,还有要引入的映射文件,这里要引入两个映射文件。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration><session-factory><!-- 连接数据库的基本参数 --><property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property><property name="hibernate.connection.url">jdbc:mysql:///hibernate_day03</property><property name="hibernate.connection.username">root</property><property name="hibernate.connection.password">abc</property><!-- 配置Hibernate的方言 --><property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property><!-- 可选配置================ --><!-- 打印SQL --><property name="hibernate.show_sql">true</property><!-- 格式化SQL --><property name="hibernate.format_sql">true</property><!-- 自动创建表 --><property name="hibernate.hbm2ddl.auto">update</property><!-- 配置C3P0连接池 --><property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property><!--在连接池中可用的数据库连接的最少数目 --><property name="c3p0.min_size">5</property><!--在连接池中所有数据库连接的最大数目 --><property name="c3p0.max_size">20</property><!--设定数据库连接的过期时间,以秒为单位,如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 --><property name="c3p0.timeout">120</property><!--每3000秒检查所有连接池中的空闲连接 以秒为单位--><property name="c3p0.idle_test_period">3000</property><!-- 设置事务隔离级别 --><property name="hibernate.connection.isolation">4</property><!-- 配置当前线程绑定的Session --><property name="hibernate.current_session_context_class">thread</property><!-- 引入映射 --><!-- <mapping resource="com/itheima/hibernate/domain/Customer.hbm.xml"/><mapping resource="com/itheima/hibernate/domain/LinkMan.hbm.xml"/> --><mapping resource="com/itheima/hibernate/domain/User.hbm.xml"/><mapping resource="com/itheima/hibernate/domain/Role.hbm.xml"/></session-factory> </hibernate-configuration>
7、创建测试类
package com.itheima.hibernate.demo1;import org.hibernate.Session; import org.hibernate.Transaction; import org.junit.Test;import com.itheima.hibernate.domain.Customer; import com.itheima.hibernate.domain.LinkMan; import com.itheima.hibernate.utils.HibernateUtils;/*** 一对多的测试类* @author jt**/ public class HibernateDemo1 {@Test// 保存2个客户 和 3个联系人 并且建立好关系public void demo1(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 创建两个客户Customer customer1 = new Customer();customer1.setCust_name("王东");Customer customer2 = new Customer();customer2.setCust_name("赵洪");// 创建三个联系人LinkMan linkMan1 = new LinkMan();linkMan1.setLkm_name("凤姐");LinkMan linkMan2 = new LinkMan();linkMan2.setLkm_name("如花");LinkMan linkMan3 = new LinkMan();linkMan3.setLkm_name("旺财");// 设置关系: linkMan1.setCustomer(customer1);linkMan2.setCustomer(customer1);linkMan3.setCustomer(customer2);customer1.getLinkMans().add(linkMan1);customer1.getLinkMans().add(linkMan2);customer2.getLinkMans().add(linkMan3);// 保存数据: session.save(linkMan1);session.save(linkMan2);session.save(linkMan3);session.save(customer1);session.save(customer2);tx.commit();} @Test/*** 区分cascade和inverse的区别*/public void demo9(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("李兵");LinkMan linkMan = new LinkMan();linkMan.setLkm_name("凤姐");customer.getLinkMans().add(linkMan);// 条件在Customer.hbm.xml上的set中配置了cascade="save-update" inverse="true"session.save(customer); // 客户会插入到数据库,联系人也会插入到数据库,但是外键为null tx.commit();} }
(二)、Hibernate 的一对多相关操作
1、一对多关系只保存一边是否可行
将数据做了双向关连以后,是否可以 只保存一边的 数据呢?不可以,回报一个瞬时对象异常的错误。什么是瞬时对象异常啊,就是说持久态的对象关联了一个瞬时态的对象。客户对象本来是瞬时态的对象 ,一保存就变成持久态的对象了,之前和联系人对象做了关联,但联系人对象 还是瞬时态的对象,所以会报错。
@Test// 一对多关系只保存一边是否可以public void demo2(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("赵洪");LinkMan linkMan = new LinkMan();linkMan.setLkm_name("如花");customer.getLinkMans().add(linkMan);linkMan.setCustomer(customer);// 只保存一边是否可以:不可以,报一个瞬时对象异常:持久态对象关联了一个瞬时态对象。// session.save(customer); session.save(linkMan);tx.commit();}
2、一对多的级联操作
如果想只保存一边,那么要通过一个配置,叫做级联操作。
级联操作包含有常用的两种级联操作,一种叫级联保存或更新,一种叫级联删除。
什么叫做级联
级联指的是,操作一个对象的时候,是否会同时操作其关联的对象。
级联是有方向性
操作一的一方的时候,是否操作到多的一方
操作多的一方的时候,是否操作到一的一方
之前保存一边是会报异常的,报的是瞬时对象异常。现在要通过配置完成一个级联操作,要看你操作的主体是什么,我们现在操作的主体是 客户对象,需要在客户实体的映射文件中进行配置 。
① 级联保存或更新
保存客户级联联系人(保存“一”的一方,级联“多”的一方)
映射文件的配置
<!-- set标签 :* name :多的一方的对象集合的属性名称。* cascade:级联* inverse:放弃外键维护权。--><set name="linkMans" cascade="save-update">
测试类
@Test/*** 级联保存或更新操作:* * 保存客户级联联系人,操作的主体是客户对象,需要在Customer.hbm.xml中进行配置* * <set name="linkMans" cascade="save-update">*/public void demo3(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("赵洪");LinkMan linkMan = new LinkMan();linkMan.setLkm_name("如花");customer.getLinkMans().add(linkMan);linkMan.setCustomer(customer);session.save(customer);tx.commit();}
保存联系人级联客户(保存“多”的一方,级联“一”的一方)
映射文件的配置
<!-- many-to-one标签* name :一的一方的对象的属性名称。* class :一的一方的类的全路径。* column :在多的一方的表的外键的名称。操作 column 就相当与操作数据库中表的外键--><many-to-one name="customer" cascade="save-update" class="com.itheima.hibernate.domain.Customer" column="lkm_cust_id"/>
测试类
@Test/*** 级联保存或更新操作:* * 保存联系人级联客户,操作的主体是联系人对象,需要在LinkMan.hbm.xml中进行配置* * <many-to-one name="customer" cascade="save-update" class="com.itheima.hibernate.domain.Customer" column="lkm_cust_id"/>*/public void demo4(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("李兵");LinkMan linkMan = new LinkMan();linkMan.setLkm_name("凤姐");customer.getLinkMans().add(linkMan);linkMan.setCustomer(customer);session.save(linkMan);tx.commit();}
3、测试对象的导航
我们需要注意的是,现在做的属于双向关联,就是客户关联联系人,联系人也关联了客户,其实这种也叫作对象的导航。对象导航是怎么去设置的,怎么往数据库中保存或更新的。
没有关联,外键默认是 none。
对象的导航,一定要搞清楚关联关系的设置,那边配置了 cascade 对另一半的影响。
@Test/*** 测试对象的导航* * 前提:一对多的双方都设置cascade="save-update"*/public void demo5(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("李兵");LinkMan linkMan1 = new LinkMan();linkMan1.setLkm_name("凤姐");LinkMan linkMan2 = new LinkMan();linkMan2.setLkm_name("如花");LinkMan linkMan3 = new LinkMan();linkMan3.setLkm_name("芙蓉");linkMan1.setCustomer(customer);customer.getLinkMans().add(linkMan2);customer.getLinkMans().add(linkMan3);// 双方都设置了cascade// session.save(linkMan1); // 发送几条insert语句 4条 linkMAn1 custome linkMan2 linkMan2// session.save(customer); // 发送几条insert语句 3条 customer linkMan2 linkman3session.save(linkMan2); // 发送几条insert语句 1条 linkMan1 tx.commit();}
5、级联删除
什么是级联删除?
删除一边的,同时将另一方的数据也一并删除。
在 JDBC 情况,要想做级联删除,除非先把要删除的那个客户(“一”)所关联的联系人(“多”)删除干净,才能继续删除客户。但是 Hibernate 是可以直接级联你删除的。
Hibernate 情况将要删除的外键设置为空,再删除数据,只删除了一方。
Hibernate 删除数据需要先查询,再删除。如果创建对象后直接删除,这是对象所对应的集合还是空的;先查询,数据将自动添加到对象所对应的集合,再删除。
删除客户级联删除联系人
@Test/*** 级联删除:* * 删除客户级联删除联系人,删除的主体是客户,需要在Customer.hbm.xml中配置
* * 多个操作之间用逗号隔开* * <set name="linkMans" cascade="save-update,delete">*/public void demo6(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 没有设置级联删除,默认情况:修改了联系人的外键,删除客户/*Customer customer = session.get(Customer.class, 1l);session.delete(customer);*/// 删除客户,同时删除联系人Customer customer = session.get(Customer.class, 1l);session.delete(customer);tx.commit();}
删除联系人级联删除客户(基本不用)
@Test/*** 级联删除:* * 删除联系人级联删除客户,删除的主体是联系人,需要在LinkMan.hbm.xml中配置* * <many-to-one name="customer" cascade="delete">*/public void demo7(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 删除客户,同时删除联系人LinkMan linkMan = session.get(LinkMan.class, 3l);session.delete(linkMan);tx.commit();}
6、一对多设置了双向关联产生多余的 SQL 语句
在一对多 的操作中,如果设置了双向关联,会产生一些多余的 SQL 语句。
怎么避免掉这个问题呢?在这里举一个例子。
首先先让它重新建一下数据库表,
接下来运行 demo1 的程序,往数据表中插入两个客户和三个联系人,让它重新建议下表。建好以后,再将配置给位 update。
public void demo1(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 创建两个客户Customer customer1 = new Customer();customer1.setCust_name("王东");Customer customer2 = new Customer();customer2.setCust_name("赵洪");// 创建三个联系人LinkMan linkMan1 = new LinkMan();linkMan1.setLkm_name("凤姐");LinkMan linkMan2 = new LinkMan();linkMan2.setLkm_name("如花");LinkMan linkMan3 = new LinkMan();linkMan3.setLkm_name("旺财");// 设置关系: linkMan1.setCustomer(customer1);linkMan2.setCustomer(customer1);linkMan3.setCustomer(customer2);customer1.getLinkMans().add(linkMan1);customer1.getLinkMans().add(linkMan2);customer2.getLinkMans().add(linkMan3);// 保存数据: session.save(linkMan1);session.save(linkMan2);session.save(linkMan3);session.save(customer1);session.save(customer2);tx.commit();}
现在可以在数据库中看到,两个客户和三个联系人。
原来2号联系人属于1号客户,现在要把它改成属于2号客户(没有用 update 方法,因为两个对象都是持久态对象,发生改变会自动更新数据可库)。
@Test/*** 将2号联系人原来归1号客户,现在改为2号客户*/public void demo8(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 查询2号联系人LinkMan linkMan = session.get(LinkMan.class, 2l);// 查询2号客户Customer customer = session.get(Customer.class, 2l);// 双向的关联 linkMan.setCustomer(customer);customer.getLinkMans().add(linkMan);tx.commit();}
看发送的数据库语句,数据库中已经修改成功,发了两条update语句,两次都修改了外键,为什么会改两回呢?
当你进行执行的时候,Hibernate 会有一级缓存区,有一块是缓存区,有一块是快照区。当你执行 get 查询的时候,它会将数据放到缓存区一份,并对这份数据进行快照。当事务提交的时候,Hibernate 会比对一级缓存区域和快照区的数据,如果不一致,会更新数据库。
都会进行更新外键,其实只要更新一次外键就可以了,所以在这里会产生多余的 SQL 语句
解决多余的 SQL语句
单向维护,就用一边去做,比如只让客户关联联系人,或是只让联系人关联客户,这么做是可以的。但是这种方法在当前条件下做是可以的,但是有的地方还是不行还是会出现一些问题。
还有一种方法是使一方放弃外键维护权,为什么会产生多余的 SQL,就是因为现在两边都可以维护这个外键。
因为我们之需要更改一次外键就行了,所以必须让一方放弃外键维护权。那么,让哪一方放弃呢?
一的一方放弃,为什么一的一方放弃呢。因为关系的维护应当由多的一方发起(让一个国家主席记住所有人的名字,和所有人记住国家主席的名字)。
那怎么让一的一方放弃呢,就需要在一的一方的配置那,在 set 集合那加上一个配置 inverse,值设置为 true。
那么放弃外键的维护权,在哪个地方还会用到呢?一对多的查询的修改的时候,现在还没有做带页面的,做带页面的时候,既有商品,又有分类。能查出分类,当你查出分类的时候,点击修改分类时候,会跳到一个界面,有分类的名称。。然后就提交了,当你提交过去以后,这个集合是空的,因为页面没有去提交集合;当你一修改,它会把这个分类下原来所有的商品的外键全都改空。这个时候,就必须放弃一的一方的外键维护权。
减轻了服务器的压力,少发一些 SQL 语句。
区分cascade和inverse
@Test/*** 区分cascade和inverse的区别*/public void demo9(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();Customer customer = new Customer();customer.setCust_name("李兵");LinkMan linkMan = new LinkMan();linkMan.setLkm_name("凤姐");customer.getLinkMans().add(linkMan);// 条件在Customer.hbm.xml上的set中配置了cascade="save-update" inverse="true"session.save(customer); // 客户会插入到数据库,联系人也会插入到数据库,但是外键为null tx.commit();}
执行程序,客户会插入到数据库,联系人也会插入到数据库 ,但是外键为空。注意,cascade是操作关联对象的,也就是客户进去了,关联对象(联系热人)也会进去,这是由cascade控制的;但是具体有没有外键,是靠inverse去控制的。
Hibernate 的多对多关联映射
(一)、Hibernate 多对多关系的配置
多对多和一对多还有一点点的渊源,为什么这么说呢?其实就是因为如果你不会多对多,其实你也可以建两个一对多,也是可以的。
1、创建表
角色表
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=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;
现在有了用户表和角色表,一个客户可以选择多个角色(多对多关系),所以还需要有一张中间表。中间表就两个字段,一个是角色 id,一个是用户 id;分别创建两个外键,分别去指向角色表的主键和用户表的主键。
中间表
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;
2、创建实体
用户的实体
package com.itheima.hibernate.domain;import java.util.HashSet; import java.util.Set;/*** 用户的实体* @author jt*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=1 DEFAULT CHARSET=utf8;*/ 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>();
// 生成相应的get、setpublic 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.itheima.hibernate.domain;import java.util.HashSet; import java.util.Set;/*** 角色的实体* @author jt*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;*/ 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;}}
现在实体的一些基本的属性已经建好了,但是现在我们要创建的是一个多对多关系的实体。也就是一个用户可以选择多个角色,那么如何去设置多对多的关系呢?多对多关系设置的时候,要想表示之间的关系,放置的都是对方的集合。
3、创建映射
用户的映射
<?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.itheima.hibernate.domain.User" table="sys_user"><!-- 建立OID与主键的映射 --><id name="user_id" column="user_id"><generator class="native"/></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 :对方的集合的属性名称。* table :多对多的关系需要使用中间表,放的是中间表的名称。--><set name="roles" table="sys_user_role" cascade="save-update,delete" ><!-- key标签:* column :当前的对象对应中间表的外键的名称。--><key column="user_id"/><!-- many-to-many标签:* class :对方的类的全路径* column :对方的对象在中间表中的外键的名称。--><many-to-many class="com.itheima.hibernate.domain.Role" column="role_id"/></set></class> </hibernate-mapping>
角色的映射
<?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.itheima.hibernate.domain.Role" table="sys_role"><!-- 建立OID与主键的映射 --><id name="role_id" column="role_id"><generator class="native"/></id><!-- 建立普通属性与字段的映射 --><property name="role_name" column="role_name"/><property name="role_memo" column="role_memo"/><!-- 与用户的多对多的映射关系 --><!-- set标签* name :对方的集合的属性名称。* table :多对多的关系需要使用中间表,放的是中间表的名称。--><set name="users" table="sys_user_role" cascade="save-update,delete" inverse="true"><!-- key标签:* column :当前的对象对应中间表的外键的名称。--><key column="role_id"/><!-- many-to-many标签:* class :对方的类的全路径* column :对方的对象在中间表中的外键的名称。--><many-to-many class="com.itheima.hibernate.domain.User" column="user_id"/></set></class> </hibernate-mapping>
映射配置好以后,别忘了,需要把映射文件添加到核心配置里面去 。
<mapping resource="com/itheima/hibernate/domain/User.hbm.xml"/><mapping resource="com/itheima/hibernate/domain/Role.hbm.xml"/>
4、编写测package com.itheima.hibernate.demo2;import org.hibernate.Session;import org.hibernate.Transaction;import org.junit.Test;
import com.itheima.hibernate.domain.Role; import com.itheima.hibernate.domain.User; import com.itheima.hibernate.utils.HibernateUtils;/*** Hibernate的多对多的映射* @author jt**/ public class HibernateDemo2 {@Test/*** 保存多条记录:保存多个用户和角色*/public void demo1(){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);
// 这样一执行,就会出错(一对多也是这样设置的,不会出错)
// 设置了双向的关联关系,在一对多的时候,是做了两次修改,关系维护是靠多的一方的外键,修改外键就可以了,修改几次都没关系;
// 但是,多对多的关系想要去维护,靠的是中间表,中间表关系的维护是往里面插入
// 两个字段共同决定一条记录,共同作为主键,两个字段不能同时重复。必须有一方放弃外键维护权-
// 一般是被动方放弃外键维护权,像我们用户选角色,角色一般是被动的;所以我们需要在角色这边的配置里,在set这需要添加inverse=true
// 如果单向关联,这种是可以的;但有的时候需要双向关联,双向关联的时候,让一方放弃就可以了。tx.commit();} }
多对多的操作,只保存一边是否可以?不可以,瞬时对象异常
如果想要执行,需要设置级联
@Test/*** 多对多的操作:* * 只保存一边是否可以?不可以,瞬时对象异常*/public void demo2(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 创建2个用户User user1 = new User();user1.setUser_name("赵洪");// 创建3个角色Role role1 = new Role();role1.setRole_name("研发部");// 设置双向的关联关系: user1.getRoles().add(role1);role1.getUsers().add(user1);// 只保存用户:// session.save(user1); session.save(role1);tx.commit();}
多对多的级联保存或更新
保存那个对象,外键也需要交给它来维护
@Test/*** 多对多的级联保存:* * 保存用户级联保存角色。在用户的映射文件中配置。* * 在User.hbm.xml中的set上配置 cascade="save-update"*/public void demo3(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 创建2个用户User user1 = new User();user1.setUser_name("赵洪");// 创建3个角色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"*/@Testpublic void demo4(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 创建2个用户User user1 = new User();user1.setUser_name("李兵");// 创建3个角色Role role1 = new Role();role1.setRole_name("公关部");// 设置双向的关联关系: user1.getRoles().add(role1);role1.getUsers().add(user1);// 只保存用户: session.save(role1);tx.commit();}
多对多的级联删除(基本用不上)
/*** 多对多的级联删除:* * 删除用户级联删除角色* * 在User.hbm.xml中的set上配置 cascade="delete"*/@Testpublic void demo5(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 查询1号用户:User user = session.get(User.class, 1l);session.delete(user);tx.commit();}/*** 多对多的级联删除:* * 删除角色级联删除用户* * 在Role.hbm.xml中的set上配置 cascade="delete"*/@Testpublic void demo6(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 查询2号角色:Role role = session.get(Role.class, 2l);session.delete(role);tx.commit();}
给用户选择角色,或给用户改选角色,或是给用户删除角色。
@Test/*** 给用户选择角色*/public void demo7(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 给1号用户多选2号角色// 查询1号用户User user = session.get(User.class, 1l);// 查询2号角色Role role = session.get(Role.class, 2l);user.getRoles().add(role);tx.commit();}@Test/*** 给用户改选角色*/public void demo8(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 给2号用户将原有的2号角色改为3号角色// 查询2号用户User user = session.get(User.class, 2l);// 查询2号角色Role role2 = session.get(Role.class, 2l);Role role3 = session.get(Role.class, 3l);user.getRoles().remove(role2);user.getRoles().add(role3);tx.commit();}@Test/*** 给用户改选角色*/public void demo9(){Session session = HibernateUtils.getCurrentSession();Transaction tx = session.beginTransaction();// 给2号用户删除1号角色// 查询2号用户User user = session.get(User.class, 2l);// 查询2号角色Role role = session.get(Role.class, 1l);user.getRoles().remove(role);tx.commit();}
两者之间关系的维护,主要靠的是集合。操作集合,就可以操作他们之间的关系,所以说多对多一般进行操作的时候,都操作的是集合。