day30_Hibernate学习笔记_02

一、Hibernate中对象的状态

对象状态
  瞬时态|临时态:Transient
    1、没有与Hibernate产生关联(没有与Session有关联)。
    2、与数据库中的记录没有产生关联,即对象没有ID(有关联:就是与数据库中的ID有对应)。
  持久态:Persistent
    1、与Hibernate有关联(与session有关联)。
    2、对象有ID。
  游离态|脱管态:Detached
    1、没有与Hibernate产生关联。
    2、对象有ID。
三种状态转换如下图所示:


示例代码如下:
package com.itheima.a_state;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// 对象的三种状态
public class Demo1 {
    @Test
    // 演示对象三种状态
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();    // 瞬时状态
        u.setName("tom");       // 瞬时状态
        u.setPassword("1234");  // 瞬时状态

        session.save(u);        // 持久状态
        // 问题: 调用完save方法后,数据库中有没有对应记录?
        // 答:数据库中没有对应记录,但是最终(未来)会被同步到数据库中,所以仍然是持久状态。

        session.getTransaction().commit(); // 持久状态,在事务提交时,会把持久化状态对象同步(更新)到数据库中
        session.close();        // 游离状态
    }

    @Test
    // 三种状态的转换
    // 瞬时  => 持久
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();    // 瞬时状态
        u.setName("tom");       // 瞬时状态
        u.setPassword("1234");  // 瞬时状态

        session.save(u);        // 持久状态 
        // save方法会使用主键生成策略,只是为User对象指定id =>
        // native    => 主键自增,会打印 insert into 语句
        // increment => 数据库自己生成主键,先从数据库中查询最大的ID值,将ID值加1作为新的主键,不建议使用,存在线程并发问题
        // assigned  => 需要手动指定主键,不手动指定将会报错

        session.getTransaction().commit(); // 持久状态,在事务提交时,会把持久化状态对象同步(更新)到数据库中
        session.close();        // 游离状态
    }

    // 瞬时  => 游离
    // 瞬时: 没有关联,没有id
    // 游离: 没有关联,有id(与数据库中对应的id)
    @Test
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();    // 瞬时状态
        u.setId(1);             // 游离状态

        session.getTransaction().commit(); // 游离状态
        session.close(); // 游离状态        
    }

    @Test
    // 持久  => 瞬时
    // 持久: 有关联,有id
    // 瞬时: 无关联,无id
    public void fun4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态

        session.getTransaction().commit(); // 持久状态,在事务提交时,会把持久化状态对象同步(更新)到数据库中
        session.close();    // 游离状态

        u.setId(null);      // 瞬时状态
    }

    @Test
    // 持久  => 瞬时
    // 持久: 有关联,有id
    // 瞬时: 无关联,无id
    public void fun5() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态
        session.evict(u);   // 游离状态,该方法将User对象与session的关联移除
        u.setId(null);      // 瞬时状态

        session.save(u);    // 持久状态

        session.getTransaction().commit(); // 持久状态,在事务提交时,会把持久化状态对象同步(更新)到数据库中
        session.close();    // 游离状态
    }

    @Test
    // 持久  => 游离
    // 只需要将session的关联取消
    public void fun6() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态
        session.evict(u);   // 游离状态

        session.getTransaction().commit(); // 游离状态
        session.close();    // 游离状态
    }

    @Test
    // 游离  => 瞬时
    // 移除ID
    public void fun7() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态
        session.evict(u);   // 游离状态
        u.setId(null);      // 瞬时状态

        session.getTransaction().commit(); // 瞬时状态
        session.close(); // 瞬时状态
    }

    @Test
    // 游离  => 持久
    // 是否与Session关联
    public void fun8() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态
        session.evict(u);   // 游离状态
        session.update(u);  // 持久状态

        session.getTransaction().commit(); // 持久状态 ,在事务提交时,会把持久化状态对象同步(更新)到数据库中
        session.close();    // 瞬时状态
    }

    // 三种状态有什么用?
    //  答: 持久状态,我们使用Hibernate主要是为了持久化我们的数据。
    //      对于对象的状态,我们期望我们需要同步到数据库的数据,都被转换成持久状态。
    //      持久化状态的特点:Hibernate会自动将持久化状态对象的变化同步到数据库中。
    @Test
    public void fun9() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 通过get方法,得到持久状态对象
        User u = (User) session.get(User.class, 1); // 持久状态

        // u.setName("jerry");  // 依然是持久状态
        u.setId(3);             // 会报错。因为Hibernate中规定:与session建立关联的对象的ID,不允许修改!

        session.update(u);      // 多余代码  => 因为Hibernate会自动将持久化状态对象的变化同步(更新)到数据库中。

        session.getTransaction().commit(); // 持久状态  => 打印update语句
        session.close();        // 瞬时状态
    }
}

