Hibernate

Hibernate学习

hibernate集成

第一步:必须把官网下载的hibernate包解压,lib/required目录下是必须要集成的,还有需要集成jpa的包

第二步:在WEB-INF/classes目录下创建hibernate.cfg.xml,可以在project/etc 目录下找到示例。

第三步:创建javabean 在实体类的同一个目录下 ,创建 类名.hbm.xml,该文件用来配置实体和数据库字段的映射的。

示例:

JavaBean Customer

package com.wgp.domain;

public class Customer {
    private int id;
    private String name;
    private int age;
    private String city;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", city='" + city + '\'' +
                '}';
    }
}

在实体类的同目录下创建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>
    <!--将Customer类与customer表 映射起来-->
    <!--
        class 完整类名
        table 表名
        catalog 数据库名,不指定 使用jdbc 的url中的数据库

    -->

    <class name="com.wgp.domain.Customer"  >
        <!--配置主键关联属性-->
        <!--
            name 属性
            column 表中列名
            type 类型
        -->
        <id name="id"  type="int">
            <generator class="native"/>
        </id>

        <property name="name"  type="java.lang.String"></property>

        <property name="city"  type="string"/>

        <property name="age" type="int"/>

        <!--  column 和 type 可以不写,默认就用customer类的字段本身的类型-->
    </class>


</hibernate-mapping>

创建hibernate的配置文件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.cj.jdbc.Driver</property>

        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernateday1</property>

        <property name="connection.username">root</property>

        <property name="connection.password">root</property>

        <!--配置方言-->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!--常见其他配置-->
        <!-- 控制台上打印sql-->
        <property name="hibernate.show_sql">true</property>
        <!-- 控制台输出时,对sql语句格式化-->
        <property name="hibernate.format_sql">true</property>
        <!--自动建表-->
        <property name="hibernate.hbm2ddl.auto">update</property>


        <!-- 在核心配置文件中引用 mapping 映射文件-->
        <mapping resource="com/wgp/domain/Customer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

配置完成后,就是用hibernate进行正删改查操作了

模板代码:

Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();

/**
*处理具体业务,正删改查
*/


//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();

插入数据示例:

@Test
public void insert(){//模板代码
    //实例化配置对象,加载配置文件hibernate.cfg.xml
    Configuration configure = new Configuration().configure();
    //创建会话工厂
    SessionFactory sessionFactory = configure.buildSessionFactory();
    //创建会话
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();

    Customer customer = new Customer();
    customer.setName("小明");
    customer.setAge(10);
    customer.setCity("北京");

    session.save(customer);

    //提交事务、释放资源
    transaction.commit();
    session.close();
    sessionFactory.close();

}

查找数据:

//查询 用get和load方法 两个方法有什么区别 后面再讲
@Test
public void find(){
    Configuration configure = new Configuration().configure();
    //创建会话工厂
    SessionFactory sessionFactory = configure.buildSessionFactory();
    //创建会话
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();

    Customer customer = session.get(Customer.class, 1);
    System.out.println(customer);

    //提交事务、释放资源
    transaction.commit();
    session.close();
    sessionFactory.close();

}

更新示例:

@Test
public void update(){
    Configuration configure = new Configuration().configure();
    //创建会话工厂
    SessionFactory sessionFactory = configure.buildSessionFactory();
    //创建会话
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();

    /**
     *
     * hibernate是全字段的修改,也就是你要修改一个字段的数据,
     * hibernate会吧其他的字段也一起修改掉,所以在做修改的时候要小心
     * 把所有其他的字段的数据也要带上,要不其他字段的数据就成null了
     */

    //要先查询 后修改
    Customer customer = session.get(Customer.class, 1);
    customer.setCity("上海");
    session.update(customer);

    //提交事务、释放资源
    transaction.commit();
    session.close();
    sessionFactory.close();
}

删除示例:

@Test
public void dalete(){
    Configuration configure = new Configuration().configure();
    //创建会话工厂
    SessionFactory sessionFactory = configure.buildSessionFactory();
    //创建会话
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();

    Customer customer = new Customer();
    customer.setId(1);
    session.delete(customer);


    //提交事务、释放资源
    transaction.commit();
    session.close();
    sessionFactory.close();

}

Hibernate 核心API

hibernate体系结构

  • hibernate的持久化方案,将用户从原始的jdbc底层sql访问中解救出来。
  • 用户无须关注底层数据操作,只要通过操作映射到数据表的java对象,就可以对数据库进行增删改查。
  • hibernate框架支持两种hibernate属性配置方式:hibernate.properties和hibernate.cfg.xml。
  • 采用properties方式,必须编程加载hbm文件或者持久化类。
  • 采用xml配置方式,可以配置hbm文件

hibernate常见属性:

hibernate.connection.driver_class:加载数据库驱动
hibernate.connection.url:连接数据库的url
connection.username:连接数据库的用户名
connection.password:连接数据库的密码
hibernate.dialect:配置方言(方言就是不同的数据库sql不完全一样)
hibernate.show_sql:控制台输出sql语句
hibernate.format_sql:格式化输出的sql语句
hibernate.hbm2ddl.auto:DDL策略
    策略1:create:表示启动的时候先drop,再create,也就是先删除表再创建表
    策略2:create-drop:也表示创建,只不过再细谈关闭前执行一下drop
    策略3: update:这个操作启动的时候会去检查schema是否一致,如果不一致会做schema更新。说白了就是hibernate在执行增删改查的时候发现表Customer.hb.xml的配置实体对应的列发生变化了,数据库中如果已经有表那么就会添加新字段在表中,如果没有表就会创建表。
    策略4:validate:启动时验证现有schema与你配置的hibernate是否一致,如果不一致就抛出异常,并不做更新。这个说白了就和策略3不太一样,这个如果有变化,hibernate直接抛异常。hibernate并不会帮你创建列

