Hibernate入门经典

Hibernate概述

1.1 什么是Hibernate

hibernate就是一个持久层的ORM框架,对JDBC做了封装,说的简单一点就是和Dao的功能类似,用来简化操作数据库的一个框架。

1.2 什么是ORM

ORM:(Object Relational Mapping)对象关系映射,将Java中一个对象与数据库中的表建立映射关系,从而操作对象就可以操作数据库中的表。

Hibernate入门

  • 下载Hibernate

  • Hibernate目录结构

    documentation :Hibernate的开发规范和文档

    lib :Hibernate的开发使用的jar包

    project :Hibernate的提供测试的工程。

  • 导入Jar包

    • 引入 lib/required/*.jar 所有jar包
    • 数据库驱动包(c3p0):lib\optional\c3p0\*.jar
    • 日志包 log4j
  • 创建数据库和表

  • 创建实体类(JavaBean)

  • 创建映射文件(JavaBean.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	:类的全路径
                  * table	:数据库表名称
           -->
          <class name="com.daohewang.domin.Customer" table="customer">
              
          </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.daohewang.domin.Customer" table="customer">
              <!-- 建立类中的属性与表中的字段的映射 -->
              <!--
                  id标签:用来建立表中的主键字段与类中的属性的映射
               -->
              <id name="cust_id" column="cust_id">
                  <!--主键生成策略-->
                  <generator class="native" />
              </id>
              <!-- 其他的属性都是用property建立映射 -->
              <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"/>
          </class>
      </
              
              
               
               hibernate
              
              -
              
              
               
               mapping
              
              >
      

      class标签:建立类和表的映射

      • name :类的全路径

      • table :数据库中表的名称。

      • catalog :数据库名称(可以省略)

      • id标签:建立主键和类中属性映射

      • name :类中的属性的名称

      • column :表中的字段名称。(如果类中的属性名和表中的字段名一致,column可以省略)

      property标签:建立普通字段与类中属性映射

      • name :类中的属性的名称

      • column :表中的字段名称。(如果类中的属性名和表中的字段名一致,column可以省略)

      • length :字段的长度(自动创建表)

      • not-null :非空(自动创建表)

      • unique :唯一(自动创建表)

  • 创建核心配置文件(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</property>
            <!--数据库用户名-->
            <property name="
          
          
           
           hibernate
          
          .connection.username">root</property>
            <!--数据库密码-->
            <property name="
          
          
           
           hibernate
          
          .connection.password">123</property>
    
            <!--
                配置Hibernate的相关属性
            -->
            <!--数据库的方言
                分页:
                    Mysql     : select * from 表名 limit ?, ?
                    sqlServer :  select * from 表名 top ?, ?
                    让hibernates生成符合mysql数据库的sql语句
            -->
            <property name="
          
          
           
           hibernate
          
          .
          
          
           
           dialect
          
          ">org.hibernate.dialect.MySQLDialect</property>
            <!--c3p0配置-->
            <property name="provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
            <!--
                可选属性
            -->
            <!--显示sql-->
            <property name="
          
          
           
           hibernate
          
          .show_sql">true</property>
            <!--格式化SQL-->
            <property name="
          
          
           
           hibernate
          
          .format_sql">true</property>
            <!--这表示是否会自动更新数据库的表结构,有这句话,其实是不需要创建表的,因为Hibernate会自动去创建表结构-->
            <!--
                hbm2ddl.auto: Hibernate的映射转成DDL
                *create :每次都会创建一个新的表(如果没有表,创建表,如果数据库
                有表,删除该表,重新创建一个表)-测试时候用
                *update :update : 如果没有表,创建一个新表,如果有表,更新表结构
                *validate : 只会使用原来的表,检验映射和表结构是否一致
            -->
            <property name="hbm2ddl.auto">update</property>
            <!-- 加载该文件时   ,就加载customer.hbm.xml映射文件: -->
            <
          
          
           
           mapping
          
           resource="com/daohewang/domin/Customer.hbm.xml"/>
        </session-factory>
    </
          
          
           
           hibernate
          
          -configuration>
    
    • 必须的配置

      • 连接数据库的基本的参数
        • 驱动类,
        • 数据库地址(url路径),
        • 用户名,
        • 密码
    • 方言

    • 可选的配置

      • 显示SQL
      • 格式化SQL
      • 自动建表
        • none
        • create
        • create-drop
        • update : 如果数据库中有表,使用原有表,没有表,创建新表(更新表结构)
        • **validate 😗*如果没有表,不会创建表。只会使用数据库中原有的表(检验映射和表结构)
    • 映射文件的引入

      <
              
              
               
               mapping
              
               resource="com/daohewang/domin/Customer.hbm.xml"/>
      
      • 映射文件就是将类与表建立映射关系文件,这个文件只要是XML即可。通常名称:类名.hbm.xml
  • 测试类

    package com.daohewang.test;
    
    import com.daohewang.domin.Customer;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.junit.Test;
    /**
     * @author swordsmanye
     * @data 2019/12/17 21:37
     */
    public class HibernateTest {
        /**
         * 使用hibernate提供的api来操作
         */
        @Test
        public void testHibernate() {
            // 1. 加载核心配置文件
            Configuration configuration = new Configuration();
            configuration.
         
         
          
          configure
         
         ();
            // 2. 创建一个SessionFactory对象(类似于连接池)
            SessionFactory sessionFactory = configuration.buildSessionFactory();
            // 3. 获得Session对象,连接对象
            Session session = sessionFactory.openSession();
            // 4. 开启事务
            Transaction transaction = session.beginTransaction();
            // 5. 操作
            Customer customer = new Customer();
            customer.setCust_name("小武");
            session.save(customer);
            // 6. 事务提交 -除了查询,都得加事务
            transaction.commit();
            // 7. 资源释放
            session.close();
        }
    }
    
    • Configuration : 配置对象
      • 加载核心配置
      • 加载映射文件
    • SessionFactory : Session工厂
      • 内部维护连接池和二级缓存,是线程安全的,重量级对象,创建耗时,所以只创建一次
    • Session
      • 维护一级缓存,与数据库交互桥梁
      • 方法:save; get , load; update; delete; saveOrUpdate; createQuery; createSQLQuery
    • Transaction : 事务管理对象
      • commit
      • rollback

二、Hibernate持久化类和对象标识符

2.1 持久化类的编写规范

2.1.1 什么是持久化类:

Hibernate是持久层的ORM映射框架,专注于数据的持久化工作。

  • 持久化:就是将内存中的数据永久存储到关系型数据库中
  • 持久化类:(Java类+映射文件)
    其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为是持久化类。其实你可以简单的理解为持久化类就是一个Java类有了一个映射文件与数据库的表建立了关系。
2.1.2 持久化类的编写规则

我们在编写持久化类的时候需要注意以下几点:

  • 持久化类需要提供无参数的构造方法。因为在Hibernate的底层需要使用反射生成
    类的实例。

  • 持久化类的属性需要私有,对私有的属性提供公有的get和set方法。因为在
    Hibernate底层会将查询到的数据进行封装。

  • 持久化类的属性要尽量使用包装类的类型。因为包装类和基本数据类型的默认值不
    同,包装类的类型语义描述更清晰而基本数据类型不容易描述。举个例子:

    假设表中有一列员工工资,如果使用double类型,如果这个员工工资忘记录入到系统中,系统会将默认值0存入到数据库,如果这个员工工资被扣完了,也会向系统中存入0.那么这个0就有了多重含义,而如采使用包装类类型就会避免以上情况,如果使用Double类型,忘记录入工资就会存入null,而这个员工工资被扣完了,就会存入0,不会产生歧义。

  • **持久化类要有一个唯一标识OID与表的主键对应。**因为Hibernate中需要通过这个
    唯一标识OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否
    是同一个对象的,在关系型数据库的表中是通过主键区分是否同一条记录。那么
    Hibernate就是通过这个OID来进行区分的。Hibernate是不允许在内存中出现两个
    OID相同的持久化对象的。

  • 持久化类尽量不要使用final进行修饰。因为Hibernate中有延迟加载的机制,这个
    机制中会产生代理对象,Hibernate产生代理对象使用的是字节码的增强技术完成
    的,其实就是产生了当前类的一个子类对象实现的。如果使用了 final修饰持久化
    类。那么就不能产生子类,从而就不会产生代理对象,那么Hibernate的延迟加载
    策略(是一种优化手段)就会失效。

持久化类我们己经可以正常编写了,但是在持久化类中需要有一个唯一标识OID与表的主键去建立映射关系。而且主键一般我们是不会让客户手动录入的,一般我们是由程序生
成主键。那么Hibernate中也提供了相应的主键生成的方式,那么我们来看下Hibernate的主键生成策略。

  • 简单的说:
    我们的实体类都需要遵从JavaBean的编写规范。
  • 什么是JavaBean:
    • Bean:在软件开发领域,Bean表示可重用组件。
    • JavaBean就是用java语言开发的可重用组件。
  • JavaBean的编写规范是什么
    • 类都是public的
    • 都有默认无参构造函数
    • 成员变量都是私有的
    • 都有公有的get/set方法
    • 一般都实现Serializable接口
  • 基本类型和包装类的选择问题:
    • 由于包装类可以有null值。所以实际开发中都是用包装类。

2.2 Hibernate中对象标识符(OID)

OID全称是Object Identifier,又叫做对象标识符。
它是hibernate用于区分两个对象是否是同一个对象的标识。
我们都知道,JVM虚拟机内存区分两个对象看的是内存的地址是否一致。数据库区分两个对象,靠的是表的主键。hibernate负贵把内存中的对象持久化到数据库表中,靠的就是对象标识符来区分两个对象是否是同一个。实体类中映射主键的字段就是OID,如下图所示:

2.3 Hibernate的主键生成策略