二、一级缓存

  一级缓存(更深层次理解Hibernate中对象的操作)
  缓存:Hibernate中也存在缓存。Hibernate中存在的缓存也是用来提高效率的。
  Hibernate中存在两种缓存
    1、线程级别的缓存。也叫Session缓存、Hibernate一级缓存(今天学)
    2、进程级别的缓存。也叫Hibernate二级缓存(最后一天学)
  Session缓存:就是Session对象中存在的缓存,缓存中存在的是(持久化)对象。

  一级缓存:又称为session级别的缓存。当获得一次会话(session),hibernate在session中创建多个集合(map),用于存放操作数据(PO对象),为程序优化服务,如果之后需要相应的数据,hibernate优先从session缓存中获取,如果有就使用;如果没有再查询数据库。当session关闭时,一级缓存销毁。

2.1、Session缓存的细节问题

示例代码如下:

package com.itheima.b_cache;

import java.util.List;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// session缓存 的细节问题
public class Demo2 {
    @Test
    // 1、
    //  保存对象时使用 save方法
    //  保存对象时使用 persist方法
    //  有区别吗?答:没有区别,名称不同,功能一样。
    //      persist(持久)方法 来自于JPA接口
    //      save(保存)方法来自于Hibernate
    public void fun1() {
        Session session = HibernateUtils.openSession();
        // session.beginTransaction();

        User u = new User();
        u.setName("张三");

        // session.save(u); // insert into 语句被打印  => 目的:获得id
        session.persist(u);

        // session.getTransaction().commit();
        session.close(); 
    }

    // 2.1、HQL语句查询是否会使用一级缓存?答:HQL语句查询不会使用一级缓存。
    @Test
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<User> list1 = session.createQuery("from User").list(); // 会打印一条select语句

        List<User> list2 = session.createQuery("from User").list(); // 又会打印一条select语句

        List<User> list3 = session.createQuery("from User").list(); // 又又会打印一条select语句

        session.getTransaction().commit();
        session.close(); 
    }

    // 2.2、HQL语句批量查询时,查询结果是否会进入一级缓存?答:HQL语句查询的结果会放入一级缓存中。
    @Test
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<User> list1 = session.createQuery("from User").list(); // 会打印一条select语句

        User u = (User) session.get(User.class, 1); // 不会再打印一条select语句了,因为使用了一级缓存

        session.getTransaction().commit();
        session.close(); 
    }

    @Test
    // 3.1、原生SQL语句查询的结果会不会放入一级缓存中?答:如果把查询结果封装到对象中,对象会放入一级缓存中。
    public void fun4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<User> list1 = session.createSQLQuery("select * from t_user").addEntity(User.class).list(); // 会打印一条select语句

        User u = (User) session.get(User.class, 1); // 不会再打印一条select语句了,因为使用了一级缓存

        System.out.println(u);

        session.getTransaction().commit();
        session.close(); 
    }

    @Test
    // 3.2、原生SQL语句查询的结果会不会放入一级缓存中?答:如果没有把查询结果封装到对象中,对象不会放入一级缓存中。
    public void fun5() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List list1 = session.createSQLQuery("select * from t_user").list();  // 会打印一条select语句

        User u = (User) session.get(User.class, 1);  // 又会打印一条select语句

        System.out.println(u);

        session.getTransaction().commit();
        session.close();
    }

    // 同理:Hibernate中的criteria查询  => 也会将查询结果放入一级缓存. 但是查询不会使用一级缓存,与HQL查询结论一致。
}

