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  linkman3
        session.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"
     */
    @Test
    public 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"
     */
    @Test
    public 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"
     */
    @Test
    public 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();
    }

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

 

posted @ 2018-08-28 09:52  昱晟  阅读(402)  评论(0编辑  收藏  举报