在讲解Hibernate的主键生成策略之前,先来了解两个概念,即自然主键和代理主键,
具体如下:

  • **自然主键:**把具有业务含义的字段作为主键,称之为自然主键。例如在customer
    表中,如果把name字段作为主键,其前提条件必须是:每一个客户的姓名不允许
    为null,不允许客户重名,并且不允许修改客户姓名。尽管这也是可行的,但是不
    能满足不断变化的业务需求,一旦出现了允许客户重名的业务需求,就必须修改数
    据模型,重新定义表的主键,这给数据库的维护增加了难度。
  • **代理主键:把不具备业务含义的字段作为主键,称之为代理主键 **。该字段一般取名
    为“ID”,通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。
    在上面例子中,显然更合理的方式是使用代理主键。
名称描述
increment用于long、short、或int类型,由Hibernate自动以递增的方式生成唯一标识符,每次增量为1。只有当没有其它进程向冋一张表中插入数据时才可以使用,不能在集群环境下使用,适用于代理主键。
identity釆用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长数据类型。在DB2, MySQL, MS SQL Server, Sybase和 HypcrsomcSQL数据库中可以使用该生成器,该生成器要求在数据库中 把主键定义成自增长类型。适用于代理主键。
sequenceHibernate根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。
native根据底层数据库对自动生成表示符的能力来选择identity、 sequence, hilo三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。
uuidHibernate釆用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为 32位的十六进制字符串。这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。适用于代理主键。
assigned由java程序负责生成标识符,如果不指定id元紊的generator属性, 则默认使用该主键生成策略。适用于自然主键

三、Hibernate的一级缓存和对象状态

3.1 Hibernate的一级缓存

3.1.1 hibernate中的一级缓存

Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相
互管理的_java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在
Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。

在Session接口的实现中包含一系列的Java集合,这些Java集合构成了 Session缓存。只要session实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。固一级缓存也被称为是Session基本的缓存。

Hibernate的一级缓存有如下特点:

  • 当应用程序调用Session接口的save()、update()、saveOrUpdate()时,如果Session缓存中没有相应的对象,Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存中去。
  • 当调用Session接口的load()、get()方法,以及Query接口的list()、iterator()方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询对象,再去数据库中查询对应对象,并添加到一级缓存中。
  • 当调用Session的close()方法时,Session缓存会被清空。

3.1.2 测试一级缓存
public class HibernateSession {
    @Test
    public void TestHibernateSession() {
        Session session = HibernateUtil.openSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();
        // 马上发送一条sql查询1号客户,并将数据存入了一级缓存
        Customer customer = session.get(Customer.class, 1L);
        System.out.println(customer);
        // 没有发送sql语句,从一级缓存中获取到数据
        Customer customer1 = session.get(Customer.class, 1L);
        System.out.println(customer1);

        // true, 一级缓存的是对象的地址
        System.out.println(customer == customer1);
        session.close();
    }
}

结果:
Hibernate: 
    select
        customer0_.cust_id as cust_id1_0_0_,
        customer0_.cust_name as cust_nam2_0_0_,
        customer0_.cust_source as cust_sou3_0_0_,
        customer0_.cust_industry as cust_ind4_0_0_,
        customer0_.cust_level as cust_lev5_0_0_,
        customer0_.cust_phone as cust_pho6_0_0_,
        customer0_.cust_mobile as cust_mob7_0_0_ 
    from
        customer customer0_ 
    where
        customer0_.cust_id=?
Customer{cust_id=1, cust_name='文艺', cust_source='null', cust_industry='null', cust_level='null', cust_phone='null', cust_mobile='null'}
Customer{cust_id=1, cust_name='文艺', cust_source='null', cust_industry='null', cust_level='null', cust_phone='null', cust_mobile='null'}
true
3.1.3 快照机制:

Hibernate向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用
commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照:如果一致,则不执行update语句。Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。

代码测试 :

package com.daohewang.test;

import com.daohewang.domin.Customer;
import com.daohewang.utils.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

/**
 * @author swordsmanye
 * @data 2019/12/19 14:12
 */
public class HibernateSession {
    // 测试快照机制
    @Test
    public void TestHibernateQuickShot() {
        // 内存中的一个对象
        Customer customer = null;
        // 获取session对象
        Session session = HibernateUtil.openSession();
        // 使用session对象开启事务
        Transaction tx = session.beginTransaction();
        // 执行查询
        customer = session.get(Customer.class, 2L);
        // cust_level -> 程序员
        System.out.println(customer);

        // 修改cust_level -> 产品经理
        customer.setCust_level("产品经理");
        System.out.println(customer);
        // 提交事务
        tx.commit();
        // 关闭session
        session.close();

        System.out.println(customer);
    }
}

结果:
    Hibernate: 
    select
        customer0_.cust_id as cust_id1_0_0_,
        customer0_.cust_name as cust_nam2_0_0_,
        customer0_.cust_source as cust_sou3_0_0_,
        customer0_.cust_industry as cust_ind4_0_0_,
        customer0_.cust_level as cust_lev5_0_0_,
        customer0_.cust_phone as cust_pho6_0_0_,
        customer0_.cust_mobile as cust_mob7_0_0_ 
    from
        customer customer0_ 
    where
        customer0_.cust_id=?
Customer{cust_id=2, cust_name='小武', cust_source='深圳', cust_industry='华为', cust_level='程序员', cust_phone='null', cust_mobile='null'}
Customer{cust_id=2, cust_name='小武', cust_source='深圳', cust_industry='华为', cust_level='产品经理', cust_phone='null', cust_mobile='null'}
Hibernate: 
    update
        customer 
    set
        cust_name=?,
        cust_source=?,
        cust_industry=?,
        cust_level=?,
        cust_phone=?,
        cust_mobile=? 
    where
        cust_id=?
Customer{cust_id=2, cust_name='小武', cust_source='深圳', cust_industry='华为', cust_level='产品经理', cust_phone='null', cust_mobile='null'}

问题:
小武的cust_level输出的到底是什么?

  • 分析:
    如果输出是程序员,则表示我们修改的代码没启任何作用,写于没写一个作用
    如果输出是产品经理,则表示我们程序内存的数据可能和数据库表中的不一致了,那就是脏数
  • 思考:
    有没有可能输出的是产品经理并且数据库的数据也变成了产品经理呢?
    如果真的发生了这种情况,是如何做到的呢?
  • 答案:
    hibernate的快照机制

3.2 对象的三种状态

3.2.1 对象的状态说明

了解了主键的生成策略之后,我们可以进一步来了解持久化类了。Hibernate为了更好
的来管理持久化类,特将持久化类分成了三种状态。在Hibernate中持久化的对象可以划分为三种状态,分别是瞬时态、持久态和脱管态,一个持久化类的实例可能处于三种不同状态中的某一种,三种状态的详细介绍如下。

  1. 瞬时态(transient)
    瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的
    对象,不存在持久化标识OID (相当于主键值),尚未与Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。
  2. 持久态(persistent)
    持久态的对象存在持久化标识OID,加入到了 Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还未提交前变成持久态的。
  3. 脱管态(detached)
    脱管态也称离线态或者游离态,当某个持久化状态的实例与Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时Hibernate不能检测到。
3.2.2 学习对象状态我们要明确的:

a、是为了更好的掌握hibernate中操作的方法。
b、区分状态只有两个标识

  • 一是否有OID
  • 二是否和Session建立的关系
    • 临时状态:
      • 没有OID,和Session没有关系。
    • 持久化状态:
      • 有OID,和Session有关系。
    • 脱管状态:
      • 有OID,和Session没有关系。

3.2.3 对象状态的转换

持久化对象的三种状态可以通过调用Session中的一系列方法实现状态间的转换,具体
如下:
1、瞬时态转换到其他状态

通过前面介绍可知,瞬时态的对象由new关键字创建,瞬时态对象转换到其他状态总
结如下:

  • 瞬时态转换为持久态:执行Session的save()或saveOrUpdate()方法。

  • 瞬时态转换为脱管态:为瞬时态对象设置持久化标识OID。
    由于持久化对象状态演化图中没有涉及到瞬时态转换到脱管态的情况,这里做下简要的说明,在前面介绍中可知,脱管态对象存在OID,但是没有Session的关联,也就是说脱管态和瞬时态的区别就是OID有没有值,所以可以通过为瞬时态对象设置OID,使其变成脱管态对象。

    Customer customer = new Customer(); // 瞬时态
    customer.setCust_id(1);	//	脱管态
    

2、持久态对象转换到其他状态
持久化对象可以直接通过Hibernate中Session的get()、load()方法,或者Query查询从
数据库中获得,持久态对象转换到其他状态总结如下:

  • 持久态转换为瞬时态:执行Session的delete()方法,需要注意的是被删除的持久化
    对象,不建议再次使用。
  • 持久态转换为脱管态:执行Session的evict()、close()或clearO方法。evict()方法用
    于清除一级缓存中某一个对象:close()方法用于关闭Session,清除一级缓存;clear()方法用于淸除一级缓存的所有对象。

3、脱管态对象转换到其他状态
脱管态对象无法直接获得,是由其他状态对象转换而来的,脱管态对象转换到其他状
态总结如下:

  • 脱管态转换为持久态:执行Session的update()、saveOrUpdate()或lock()方法。
  • 脱管态转换为瞬时态:将脱管态对象的持久化标识OID设置为null。
    由于持久化对象状态演化图中没有涉及到脱管态转换到瞬时态的情况,这里做下简要的说明,跟瞬时态转换到脱管态的情况相似,脱管态和瞬时态的区别就是OID有没有值,所以可以通过将脱管态对象的OID设置为null,使其变成瞬时态对象。例如在session.close()操作后,加入代码customer.setCust_id(null);,customer对象将由脱管态转换为瞬时态。

四、Hibernate的事务控制

4.1 配置Session和线程绑定

在Hibernate中,可以通过代码来操作管理事务,如通过“ Transaction tx =
session.beginTransadion();”开启一个事务:持久化操作后,通过“tx.commit();”提交事务:如果事务出现异常,又通过“tx. rollback (),”操作来撤销事务(事务回滚)。

除了在代码中对事务开启,提交和回滚操作外,还可以在Hibernate的配置文件中对事务务进行配置。配置文件中,可以设置事务的隔离级别。其具体的配置方法是在
hibemate.cfg.xml文件中的<session-factory>标签元素中进行的。配置方法如下所示。