2.2、一级缓存快照【掌握】

  快照:与一级缓存一样的存放位置,对一级缓存数据备份。保证数据库的数据与一级缓存的数据必须一致。
  如果一级缓存修改了,在执行commit提交时,将自动刷新一级缓存,执行update语句,将一级缓存的数据更新到数据库。
  示例代码如下:

package com.itheima.b_cache;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// session缓存
public class Demo1 {
    @Test
    // 证明session缓存的存在
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        // 把持久化状态的对象  => 存到缓存中
        User u1 = (User) session.get(User.class1); // 发送select语句,从数据库取出记录,并封装成对象

        User u2 = (User) session.get(User.class1); // 再次查询时,会从缓存中查找,不会再发送select

        User u3 = (User) session.get(User.class1); // 再次查询时,会从缓存中查找,不会再发送select

        System.out.println(u1 == u2); // true
        System.out.println(u1 == u3); // true

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // session缓存中的快照
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class1); // 持久状态,会打印select

        session.update(u1);     // 持久状态,不会打印select语句

        session.getTransaction().commit(); // 不会打印select语句
        session.close(); 
    }

    @Test
    // session缓存中的快照
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = new User();   // 瞬时状态
        u1.setId(1);            // 游离状态
        u1.setName("jerry");    // 游离状态
        u1.setPassword("1234"); // 游离状态

        session.update(u1);     // 持久状态,不会打印select语句

        session.getTransaction().commit(); // 会打印select语句
        session.close();
    }

    @Test
    // 感受一级缓存效率的提高
    public void fun4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class1); // 持久化状态

        u1.setName("tom");
        session.update(u1);
        u1.setName("jack");
        session.update(u1);
        u1.setName("rose");
        session.update(u1); // 持久化状态: 本质就是存在缓存中的对象。

        session.getTransaction().commit();
        session.close(); 
    }
}

一级缓存中对象和快照图解如下图所示:

2.3、关于学习Session缓存的几个问题

示例代码如下_01:

package com.itheima.c_question;

import java.util.List;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// 学生问题
// 1、save方法 和 persist方法 对比之后,没有区别?
public class Demo1 {
    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();

        u.setId(99);

        session.persist(u); // 体现的是持久化,persist提供的理念是将对象完整的持久化,持久化也包括对象的ID。
                            // 如果在持久化之前设置了ID,那么就会将设置的ID进行insert,但是主键策略是由数据库来维护的。所以会产生矛盾,抛出异常。

        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();

        u.setId(99);

        session.save(u); // save方法,如果保存的对象在保存之前设置了ID,那么该ID也被认为是无效的ID。

        System.out.println(u.getId()); // ID是根据主键策略自动生成的,不是99

        session.getTransaction().commit();
        session.close();
    }
}

示例代码如下_02:

package com.itheima.c_question;

import java.util.List;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// 学生问题
// 2、Hql查询,查询的结果会放入Session一级缓存中,但是为什么每次调用Hql查询都会生成Sql语句呢?
// 并不代表 Hql没有使用一级缓存。
public class Demo2 {
    @Test
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        List<User> list1 = session.createQuery("from User").list(); // 发送sql
        List<User> list2 = session.createQuery("from User").list(); // 发送sql

        System.out.println(list1.get(0) == list2.get(0)); // true =>

        session.getTransaction().commit();
        session.close();
    }

// 3、缓存中的数据如果与数据库中的数据不同步,会怎么样?
//     答:会优先使用缓存中的。
//    如何解决不同步问题呢?
//     答:使用JDBC

//    在一级缓存中出现该问题的几率比较小。因为一级缓存的生命周期比较短。一级缓存的生命周期类似于request对象。
//        openSession();    => 一级缓存生命周期开始
//        session.close();  => 一级缓存销毁
    @Test
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class, 1);
        User u2 = (User) session.get(User.class, 1);

        session.getTransaction().commit();
        session.close();
    }
}

createQuery语句查询图解如下:

2.4、Session其他API(大部分都是了解即可)

示例代码如下:

package com.itheima.d_api;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.User;
import com.itheima.utils.HibernateUtils;