策略怎么使用呢?一般情况 测试环境用create/create-drop  正式环境 update validate

hibernate.connection.autocommit: 事务是否自动提交。如果没设置,采用数据库默认事务是否自动提交,mysql数据库默认提交,Oracle默认回滚。



Hibernate体系结构详解

核心api:

  1. Configuration :配置文件的加载

  2. SessionFactory : 连接池工厂

  3. Session :会话 相当于Connection

  4. Transaction : 事务

  5. Query : 查询对象

  6. Criteria : 高级查询对象

Configuration类

  • Configuration类负责管理Hibernate的配置信息,包括加载hibernate.properties和hibernate.cfg.xml,持久化类和数据表的映射关系(*.hbm.xml文件)
  • 创建Configuration的两种方式,方式一:属性文件hibernate.properties Configuration cfg = new Configuration(),手动加载hbm;方式二:xml文件hibernate.cfg.xml Configuration cfg = new Configuration().configure();

采用手动方式加载hbm或者持久化类

  • 通过Configuration对象 addResource方法添加hbm文件映射,

    示例代码:new Configuration().addResource("com/wgp/domain/Customer.hb.xml");

  • 另一种方式,通过addClass添加持久化类,hibernate会在类所在包,自动搜索hbm映射文件

    示例代码:new Configuration().addClass(Customer.class);

现在我们不是我们常用的方式,因为我们之间在配置文件中配置了映射文件。学了spring 配置能省了,spring可以自动扫描。

SessionFactory接口

Configuration对象根据当前的配置信息生成SessionFactory对象,SessionFactory对象中保存了当前的数据库信息和所有映射关系以及预定义的sql语句。

SessionFactory对象是线程安全的。

SessionFactory还负责维护Hibernate的二级缓存:

Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();

可以通过SessionFactory对象获取Session对象。

构造SessionFactory很销毁资源,一般情况下一个应用只初始化一个。

Session接口

相当于jdbc的Connection

Session是应用程序和数据库之间交互操作的一个单线程对象,是Hibernate运作的中心。

Session是线程不安全的。

所有持久化对象必须在Session的管理下才可以进行持久化操作。

Session对象有一个一级缓存,显示执行flush之前,所有的持久层操作的数据库都缓存在Session对象处。

持久化类与Session关联起来后就具有了持久化的能力。

常用方法:

save()/persist()、update()、saveOrUpdate()增加和修改对象。

delete()删除对象。

get()/load()根据主键查询。

createQuery()/createSQLQuery()数据库操作对象。

createCriteria()条件查询。

Session代表连接,线程不安全,那么怎么才能安全的使用Session呢?那就是在方法内使用Session,没有线程安全问题,为什么呢 ,是因为方法内的所有引用,都是位于栈空间,栈空间是线程私有的。

Transaction接口

代表数据库操作的事务对象。

Transaction transaction = session.beginTransaction();

提供事务管理的方法:

commit() 提交相关联的Session实例;

rollback(): 撤销事务操作;

wasCommitted(): 检查事务是否提交。

如果没有开启事务,那么每个Session的操作,都相当于一个独立的事务。

Query接口

  • Query代表面向对象的一个Hibernate查询操作。
  • Session.createQuery 接收一个hql语句。
  • hql是Hibernate query language缩写,语法很想sql语法,但是完全面向对象的。
  • 使用query对象步骤
    1. 获取Hibernate Session对象
    2. 编写hql语句
    3. 调用Session.createquery 创建查询对象
    4. 如果hql语句包含参数,则调用query的setXXX设置参数
    5. 调用query对象的list() 或uniqueResult()方法执行查询
  • Query还包含两个方法,用于控制返回结果
    1. setFirstResult( int firstResult) 设置返回结果从第几条开始
    2. setMaxResult(int maxResult) 设置本次返回结果记录条数

示例代码:查询所有记录