<!--
 事务隔离级别
    1 Read 
   
   
    
    uncommitted
   
    isolation
    2—Read committed isolation
    4—
   
   
    
    Repeatable
   
    read isolation
    8—Serializable isolation
-->
<property name="
    
    
     
     hibernate
    
    .connection.isolation">4</property>

到这我们己经设置了事务的隔离级别,那么我们在真正进行事务管理的时候,需要考虑
事务的应用的场景,也就是说我们的事务控制不应该是在DAO层实现的,应该在Service
层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。具体操作如下显示:

其实最主要的是如何保证在Service中开启的求务时使用的Session对象和DAO中多个
操作使用的是同一个Session对象。
其实有两种办法可以实现:

  • 可以在业务层获取到Session,并将Session作为参数传递给DAO。
  • 可以使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中
    获取Session的时候,都从当前线程中获取。

其实使用第二种方式肯定是最优方案,那么具体的实现己经不用我们自己来完成了,
Hibernate的内部己经将这个事情做完了。我们只需要完成一段配置即可。

  • Hibemate5中自身提供了三种管理Session对象的方法
    • Session对象的生命周期与本地线程绑定
    • Session对象的生命周期与JTA事务绑定
    • Hibernate委托程序管理Session对象的生命周期

在 Hibernate 的配置文件中,hibernate.current_session_context_class 属性用于指定
Session管理方式,可选值包括

  • thread: Session对象的生命周期与本地线程绑定
  • jta: Session对象的生命周期与JTA事务绑定
  • managed: Hibernate委托程序来管理Session对象的生命周期

配置步骤:

  1. 在hibernate.cfg.xml文件中配置

    <!--把session绑定到当前线程上-->
    <property name="
          
          
           
           hibernate
          
          .current_session_context_class">thread</property>
    
  2. 获取session时使用的方法

    HibernateUtil.java
    /*
    	每次都是从当前线程上获取session
    */
    public static Session getCurrentSession(){
        return factory.getCurrentSession();
    }
    细节:当我们把Session绑定到当前线程之后,关闭Session就是hibernate来做的,我们就不用关了
    

到这里我们己经对Hibernate的事务管理有了基本的了解,但是之前我们所做的CRUD的操作其实还没有查询多条记录。那如果我们耑要查询多条记录要如何完成呢,我们接下来去价绍一下Hibernate的其他的相关的API。

五、Hibernate查询对象API

5.1 Query :(掌握)

5.1.1概述

Query代表面向对象的一个Hibernate查询操作。在Hibernate中,通常使用
session.createQuery()方法接受一个HQL语句,然后调用Query的list()或uniqueResult()方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。

在Hibernate中使用Query对象的步骤,具体所示:

  1. 获得 Hibernate 的 Session 对象。
  2. 编写HQL语句。
  3. 调用session, createQuery创建查询对象。
  4. 如果HQL语句包含参数,则调用Query的setXxx设置参数。
  5. 调用Query对象的方法执行查询。

HQL的说明:
把表的名称换成实体类名称。把表字段名称换成实体类属性名称。
例如:

SQLselect * from cst_customer where cust_name like ?
HQL: select * from Customer where cust_Name = ?

其中 select * 可以省略,写为:

from Customer where custName = ?

了解了使用Query对象的步骤后,接下来,通过具体示例来演示Query对象的查询操作。

5.1.2 常用查询
5.1.2.1 基本查询
 @Test
    public void 
   
   
    
    query
   
   () {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        // 1.获取Query对象
        Query query = s.createQuery("from Customer");
        // 2. 执行对象的方法,获取结果集
        List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }
5.1.2.2 条件查询
/**
     * 条件查询
     * hibernate的参数占位符索引是从0开始的
     */
    @Test
    public void queryWithCondition() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 1. 获取Query对象
        Query query = s.createQuery("from Customer where cust_name like ? and cust_level = ? ");
        // 2. 给参数占位符赋值
        query.setString(0, "%艺");
        query.setString(1, "HR");
        // 3. 执行对象的方法,获取结果集
        List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }


/**
     * 条件查询
     * 给参数占位符提供一个具体的名称
     * 参数占位符的写法
     *   :名称
     *   复制的时候不需要写冒号,直接写名称
     * hibernate的参数占位符索引是从0开始的
     */
    @Test
    public void queryWithCondition2() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 1. 获取Query对象
        Query query = s.createQuery("from Customer where cust_name like :cust_name and cust_level = :cust_level ");
        // 2. 给参数占位符赋值
        query.setString("cust_name", "%艺");
        query.setString("cust_level", "HR");
        // 3. 执行对象的方法,获取结果集
        List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }

5.1.2.3 分页查询
/**
     * 分页查询
     * mysql的分页关键字
     *   limit ?,?
     *   第一个: 查询的开始记录索引
     *   第二个: 每次查询的条数
     *
     * hibernate中针对分页提供了两个方法
     *    setFirstResult(int first)
     */
    @Test
    public void QueryWithPage() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 1.获取query对象
        Query query = s.createQuery("from Customer ");
        // 2. 设置分页的方法
        query.setFirstResult(0);
        query.setMaxResults(1);
        // 3. 执行query对象的方法,获取结果集
        List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }

5.1.2.4 排序查询
/**
     * 排序查询
     * 使用关键字:
     *   order by
     *   升序:
     *      asc  默认值
     *   降序:
     *      desc
     */
    @Test
    public void testRank() {
        // 1. 获取Session
        Session s = HibernateUtil.getCurrentSession();
        // 2. 开启事务
        Transaction tx = s.beginTransaction();
        // 3. 执行查询
        Query query = s.createQuery("from Customer order by cust_name desc");
        List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }
        // 提交事务
        tx.commit();
    }
5.1.2.5 统计查询
/**
     * HQL使用聚合函数
     *   统计查询
     *
     * 聚合函数
     *      count sum max min avg
     *
     * sql语句使用聚合函数时,在不使用 group by子句的情况下, 返回的结果,永远只有一行一列的情况
     *
     * 在SQL语句时:
     *      select count(*) from table  它是统计所有字段,效率没有只统计主键字段高
     *      select count(主键) from table  它和第一个的结果是一样的,但是效果更高
     *      select count(非主键) from table 只统计不为null的字段
     */

    @Test
    public void testFunction() {
        Session s = HibernateUtil.getCurrentSession();
        // 开启事务
        Transaction tx = s.beginTransaction();
        Query query = s.createQuery("select count(cust_id) from Customer");

    /* 最终仍然会转换成SQL语句
      List list = query.list();
        for (Object o : list) {
            System.out.println(o);
        }*/

        // 返回的是一个唯一的结果集,只有结果集唯一时,才能使用
        Long total = (Long)query.uniqueResult();
        System.out.println(total);
        tx.commit();
    }

5.1.2.6 投影查询
/**
     * 投影查询
     *      投影:使用一个实体的部分字段信息,来构建实体类对象,叫做对象的投影(Hibernate中叫法)
     *
     *      使用HQL的方式查询实体类的部分字段信息,并且封装到实体类中(QBC也能实现投影查询,但是不如
     *      HQL的好用,所以使用投影查询,一般都是用HQL)
     *
     *      HQL语句的写法:
     *          select new Customer() from Customer
     *          如果工程只有一个唯一的类,可以不写全限定类名,否则必须写全限定类名
     *          实体类要求:
     *              必须提供一个相同参数列表的构造函数
     */
    @Test
    public void testShadow() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        Query query = s.createQuery("select new Customer(cust_id, cust_name) from Customer ");
        List<Customer> list = query.list();
        for (Customer c : list) {
            System.out.println(c);
        }
        tx.commit();
    }

5.1.3 Query中的方法说明:

  • list方法:该方法用于查询语句,返回的结果是一个list集合。
  • uniqueResult方法:该方法用于查询,返回的结果是一个Object对象。
  • setter方法:Query接口中提供了一系列的setter方法用于设置查询语句中的参数,
    针对不同的数据类型,需要用到不同的setter方法。
  • uniqueResult()方法:该方法用于返回唯一的结果,在确保只有一条记录的查询时可以使用该方法。
  • setFirstResult ()方法:该方法可以设置获取第一个记录的位置,也就是它表示从第几条记录开始查询,默认从0开始计算。
  • setMaxResult()方法:该方法用于设置结果集的最大记录数,通常与setFirstResult()方法结合使用,用于限制结果集的范围,以实现分页功能。

5.2 Criteria

5.2.1 概述

Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库
底层如何实现,以及SQL语句如何编写,它是Hibernate框架的核心查询对象。Criteria查询,又称为QBC查询(Query By Criteria ),它是Hibernate的另一种对象检索方式。

org. hibernate . criterion .Criterion是Hibernate提供的一个面向对象查询条件接口,一个单独的查询就是Criterion接口的一个实例,用于限制Criteria对象的查询,在Hibernate中Criterion对象的创建通常是通过Restrictions工厂类完成的,它提供了条件查询方法。

通常,使用Criteria对象查询数据的主要步骤,具体如下:

  1. 获得 Hibernate 的 Session 对象。
  2. 通过 Session 获得 Criteria 对象。
  3. 使用Restrictions的静态方法创建Criterion条件对象。Restrictions类中提供了一系
    列用于设定查询条件的静态方法,这些静态方法都返回Criterion实例,每个
    Criterion实例代表一个查询条件。
  4. 向Criteria对象中添加Criterion查询条件。Criteria的add()方法用于加入查询条
    件。
  5. 执行 Critenta 的 listO 或 uniqueResultO 获得结果。

细节:

  • HQL能查的,QBC都能查,反之亦然。

了解了 Criteria对象的使用步骤后,接下来,通过具体示例来演示Criteria对象的查询
操作。

5.2.2 常用查询

5.2.2.1 基本查询
@Test
    public void queryCriteria() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        // 1.获取Query对象,相当于HQL的from Customer
        Criteria criteria = s.createCriteria(Customer.class);
        // 2. 执行对象的方法,获取结果集
        List list = criteria.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }
5.2.2.1 条件查询
@Test
    public void queryWithConditionCriteria() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 1. 获取Criteria对象
        Criteria criteria = s.createCriteria(Customer.class);
        // 2. 设置查询条件
        criteria.add(Restrictions.like("cust_name", "%艺"));
        criteria.add(Restrictions.like("cust_level", "HR"));
        // 3. 执行对象的方法,获取结果集
        List list = criteria.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }
5.2.2.1 分页查询
@Test
    public void QueryWithPageCriteria() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 1.获取query对象
        Criteria criteria = s.createCriteria(Customer.class);
        // 2. 设置分页的方法
        criteria.setFirstResult(0);
        criteria.setMaxResults(1);
        // 3. 执行query对象的方法,获取结果集
        List list = criteria.list();
        for (Object o : list) {
            System.out.println(o);
        }
        tx.commit();
    }
5.2.2.1 排序查询
@Test
    public void testRank() {
        // 1. 获取Session
        Session s = HibernateUtil.getCurrentSession();
        // 2. 开启事务
        Transaction tx = s.beginTransaction();
        // 3. 执行查询
        Criteria criteria = s.createCriteria(Customer.class);
        // 3.1 设置排序
        criteria.addOrder(Order.desc("cust_id"));
        // 3.2 获取结果集
        List list = criteria.list();
        for (Object o : list) {
            System.out.println(o);
        }
        // 提交事务
        tx.commit();
    }
5.2.2.1 统计查询
/**
     * QBC使用聚合函数
     *   统计查询
     * 涉及的方法
     *  setProjection(Projection p);
     *  参数的含义
     *      Projection:要添加的查询投影
     */
    @Test
    public void testFunctionCriteria() {
        Session s = HibernateUtil.getCurrentSession();
        // 开启事务
        Transaction tx = s.beginTransaction();
        // 1. 获取对象
        // 等同于 select * from customer | from Customer
        Criteria criteria= s.createCriteria(Customer.class);

        // 2. 想办法把select * 变成 select count(*)
        // 下面语句等同于select count(*)
        // 
   
   
    
    criteria
   
   .setProjection(Projections.rowCount());

        // 下面语句等同与select count(cust_id);
        criteria.setProjection(Projections.count("cust_id"));
        // 返回的是一个唯一的结果集,只有结果集唯一时,才能使用
        Long total = (Long)criteria.uniqueResult();
        System.out.println(total);
        tx.commit();
    }
5.2.2.1 离线查询
/**
     * 离线查询
     *  离线:
     *  它是和在线对应的-
     *	Criteria对象是一个在线对象,它是由一个可用的(活动的)Session对象获取的出来
     *	当session失效时,就无法再获取该对象了。
     *  有一个对象,它也可以用于设置条件,但是获取的时候并不需要Session对象。
     *  该对象就叫做离线对象:
     *	    DetachedCriteria 对象
     *
     * 使用该对象进行的査询就叫做:离线査询
     *
     *如何获取该对象
     *	DetachedCriteria dCriteria = DetachedCriteria.forClass(要査询的实体类字节码);

     */
    @Test
    public void testOfflineCriteria() {
        // 模拟一次web操作:
        // 浏览器发送请求-调用servlet-调用service-调用dao-拿到结果到jsp上展示
        List list = servletFindAllCustomer();
        for (Object o : list) {
            System.out.println(o);
        }
    }

    // 模拟servlet
    public List<Customer> servletFindAllCustomer() {
        // 离线对象
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
        // 设置条件 :和Criteria是一样的
        detachedCriteria.add(Restrictions.like("cust_name", "%艺"));
        return ServiceFindAllCustomer(detachedCriteria);
    }

    public List<Customer> ServiceFindAllCustomer(DetachedCriteria detachedCriteria) {
        return daoFindAllCustomer(detachedCriteria);
    }

    public List<Customer> daoFindAllCustomer(DetachedCriteria detachedCriteria) {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 把离线对象使用可用Session激活
        Criteria c = detachedCriteria.getExecutableCriteria(s);
        List<Customer> list = c.list();
        tx.commit();
        return list;
    }

QBC常用查询条件说明

短语含义
Restrictions.eq等于=
Restrictions.allEq使用Map,使用key/value进行多个等于的判断
Restrictions.gt大于>
Restrictions.ge大于等于>=
Restrictions.lt小于<
Restrictions.le小于等于<=
Restrictions.between对应sql的between子句
Restrictions.like对应sql的like子句
Restrictions.in对应sql的in子句
Restrictions.andand关系
Restrictions.oror关系
Restrictions.sqlRestrictionSql限定查询
Restrictions. asc()根据传入的字段进行升序排序
Restrictions. desc()根据传入的字段进行降序排序
运算类型HQL运算符QBC运算方法
比较运算=Restrictions.eq()
<>Restrictions.not(Restrictions. eq())
>=Restrictions. ge()
<Restrictions. It()
<=Restrictions. le()
is nullRestrictions. isNull()
is not nullRestrictions. isNotNull()
范围运算符inRestrictions. in()
not inRestrictions.not(Restnctions. in())
betweenRestrictions.between()
not betweenRestrictions.not(Restrictions.between())
字符串模式匹配likeRestrictions. like()
逻辑andRestrictions.and() l Restrictions.conjunction()
orRestrictions.or() I Restrictions.disjunction()
notRestrictions.not()

六 Hibernate关联关系映射

下面我们通过案例的方式一步一步讲解Hibernate的关联关系映射

6.1 完成一对多的关联关系映射并操作

案例需求:

6.1.1 需求描述

一个客户对应多个联系人,单独在联系人管理模块中对联系人信息进行维护,功能包括:
添加联系人、修改联系人、删除联系人。
添加联系人:添加联系人时指定所属客户,添加信息包括联系人名称、联系电话等
修改联系人:允许修改联系人所属客户、联系人名称、联系人电话等信息
删除联系人:删除客户的同时删除下属的联系人,可以单独删除客户的某个联系人

6.1.2 相关知识点

Hibernate框架实现了 ORM的思想,将关系数据库中表的数据映射成对象,使开发人员把对数据库的操作转化为对对象的操作,Hibernate的关联关系映射主要包括多表的映射配置、数据的增加、删除等。

数据库中多表之间存在着三种关系,也就是系统设计中的三种实体关系。如图所示。

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。在数据库中,实体表之间的关系映射是采用外键来描述的,具体如下。

6.1.2.1 表与表之间的三种关系
【一对多】

什么样关系属于一对多?

  • 一个部门对应多个员工,一个员工只能属于某一个部门。
  • 一个客户对应多个联系人,一个联系人只能属于某一个客户。

建表原则:在多的一方创建外键指向一的一方的主键:

【多对多】

什么样关系属于多对多?

  • 一个学生可以选择多门课程,一门课程也可以被多个学生选择。
  • 一个用户可以选择多个角色,一个角色也可以被多个用户选择。
建表原则:创建一个中间表,中间表中至少两个字段作为外键分别指向多对多双方的主键.

【一对一】

什么样关系属于一对一?

  • 一个公司只能有一个注册地址,一个注册地址只能被一个公司注册。

建表原则有两种:

  1. 唯一外键对应:假设一对一种的任意一方为多,在多的一方创建外键指向一的一方的主键,然后将外键设置为唯一。
  2. 主键对应:一方的主键作为另一方的主键。

数据库表能够描述的实体数据之间的关系,通过对象也可以进行描述,所谓的关联映射就是将关联关系映射到数据库里,在对象模型中就是一个或多个引用。在Hibernate中采用Java对象关系来描述数据表之间的关系,具体如图所示。

从图可以看出:

  • 通过一对一的关系就是在本类中定义对方类型的对象,如A中定义B类类型的属性b,B类中定义A类类型的属性a;
  • —对多的关系,图中描述的是一个A对应多个B类类型的情况,需要在A类以Set集合的方式引入B类型的对象,在B类中定义A类类型的属性a;
  • 多对多的关系,在A类中定义B类类型的Set集合,在B类中定义A类类型的Set集合,这里用Set集合的目的是避免了数据的重复。

以上就是系统模型中实体设计的三种关联关系,由于一对一的关联关系在开发中不常使用,所
以我们不单独讲解,了解即可。

6.2 Hibernate的一对多关联映射

  • 创建一个项目,引入相应jar包(前面已经介绍过了,这里不过赘述)

  • 创建数据库和表

    联系人表中存在外键(lkm_cust_id 即所属客户id,外键指向客户表)

    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;
    

准备工作:
6.2.1 创建实体

【客户实体】

package com.daohewang.domin;

import java.util.HashSet;
import java.util.Set;
public class CstCustomer {
    private Long cust_id;                //'客户编号(主键)',
    private String cust_name;            // '客户名称(公司名称)',
    private String cust_source;           // '客户信息来源',
    private String cust_industry;          // '客户所属行业',
    private String cust_level;             // '客户级别',
    private String cust_phone;             // '固定电话',
    private String cust_mobile;            //'移动电话',

    // 一个客户有多个联系人:客户中应该放有联系人的集合
    private Set<CstLinkman> linkMans = new HashSet<>();

    public Long getCust_id() {
        return cust_id;
    }

    public void setCust_id(Long cust_id) {
        this.cust_id = cust_id;
    }

    public String getCust_name() {
        return cust_name;
    }

    public void setCust_name(String cust_name) {
        this.cust_name = cust_name;
    }

    public String getCust_source() {
        return cust_source;
    }

    public void setCust_source(String cust_source) {
        this.cust_source = cust_source;
    }

    public String getCust_industry() {
        return cust_industry;
    }

    public void setCust_industry(String cust_industry) {
        this.cust_industry = cust_industry;
    }

    public String getCust_level() {
        return cust_level;
    }

    public void setCust_level(String cust_level) {
        this.cust_level = cust_level;
    }

    public String getCust_phone() {
        return cust_phone;
    }

    public void setCust_phone(String cust_phone) {
        this.cust_phone = cust_phone;
    }

    public String getCust_mobile() {
        return cust_mobile;
    }