// 其他API(大部分都是了解即可)
public class Demo1 {
    @Test
    // 1、evict(); 将缓存中的对象移除
    // 2、clear(); 清空一级缓存
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class, 1);
        session.clear(); // 清空一级缓存
        User u2 = (User) session.get(User.class, 1);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 3、refresh(Object); 刷新 => 强制刷新缓存中的对象 => (可以用来解决缓存与数据库数据不同步的问题(局部解决))
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class, 1);
        session.refresh(u1); // 将缓存中的对象立刻与数据库同步,会再发送一个sql语句

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 4、flush(); 对比快照,并提交缓存对象
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class, 1);
        // u1.setName("zhangsan"); // 使得缓存和快照不一致
        session.flush(); // 立刻提交session缓存中的对象到数据库

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 代理主键   => native
    // 5.1、saveOrUpdate(Object); 方法
    // saveOrUpdate 可以同时完成保存或更新操作
    //      主键为空 时   =>  执行save语句
    //      主键有值 时   =>  执行update语句
    public void fun4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();
        u.setId(99);
        u.setName("jack");
        u.setPassword("1234");

        session.saveOrUpdate(u);

        session.getTransaction().commit();
        session.close();
    }

    // 自然主键   => assigned
    // 5.2、update 与 saveOrUpdate方法
    // saveOrUpdate 可以同时完成保存或更新操作
    //      主键为空时   =>  报错,因为无论是save还是update,都必须指定id
    //      主键有值时   =>  先会根据主键查询数据库,看数据库是什么情况:
    //          数据库中存在这条数据时      =>  执行update语句
    //          数据库中不存在这条数据时  =>  执行insert语句
    @Test
    public void fun5() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u = new User();
        u.setId(88);
        u.setName("jack01");
        u.setPassword("1234");

        session.saveOrUpdate(u);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 在我们使用Hibernate的时候,注意避免出现:两个相同的ID对象,放入一级缓存的情况。因为一级缓存本质上是一个Map结合。键是ID不能相同。
    public void fun6() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        User u1 = (User) session.get(User.class, 1);  // 持久状态,缓存中存在
        session.evict(u1); // 游离状态,缓存中不存在
        User u2 = (User) session.get(User.class, 1);  // 持久状态,缓存中存在,id一样,但是对象不是同一个

        session.update(u1); // 将u1重新转换为持久状态,缓存中存在,报错
        session.getTransaction().commit();
        session.close();
    }
}

三、多表设计

  • 在开发中,前期进行需求分析,需求分析提供ER图(实体关系图),根据ER图编写表结构。
  • 表之间关系存在3种:一对多、多对多、一对一。(回顾)

    一对多:1表(主表)必须主键和多表(从表)必须外键,主表主键与从表外键形成主外键关系。
    多对多:提供中间表(从表),提供2个字段(外键)分别对应两个主表。
    一对一:非常少见。
  • 如何使用面向对象的思想通过代码描述对象与对象之间关系?如下所示:
一对多:客户Customer和订单Order
private class Customer {
    // 一对多:一个客户拥有多个订单
    private Set<Order> orderSet;
    ......
}
private class Order {
    // 多对一:多个订单属于一个客户
    private Customer customer;
    ......
}

多对多:Student学生和Course课程
private class Student {
    // 多对多:多个学生学习不同课程
    private Set<Course> courseSet;
    ......
}
private class Course {
    // 多对多:多个课程被不同学生学习
    private Set<Student> student;
    ......
}

一对一:公司Company 和地址Address
private class Company {
    private Address address;
    ......
}
private class Address {
    private Company company;
    ......
}

四、Hibernate的关联关系映射(一对多)

4.1、一对多实现【掌握】

4.1.1、实现类

Customer.java

package com.itheima.domain;

import java.util.HashSet;
import java.util.Set;

public class Customer {
    private Integer cid;
    private String cname;
    // 一对多:一个客户拥有多个订单
    // 在一的一方,使用集合表达持有多的一方引用,建议使用Set(不重复、无序)
    private Set<Order> orders = new HashSet<Order>(); // 建议实例化,使用方便

    // getter和setter方法
}

Order.java

package com.itheima.domain;

public class Order {
    private Integer oid;
    private String oname;
    // 多对一:多个订单 属于 一个客户
    private Customer customer;

    // getter和setter方法
}

