zoukankan      html  css  js  c++  java
  • 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>
  • 相关阅读:
    vue+element的el-menu组件实现路由跳转及当前项的设置
    继承与多态
    八、使用for解决简单的问题
    六、Js数组的使用方法
    五、JS操作HTML方法
    四、初步入门JS的用法
    三、html总结
    二、表格<table>的使用
    一、初步接触html,基本标签和ul、ol的用法
    运算符的分类
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/9295915.html
Copyright © 2011-2022 走看看