    public void setCust_mobile(String cust_mobile) {
        this.cust_mobile = cust_mobile;
    }

    public Set<CstLinkman> getLinkMans() {
        return linkMans;
    }

    public void setLinkMans(Set<CstLinkman> linkMans) {
        this.linkMans = linkMans;
    }
}

【联系人实体】

package com.daohewang.domin;
public class CstLinkman {
    private Long lkm_id;            //联系人编号(主键)',
    private String lkm_name;        //联系人姓名',
    private String lkm_cust_id;     //客户id',
    private String lkm_gender;      //联系人性别',
    private String lkm_phone;       //联系人办公电话',
    private String lkm_mobile;      //联系人手机',
    private String lkm_email;       //联系人邮箱',
    private String lkm_qq;          //联系人qq',
    private String lkm_position;    //联系人职位',
    private String lkm_memo;        //联系人备注',

    private CstCustomer customer;

    public Long getLkm_id() {
        return lkm_id;
    }

    public void setLkm_id(Long lkm_id) {
        this.lkm_id = lkm_id;
    }

    public String getLkm_name() {
        return lkm_name;
    }

    public void setLkm_name(String lkm_name) {
        this.lkm_name = lkm_name;
    }

    public String getLkm_cust_id() {
        return lkm_cust_id;
    }

    public void setLkm_cust_id(String lkm_cust_id) {
        this.lkm_cust_id = lkm_cust_id;
    }

    public String getLkm_gender() {
        return lkm_gender;
    }

    public void setLkm_gender(String lkm_gender) {
        this.lkm_gender = lkm_gender;
    }

    public String getLkm_phone() {
        return lkm_phone;
    }

    public void setLkm_phone(String lkm_phone) {
        this.lkm_phone = lkm_phone;
    }

    public String getLkm_mobile() {
        return lkm_mobile;
    }

    public void setLkm_mobile(String lkm_mobile) {
        this.lkm_mobile = lkm_mobile;
    }

    public String getLkm_email() {
        return lkm_email;
    }

    public void setLkm_email(String lkm_email) {
        this.lkm_email = lkm_email;
    }

    public String getLkm_qq() {
        return lkm_qq;
    }

    public void setLkm_qq(String lkm_qq) {
        this.lkm_qq = lkm_qq;
    }

    public String getLkm_position() {
        return lkm_position;
    }

    public void setLkm_position(String lkm_position) {
        this.lkm_position = lkm_position;
    }

    public String getLkm_memo() {
        return lkm_memo;
    }

    public void setLkm_memo(String lkm_memo) {
        this.lkm_memo = lkm_memo;
    }

    public CstCustomer getCustomer() {
        return customer;
    }

    public void setCustomer(CstCustomer customer) {
        this.customer = customer;
    }
}

6.2.2 创建映射

【客户的映射】CstCustomer.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	:类的全路径
            * table	:数据库表名称
     -->
    <class name="com.daohewang.domin.CstCustomer" table="cst_customer">
        <!-- 建立类中的属性与表中的字段的映射 -->
        <!--
            id标签:用来建立表中的主键字段与类中的属性的映射
         -->
        <id name="cust_id" column="cust_id">
            <!--主键生成策略-->
            <generator class="native" />
        </id>
        <!-- 其他的属性都是用property建立映射 -->
        <property name="cust_name" length="32"/>
        <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属性:多的一方的集合的属性名称
        -->
        <set name="linkMans">
            <!--
                key标签
                *column属性:多的一方的外键名称
            -->
            <key column="lkm_cust_id"></key>
            <!--
                one-to-many标签:
                class属性:多的一方的类全路径
            -->
            <one-to-many class="com.daohewang.domin.CstLinkman"/>
        </set>
    </class>
</
    
    
     
     hibernate
    
    -
    
    
     
     mapping
    
    >

使用set集合来描述CstCustomer.java类中的属性linkMans。在Hibernate的映射文件中,使用标签用来描述被映射类中的Set集合,标签的column属性值对应文件多的一方的外键名称,在CstCustomer.java客户类中,客户与联系人是一对多的关系,Hibernate的映射文件中,使用<0ne-to-many>#签来描述持久化类的一对多关联,其中class属性用来描述映射的关联类。

【联系人映射】

<?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	:类的全路径
            * table	:数据库表名称
     -->
    <class name="com.daohewang.domin.CstLinkman" table="cst_linkman">
        <!-- 建立类中的属性与表中的字段的映射 -->
        <!--
            id标签:用来建立表中的主键字段与类中的属性的映射
         -->
        <id name="lkm_id" column="lkm_id">
            <!--主键生成策略-->
            <generator class="native" />
        </id>
        <!-- 其他的属性都是用property建立映射 -->
        <property name="lkm_name" column="lkm_name"/>
        <property name="lkm_gender" column="lkm_gender"/>
        <property name="lkm_phone" column="lkm_phone"/>
        <property name="lkm_mobile" column="lkm_mobile"/>
        <property name="lkm_email" column="lkm_email"/>
        <property name="lkm_qq" column="lkm_qq"/>
        <property name="lkm_position" column="lkm_position"/>
        <property name="lkm_memo" column="lkm_memo"/>

        <!--
            many-to-one : 代表多对一
            name:一的一方的对象名称
            class:一的一方的的类的全路径
            column:表的外键的名称
        -->
        <many-to-one name="customer" class="com.daohewang.domin.CstCustomer" column="lkm_cust_id"/>
    </class>
</
    
    
     
     hibernate
    
    -
    
    
     
     mapping
    
    >

标签定义两个持久化类的关联,这种关联是数据表间的多对一关联,联系人与客户就是多对一的关系,所以用标签来描述。标签的name属性用来描述customer在CstLinkMan.java类中的属性的名称,class属性用来指定映射的类,column属性值对应表中的外键列名。

6.2.3 将映射添加到配置文件
<!-- 加载该文件时   ,就加载Cstcustomer.hbm.xml映射文件: -->
<
    
    
     
     mapping
    
     resource="com/daohewang/domin/CstCustomer.hbm.xml"/>
<!-- 加载该文件时   ,就加载CstLinkman.hbm.xml映射文件: -->
<
    
    
     
     mapping
    
     resource="com/daohewang/domin/CstLinkman.hbm.xml"/>
6.2.4 代码测试
package com.daohewang.test;

import com.daohewang.domin.CstCustomer;
import com.daohewang.domin.CstLinkman;
import com.daohewang.utils.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
public class HibernateRelation {
    @Test
    public void demo1() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 创建一个客户
        CstCustomer cstCustomer = new CstCustomer();
        cstCustomer.setCust_name("姜总");

        // 创建两个联系人
        CstLinkman linkman1 = new CstLinkman();
        linkman1.setLkm_name("李秘书");

        CstLinkman linkman2 = new CstLinkman();
        linkman2.setLkm_name("王助理");

        // 建立关系
        cstCustomer.getLinkMans().add(linkman1);
        cstCustomer.getLinkMans().add(linkman2);
        linkman1.setCustomer(cstCustomer);
        linkman2.setCustomer(cstCustomer);

        s.save(cstCustomer);
        s.save(linkman1);
        s.save(linkman2);

        tx.commit();
    }
}

在配置文件中添加了自动建表信息后,运行程序时,程序会自动创建两张表,并且插入数据。使用Junit4运行demol()方法后,控制台输出结果,如图所示