4.1.2、配置文件

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  package="com.itheima.domain">
    <class name="Customer" table="t_customer">
        <id name="cid" column="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname" column="cname" type="String"></property>

        <!-- 一对多:一个客户当前客户 拥有 多个订单
            1 、确定容器set  <set>
            2、name确定对象属性名
            3 、确定从表外键的名称   <key>
            4 、确定关系,及关联的另一方的完整类名
            注意:
                在hibernate中可以只进行单向配置。
                每一个配置项都可以完整的描述彼此关系。
                一般情况采用双向配置,双方都可以完成描述表与表之间关系。
        -->

        <set name="orders">
            <key column="customer_id"></key>
            <one-to-many class="Order"/>
        </set>
    </class>
</hibernate-mapping>

Order.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.itheima.domain">
    <class name="Order" table="t_order">
        <id name="oid" column="oid">
            <generator class="native"></generator>
        </id>
        <property name="oname" column="oname" type="String"></property>

        <!-- 多对一:多个订单属于一个客户
            1、name 确定属性名称
            2、column 确定从表的外键名称
            3、class 关联的另一方的完整类名
        -->

        <many-to-one name="customer" column="customer_id" class="Customer"></many-to-one>
    </class>
</hibernate-mapping>

hibernate.cfg.xml

......
<!-- 添加ORM映射文件 ,填写src之后的路径-->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
<mapping resource="com/itheima/domain/Order.hbm.xml"/>
......

4.1.3、测试代码

测试一对多代码:

package com.itheima.e_one2many;

import java.util.Set;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.Customer;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;

// 测试:一对多实现
public class Demo1 {
    @Test
    // 1 、测试一对多关系中:保存操作
    //      共打印5条语句
    //      前3条打印insert     =>  保存对象,维护外键
    //      后两条打印update =>  维护外键

    //      维护了两次外键,有些多余,该如何解决呢?
    //      解决  =>  单纯指定关系由其中一方来维护,另一方不维护关系(放弃维护)。
    //      注意  =>  外键维护的放弃,只能由非外键所在对象来放弃。
    //          =>  需要在非外键所在对象的配置文件中进行配置。
    //      本例中:配置Customer的inverse属性为:true
    //      只打印3条语句 =>  外键由Order自己来维护
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = new Customer();
        c.setCname("tom");

        Order o1 = new Order();
        o1.setOname("肥皂");
        Order o2 = new Order();
        o2.setOname("蜡烛");

        // 外键维护的放弃,只能由非外键所在对象来放弃。需要在非外键所在对象的配置文件中进行配置。配置好后,下面两句代码可以省略了。
        // c.getOrders().add(o1); // 维护关系
        // c.getOrders().add(o2); // 维护关系

        o1.setCustomer(c); // 维护关系
        o2.setCustomer(c); // 维护关系

        session.save(c); // 保存对象
        session.save(o1); // 保存对象
        session.save(o2); // 保存对象

        session.getTransaction().commit();
        session.close(); 
    }
    // 多表关系 =>  删除
    // 直接删除 Customer时 ,会先移除 Customer中引用的外键,然后再删除Customer。
    // 结论: 在维护一方的对象时,会自动维护这一方对象与另一方对象的关系。

    // 当把Customer的 inverse属性设置为:true 后,
    // 再次演示删除会报错    =>  因为Customer不负责维护外键了,直接删除Customer会导致,Order引用了无效的id,违反了外键约束。

    // 那么该如何解决呢?
    // 答:因为现在是通过Order来维护外键的,所以应该先单独设置订单不属于任何Customer后,再删除Customer。

    // 什么时候配置inverse属性呢?
    // 答:主要看业务即(看实际需求)。
    //       如果一的一方经常需要自己来维护外键,那么在一的一方就不要配置inverse属性了。
    //       如果一的一方经常要通过多的一方来维护外键,那么在一的一方要配置inverse属性为true。

    @Test
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 3);

        // 答:因为现在是通过Order来维护外键的,所以应该先单独设置订单不属于任何Customer后,再删除Customer。
        Set<Order> set = c.getOrders(); // 得到的是持久状态,修改后不需要更新,在提交的时候会自动同步数据库
        for (Order o : set) {
            o.setCustomer(null); // 先单独设置订单不属于任何Customer
        }

        session.delete(c);

        session.getTransaction().commit();
        session.close();
    }
}

4.2、级联操作【读、理解】

4.2.1、save-update 级联保存或更新

  示例代码同下:

4.2.2、delete 级联删除

  示例代码如下所示:

package com.itheima.e_one2many;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.Customer;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;

// 测试:一对多操作
public class Demo2 {
    @Test
    // 增    =>  级联保存
    // 我们希望在保存Customer时,自动将Customer集合中未保存的瞬时状态对象Order进行保存。并不会维护关系哦!
    // 配置 Customer 的 cascade 属性为: save-update 级联保存和级联修改。

    // 级联保存和反转的对比:
    // 首先在配置级联保存,Customer的cascade属性为: save-update 后
    // 接着配置Customer的inverse属性为:true 后,
    // 会出现一个问题,Order的外键的值为null了。我们怎么使得外键有值呢?
    // 答:法一:我们可以手动维护关系。如下,此时运行代码,打印3条insert语句。
    //       法二:将Customer的inverse属性改为默认值:false 后,此时运行代码,打印5条sql语句:3条insert,2条update。
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = new Customer();
        c.setCname("tom");

        Order o1 = new Order();
        o1.setOname("肥皂");
        Order o2 = new Order();
        o2.setOname("蜡烛");

        c.getOrders().add(o1); // 维护关系
        c.getOrders().add(o2); // 维护关系

        // 配置Customer的inverse属性为:true 后,配置好后,下面两句代码可以省略了。即可以不用多维护一次外键了。
        // c.getOrders().add(o1); // 维护关系
        // c.getOrders().add(o2); // 维护关系

        // 在配置Customer的inverse属性为:true 后,
        // 但是会出现一个问题,Order的外键的值为null了。我们怎么使得外键有值呢?答:法一:我们可以手动维护关系。
        // o1.setCustomer(c); // 手动维护关系
        // o2.setCustomer(c); // 手动维护关系

        session.save(c); // 保存对象
        // 配置Customer的cascade属性为:save-update 后,配置好后,下面两句代码可以省略了。
        // session.save(o1); // 保存对象
        // session.save(o2); // 保存对象

        session.getTransaction().commit();
        session.close(); 
    }

    @Test
    // 增    =>  级联修改
    // 我们希望在保存Customer时,自动将Customer集合中未保存的瞬时状态对象Order进行保存。并不会维护关系哦!
    // 配置 Customer 的 cascade 属性为: save-update 级联保存和级联修改。
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 4); // 1条 select 语句

        for (Order o : c.getOrders()) { // 1条 select 语句,根据外键查到的
            o.setOname("哇哈哈"); // 修改订单
        }

        session.getTransaction().commit(); // 2条update 语句,因为该用户有2个订单,又因为设置级联修改,会自动将订单的修改同步(更新)到数据库
        session.close();
    }

    @Test
    // 删除   =>  级联删除
    // 配置 Customer 的 cascade 属性为: delete 级联删除。 
    // 在删除Customer时 ,会将Customer下的订单一并删除。

    // 前提:该用户有2个订单
    // 此时若再配置Customer的  inverse 属性为:
    //      false 时,共6条sql语句
    //      true 时,共5条sql语句, 比上面少一条维护外键语句
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 5); // 1条 select 语句

        session.delete(c); // 删除Customer,并删除两个Order,1条 select 语句 

        session.getTransaction().commit(); // 之后,1条 update 语句  + 3条  delete 语句
        session.close();
    }

    @Test
    // 删除   =>  级联删除
    // 配置 Customer 的 cascade 属性为: delete 级联删除。 

    // 操作的两方 cascade 值都为 delete 时
    // 需要注意: 千万不要在两方都配置级联删除,如果这样配置的话,删除任何一方,都会导致整个关系链对象全部删除。
    public void fun4() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Order o = (Order) session.get(Order.class, 9); // 1条 select 语句

        session.delete(o);
        // 找到所有关联的 Customer,1条 select 语句
        // 此时 Customer 也配置了级联删除 =>  1条 select 语句, 找下面的 Order
        // 删除所有 Order
        // 再删除 Customer

        session.getTransaction().commit();
        session.close();
    }
}

4.2.3、孤儿删除

  一对多关系中存在父子关系。1表(主表)可以称为父表,多表(从表)称为子表
总结:
  主表不能删除从表已经引用(关联)的数据。
  从表不能添加主表不存在的数据。

