Hibernate(三) - hibernate 表操作-多对多配置

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、set
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.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();}

两者之间关系的维护,主要靠的是集合。操作集合,就可以操作他们之间的关系,所以说多对多一般进行操作的时候,都操作的是集合。

 

转载于:https://www.cnblogs.com/xifengbuqi/p/9546276.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/266213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vb treeview 展开子节点_详解最长公共子序列问题,秒杀三道动态规划题目

学算法认准 labuladong后台回复进群一起力扣?读完本文&#xff0c;可以去力扣解决如下题目&#xff1a;1143.最长公共子序列(Medium)583. 两个字符串的删除操作(Medium)712.两个字符串的最小ASCII删除和(Medium)好久没写动态规划算法相关的文章了&#xff0c;今天来搞一把。不…

linux查看数据积压,查看kafka消息队列的积压情况

创建topickafka-topics --create --zookeeper master:2181/kafka2 --replication-factor 2 --partitions 3 --topic mydemo5列出topickafka-topics --list --zookeeper master:2181/kafka2描述topickafka-topics --describe --zookeeper master:2181/kafka2 --topic mydemo5生产…

python 三引号_Python 基础(一):入门必备知识

目录1 标识符2 关键字3 引号4 编码5 输入输出6 缩进7 多行8 注释9 数据类型10 运算符10.1 常用运算符10.2 运算符优先级1 标识符标识符是编程时使用的名字&#xff0c;用于给变量、函数、语句块等命名&#xff0c;Python 中标识符由字母、数字、下划线组成&#xff0c;不能以数…

排序算法:冒泡和快排 摘自网络

冒泡排序&#xff1a; 首先我们自己来设计一下“冒泡排序”&#xff0c;这种排序很现实的例子就是&#xff1a; 我抓一把沙仍进水里&#xff0c;那么沙子会立马沉入水底&#xff0c; 沙子上的灰尘会因为惯性暂时沉入水底&#xff0c;但是又会立马像气泡一样浮出水面&#xff0c…

镭波笔记本安装linux,镭波笔记本windows7旗舰版系统下载与安装教程

镭波笔记本windows7旗舰版系统下载地址以及安装教程有很多盆友询问&#xff0c;今天&#xff0c;我就将镭波电脑下载安装win7旗舰版系统的详细步骤分享给你们,一起来了解一下镭波电脑是如何安装windows7旗舰版。镭波笔记本Windows7旗舰版系统下载&#xff1a;64位Windows7旗舰版…

linux运维和3dmax哪个简单,牛逼运维常用的工具系列-2

劳动最光荣nmonnmon是linux性能监视和分析数据的工具&#xff0c;它的安装很简单&#xff0c;下载解压后&#xff0c;添加可执行权限&#xff0c;即可运行下载解压后&#xff0c;通过文件名可以发现&#xff0c;是多个发行版本的&#xff0c;根据自己的发行版本&#xff0c;然后…

语义分割和实例分割_语义分割入门的一点总结

点击上方“CVer”&#xff0c;选择加"星标"或“置顶”重磅干货&#xff0c;第一时间送达作者&#xff1a;Yanpeng Sunhttps://zhuanlan.zhihu.com/p/74318967本文已由作者授权&#xff0c;未经允许&#xff0c;不得二次转载语义分割目的&#xff1a;给定一张图像&…

linux 音频驱动的流程,Intel平台下Linux音频驱动流程分析

【软件框架】在对要做的事情一无所知的时候&#xff0c;从全局看看系统的拓扑图对我们认识新事物有很大的帮助。Audio 部分的驱动程序框架如下图所示&#xff1a;这幅图明显地分为 3 级。上方蓝色系的 ALSA Kernel 整体属于Linux Kernel&#xff0c;是原生Linux 操作系统的一部…

Windows Server 2008 R2Cisco2960 配置Radius服务 实现802.1x认证 实战

实战配置Windows Server 2008 R2 Radius服务 与Cisco 2960 实现 802.1x认证实验拓扑1.Radius服务器 安装 dc 域名 wjl.com &#xff0c;和ca 安装步骤不再详解2.安装完ca之后&#xff0c;打开MMC 添加计算机证书&#xff0c;查看个人-证书里面有没有ca颁发给计算机的证书&…