Hibernate: 
    insert 
    into
        cst_customer
        (cust_name, cust_source, cust_industry, cust_level, cust_phone, cust_mobile) 
    values
        (?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        cst_linkman
        (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        cst_linkman
        (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    update
        cst_linkman 
    set
        lkm_cust_id=? 
    where
        lkm_id=?
Hibernate: 
    update
        cst_linkman 
    set
        lkm_cust_id=? 
    where
        lkm_id=?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2f8AAE4-1577752970131)(https://i.loli.net/2019/12/26/TnKgDaiHVQcWsSB.png)]

从图的输出结果可以看到,控制台成功输出了三条insert语句和两条update语句.从查询结果可以看出,数据表创建成功,并成功插入了相应数据。那么一个基本的一对多的关联关系映射就己经配置好了。以上我们的代码可以发现我们建立的关系是双向的,即客户关联了联系人,同时联系人也关联了客户。

// 客户关联了联系人
cstCustomer.getLinkMans().add(linkman1);
cstCustomer.getLinkMans().add(linkman2);
// 联系人关联了客户
linkman1.setCustomer(cstCustomer);
linkman2.setCustomer(cstCustomer);

这就是双向关联,那么既然己经进行了双向的关联关系的设置,那么我们还保存了双方,那如果我们只保存一方是否可以呢?也就是说我们建立了双向的维护关系,只保存客户或者只保存联系人是否可以。那么我们来进行一下测试。

 @Test
    public void demo2() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 创建一个客户
        CstCustomer cstCustomer = new CstCustomer();
        cstCustomer.setCust_name("姜总");

        // 创建两个联系人
        CstLinkman linkman1 = new CstLinkman();
        linkman1.setLkm_name("李秘书");

        CstLinkman linkman2 = new CstLinkman();
        linkman2.setLkm_name("王助理");

        // 建立关系
        cstCustomer.getLinkMans().add(linkman1);
        cstCustomer.getLinkMans().add(linkman2);
        linkman1.setCustomer(cstCustomer);
        linkman2.setCustomer(cstCustomer);
		// 瞬时对象异常,持久态的对象关联了一个瞬时态对象的异常
        s.save(cstCustomer);
     
        tx.commit();
    }

执行上面代码会出现如下错误

image.png

这样操作显然不行,无论从那一方保存都会出现同样的异常:瞬时对象异常,一个持久态对象关联了一个瞬时态对象,那就说明我们不能只保存一方。那如果我们就想只保存一个方向应该如何进行操作呢,那么我们可以使用Hibernate的级联操作,那么我们来看下Hibernate的级联吧。

6.3 一对多的相关操作

级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。在映射文件中通过对cascade属性的设置来控制是否对关联对象采用级联操作,级联操作对各种关联关系都是有效的。

6.3.1 级联保存或更新

级联是有方向性的,所谓的方向性指的是,在保存一的一方级联多的一方和在保存多的一方级联一的一方。

【保存客户级联联系人】
首先要确定我们要保存的主控方是哪一方,我们要保存客户,所以客户是主控方,那么需要在客户的映射文件中进行如下的配置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmm6wyvv-1577752970141)(https://i.loli.net/2019/12/26/86QiWI9JHCGNtbw.png)]

接下来运行上面的测试代码,就可以正常运行了

【保存联系人候级联客户】
同样我们需要确定主控方,现在我们的主控方是联系人。所以需要在联系人的映射文件中进行配置,内容如下:

<!--
            many-to-one : 代表多对一
            name:一的一方的对象名称
            class:一的一方的的类的全路径
            column:表的外键的名称
        -->
<many-to-one name="customer" 
    
    
     
     cascade
    
    ="save-update" class="com.daohewang.domin.CstCustomer" column="lkm_cust_id"/>

编程测试代码,进行测试

©Test
//级联保存:只保存一边的问题.
//级联是有方向性,保存联系人同时级联客户.
// 在 CstLinkMan.hbm.xml中在<many-to-one>标签上配置 cascade="save-update”
public void demo4(){
    Session session = HibernateUtils. getCurrentSession();
    Transaction transaction = session.beginTransaction();
    //创建一个客户
    CstCustomer customer = new CstCustomer();
    customer.setCust_name ("刘总");
    //创建两个联系人:
    CstLinkMan linkManl = new CstLinkMan();
    linkManl.setLkm_name ("王秘书");
    //建立关系:
    customer.getLinkMans().add(linkManl);
    linkManl.setCustomer(customer);
    
    session.save(linkManl);
    
    transaction.commit();
}    

到这我们己经可以看到级联保存或更新的效果了。那么我们维护的时候都是双向的关系维护。那么这种关系到底表述的是什么含义呢?我们可以通过下面的测试来更进一步了解关系的维护(对象导航)的含义。

6.3.2 测试对象的导航的问题

我们所说的对象导航其实就是在维护双方的关系。

customer.getLinkMans().add(linkManl);
linkManl.setCustomer(customer)

这种关系有什么用途呢?我们可以通过下面的测试来更进一步学习Hibernate。
我们在客户和联系人端都配置了 cascade=“save-update”,然后进行如下的关系设置。会产生什么样的效果呢?

image.png

我们执行第24行的时候,问会执行几条insert语句呢?其实发现会有4条insert语句的,因为联系人1关联了客户,客户又关联了联系人2和联系人3,所以当保存联系人1的时候,联系人1是可以进入到数据库的,它关联的客户也是会进入到数据库的(因为联系人一端配置了级联),那么客户进入到数据库以后,联系人2和联系人3也同样会进入到数据库(因为客户一端配置了级联),所以会有4条insert语句。

我们执行25行的时候,问会有几条insert语句?这时我们保存的客户对象,可以对象关联了联系人2和联系人3,那么客户在保存进入数据库的时候,联系人2和联系人3也会进入到数据库,所以是3条insert语句。

同理我们执行26行代码的时候,只会执行1条msert语句,因为联系人2会保存到数据库,但是联系人2没有客户客户建立关系。所以客户不会保存到数据库。

到这我们应该更加理解了 Hibernate的这种关系的建立和级联的关系了。那么级联还有那些操作呢?接下来我们来了解下级联删除的操作。

6.3.3 Hibernate 的级联删除

我们之前学习过级联保存或更新,那么再来看级联删除也就不难理解了,级联删除也是有方向性的,删除客户同时级联删除联系人,也可以删除联系人同时级联删除客户(这种需求很少)。

原来JDBC中删除客户和联系人的时候,如果有外键的关系是不可以删除的,但是现在我们使用了 Hibernate,其实Hibernate可以实现这样的功能,但是不会删除客户同时删除联系人,默认情况下Hibernate会怎么做呢?我们来看下面的测试:

@Test
    public void demo5() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        CstCustomer cstCustomer = s.get(CstCustomer.class, 1L);
        s.delete(cstCustomer);
        tx.commit();
    }

默认的情况下如果客户下面还有联系人,hibernate会将联系人的外键置为null,然后去删除客户。那么其实有的时候我们需要删除客户的时候,同时将客户关联的联系人一并删除。这个时候我们就需要使用Hibernate的级联保存操作了。

【删除客户的时候同时删除客户的联系人】
确定删除的主控方式客户,所以需要在客户端配置:

// 让cascade="delete"
<set name="linkMans" 
    
    
     
     cascade
    
    ="delete" >
            <!--
                key标签
                *column属性:多的一方的外键名称
            -->
            <key column="lkm_cust_id"></key>
            <!--
                one-to-many标签:
                class属性:多的一方的类全路径
            -->
            <one-to-many class="com.daohewang.domin.CstLinkman"/>
        </set>

如果还想有之前的级联保存或更新,同时还想有级联删除,那么我们可以进行如下的配置

// 让cascade="delete,save-update"
<set name="linkMans" 
    
    
     
     cascade
    
    ="delete,save-update" >
            <!--
                key标签
                *column属性:多的一方的外键名称
            -->
            <key column="lkm_cust_id"></key>
            <!--
                one-to-many标签:
                class属性:多的一方的类全路径
            -->
            <one-to-many class="com.daohewang.domin.CstLinkman"/>
</set>

配置完成以后我们可以来编写测试代码了,代码如下:

@Test
//级联删除:级联刪除有方向性.
//删除客户同时级联删除联系人.
// 在 CstCustomer.hbm.xml 中 set 标签上配罝 cascade="delete”
public void demo7(){
    Session session = HibernateUtils.getCurrentSession();
    Transaction transaction = session.beginTransaction();
    //级联删除:必须是先査询再删除的.
    //因为査询到客户,这个时候客户的联系人的集合中就会有数据.
    CstCustomer customer = session.get(CstCustomer.class, 11);
    session.delete(customer);
    transaction.commit();
}

【删除联系人的时候同时删除客户】
同样我们删除的是联系人,那么联系人是主控方,需要在联系人端配置:

<!--
            many-to-one : 代表多对一
            name:一的一方的对象名称
            class:一的一方的的类的全路径
            column:表的外键的名称
        -->
<many-to-one name="customer" 
    
    
     
     cascade
    
    ="delete" class="com.daohewang.domin.CstCustomer" column="lkm_cust_id"/>

如果还想有之前的级联保存或更新,同时还想有级联删除,那么我们可以进行如下的配置

<!--
            many-to-one : 代表多对一
            name:一的一方的对象名称
            class:一的一方的的类的全路径
            column:表的外键的名称
        -->
<many-to-one name="customer" 
    
    
     
     cascade
    
    ="delete,save-update" class="com.daohewang.domin.CstCustomer" column="lkm_cust_id"/>

配置完成以后我们可以来编写测试代码了,代码如下:

@Test
//级联删除:级联刪除有方向性.
//删除客户同时级联删除联系人.
// 在 CstCustomer.hbm.xml 中 set 标签上配罝 cascade="delete”
public void demo8(){
    Session session = HibernateUtils.getCurrentSession();
    Transaction transaction = session.beginTransaction();
    //级联删除:必须是先査询再删除的.
    //因为査询到客户,这个时候客户的联系人的集合中就会有数据.
    CstLinkMan linkMan = session.get(CstLinkMan.class, 11);
    session.delete(linkMan);
    transaction.commit();
}
6.3.3 双向关联产生多余的SQL语句

到这我们己经了解了 Hibernate级联的基本配置和使用。但是有些时候我们需要进行如下的操作:

数据库中记录如下:
客户表

image.png

联系人表

image.png

需要将2号联系人关联给2号客户。也就是将2号李秘书这个联系人关联给2号刘总这个客户。编写修改2号客户关联的联系人的代码。

@Test
    // 将2号联系人关联的客户改为2号客户
    public void demo9() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        // 先查询
        CstCustomer cstCustomer = s.get(CstCustomer.class, 2L);
        CstLinkman linkman = s.get(CstLinkman.class, 2L);
        // 再更新
        linkman.setCustomer(cstCustomer);
        cstCustomer.getLinkMans().add(linkman);

        tx.commit();
    }

运行代码,控制台会输出如下内容

Hibernate: 
    select
        cstcustome0_.cust_id as cust_id1_0_0_,
        cstcustome0_.cust_name as cust_nam2_0_0_,
        cstcustome0_.cust_source as cust_sou3_0_0_,
        cstcustome0_.cust_industry as cust_ind4_0_0_,
        cstcustome0_.cust_level as cust_lev5_0_0_,
        cstcustome0_.cust_phone as cust_pho6_0_0_,
        cstcustome0_.cust_mobile as cust_mob7_0_0_ 
    from
        cst_customer cstcustome0_ 
    where
        cstcustome0_.cust_id=?
Hibernate: 
    select
        cstlinkman0_.lkm_id as lkm_id1_1_0_,
        cstlinkman0_.lkm_name as lkm_name2_1_0_,
        cstlinkman0_.lkm_gender as lkm_gend3_1_0_,
        cstlinkman0_.lkm_phone as lkm_phon4_1_0_,
        cstlinkman0_.lkm_mobile as lkm_mobi5_1_0_,
        cstlinkman0_.lkm_email as lkm_emai6_1_0_,
        cstlinkman0_.lkm_qq as lkm_qq7_1_0_,
        cstlinkman0_.lkm_position as lkm_posi8_1_0_,
        cstlinkman0_.lkm_memo as lkm_memo9_1_0_,
        cstlinkman0_.lkm_cust_id as lkm_cus10_1_0_ 
    from
        cst_linkman cstlinkman0_ 
    where
        cstlinkman0_.lkm_id=?