package com.itheima.e_one2many;

import java.util.HashSet;
import java.util.Iterator;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.Customer;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;

// 测试:一对多操作:孤儿删除
public class Demo3 {
    @Test
    // 配置 Customer 的 inverse 属性为: false
    // 配置 Customer 的 cascade 属性为: delete-orphan 孤儿删除    =>  当没有任何外键引用Order时,Order会被删除
    // 示例前提:该用户有2个订单
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 3); // 1条 select 语句

        Iterator<Order> it = c.getOrders().iterator(); // 1条 select 语句,1条 update 语句,2条 delete 语句
        while (it.hasNext()) {  // 遍历Customer下的订单,解决客户和订单的关系
            it.next();          // 默认:客户和订单解除关系后,外键被设置成null,此时订单就是孤儿。客户和订单都存在。
            it.remove();        // 孤儿删除(孤子删除),当订单为孤儿时,一并删除。客户仍存在。
        }

        // 注意: 删除Customer下的订单时,不能使用 下面两句代码:
        // c.setOrders(null); 
        // c.setOrders(new HashSet<Order>());

        session.getTransaction().commit();
        session.close();
    }
}

4.2.4、演示all-delete-orphan

示例代码如下:

package com.itheima.e_one2many;

import java.util.Iterator;

import org.hibernate.Session;
import org.junit.Test;

import com.itheima.domain.Customer;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;

//测试:一对多关系:all 和 all-delete-orphan
public class Demo4 {
    @Test
    // 配置 Customer 的 cascade 属性为: all-delete-orphan =>  相当于配置了 save-update,delete,delete-orphan
    public void fun1() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = new Customer();
        c.setCname("tom");

        Order o1 = new Order();
        o1.setOname("肥皂");
        Order o2 = new Order();
        o2.setOname("蜡烛");

        c.getOrders().add(o1); // 维护关系
        c.getOrders().add(o2); // 维护关系

        session.save(c);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 配置 Customer 的 cascade 属性为: all-delete-orphan =>  相当于配置了 save-update,delete,delete-orphan
    public void fun2() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 10);
        session.delete(c);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    // 配置 Customer 的 cascade 属性为: all-delete-orphan =>  相当于配置了 save-update,delete,delete-orphan
    public void fun3() {
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        Customer c = (Customer) session.get(Customer.class, 12);

        Iterator<Order> it = c.getOrders().iterator(); // 1条 select 语句,1条 update 语句,2条 delete 语句
        while (it.hasNext()) {  // 遍历Customer下的订单,解决客户和订单的关系
            it.next();          // 默认:客户和订单解除关系后,外键被设置成null,此时订单就是孤儿。客户和订单都存在。
            it.remove();        // 孤儿删除(孤子删除),当订单为孤儿时,一并删除。客户仍存在。
        }

        session.getTransaction().commit();
        session.close();
    }
}

4.2.5、总结

核心配置文件hibernate.cfg.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.itheima.domain">
    <class name="Customer" table="t_customer">
        <id name="cid" column="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname" column="cname" type="string"></property>

        <!-- 一对多:一个客户当前客户 拥有 多个订单
            1 、确定容器set  <set>
            2、name确定对象属性名
            3 、确定从表外键的名称   <key>
            4 、确定关系,及关联的另一方的完整类名
            注意:
                在hibernate中可以只进行单向配置。
                每一个配置项都可以完整的描述彼此关系。
                一般情况采用双向配置,双方都可以完成描述表与表之间关系。

            inverse属性:反转    
                是否将关系的维护反转给对方,默认值是:false,
                设置成true表示放弃维护,将维护关系反转。
            cascade属性:级联操作
                save-update:级联保存和级联修改。保存A时,同时保存B
                delete:删除A时,同时删除B,A、B都不存在了。
                delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。
                all:save-update 和   delete 整合
                all-delete-orphan:三个整合  在一起

            如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">
        -->

        <set name="orders" inverse="false" cascade="all-delete-orphan">
            <key column="customer_id"></key>
            <one-to-many class="Order"/>
        </set>
    </class>
</hibernate-mapping>
posted @ 2018-07-11 17:54  黑泽君  阅读(370)  评论(0编辑  收藏  举报