linux文件编程(3)—— main函数传参、myCp(配置成环境变量)、修改配置文件、将整数和结构体数组写到文件

参考&#xff1a;linux文件编程&#xff08;3&#xff09;—— 文件编程的简单应用&#xff1a;myCp、修改配置文件 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-04-09 23:45:05 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/115209404 …

linux 修改文件名_Linux常用命令

Linux下一切皆文件查看型ls 查看当前文件夹内容 选项 -a 查看隐藏文件 -l 查看文件详细信息pwd 查看当前所在路径su 切换用户cat /etc/passwd 查看当前系统的用户cat 文件 查看文件内容选项 -n 加上编号 -E 每行末尾加上$ifconfig 查看网卡名&#xff0c;IP地址等网络信息route…

c语言mfc弹出窗口函数,CMFCDesktopAlertWnd实现桌面弹出消息框

1.创建一个CMFCDesktopAlertWnd指针CMFCDesktopAlertWnd* pPopup new CMFCDesktopAlertWnd;2.设置参数pPopup->SetAnimationType((CMFCPopupMenu::ANIMATION_TYPE) 2);pPopup->SetAnimationSpeed(100);pPopup->SetTransparency((BYTE)128);pPopup->SetSmallCaptio…

linux文件编程(2)——系统文件描述符、动静态文件、块设备介绍

参考&#xff1a;linux文件编程&#xff08;2&#xff09;——文件操作原理简述之文件描述符、动静态文件、块设备 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-04-09 11:14:12 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/115209312 目…

java中volatile的使用方式

2019独角兽企业重金招聘Python工程师标准>>> 转载地址&#xff1a; http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 转载于:https://my.oschina.net/wangfree/blog/122664

linux文件编程(1)—— open、write、read、lseek、阻塞问题(ps文件操作/文件描述符/重定向原理/缓冲区/标准错误)

参考&#xff1a;linux文件编程&#xff08;1&#xff09;—— 常用API之open、write、read、lseek 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-04-08 22:19:28 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/115209134 【Linux】文件操…

linux文件编程(4)—— 用ANSIC标准C库函数进行文件编程:fopen、fread、fwrite、fseek

参考&#xff1a;linux文件编程&#xff08;5&#xff09;—— 用ANSIC标准中的C库函数进行文件编程 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-04-11 11:58:25 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/115209680 部分参照&#…

swig封装 c语言函数到python库,python swig 调用C/C++接口

转载&#xff1a;https://www.cnblogs.com/dda9/p/8612068.html当你觉得python慢的时候&#xff0c;当你的c/c代码难以用在python上的时候&#xff0c;你可能会注意这篇文章。swig是一个可以把c/c代码封装为python库的工具。(本文封装为python3的库)文章结构整体看封装只使用py…

Java学习---面试基础知识点总结

Java中sleep和wait的区别① 这两个方法来自不同的类分别是&#xff0c;sleep来自Thread类&#xff0c;和wait来自Object类。sleep是Thread的静态类方法&#xff0c;谁调用的谁去睡觉&#xff0c;即使在a线程里调用b的sleep方法&#xff0c;实际上还是a去睡觉&#xff0c;要让b线…

使用NPOI和委托做EXCEL导出

首先&#xff0c;在用NPOI导出时&#xff0c;学习了邀月这篇文章NPOI根据Excel模板生成原生的Excel文件实例&#xff0c;在这里先行谢过了。 本篇文章在邀月的基本上&#xff0c;做了一些小的改动&#xff0c;加上委托的机制。因为在做导出时&#xff0c;加载模板&#xff0c;下…

android 放大镜功能,简单实现Android放大镜效果

利用之前学过的图形图像绘画技术和图片添加特效技术&#xff0c;我们来实现一个Android放大镜的简单应用。最终效果如图具体实现:用来显示自定义的绘图类的布局文件res/layout/main.xml:xmlns:tools"http://schemas.android.com/tools"android:layout_width"fil…