Hibernate: 
    select
        linkmans0_.lkm_cust_id as lkm_cus10_1_0_,
        linkmans0_.lkm_id as lkm_id1_1_0_,
        linkmans0_.lkm_id as lkm_id1_1_1_,
        linkmans0_.lkm_name as lkm_name2_1_1_,
        linkmans0_.lkm_gender as lkm_gend3_1_1_,
        linkmans0_.lkm_phone as lkm_phon4_1_1_,
        linkmans0_.lkm_mobile as lkm_mobi5_1_1_,
        linkmans0_.lkm_email as lkm_emai6_1_1_,
        linkmans0_.lkm_qq as lkm_qq7_1_1_,
        linkmans0_.lkm_position as lkm_posi8_1_1_,
        linkmans0_.lkm_memo as lkm_memo9_1_1_,
        linkmans0_.lkm_cust_id as lkm_cus10_1_1_ 
    from
        cst_linkman linkmans0_ 
    where
        linkmans0_.lkm_cust_id=?
Hibernate: 
    update
        cst_linkman 
    set
        lkm_name=?,
        lkm_gender=?,
        lkm_phone=?,
        lkm_mobile=?,
        lkm_email=?,
        lkm_qq=?,
        lkm_position=?,
        lkm_memo=?,
        lkm_cust_id=? 
    where
        lkm_id=?
Hibernate: 
    update
        cst_linkman 
    set
        lkm_cust_id=? 
    where
        lkm_id=?

我们会发现执行了两次update语句,其实这两个update都是修改了外键的操作,那么为什么发送两次呢?那么我们来分析一下产生这种问题的原因:

image.png

我们己经分析过了,因为双向维护了关系,而且持久态对象可以自动更新数据库,更新客户的时候会修改一次外键,更新联系人的时候同样也会修改一次外键。这样就会产生了多余的SQL,那么问题产生了,我们又该如何解决呢?

其实解决的办法很简单,只需要将一方放弃外键维护权即可。也就是说关系不是双方维护的,只需要交给某一方去维护就可以了。通常我们都是交给多的一方去维护的。为什么呢?因为多的一方才是维护关系的最好的地方,举个例子,一个老师对应多个学生,一个学生对应一个老师,这是典型的一对多。那么一个老师如果要记住所有学生的名字很难的,但如果让每个学生记住老师的名字应该不难。其实就是这个道理。所以在一对多中,一的一方都会放弃外键的维护权(关系的维护)。

这个时候如果想让一的一方放弃外键的维护权,只需要进行如下的配置即可。

<!--
            set标签:
                *name属性:多的一方的集合的属性名称
        -->
        <set name="linkMans" 
    
    
     
     cascade
    
    ="delete,save-update" 
    
    
     
     inverse
    
    ="true">
            <!--
                key标签
                *column属性:多的一方的外键名称
            -->
            <key column="lkm_cust_id"></key>
            <!--
                one-to-many标签:
                class属性:多的一方的类全路径
            -->
            <one-to-many class="com.daohewang.domin.CstLinkman"/>
        </set>

inverse的默认值是false,代表不放弃外键维护权,配置值为true,代表放弃了外键的维护权。这个时候再来执行之前的操作:

Hibernate: 
    select
        cstcustome0_.cust_id as cust_id1_0_0_,
        cstcustome0_.cust_name as cust_nam2_0_0_,
        cstcustome0_.cust_source as cust_sou3_0_0_,
        cstcustome0_.cust_industry as cust_ind4_0_0_,
        cstcustome0_.cust_level as cust_lev5_0_0_,
        cstcustome0_.cust_phone as cust_pho6_0_0_,
        cstcustome0_.cust_mobile as cust_mob7_0_0_ 
    from
        cst_customer cstcustome0_ 
    where
        cstcustome0_.cust_id=?
Hibernate: 
    select
        cstlinkman0_.lkm_id as lkm_id1_1_0_,
        cstlinkman0_.lkm_name as lkm_name2_1_0_,
        cstlinkman0_.lkm_gender as lkm_gend3_1_0_,
        cstlinkman0_.lkm_phone as lkm_phon4_1_0_,
        cstlinkman0_.lkm_mobile as lkm_mobi5_1_0_,
        cstlinkman0_.lkm_email as lkm_emai6_1_0_,
        cstlinkman0_.lkm_qq as lkm_qq7_1_0_,
        cstlinkman0_.lkm_position as lkm_posi8_1_0_,
        cstlinkman0_.lkm_memo as lkm_memo9_1_0_,
        cstlinkman0_.lkm_cust_id as lkm_cus10_1_0_ 
    from
        cst_linkman cstlinkman0_ 
    where
        cstlinkman0_.lkm_id=?
Hibernate: 
    select
        linkmans0_.lkm_cust_id as lkm_cus10_1_0_,
        linkmans0_.lkm_id as lkm_id1_1_0_,
        linkmans0_.lkm_id as lkm_id1_1_1_,
        linkmans0_.lkm_name as lkm_name2_1_1_,
        linkmans0_.lkm_gender as lkm_gend3_1_1_,
        linkmans0_.lkm_phone as lkm_phon4_1_1_,
        linkmans0_.lkm_mobile as lkm_mobi5_1_1_,
        linkmans0_.lkm_email as lkm_emai6_1_1_,
        linkmans0_.lkm_qq as lkm_qq7_1_1_,
        linkmans0_.lkm_position as lkm_posi8_1_1_,
        linkmans0_.lkm_memo as lkm_memo9_1_1_,
        linkmans0_.lkm_cust_id as lkm_cus10_1_1_ 
    from
        cst_linkman linkmans0_ 
    where
        linkmans0_.lkm_cust_id=?
Hibernate: 
    update
        cst_linkman 
    set
        lkm_name=?,
        lkm_gender=?,
        lkm_phone=?,
        lkm_mobile=?,
        lkm_email=?,
        lkm_qq=?,
        lkm_position=?,
        lkm_memo=?,
        lkm_cust_id=? 
    where
        lkm_id=?

这个时候我们会发现就不会出现上述的问题了,不会产生多余的SQL 了(因为一的一方已经放弃了外键的维护权)。

那么这个问题我们己经解决了,但是有很多同学对应cascade和inverse还是不是太懂,我们可以通过下面的案例区分cascade和mverse的区别,因为这两个参数我们以后的开发中会经常使用。

 @Test
    // cascade和inverse
    // cascade强调的是操作一个对象的时候,是否操作其关联对象
    // inverse强调的是外键的维护权
    public void demo10() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        CstCustomer customer = new CstCustomer();
        customer.setCust_name("王总");

        CstLinkman linkman = new CstLinkman();
        linkman.setLkm_name("李秘书");
        // 在CstCustomer.hbm.xml中的set上设置cascade="save-update" inverse="true"
        customer.getLinkMans().add(linkman);
        // 客户关联的联系人是否被保存到数据库,外键是啥
        s.save(customer);
        tx.commit();
    }

这个时候我们会发现,如果在set集合上配置cascade=“save-update” inverse=“true” 了,那么执行保存客户的操作,会发现客户和联系人都进入到数据库了,但是没有外键,是因为配置了 cascade了所以客户关联的联系人会进入到数据库,但是客户一端放弃了外键维护权,所以联系人插入到数据库以后是没有外键的。

一对多己经完成了,那么我们来看下Hibernate中的多对多的关系的配置。

6.4 Hibernate的多对多关联关系映射

6.4.1 创建表

创建 sys_user、sys_role、sys_user_role 三张表.
数据模型如下:

image.png

6.4.2 创建实体

【用户实体】

package com.daohewang.domin;

import java.util.HashSet;
import java.util.Set;
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>();

    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.daohewang.domin;

import java.util.HashSet;
import java.util.Set;
public class Role {
    private Long role_id;
    private String role_name;
    private String role_memo;

    // 一个角色包含多个用户
    private Set<User> users = new HashSet<>();

    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;
    }
}
6.4.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	:类的全路径
            * table	:数据库表名称
     -->
    <class name="com.daohewang.domin.User" table="sys_user">
        <!-- 建立类中的属性与表中的字段的映射 -->
        <!--
            id标签:用来建立表中的主键字段与类中的属性的映射
         -->
        <id name="user_id" column="user_id">
            <!--主键生成策略-->
            <generator class="native" />
        </id>
        <!-- 其他的属性都是用property建立映射 -->
        <property name="user_code" />
        <property name="user_name" />
        <property name="user_password" />
        <property name="user_state" />
        <!--配置关联关系映射-->
        <!--
            set标签:
                *name属性:关联的另一方的集合的属性名称
                table :中间表的名称
        -->
        <set name="roles" table="sys_user_role">
            <!--
                key标签
                *column属性:当前对象在中间表的外键的名称
            -->
            <key column="user_id"/>
            <!--
                many-to-money标签
                    class :关联另一方的类的全路径
                    column:关联的另一方在中间表的外键名称
            -->
            <many-to-many class="com.daohewang.domin.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	:类的全路径
            * table	:数据库表名称
     -->
    <class name="com.daohewang.domin.Role" table="sys_role">
        <!-- 建立类中的属性与表中的字段的映射 -->
        <!--
            id标签:用来建立表中的主键字段与类中的属性的映射
         -->
        <id name="role_id" column="role_id">
            <!--主键生成策略-->
            <generator class="native" />
        </id>
        <!-- 其他的属性都是用property建立映射 -->
        <property name="role_name" />
        <property name="role_memo" />
        <!--配置关联关系映射-->
        <!--
            set标签:
                *name属性:关联的另一方的集合的属性名称
                table :中间表的名称
        -->
        <set name="users" table="sys_user_role">
            <!--
                key标签
                *column属性:当前对象在中间表的外键的名称
            -->
            <key column="role_id"/>
            <!--
                many-to-money标签
                    class :关联另一方的类的全路径
                    column:关联的另一方在中间表的外键名称
            -->
            <many-to-many class="com.daohewang.domin.User" column="user_id"/>
        </set>
    </class>
</
    
    
     
     hibernate
    
    -
    
    
     
     mapping
    
    >

核心配置文件加入映射文件

<
    
    
     
     mapping
    
     resource="com/daohewang/domin/User.hbm.xml"/>
<
    
    
     
     mapping
    
     resource="com/daohewang/domin/Role.hbm.xml"/>