@Test
public void query(){
    Session session = HibernateUtils.getSession();
    Transaction transaction = session.beginTransaction();

    //编写hql  Customer是类
    String hql = "from Customer";
    //获得query对象
    Query query = session.createQuery(sql);

    //查询结果list()返回结果,uniqueResult 返回单一结果
    List list = query.list();

    System.out.println(list);


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

带条件查询:

@Test
public void query2(){
    Session session = HibernateUtils.getSession();
    Transaction transaction = session.beginTransaction();

    //Customer是类名
    String hql = "from Customer";
    Query query = session.createQuery(hql);

    //代表从第21条开始
    query.setFirstResult(20);
    //查询10条
    query.setMaxResults(10);


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

查询某一个列是数据:

@Test
public void query3(){
    Session session = HibernateUtils.getSession();
    Transaction transaction = session.beginTransaction();
    
    //Customer是类名  name是Customer的属性
    String hql = "select name from Customer";
    //查询所有名字
    Query query = session.createQuery(hql);

    List list = query.list();
    System.out.println(list);


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

条件添加使用占位符(:name),也可以使用匿名占位符(?)

匿名占位符 from Customer where name = ? 设值 query.setParameter(0,值);

有名称占位符 from Customer where name = :a 设值 query.setParameter("a",值);

Criteria接口 高级条件查询接口

当查询非常复杂,具有多个条件,使用Criteria,可以以面向对象方式,添加条件查询。

  1. 通过session对象获得Criteria对象
  2. 使用Restrictions的静态方法创建Criterion条件对象
  3. 向Criteria对象中添加Criterion查询条件
  4. 执行Criteria的list() 或uniqueResult()获得结果

示例代码:

@Test
public void criteria(){
    Session session = HibernateUtils.getSession();
    Transaction transaction = session.beginTransaction();
    
    //session.createCriteria()//这个方法已经过时
    

    CriteriaQuery<Customer> query = session.getCriteriaBuilder().createQuery(Customer.class);
    query.from(Customer.class);

    List<Customer> resultList = session.createQuery(query).getResultList();
    for (int i = 0; i < resultList.size(); i++) {
        System.out.println(resultList.get(i).getName());
    }


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

Hibernate持久化配置和操作

  • Hibernate采用普通、传统的java对象(pojo),作为持久化类,与数据表进行映射。

  • 编写规则

    1. 提供一个无参public访问控制符的构造器
    2. 提供一个标识属性,映射数据表主键字段
    3. 所有属性提供public访问控制符的setter 和getter方法
    4. 标识属性应尽量使用基本数据类型的包装类型
    5. 不要用final修饰(将无法生成代理对象进行优化)
  • 理解Session的get方法和load方法区别

    get方法是立即执行查询,获取数据;

    load 返回的是代理对象,并没有立即执行查询;当我们使用到数据的时候才会去执行操作数据库。

持久化的唯一标识OID

  • java按地址区分同一个类的不同对像。
  • 关系数据库用主键区分同一条记录
  • Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系
  • 对象的OID和数据库的表的主键对应,为保证OID的唯一性,应该让Hibernate来为OID赋值

区分自然主键和代理主键

在关系数据库表中,用主键来识别记录并保证每条记录的唯一性,作为主键的字段必须满足以下条件:

  1. 不允许为null
  2. 每条记录具有唯一的主键值,不允许主键值重复
  3. 每条记录的主键值永远不会改变

在Customer表中,如果把name字段作为主键,前提条件是:

  1. 每条记录的客户姓名不允许为null
  2. 不允许客户重名
  3. 不允许修改客户姓名

name字段具有业务含义的字段,把这种字段作为主键,称为自然主键。尽管也是可行的,但是不能满足不断变化的业务需求,一点出现了允许重名的业务需求,就必须修改数据模型,重新定义表的主键,这给数据库的维护增加了难度。

因此,更合理的方式是使用代理主键,即不具备业务含义的字段,该字段一般取名为“ID”。代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。那么代理主键的值从何而来呢?许多数据库系统提供了自动生成代理主键值的机制。

自然主键:采用数据库中有意义的列的值作为主键。

代理主键:杜宇主键列,采用自动生成、流水号、uuid,主键列无意义。

现在企业开发中,更常用的是代理主键。

是用基本类型?还是包装类型?

基本属性类型和包装类型对应Hibernate的映射类型相同。

基本类型可以直接运算、无法表达null、数字类型的默认值为0

包装类型默认值是null。当对于默认值有业务意义的时候需要使用包装类。

主键生成策略

在配置JavaBean的映射文件时,我们需要配置主键的生成策略,

<id name="id"  type="int">
    <!--主键生成策略-->
    <generator class="native"/>
</id>

主键生成策略常用的有6种:

  1. increment
  2. identity
  3. sequence
  4. native
  5. uuid
  6. assigned

increment 策略:

  • increment标识符生成器有Hibernate以递增的方式为代理主键赋值。
  • Hibernate会先读取news表中的主键的最大值,而接下来想news表中插入记录时,就在max(id)的基础上递增,增量为1。
  • 适用范围:
    1. 由于increment生成标识符机制不依赖于底层数据库系统,因此他适合所有的数据库系统。
    2. 适用于只有单个Hibernate应用进程访问同一个数据库的场合。
    3. OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
    4. 有线程并发问题。

identity策略:

由底层数据库来完成自增,要求数据库必须支持自增主键,mysql支持,Oracle不支持。线程安全。

OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。

sequence策略:

由底层数据库提供序列,来完成主键自增,要求数据库必须支持序列,mysql不支持,Oracle支持。

sequence标识符生成器利用底层数据库提供的序列来生成标识符。

<id name="id">
    <generator class="sequence">
        <!--Oracle数据库中序列的名称 news_seq -->
        <param name="sequence">news_seq</param>
    </generator>

</id>

Hibernate在持久化一个news对象时,先从底层数据库的news_seq序列中获得一个唯一的标识号,再把它作为主键值。

OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。

适用范围:

依赖底层数据库必须支持序列,支持序列的数据库包括DB2 、Oracle等。

OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。

native策略:

native标识符生成器依据底层数据库对自动生成标识符的支持能力,来选择使用identity、sequence或hilo标识符生成器。

适用范围:

由于native能根据底层数控系统的类型,自动选择合适的标识符生成器,因此很适合跨数据平台开发。

OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。

increment、identity、sequence和native策略要求数据库主键必须为数字,因为只有数字才能自增。

uuid策略:

32位,唯一字符串,主键使用varchar类型。

assigned策略:

映射单个自然主键。

假如Customer表没有定义id代理主键,而是以name字段作为主键,那么相应地,在Customer类中不必定义id属性,Customer类的OID为name属性,它的映射代码如下:

<id name="name" column="name" type="string" >
    <generator class="assigned"/>
</id>

映射复合主键:

复合主键,是一种特殊assigned类型的自然主键(通常需要手动指定),联合主键实体类必须实现序列化接口

<hibernate-mapping>
    <!--将Customer类与customer表 映射起来-->
    <class name="com.wgp.domain.Customer"  >
     
        <!--复合主键:配置,就是这个Customer表中有多个主键字段,让我想到了多多的中间表,用的是主键就是联合主键,两个字段作为中间表的主键。-->
       <composite-id>
            <key-property name="firstname" column="firstname" type="string"/>
            <key-property name="lastname" column="lastname" type="string"/>
        </composite-id>

        <property name="name"  type="java.lang.String"></property>

        <property name="city"  type="string"/>

        <property name="age" type="int"/>

    </class>

</hibernate-mapping>

持久化类的属性及访问方法

Hibernate访问持久化类属性的策略:

  1. property默认值:表明Hibernate通过getter和setter来访问类的属性。推荐使用,提供域模型透明性。private也

  2. field:Hibernate通过java反射机制直接访问类属性。对于没有javabean方法的属性可设置该方法策略。

  3. noop(了解):它映射java持久化类中不存在的属性,即主要用于hql(query接口测试,使用hql语句)中,当数据库中有某列,而实体中不存在的情况。

    <!--该属性在Customer类中不存在get和set方法-->
    <property name="name" column="name" type="string"  access="field"/>
    
    <!--该属性在Customer类中不存在,但在数据库存在该字段,使用noop处理,查询的时候忽略该字段-->
    <property name="name" column="name" type="string"  access="noop"/>
    

设置派生属性及访问方法

实体类中有的属性,数据库没有该字段对应的列情况。

利用<property > 元素的formula属性,用来设置一个sql表达式,Hibernate将根据它来计算出派生属性的值。

例如:

在Customer类中增加两个属性 private Double price; private Double totalprice; //totalprice在数据库中没有对应的列。

在Customer.hbm.xml文件中增加如下配置:

<property name="price" column="price" type="double"  />

<!--在sql语句中使用别名,派生属性中使用的是sql语句-->
<property name="totalprice" formula="(select sum(o.price) from customer o)" />

控制insert、update语句

设置列是否参与插入和更新

映射属性 作用
<property> insert 属性 若为false,在insert语句中不包含该字段,该字段永远不能被插入。默认为true
<property> update 属性 若为false,update语句不包含该字段,字段永远不能被更新。默认为true
<class> mutable 属性 若为false,等价于所有的<property> 元素的update为false,整个示例不能被更新。默认为true
<class>dynamic-insert属性 若为true,等价于所有的<property>元素的insert为true,保存对象时,动态生成insert语句,语句仅包含取值部位null的字段。默认false
<class>dynamic-update属性 若为true,等价于所有的<property>元素的update为true,更新一个对象时,动态生成update语句,语句中仅包含属性值发生变化的字段。默认false

插入特殊列名

<key-property name="secondname" column="second name"/>含有空格特殊字符的情况,可以用 ` second name` 两个特殊是点,在键盘的左上角,扩起来这个字符串就可以插入到数据库了。

Hibernate持久化对象状态

Hibernate持久化对象存在三种状态

  • 瞬时态 transient 尚未与Hibernate Session关联对象,被认为处于瞬时状态,失去引用将被jvm回收。无持久化表示OID,未与Session关联。
  • 持久态 persistent 数据库中有数据与之对应并与当前Session有关联,并且相关联的Session没有关闭数据库并且事务未提交。存在持久化标识OID,与Session关联。
  • 托管态 detached 数据库中有数据与之对应,但当前没有Session与之关联,托管状态改变Hibernate不能检测到。存在持久化标识OID,未与Session关联。

持久化状态转换图:

持久化对象状态转换.png

Session缓存(一级缓存)

Hibernate中分两级缓存,一级缓存是Session、二级缓存是SessionFactory缓存。

  • 在Session接口的实现中包含一系列的java集合,这些java集合构成了Session缓存,只要Session实例没有结束生命周期,存放在它缓存中的需也不会结束生命周期。
  • 当Session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图get()、load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库。
  • Session能够在某些时间点,按照缓存中对象的变化来执行相关的sql语句,来同步更新数据库,这个过程被称为刷出缓存(flush)。
  • 默认情况下Session在一些时间点刷出缓存:
    1. 当应用程序调用Transaction的commit()方法的时,该方法先刷出缓存(session.flush()),然后在向数据库提交事务。
    2. 当应用程序执行一些操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。
    3. 调用session的flush()方法。

对象在什么时候放入一级缓存?

当对象称为持久态对象,就会被放入一级缓存 save、update、get、load、query、criteria。

清理Session的缓存

  • Session的flush方法让缓存的数据刷出到数据库。
  • Session的clear方法清空缓存数据。
  • Session的evict方法清空指定对象一级缓存数据,使对象变为脱离状态。

Session加载对象后,会为对象的值类型的属性复制一份快照,当刷出缓存时,通过比较对象的当前属性和快照,来判断对象的那些属性发生了变化。从而产生更新语句。

什么是快照? 向一级缓存存放数据时,在快照区存放一个备份数据。

refresh刷新一级缓存

当Session.load加载对象后,修改属性,调用refresh方法更新一级缓存,此时设置的属性值重新被数据表中激励覆盖。

也就是说,refresh后会重新加载数据库中数据,覆盖一级缓存的数据。之前的修改就无效了。

一级缓存的FlushMode

清理Session的缓存(设置缓存的flush模式),Session.setFlushMode(FlushMode.AUTO)是默认模式;

FlushMode.AUTO:默认设置,Session查询方法会刷出,事务提交会刷出,Session的flush方法会刷出。

FlushMode.COMMIT:Session查询方法不刷出,事务提交会刷出,Session执行flush方法会刷出。

FlushMode.ALWAYS:Session查询方法刷出,事务提交会刷出,Session执行flush方法会刷出。

FlushMode.MANYAL:Session查询方法不刷出,事务提交不会刷出,Session执行flush方法会刷出。

ALWAYS和AUTO的区别:当Hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当Session设置为FlushMode.AUTO时,Hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷出数据库,不是则不刷,而ALWAYS是直接刷新,不进行任何判断。很显然auto比ALWAYS要高效的多。

操作持久化对象的方法

操作持久化对象save()

  • Session的save()方法使一个瞬时对象转变为持久化对象.

  • Session的save方法完成以下操作:

    1. 把瞬时对象加入到Session缓存中,使他进入持久化状态。
    2. 选用映射文件指定的标识符生成器,为持久化对象分配唯一的OID,在使用代理主键的情况下,setId()方法为瞬时对象设置OID是无效的。
    3. 计划执行一条insert语句,把对象当前的属性值组装到insert语句中。
  • Hibernate通过持久化对象的OID来位置它和数据库相关记录的对应关系。当对象处于持久化状态时,不允许程序随意修改它的id。

  • Session的update方法使一个托管对象转变为持久化对象,并且计划执行一条update语句。如果程序对象,已经是持久化对象,不需要手动update;如果程序中出现两个不同对象,都是持久化对象具有相同OID会报错。

  • 若希望Session仅修改了对象的属性时,才执行update()语句,可以把映射文件中的<class> 元素的select-before-update(更新之前先查询)设置为true,该属性默认值为false。

  • 当update()方法关联一个脱管对象时,如果在数据库中不存在相应的记录,也会抛出异常。

saveOrUpdate()方法:

该方法同上包含save和update方法,如果参数是瞬时对象就用save方法,如果是脱管对象就用update方法,如果持久化对象就直接返回。

判断对象为临时对象的标准:

java对象的OID为null。

映射文件中<id>设置了unsaved-value属性,并且java对象的OID取值与这个unsaved-value属性值匹配。

操作持久化对象get、load方法:

都是可以根据给定的OID从数据库中加载一个持久化对象。

区别是:当数据库中不存在与OID对应的记录时,load()方法抛出ObjectNotFoundException异常,而get()方法返回null。

两者采用不同的延迟检索策略。

操作持久化对象delete方法:

Session的delete()方法即可以删除一个脱管对象,也可以删除一个持久化对象。

如果参数是持久化对象,就执行一个delete语句,若为脱管对象,先使对象被Session关联,使他变为持久化对象,执行delete语句,把对象从Session缓存中删除,该对象进入脱管状态,刷新缓存后,该对象将被从数据库中删除。

数据库实体表之间关系映射

  • 数据库采用外键来描述数据表之间的关系
  • 一对多:在多的一方添加一的一方的外键
  • 一对一:在任意一方添加对方的主键作为外键
  • 多对多:必须创建第三张关系表,分别引入双方的主键作为外键

Hibernate关联关系映射

Hibernate是采用java对象关系来描述数据表实体之间的关系。

一对一示例:

class A{
    B b;
}

class B{
    A a;
}

一对多:

class A{
    Set<B> bs;//B的集合
}

class B{
    A a;
}

多对多:

class A{
    Set<B> bs;//B的集合
}
class B{
    Set<A> as;//A的集合
}

一对多 关联关系映射

客户和订单的示例,可以两边都配置也可以配置一边

package com.wgp.domain;

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

public class Customer {

    private Integer id;
    private String name;
    private Integer age;

    //一个客户关联多个订单
    private Set<Order> orders = new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Set<Order> getOrders() {
        return orders;
    }

    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
}

Customer.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="com.wgp.domain.Customer" table="customer">
        <id name="id">
            <generator class="native"/>
        </id>

        <property name="name"></property>
        <property name="age"></property>

        <!--配置一对多映射关联集合,在Hibernate中不同的集合,配置方式不同-->
        <set name="orders">
            <!--配置外键,外键列名在多方生成,名字和多的一方保持一致-->
            <key column="customer_id"></key>

            <one-to-many class="com.wgp.domain.Order"/>
        </set>
    </class>

</hibernate-mapping>

package com.wgp.domain;
public class Order {

    private Integer id;
    private String address;

    private Double totalprice;

    //关联一个客户
    private Customer customer;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Double getTotalprice() {
        return totalprice;
    }

    public void setTotalprice(Double totalprice) {
        this.totalprice = totalprice;
    }

    public Customer getCustomer() {
        return customer;
    }

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

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>
    <class name="com.wgp.domain.Order" table="orders">
        <id name="id">
            <generator class="native"/>
        </id>

        <property name="address"/>
        <property name="totalprice"/>


        <!--在多对一关联中,在多的一方配置添加外键列-->
        <!--
            name 设定待映射的持久化类的名字
            class 设定持久化类的属性的类型
            column 设定和持久化类的属性对应的表的外键
            not-null 是否允许为空
        -->
        <many-to-one name="customer" class="com.wgp.domain.Customer" column="customer_id"/>

    </class>

</hibernate-mapping>


一对多操作 ,级联保存

常见操作:增加save-update、修改save-update、删除delete

持久化对象 级联 瞬时对象,将瞬时对象变为持久化对象。级联需要配置标签属性cascade="";

Customer级联Order 也就是说保存customer对象时,顺便把订单信息也保存起来,不用显示的调用session.save(order)来保存订单信息。级联要在Customer.hbm.xml配置 <set name="orders" cascade="save-update">;

Order 级联Customer的话,就修改Order.hbm.xml <many-to-one name="customer" class="com.wgp.domain.Customer" column="customer_id" cascade="save-update"/>

一对多操作,级联删除

采用级联删除操作 cascade="delete"

当没有设置级联删除时:

删除客户时会删除订单吗?删除订单时会删除客户吗?

举例因为订单表中的外键是客户表的id,所以我们删除的时候有几种情况。

删除脱离对象Customer,会自动先解除关联关系关系,再删除。

//删除脱离对象,没有配置级联删除,删除操作是成功的,但是会先解除orders表中的外键关系(如果外键列为非空,无法删除,会报错),然后再删除客户
Customer c = new Customer();
c.setId(1);
session.delete(c);

//持久的 效果和上面是一样的
Customer c2  = session.get(Customer.class,1)
 session.delete(c2);

配置级联删除

删除Customer 同时删除Order 修改Customer.hbm.xml

<set name="orders" cascade="save-update,delete">
    <!--配置外键,外键列名在多方生成,名字和多的一方保持一致-->
    <key column="customer_id"></key>

    <one-to-many class="com.wgp.domain.Order" />
</set>

删除Order 同时删除Customer 修改Order.hbm.xml

<many-to-one name="customer" class="com.wgp.domain.Customer" column="customer_id" cascade="save-update,delete"/>

只有配置了级联的才能联动执行操作,如果没有配置 就不会。

删除还有一个情况:(孤儿删除也叫孤子模式删除)配置delete-orphan级联

从查询的客户对象中移除订单对象,订单对象是否删除?

客户和订单,先有客户,后有订单,订单依赖于客户-----存在父子依赖关系。

当客户解除了一个订单的关系,订单没有下单客户,不完整,通过配置delete-orphan级联模式,完成对孤儿订单的删除。

在Customer.hbm.xml中<set name="orders" cascade="delete-orphan">,孤子删除就是用在一对多的情况下,配置的一的一方。

cascade取值非常多,值来源于JPA和Hibernate

JPA:persist、merge、lock、replicate、evict、refresh

Hibernate是JPA的实现,宽展了save-update、delete、delete-orphan(上面的不用要用了)。

cascade=“all” 包含了除去delete-orphan的所有级联,cascade=“all-delete-orphan”包含所有级联关系。

inverse

//变更20号订单的客户为9号
Customer c2 = session.get(Customer.class,9);//查询9号客户
Order  order = session.get(Order.class,20);//查询20号订单
c2.getOrders().add(order);
order.setCustomer(c2);

上面的这段代码会产生2条update语句,为什么呢?因为session的一级缓存的原因,之前我们说过,当执行查询的时候,会把结果防止session的缓存中,并把结果创建一份快照。当我们调用c2.getOrders().add(order);和order.setCustomer(c2);分别修改了持久化对象也就是一级缓存中的数据,那么一级缓存中的信息会自动和快照区的信息比对,如果不一样了,那么Hibernate就会执行update操作。

向上面的代码,只需要修改订单归属那个客户就行了,结果会产生两条update语句,显然不是最优的解决,Hibernate给我们提供了一个标签inverse=false 默认的。这个标签就是设置为true 表示由谁来维持数据表之间的关系。

通过inverse属性来设置由双方关联的哪一方来维护表和表之间的关系。inverse=false的为主动方,inverse=true的为被动方,由主动方负责维护关联关系。像客户和订单 我们在one的一方也就是客户的一方设置inverse=true ,这样就是客户方放弃了订单数据的关系表外键的维护。

面试题:inverse和cascade的区别?

cascade是级联,cascade="save-update" 保存A时会级联保存B。

inverse设置为true,表示放弃外键设置的权利,无法设置外键列的值。谁设置了就是谁放弃了。

一对多关联中的父子关系

理解什么是父子关系。

所谓父子关系:是指父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联。

一对一 关联关系映射

  • 一对一关联指两个表之间的记录是一一对应的关系。分为两种:外键关联和主键关联。
  • 比如一家公司Company和它所在地址Address。在业务逻辑中要求一家公司只能有唯一的地址,一个地址也只有一家公司。

映射一对一外键双向关联

  • 对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端,添加many-to-one元素。为many-to-one元素,添加unique="true"属性来表示1-1关联,并用name属性来指定关联属性的属性名。

    <many-to-one name="address"  class="com.wgp.domain.Address"  column="aid"  unique="true"/>
    
  • 另一端需要使用one-to-one元素,该元素使用property-ref属性指定使用被关联实体主键以外的字段作为关联字段

    <!--
    property-ref 引用Company类中 用于生成外键的属性名称
    -->
    
    <one-to-one name="company" class="com.wgp.domain" property-ref="address"/>
    

映射一对一主键双向关联

一对一的另一种解决方式就是主键关联,在这种关联关系中,要求两个对象的主键必须保持一致,通过两个表的主键建立关联关系,无须外键参与。

  • 基于主键的映射策略:指一端的主键生成器使用foreign策略,表明根据"对方"的主键来生成自己的主键,自己并不能独立生成主键。<param> 子元素指定使用当前持久化类的那个熟悉作为"对方"

    <generator class="foreign">
      <param name="property">company</param>
    </generactor>
    
  • 采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one属性还应增加constrained="true"属性;另一端(Company)增加one-to-one元素映射关联属性。

  • constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象("对方")所对应的数据库表主键。

    <!--在address这边配置-->
    <one-to-one name="company" class="com.wgp.domain.Company" constrained="true">
    </one-to-one>
    

多对多关联关系映射

多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。也就是建立中间表。

映射多对多双向关联关系1

  • 双向n-n关联需要两端都使用集合属性

  • 双向n-n关联必须使用中间表

  • 集合属性应增加key子元素用以映射外键列,集合元素里还应增加many-to-many子元素关联实体类

  • 在双向n-n关联的两边都需要指定连接表的表名及外键列的列名,两个自己和元素set的table元素的值必须指定,而且必须相同。

    <!--学生端的配置信息-->
    
    <set name="courses" table="student_course" inverse="true">
    
      <key column="sid"></key>
      <many-to-many class="com.wgp.domain.Courses" column="cid"/>
    </set>
    
    <!--课程端的配置信息-->
    <!--
      name:是Courses类中 关联student对应属性集合的名称
      table:中间表的名字
    -->
    
    <set name="stus" table="student_course" >
      <!--当前Courses对象,在中间表生成外键列列名cid-->
      <key column="cid"></key>
       
      <many-to-many column="sid" class="com.wgp.domain.Student" />
    </set>
    

映射多对多双向关联关系2

  • set元素的两个子元素:key和many-to-many都必须制定column属性。
  • 其中,key和many-to-many分别指定本持久化类和关联类在连接表中的外键名。
  • 因此两个的key与many-to-many的column属性交叉相同。也就是说,一边的set元素的key的column值为a,many-to-many的column为b;则另一边的set元素的key的column值为b,many-to-many的column为a;
  • 注意:对应双向的n-n关联,需把其中的一端的inverse设置为true,否则可能会造成主键冲突。

多对多没有父子关系,实际开发中不建议级联。

当两个持久化对象,建立关系,在关系表插入一条数据(防止重复数据)1,设置inverse=true使一端放弃外键维护;2.不要建立双向关系。

持久化对象学生移除持久化课程,就会自动产生一条delete语句。

假如我们删除学生,那么在多对多关系中Hibernate会自动帮我们删除中间表该学生的信息。

Hibernate 映射组成关系

映射组成关系

什么叫映射组成关系呢?

当我们一张数据表的数据比较的多和复杂的时候,我们会对该表对应的实体类进行分解,可以用多个实体类来共同映射该数据表,那么多个实体类之间的关系就是组成关系。

比如,客户表中有地址,那么我们可以吧地址这个信息单独提前出来一个Address类,没必要吧所有的地址相关信息都放在Customer类中。多个类映射到一张表。

示例:

package com.wgp.domain;

public class Person {
    private Integer id;
    private String name;
    private Address address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}
public class Address {
    private String city;
    private String street;
    private String zipcode;
    private String province;

    private Person person;

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }
}

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="com.wgp.domain.Person" table="person">

        <id name="id" >
            <generator class="native"/>
        </id>
        <property name="name"></property>

        <!--配置组件  使用component组件元素-->
        <component name="address" class="Address">
            <parent name="person"></parent>

            <property name="city" column="home_city"></property>
            <property name="street" column="home_street"></property>
            <property name="zipcode" column="home_zipcode"></property>
            <property name="province" column="home_province"></property>

        </component>
        
    </class>

</hibernate-mapping>

<component> 元素表明address属性是Person类的一个组成部分,在Hibernate中称之为组件。<component>元素有以下两个属性:

name:设定被映射的持久化类的属性名。

class:设定属性的类型。

<component>元素还包含一个<parent>子元素和一系列<property>子元素。<parent>元素指定Address类所属的整体类,上面例子设置为Person,与此对应,在Address类中应该定义一个person属性,以及相关的set和get方法。

组件操作:其实就是单表操作,只是把实体类拆分了而已。

继承关系映射

将数据结构中的类的继承关系,保存到数据库表中。

例如:试题(选择题、填空题等);

员工管理:分为正式员工和临时工;

Hibernate提供三种 继承映射策略:

  1. 每个具体类一张表,将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
  2. 每个类分层结构一张表对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
  3. 每个子类一张表,域模型中的每个类映射到一张表,通过关系数据模型中的外键来描述表之间的继承关系。也就是说相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。

白话描述:

1.将父类和子类数据 存放到同一张表中,在表中引入一列“辨别者列”,通过辨别者列的值,来区分该条记录是父类数据还是子类数据。

2.将公共数据放入父类表,将子类个体数据存放子类表,父子表之间通过外键关联。

3.父子类分别建表,父子表之间无关联,将父类数据存放到父类表,子类数据存放到子类表,父子表采用连续增长主键。

采用subclass元素的继承映射

subclass元素的继承映射.png
  • 采用subclass的继承映射可以实现对于继承关系中父类和子类使用同一张表。
  • 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是那个类的实例——这个列被称为辨别者列(discriminator)。
  • 在这种映射策略下,使用subclass来映射子类,使用class或subclass的discriminator-value属性指定辨别者列的值。
  • 所有子类定义的字段都不能有非空约束。如果为那些字段添加了非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中。

注意:对应继承关系映射,只需要配置父类的hbm.xml文件就行。

示例:

/**
 * 员工
 */
public class Employee {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


/**
 * 正式员工 继承 员工
 */
public class SalaryEmployee extends Employee {

        private Double salary;//月薪

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }
}


/**
 * 小时工 继承 员工
 */
public class HourEmployee extends Employee {

        private Double rate;//时薪

    public Double getRate() {
        return rate;
    }

    public void setRate(Double rate) {
        this.rate = rate;
    }
}

Employee.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <!--添加辨别者列的值-->
    <class name="com.wgp.domain.Employee" table="employee"  discriminator-value="ee">
        <id name="id">
            <generator class="native"/>
        </id>
        <!--配置辨别者列,这个必须在property标签前-->
        <discriminator column="entype" type="string"/>
        <property name="name" column="name"/>

        <!--配置子类-->
        <!--添加辨别者列的值-->
        <subclass name="com.wgp.domain.SalaryEmployee" discriminator-value="se">
            <property name="salary"></property>
        </subclass>

        <!--添加辨别者列的值-->
        <subclass name="com.wgp.domain.HourEmployee" discriminator-value="he">
            <property name="rate"></property>
        </subclass>

    </class>

</hibernate-mapping>

对应增删改查的操作,就按照单表操作就行了。继承映射查询一般用hql来查询。

采用joinde-subclass元素的继承映射

join-subclass继承映射.png

joined-subclass 这种方式是为每个父类和自类建立单独的数据表,将公共的数据存放入父类表,将个体信息存放子类表,子类表通过外键与父类表关联。

  • 采用joined-subclass元素的继承映射可以实现每个子类单独一张表
  • 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,隐藏必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
  • 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用key元素映射共有主键,该主键必须与父类标识属性的列名相同。但如果继承树的深度很深,可能查询一个子类实例时,需要跨越多个表,因为子类的数据一次保存在多个父类中。
  • 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中。

示例:

上面的员工、正式工、小时工的实体类不用变,只需要修该Employee.hbm.xml文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <!--添加辨别者列的值-->
    <class name="com.wgp.domain.Employee" table="employee"  discriminator-value="ee">
        <id name="id">
            <generator class="native"/>
        </id>
       
        <property name="name" column="name"/>

        <!--配置子类
        joined-subclass:用于映射SalaryEmployee类
        table:子类对应的表的名称
        key子元素:指定s_emp表中即作为主键又作为外键eid列
        property:table属性指定表中的列
        -->
        <joined-subclass name="com.wgp.domain.SalaryEmployee" table="s_emp" >
            <!--该列名随便起-->
            <key column="eid"/>
            <property name="salary"></property>
        </joined-subclass>

        <joined-subclass name="com.wgp.domain.HourEmployee" table="h_emp" >
            <key column="eid"/>
            <property name="rate"></property>
        </joined-subclass>

    </class>

</hibernate-mapping>


注意:对数据进行操作的时候,Hibernate会自动帮我们进行级联操作,不用管,当单表操作。删除操作父类离线对象不行,用持久化对象删除操作。

采用union-subclass元素的继承映射(了解知识)

关系树的每个类存放在各种独立的表中,没有主表和子表的概念,每个表都有全部的自身信息,所有表和在一起后,记录仍然是唯一的。

union-subclass继承映射知识点.png
union-subclass元素的继承映射.png

三种继承方式的比较

三种继承映射方式的比较.png

Hibernate检索(抓取)策略

检索策略主要进行Hibernate的查询优化的。

学习目标:掌握各种检索策略特点,能够在开发中判断该使用哪种检索策略对,对查询进行优化。

立即检索和延迟检索

类级别延迟策略 配置hbm文件中的<class> lazy 属性,lazy=false 立即检索、lazy=true 延迟检索(默认)。

针对load方法,get方法 一定使用立即检索。

query查询 对于类级别 使用立即检索。

对于get或query类级别检索都是立即检索。load通过hbm配置 使用立即或延迟检索,默认延迟。

关联级别的检索策略

如果已经获得持久化对象,通过关联的关系调用对象,而使用检索策略 如: customer.getOrders().size();

在映射文件中,用<set>元素来配置一对多关联及多对多关联关系。<set>元素有lazy和fetch属性。

  1. lazy:主要决定orders集合被初始化的时机。即到底是在加载Customer对象时就被初始化,还是在程序访问orders集合时被初始化。
  2. fetch:取值为"select"或者“subselect”时,决定初始化orders的查询语句的形式;若取值为“join”,则决定orders集合被初始化的时机。
  3. 若把fetch设置为“join”,lazy属性将被忽略。

一对多和多对多检索策略

关联级别的检索策略.png

用带子查询的select语句整批初始化orders集合(fetch属性为subselect):

1.<set>元素的fetch属性:取值为select或subselect时,决定初始化orders的查询语句的形式;若取值为join,则决定orders集合被初始化的时机,默认值为select。

2.当fetch属性为subselect时,嘉定session缓存中有n个orders集合代理类实例没有被初始化,Hibernate能够通过带子查询的select语句,来批量初始化n个orders集合代理实例。

3.当fetch属性为join时,检索Customer对象时,采用迫切左外连接策略来检索所有关联的order对象,lazy属性被忽略。query的list()方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用立即检索还是延迟加载策略有set集合的lazy属性决定。

多对一和一对一关联的检索策略

多对一和一对一关联策略.png

批量检索

1.从一的一端查询,查询所有的客户:

<set>元素有一个batch-size属性,用来延迟检索策略或理解检索策略设定批量检索的数量。批量检索能减少select语句的条目,提高延迟检索或立即检索的运行性能。默认值是1.

注:query.list()属于hql检索,hql检索忽略关联级别的迫切左外连接检索,只与lazy属性有关。

Customer.hbm.xml <set name="orders" batch-size="10" fetch="select" lazy="true">

2.多的一端 关联<many-to-one>

Customer.hbm.xml <class name="com.wgp.domain" table="customer" lazy="true" batch-size="2">,一次sql查询两个客户

三种检索策略的比较

三种检索策略的比较.png

Hibernate的注解开发

Hibernate能干什么?可以取代hbm映射文件,cfg文件也可以取代 但一般我们不用注解去取代.

http://docs.jboss.org/hibernate/orm/5.4/quickstart/html_single/ 这是Hibernate官网的文档,怎么用注解是在第3个标题中介绍的

@Entity 实体类

@Table 生成目标表

@Id 主键

@GeneratedValue 主键生成策略,IDENTITY 自增用于mysql、SEQUENCE使用序列oracle、AUTO 相当于native

@Column 定义生成列

在hibernate.cfg.xml中引入使用注解的类 <mapping class="com.wgp.domain.Book"/>

自定义主键生成策略

如果使用其他类型的生成策略需要自定义。@GenericGenerator用于自定义生成策略,@GeneratedValue的generator用于指定自定义生成策略。

例如:

@Entity
@Table(name="books")
public class Book{
    @Id
    @GenericGenerator(name="bookGenerator",strategy="uuid")//使用Hibernate的uuid
    @GeneratedValue(generator="bookGenerator")
    private String id;
}

其他注解

@Temporal 控制日期对象生成格式。DATE 只会保存日期部分到数据库;TIME 只会保存时间部分到数据库;DATETIME 日期和时间都会保存到数据库。

java.utils.Date 日期对象有3个子类 java.sql.Date java.sql.Time java.sql.TimeStamp

@Transient 控制实体类中属性,不生成到数据库表中列。

一对多配置onetomany

关联属性配置,在一的一方mappedBy(相当于inverse),在多的一方@JoinColumn。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容