编写测试类

 @Test
    public void demo11() {
        // 保存用户和角色
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        User user1 = new User();
        user1.setUser_name("张三");
        User user2 = new User();
        user1.setUser_name("李四");

        Role role1 = new Role();
        role1.setRole_name("前台");
        Role role2 = new Role();
        role1.setRole_name("人事");
        Role role3 = new Role();
        role1.setRole_name("助理");

        // 如果多对多建立了双向关联,一定要有一方放弃外键维护权
        user1.getRoles().add(role1);
        user1.getRoles().add(role3);
        user2.getRoles().add(role2);
        user2.getRoles().add(role3);

        role1.getUsers().add(user1);
        role2.getUsers().add(user1);
        role2.getUsers().add(user2);
        role3.getUsers().add(user2);

        s.save(user1);
        s.save(user2);
        s.save(role1);
        s.save(role2);
        s.save(role3);
        
        tx.commit();

    }

在多对多的保存操作中,如果进行了双向维护关系,就必须有一方放弃外键维护权。一般由被动方放弃,用户主动选择角色,角色是被选择的,所以一般角色要回放弃外键维护权。但如果只进行单向维护关系,那么就不需要放弃外键维护权了。

将数据保存到数据库了以后,那么多对多会有哪些相关的操作呢?我们来了解一下多对多的相关的操作。

6.4.4 多对多的相关操作

6.1.4.1 级联保存或更新

之前已经讲解过一对多的级联保存了,那么多对多也是一样的。如果只保存单独的一方是不可以的,还是需要保存双方的。如果就想保存一方就需要设置级联操作了。同样要看保存的主控方是哪一端,就需要在那一端进行配置。
【保存用户级联角色】
保存的主控方是用户,需要在用户一端配置:

<!--配置关联关系映射-->
        <!--
            set标签:
                *name属性:关联的另一方的集合的属性名称
                table :中间表的名称
        -->
        <set name="roles" table="sys_user_role" 
    
    
     
     cascade
    
    ="save-update">
            <!--
                key标签
                *column属性:当前对象在中间表的外键的名称
            -->
            <key column="user_id"/>
            <!--
                many-to-money标签
                    class :关联另一方的类的全路径
                    column:关联的另一方在中间表的外键名称
            -->
            <many-to-many class="com.daohewang.domin.Role" column="role_id"/>
        </set>

测试代码

多对多只保存一边也是不可以的.
配置级联:保存用户级联角色.在User.hbm.xml中<set>上配置cascade="save-update" 
@Test
    public void demo11() {
        // 保存用户和角色
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        User user1 = new User();
        user1.setUser_name("张三");
        User user2 = new User();
        user1.setUser_name("李四");

        Role role1 = new Role();
        role1.setRole_name("前台");
        Role role2 = new Role();
        role1.setRole_name("人事");
        Role role3 = new Role();
        role1.setRole_name("助理");

        // 如果多对多建立了双向关联,一定要有一方放弃外键维护权
        user1.getRoles().add(role1);
        user1.getRoles().add(role3);
        user2.getRoles().add(role2);
        user2.getRoles().add(role3);

        role1.getUsers().add(user1);
        role2.getUsers().add(user1);
        role2.getUsers().add(user2);
        role3.getUsers().add(user2);

        s.save(user1);
        s.save(user2);
        /*s.save(role1);
        s.save(role2);
        s.save(role3);*/

        tx.commit();
    }
}

【保存角色级联用户】

保存的主控方是角色,就需要在角色端进行配置

<!--配置关联关系映射-->
        <!--
            set标签:
                *name属性:关联的另一方的集合的属性名称
                table :中间表的名称
        -->
        <set name="users" table="sys_user_role" 
    
    
     
     cascade
    
    ="save-update">
            <!--
                key标签
                *column属性:当前对象在中间表的外键的名称
            -->
            <key column="role_id"/>
            <!--
                many-to-money标签
                    class :关联另一方的类的全路径
                    column:关联的另一方在中间表的外键名称
            -->
            <many-to-many class="com.daohewang.domin.User" column="user_id"/>
        </set>

测试代码:

多对多只保存一边也是不可以的.
配置级联:保存角色级联用户.在Role.hbm.xml中<set>上配置cascade="save-update" 
@Test
    public void demo11() {
        // 保存用户和角色
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        User user1 = new User();
        user1.setUser_name("张三");
        User user2 = new User();
        user1.setUser_name("李四");

        Role role1 = new Role();
        role1.setRole_name("前台");
        Role role2 = new Role();
        role1.setRole_name("人事");
        Role role3 = new Role();
        role1.setRole_name("助理");

        // 如果多对多建立了双向关联,一定要有一方放弃外键维护权
        user1.getRoles().add(role1);
        user1.getRoles().add(role3);
        user2.getRoles().add(role2);
        user2.getRoles().add(role3);

        role1.getUsers().add(user1);
        role2.getUsers().add(user1);
        role2.getUsers().add(user2);
        role3.getUsers().add(user2);

        /*s.save(user1);
        s.save(user2);*/
        s.save(role1);
        s.save(role2);
        s.save(role3);

        tx.commit();
    }
}

6.1.4.2 级联删除

【删除用户级联角色】

主控方是用户,所以需要在用户端配置

<set name="role" table="sys_user_role" 
    
    
     
     cascade
    
    ="delete">
...
</set>

测试代码

@Test
    // 级联删除:删除用户 级联删除角色
    public void demo12() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        User user = s.get(User.class, 1L);
        s.delete(user);
        
        tx.commit();
    }

这个时候我们发现用户被删除了,同时用户所关联的角色也一起被删除了。
【删除角色级联删除用户】
主控方是角色,所以需要在角色端配置:

<set name="users" table="sys_user_role" 
    
    
     
     cascade
    
    ="delete">
 ...
</set>

测试代码

@Test
    // 级联删除:删除角色 级联删除用户
    public void demo12() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();

        Role role = s.get(Role.class, 1L);
        s.delete(role);
        
        tx.commit();
    }

这个时候我们发现角色被删除了,同时角色所关联的用户也一起被删除了。

多对多的级联我们已经介绍过了,但是级联并不是主要的操作,我们其实更多的会去使用多对多的一些其他的操作,比如给某个用户选择一个新的角色,或者为某个用户改选己经选好的新角色,或者为某个用户移除某个角色。这些操作是我们以后开发中经常使用的。所以我们来讲解下如何完成类似的相关的操作。

6.1.4.3 多对多的其他操作

其实多对多的关系主要是靠中间表来维护的,那么在Hibernate中多对多主要是靠关联的集合来维护的,所以我们只需要关心如何操作集合即可。那么我们来看下面的几个示例:
【删除某个用户的角色】

©Test
//将2号用户中的1号角色去掉.
public void demo12(){
    Session session = HibernateUtils.getCurrentSession();
    Transaction tx = session.beginTransaction();
    
    // 査询2号用户:
    User user = session.get(User.class, 21);
    //査询1号角色
    Role role = session.get(Role.class, 11);
    
    //操作集合相当于操作中间表.
    user.getRoles().remove(role);
    tx.commit();
}

【将某个用户的角色改选】

//将2号用户中的1号角色去掉改为3号角色
@Test
public void demo13(){
    Session session = HibernateUtils.getCurrentSession()
    Transaction tx = session.beginTransaction();
    // 查询2号用户:
    User user = session.get(User•class, 2L);
    // 査询1号角色
    Role rolel = session.get(Role.class, 1L);
    // 査询3号角色
    Role role3 = session.get(Role.class, 3L);
    
    user.getRoles().remove(role1);
    user.getRoles().add(role3);
    
    tx.commit();
}

【给某个用户添加新角色】

@Test
//给1号用户添加2号角色
public void demo14(){
    Session session = HibernateUtils.getCurrentSession()
    Transaction tx = session.beginTransaction();
    // 查询1号用户:
    User user = session.get(User.class, 1L);
    // 査询2号角色:
    Role role = session.get(Role.class, 2L);
    
    user.getRoles().add(role);
    tx.commit();
}

以上的这些操作是我们以后在多对多的关系中经常使用的操作,所以需要大家记住。

7 Hibernate的查询方式

虽然之前已经介绍过一部分,这里我们来做个总结。在hibernate中提供了 5种查询方式。

7.1 OID查询

OID检索:Hibernate根据对象的OID(主键)进行检索

7.2.1 使用get方法
Customer customer = session.get(Customer.class, lL);
7.2.2 使用load方法
Customer customer = session.load(Customer.class, lL);
7.3 对象导航检索

Hibernate根据一个已经查询到的对象,获得其关联的一种查询方式.

LinkMan linkMan = session.get(LinkMan.class, 1L);
Customer customer = linkMan.getCustomer();

Customer customer = session.get(Customer.class, 2L);
Set<
   
   
    
    LinkMan
   
   > linkMans = customer.getLinkMans();
7.4 HQL检索

HQL查询:Hibernate Query Language,Hibernate的查询语言,是一种面向对象的方式的查询语言,语法类似SQL。通过session.createQuery(),用于接收一个HQL进行查询方式。

7.4.1 初始化数据
 /**
     * 初始化一些数据
     */
    @Test
    public void demo1() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 创建一个客户
        CstCustomer customer = new CstCustomer();
        customer.setCust_name("叶总");

        for (int i = 0; i < 10; i++) {
            CstLinkman linkman = new CstLinkman();
            linkman.setLkm_name("周文艺" + i);
            linkman.setCustomer(customer);

            customer.getLinkMans().add(linkman);
            s.save(linkman);
        }
        s.save(customer);
        tx.commit();
    }
7.4.2 HQL的简单查询
@Test
    public void demo2() {
        Session s = HibernateUtil.getCurrentSession();
        Transaction tx = s.beginTransaction();
        // 简单查询 注意:HQL不支持 *查询方式,select * from CstCustomer 会报错
        Query query = s.createQuery("from CstCustomer");
        List<CstCustomer> list = query.list();

        for (CstCustomer cstCustomer : list) {
            System.out.println(cstCustomer);
        }
        tx.commit();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值