hibernate3

一、HelloWorld

1、简介

  • ORM对象/关系映射框架
    • 面向对象:类、对象、属性
    • 面向关系:表、记录(行)、字段(列)
    • ORM思想:把对数据库的操作转化为对对象的操作,采用“对象-关系”xml映射文件来描述映射关系
  • 持久化框架
    • 对象的持久化:不仅是保存到数据库中,而且还包括删除、更新、查询、加载到内存等操作
  • 本文档使用hibernate版本
    • 3.6.8.Final,也可尝试使用4.2,稍有不同
  • 本文档使用的数据库版本
    • sqlserver2005
  • 本文档涉及的所有代码
    • 代码地址:https://gitee.com/freyfang/hibernate3.git
    • 文字如果读不懂,请先看demo

2、创建maven项目,引入依赖

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>3.6.8.Final</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.13.0-GA</version>
</dependency>
<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>sqljdbc4</artifactId>
    <version>4.0</version>
</dependency>

在这里插入图片描述

3、创建hibernate核心配置文件:hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 配置数据库连接 -->
        <property name="hibernate.connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
        <property name="hibernate.connection.url">jdbc:sqlserver://localhost:1433;DatabaseName=hibernate3</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.connection.password">123456</property>
        
        <!-- 配置数据库的方言,根据底层的数据库不同产生不同的 sql 语句,Hibernate 会针对数据库的特性在访问时进行优化-->
        <property name="hibernate.dialect">
            org.hibernate.dialect.SQLServerDialect
        </property>
        <!--是否将运行期生成的SQL输出到日志以供调试-->
        <property name="hibernate.show_sql">true</property>
        <!--是否将 SQL 转化为格式良好的 SQL -->
        <property name="hibernate.format_sql">true</property>
        <!--
        hbm2ddl.auto:该属性可帮助程序员实现正向工程, 即由 java 代码生成数据库脚本, 进而生成具体的表结构. 。
            取值 create | update | create-drop | validate
                create : 会根据 .hbm.xml  文件来生成数据表, 但是每次运行都会删除上一次的表 ,重新生成表, 哪怕二次没有任何改变
                create-drop : 会根据 .hbm.xml 文件生成表,但是SessionFactory一关闭, 表就自动删除
                update : 最常用的属性值,也会根据 .hbm.xml 文件生成表, 但若 .hbm.xml  文件和数据库中对应的数据表的表结构不同, Hiberante  将更新数据表结构,但不会删除已有的行和列
                validate : 会和数据库中的表进行比较, 若 .hbm.xml 文件中的列在数据表中不存在,则抛出异常
        -->
        <!--<property name="hibernate.hbm2ddl.auto">validate</property>-->
        <property name="hibernate.hbm2ddl.auto">update</property>
   
        <!-- 指定关联的hbm.xml映射文件 -->
        <mapping resource="cn/freyfang/hibernate/_helloworld/Employee.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

4、创建持久化类

package cn.freyfang.hibernate._helloworld;
//使用非final类,如果持久化类没有实现任何接口,hibernate使用CGLIB代理,如果使用final类,则无法生成代理
public class Employee {
    //提供一个标识字段,映射数据库的主键
    private Integer id;//映射int
    private String name;//映射varchar
    private BigDecimal salary;//映射numeric
    private Date birthDate;//映射date
    private Date createdAt;//映射datetime
    private Date birthTime;//映射time
    //无参构造器:hibernate使用反射实例化
    public Employee() {}
    public Employee(String name) {
        this.name = name;
    }
   public Employee(String name, BigDecimal salary, Date birthDate, Date createdAt, Date birthTime) {
        this.name = name;
        this.salary = salary;
        this.birthDate = birthDate;
        this.createdAt = createdAt;
        this.birthTime = birthTime;
    }
	//setter&getter ...
    //重写equals和hashCode方法:当需要把持久化实例放入set中,如配置关联映射时
    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", birthDate=" + birthDate +
                ", createdAt=" + createdAt +
                ", birthTime=" + birthTime +
                '}';
    }
}

5、创建对象-关系映射文件:xxx.hbm.xml

  • 指定类和表的映射、Oid和主键的映射、属性和字段的映射
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
hibernate-mapping 是 hibernate 映射文件的根元素
    schema: 指定所映射的数据库schema的名称。若指定该属性, 则表名会自动添加该 schema 前缀
    catalog:指定所映射的数据库catalog的名称。
    *default-cascade(默认为 none): 设置hibernate默认的级联风格. 若配置 Java 属性, 集合映射时没有指定 cascade 属性, 则 Hibernate 将采用此处指定的级联风格.
    *default-access (默认为 property): 指定 Hibernate 的默认的属性访问策略。默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定 field, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量.
    *default-lazy(默认为 true): 设置延迟加载策略. 该属性的默认值为 true, 即启用延迟加载策略. 若配置 Java 属性映射, 集合映射时没有指定 lazy 属性, 则 Hibernate 将采用此处指定的延迟加载策略
    auto-import (默认为 true): 指定是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。
    **package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名(如class的name属性/property的type属性), 就使用这个作为包名。
-->
<hibernate-mapping>
    <!--
    class 元素用于指定类和表的映射
        **name:指定该持久化类映射的类名
        **table:指定该持久化类映射的表名, Hibernate 默认以持久化类的类名作为表名
        **dynamic-insert: 若设置为 true, 表示当保存一个对象时, 会动态生成 insert 语句, insert 语句中仅包含所有取值不为 null 的字段. 默认值为 false
        **dynamic-update: 若设置为 true, 表示当更新一个对象时, 会动态生成 update 语句, update 语句中仅包含所有取值需要更新的字段. 默认值为 false
        **select-before-update:设置 Hibernate 在更新某个持久化对象之前是否需要先执行一次查询. 默认值为 false
        *batch-size:指定根据 OID 来抓取实例时每批抓取的实例数.
        *lazy: 指定是否使用延迟加载.
        *mutable: 若设置为 true, 等价于所有的 <property> 元素的 update 属性为 false, 表示整个实例不能被更新. 默认为 true.
        *discriminator-value: 指定区分不同子类的值. 当使用 <subclass/> 元素来定义持久化类的继承关系时需要使用该属性
    -->
    <class name="cn.freyfang.hibernate._helloworld.Employee" table="employee" dynamic-insert="true" dynamic-update="true" >
        <!--
        id:设定持久化类的 OID 和表的主键的映射
            **name: 标识持久化类 OID 的属性名
            **column: 设置标识属性所映射的数据表的列名(主键字段的名字).
            **unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象
            *type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型
        -->
        <id name="id" type="int" column="id">
            <!--
            generator子元素用来设定标识符生成器.Hibernate 提供了标识符生成器接口: IdentifierGenerator, 并提供了各种内置实现
                class: 指定使用的标识符生成器全限定类名或其缩写名
            -->
            <generator class="native"/>
        </id>
        <!--
        property 元素用于指定类的属性和表的字段的映射
            **name:指定该持久化类的属性的名字
            **column:指定与类的属性映射的表的字段名. 如果没有设置该属性, Hibernate 将直接使用类的属性名作为字段名.
            **type:指定 Hibernate 映射类型. 也可以不指定,而指定sql-type
            not-null:若该属性值为 true, 表明不允许为 null, 默认为 false
            **access:指定 Hibernate 的默认的属性访问策略。默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定 field, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量
            **unique: 设置是否为该属性所映射的数据列添加唯一约束.
            index: 指定一个字符串的索引名称. 当系统需要 Hibernate 自动建表时, 用于为该属性所映射的数据列创建索引, 从而加快该数据列的查询.
            length: 指定该属性所映射数据列的字段的长度
            scale: 指定该属性所映射数据列的小数位数, 对 double, float, decimal 等类型的数据列有效.
            formula:设置一个 SQL 表达式, Hibernate 将根据它来计算出派生属性的值.
                派生属性: 并不是持久化类的所有属性都直接和表的字段匹配, 持久化类的有些属性的值必须在运行时通过计算才能得出来, 这种属性称为派生属性
                使用 formula 属性时,formula="(sql)" 的英文括号不能少
                Sql 表达式中的列名和表名都应该和数据库对应, 而不是和持久化对象的属性对应
                如果需要在 formula 属性中使用参数, 这直接使用 where cur.id=id 形式, 其中 id 就是参数, 和当前持久化对象的 id 属性对应的列的 id 值将作为参数传入.
                如<property name="xxx" formula="(SELECT concat(name, salary) FROM Employee WHERE ID=ID)"/>

        -->
        <property name="name" column="name" type="string"/>
        <property name="salary" column="salary" type="big_decimal"/>
        <!--指定type,在查询时,会根据字段类型自动映射相应的sql类型,如date映射为java.sql.date-->
        <property name="birthDate" type="date">
            <!--指定sql-date,可以精确指定自动生成的字段类型-->
            <column name="birth_date" sql-type="date"></column>
        </property>
        <property name="birthTime" type="time">
            <column name="birth_time" sql-type="time"></column>
        </property>
        <property name="createdAt" type="timestamp">
            <column name="created_at" sql-type="datetime" ></column>
        </property>
    </class>
</hibernate-mapping>

6、通过hibernate API 实现CRUD

package cn.freyfang.hibernate._helloworld;
public class TestHelloWorld {
    SessionFactory sessionFactory;
    Session session;
    Transaction tx;
    @Before
    public void init() {
        //1、获取Configuration,包含配置信息和映射信息
        Configuration configure = new Configuration().configure();
        //2、获取SessionFactory:
        //  一般一个应用只初始化一个SessionFactory,因为构建很消耗资源
        sessionFactory = configure.buildSessionFactory();
        //3、获取Session
        //   Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,所有持久化对象必须在 session 的管理下才可以进行持久化操作。此对象的生命周期很短。Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久层操作的数据都缓存在 session 对象处。
        //   session相当于jdbc中的connection
        //   常用方法:save()/update()/saveOrUpdate(),delete(),get()/load()/beginTransaction()/flush()/close()
        session = sessionFactory.openSession();
        //4、开启Transaction
        //   常用方法:commit()/rollback()/wasCommitd()
        tx = session.beginTransaction();
        //5、通过session操作对象实现CRUD
    }
    //6、关闭事务、关闭session
    @After
    public void destroy() {
        tx.commit();
        session.close();
        sessionFactory.close();
    }
    @Test
    public void testSave() {
        Employee employee = new Employee("张三", new BigDecimal("1234.12"), new Date(), new Date(),new Date());
        session.save(employee);
    }
    @Test
    public void testGet() {
        Employee e = (Employee) session.get(Employee.class, 1);
        System.out.println(e);
    }
    @Test
    public void testUpdate() {
        Employee e = (Employee)session.get(Employee.class, 1);
        System.out.println(e);
        e.setName("zhang3");
//        session.update(e);//e为持久化对象,可以省略该语句
        System.out.println(e);
    }
    @Test
    public void testDelete() {
        Employee employee = new Employee();
        employee.setId(1);
//        session.delete(employee);//删除游离对象
        Employee e = (Employee) session.get(Employee.class,2);
        session.delete(e); //删除持久化对象
    }
}

二、Session

  • Session 接口是 Hibernate 向应用程序提供的操纵数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载 Java 对象的方法.

1、Session缓存(Hibernate一级缓存)

  • Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷新缓存(flush)

  • 在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期

  • Session 缓存可减少 Hibernate 应用程序访问数据库的频率

  • 操作缓存的三个方法

    • flush()方法:将缓存中的对象更新到数据库

      • Session 按照缓存中对象的属性变化来同步更新数据库。该方法可能会发送sql语句,但不会提交事务

      • 默认情况下 Session 在以下时间点刷新缓存:

        • 显式调用 Session 的 flush() 方法
        • 当应用程序调用 Transaction 的 commit()方法的时, 该方法先 flush ,然后再向数据库提交事务
        • 当应用程序执行一些查询(HQL, Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先 flush 缓存,以保证查询结果能够反映持久化对象的最新状态
      • flush 缓存的例外情况:

        • 如果对象使用 native 生成器生成 OID, 那么当调用 Session 的 save() 方法保存对象时, 会立即发送insert 语句.而不是等flush才发送。因为save方法后,必须保证id是存在的,执行了insert,数据库底层才会生成一个id
      • commit() 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务.提交事务意味着对数据库操作永久保存下来。

      • 显示设置刷新缓存的时间点

        • 若希望改变 flush 的默认时间点, 可以通过 Session 的 setFlushMode() 方法显式设定 flush 的时间点

          在这里插入图片描述

    • refresh()方法:将数据库中最新数据更新到缓存

      • 会强制发送select语句,以使session缓存中对象和数据库记录保存一致,无论当前数据库和缓存是否一致都会发送,因为hibernate不知道数据库中的状态
      • 前提:事务隔离级别不能是可重复读,因为可重复读,会避免不可重复读的问题,即无法读到其他事务已提交的更新。
    • clear() 清空缓存,再次get()会重新发送sql

2、hibernate事务之间的隔离级别

  • JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:
    • READ UNCOMMITED:1,读未提交,一个事务内可以读到另一个事务未提交的数据,会出现脏读、不可重复读、幻读
    • READ COMMITED:2,读已提交,一个事务只能读到其他事务已提交的数据,可以避免脏读,会出现不可重复读、幻读
    • REPEATABLE READ:4,可重复读,一个事务多次读取相同的行的数据是一致的,事务期间禁止其他事务操作该行。可以避免脏读、不可重复读,但会出现幻读
    • SERIALIZEABLE:8,串行化,确保事务可以从一个表中读取相同的行,事务期间禁止其他事务操作该表,可以避免所有并发问题。
  • 在配置文件中设置事务的隔离级别
    • <property name=“hibernate.connection.isolation”>2</property>
  • 数据库默认隔离界别
    • oracle支持两种隔离级别:读已提交和串行化,默认是读已提交;
    • mysql支持四种隔离级别,默认是可重复读
    • sqlserver支持六种隔离级别,默认是读已提交

3、持久化对象的状态

  • 站在持久化的角度, Hibernate 把对象分为 4 种状态: 持久化状态, 临时状态, 游离状态, 删除状态. Session 的特定方法能使对象从一个状态转换到另一个状态.

    • 临时状态的对象(Transient):即new的对象

      • 在使用代理主键的情况下, OID 通常为 null
      • 不处于 Session 的缓存中
      • 在数据库中没有对应的记录
    • 持久化状态对象(也叫”托管”)(Persist):即save()/get()/update()后的对象

      • OID 不为 null
      • 位于 Session 缓存中
      • 在数据库中已经有和其对应的记录
      • Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
      • 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
    • 删除对象(Removed):即delete()后的对象

      • 在数据库中没有和其 OID 对应的记录
      • 不再处于 Session 缓存中
    • 游离对象(也叫”脱管”) (Detached):如session关闭后,持久化对象变为游离对象

      • OID 不为 null
      • 不再处于 Session 缓存中
  • 四种状态之间的转换

    在这里插入图片描述

4、session常用方法

package cn.freyfang.hibernate._helloworld;
public class TestSession {
    SessionFactory sessionFactory;
    Session session;
    Transaction tx;
    @Before
    public void init() {
        sessionFactory = new Configuration().configure().buildSessionFactory();
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
    }
    @After
    public void destroy() {
        tx.commit();
        session.close();
        sessionFactory.close();
    }
    /*
    save() 方法
        使一个临时对象转变为持久化对象
        把对象加入到 Session 缓存中
        在 使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
        当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
        计划执行一条 insert 语句:在 flush 缓存的时候
     */
    @Test
    public void testSave() {
        Employee employee = new Employee("张三", new BigDecimal("1234.12"), new Date(), new Date(),new Date());
        employee.setId(100); //无效,因为使用的是native代理主键
        session.save(employee);
//        employee.setId(1000);// HibernateException 不允许修改,因为数据库和持久化状态
    }
    /*
    persist() vs save()
        当对一个 OID 不为 Null 的对象执行 save() 方法时, 会把该对象以一个新的 oid 保存到数据库中;  但执行 persist() 方法时会抛出一个异常.
     */
    @Test
    public void testPersist() {
        Employee employee = new Employee("张三", new BigDecimal("1234.12"), new Date(), new Date(),new Date());
        employee.setId(100);
        session.persist(employee); //PersistentObjectException
    }
    /*
    get() vs load()
        get()立即查询,返回持久化对象;load()是延迟加载个,返回的是代理对象,当使用到对象的属性时才会发送sql
        当根据oid从数据库加载一个持久化对象时,若db中没有对应记录,get()返回null;使用load()返回的对象时,抛出ObjectNotFoundException
        如果load()方法执行后,还未发送sql,如果session关闭了,则抛出懒加载异常
     */
    @Test
    public void testGet() {
        Employee e = (Employee) session.get(Employee.class, 3);//第一次查询数据库,然后将结果放入session缓存
        System.out.println(e.getClass().getName());// cn.freyfang.hibernate._helloworld.Employee
//        System.out.println(e);
//        Employee e1 = (Employee) session.get(Employee.class, 3);//第二次从session缓存中获取到了,不再查询数据库
//        System.out.println(e1);
//        System.out.println(e==e1);//true
    }
    @Test
    public void testLoad() {
        Employee e = (Employee) session.load(Employee.class, 3);
        System.out.println(e.getClass().getName());//cn.freyfang.hibernate._helloworld.Employee_$$_javassist_6
        System.out.println(e);
    }
    /*
     update()
        若更新的是持久化对象,则不需要显式调用update(),因为在commit()时会先执行flush(),即当对象的数据被修改时才会发送update sql
            当然,显示调用update()也不会出错
        若更新的是游离对象,需要显示调用update(),会使一个游离对象转变为持久化对象,无论对象的数据有没有修改,总是发送update sql
            若希望 Session 仅当修改了对象的属性时, 才执行update语句, 可以把映射文件中 <class> 元素的 select-before-update 设为 true. 该属性的默认值为 false
            通常不设置,影响效率
        当 update() 一个游离对象时,
            如果在 Session 的缓存中已经存在相同 OID 的持久化对象, 会抛出异常,因为在session缓存中不能有两个oid相同的对象
            如果在数据库中不存在相应的记录, 也会抛出异常.
     */
    @Test
    public void testUpdate() {
        Employee e = (Employee)session.get(Employee.class, 3);
        System.out.println(e);
        e.setName("zhang3");
//        session.update(e);//e为持久化对象,可以省略该语句
        System.out.println(e);

        tx.commit();
        session.close();//使e变为游离对象

        Session session1 = sessionFactory.openSession();
        Transaction tx1 = session1.beginTransaction();
//        Employee e2 = (Employee) session1.get(Employee.class,3);//当session缓存中已经存在相同oid对象,则update会异常NonUniqueObjectException

        e.setName("zhang3");// e 为游离对象,需要显示调用update()
//        e.setId(1000);//数据库中不存在,则update会异常StaleStateException
        session1.update(e);
        System.out.println(e);
        tx1.commit();
        session1.close();
    }
    /*
    saveOrUpdate
        当oid为null时(临时对象),则save(),否则(持久化对象、游离对象)update()
            update时,如果oid对象的数据库记录不存在,则StaleStateException
        当映射文件id标签设置了unsave-value=xxx时,如果oid的值是xxx,则也会save()
     */
    @Test
    public void testSaveOrUpdate() {
        Employee employee = new Employee("li4", new BigDecimal("1234.12"), new Date(), new Date(),new Date());
//        session.saveOrUpdate(employee);// insert ...
        employee.setId(10);
        session.saveOrUpdate(employee); // update ...
    }
    /*
    delete
        可以删除一个游离对象或持久化对象,变为删除对象
        如果删除oid在数据库中不存在,则异常StaleStateException
        cfg.xml配置文件中有一个 hibernate.use_identifier_rollback 属性, 其默认值为 false, 若为 true, 将改变 delete() 方法的运行行为: delete() 方法会把持久化对象或游离对象的 OID 设置为 null, 使它们变为临时对象
     */
    @Test
    public void testDelete() {
        Employee employee = new Employee();
        employee.setId(4);
//        session.delete(employee);//删除游离对象
        Employee e = (Employee) session.get(Employee.class,6);
        session.delete(e); //删除持久化对象
        System.out.println(e);
    }
    /*
    doWork()
        可以获得原生jdbc connection
            调用存储过程:hibernate没有提供api来直接调用存储过程,但可以间接通过jdbc connection 来调用
            批处理:通过 JDBC 原生的 API 进行操作, 效率最高, 速度最快!
     */
    @Test
    public void testProcedure() {
        session.doWork(new Work() {
            @Override
            public void execute(Connection connection) throws SQLException {
                System.out.println(connection);
//                CallableStatement callableStatement = connection.prepareCall("{call testProcedure()}");
//                callableStatement.executeUpdate();
            }
        });
    }
}

5、管理session

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

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

    • Hibernate 按以下规则把 Session 与本地线程绑定

      • 当一个线程(threadA)第一次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法会创建一个新的 Session(sessionA) 对象, 把该对象与 threadA 绑定, 并将 sessionA 返回
      • 当 threadA 再次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法将返回 sessionA 对象
      • 当 threadA 提交 sessionA 对象关联的事务时, Hibernate 会自动flush sessionA 对象的缓存, 然后提交事务, 关闭 sessionA 对象. 当 threadA 撤销 sessionA 对象关联的事务时, 也会自动关闭 sessionA 对象
      • 若 threadA 再次调用 SessionFactory 对象的 getCurrentSession() 方法时, 该方法会又创建一个新的 Session(sessionB) 对象, 把该对象与 threadA 绑定, 并将 sessionB 返回
    • demo

      package cn.freyfang.hibernate._helloworld;
      public class TestSessionManage {
          SessionFactory sessionFactory;
          @Test
          public void testManage() {
              sessionFactory = new Configuration().configure().buildSessionFactory();
              Session session = sessionFactory.getCurrentSession();
              Session session1 = sessionFactory.getCurrentSession();
              System.out.println("session-->" + session.hashCode());//-->575715826
              System.out.println("session1-->" + session1.hashCode());//-->575715826
              Transaction transaction = session.beginTransaction();
              Employee employee = new Employee("zhang3");
              save(employee);
              save(employee);//不会执行插入
              //若 Session 是由 thread 来管理的, 则在提交或回滚事务时, 已经关闭 Session 了.
              transaction.commit();
              System.out.println(session.isOpen());//false
              Session session2 = sessionFactory.getCurrentSession();
              System.out.println("-->" + session2.hashCode());//-->1176932104
          }
          private void save(Employee employee){
              //获取和当前线程绑定的 Session 对象
              //1. 不需要从外部传入 Session 对象
              //2. 多个 DAO 方法也可以使用一个事务!
              Session session = sessionFactory.getCurrentSession();
              System.out.println(session.hashCode());//575715826
              session.save(employee);
          }
          /**
           * 若需要传入一个 Session 对象, 则意味着Service层也需要获取到 Session 对象.
           * 和 Hibernate 的 API 紧密耦合. 所以不推荐使用此种方式.
           */
          private void save(Session session, Employee employee){
              session.save(employee);
          }
      }
      

三、对象关系映射文件

  • pojo类和数据库表之间的映射文件(xxx.hbm.xml),hibernate在运行时会根据该文件生成各种sql

1、hibernate-mapping结构

  • 每个Hibernate-mapping中可以同时定义多个类. 但更推荐为每个类都创建一个单独的映射文件

  • 类层次:class

    • 主键:id
    • 基本类型:property
    • 实体引用类: many-to-one | one-to-one
    • 集合:set | list | map | array,即one-to-many、many-to-many
    • 子类:subclass | joined-subclass
    • 其它:component | any 等
  • 查询语句:query(用来放置查询语句,便于对数据库查询的统一管理和优化)

2、Hibernate主键生成策略

在这里插入图片描述

  • increment 标识符生成器(不使用,并发问题
    • Hibernate 会先读取表中的主键的最大值, 而接下来向表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.
  • identity 标识符生成器
    • 由底层数据库来负责生成标识符
    • 它要求底层数据库把主键定义为自动增长字段类型,支持的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
    • OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
  • sequence 标识符生成器
    • 利用底层数据库提供的序列来生成标识符.
    • 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2, Oracle 等
    • OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
  • hilo 标识符生成器
    • 由 Hibernate 按照一种 high/low 算法生成标识符, 它从数据库的特定表的字段中获取 high 值
      • 默认请况下使用的表是hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。
    • 由于 hilo 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
    • OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
  • native 标识符生成器(常用
    • 依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
    • 由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
    • OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
  • assigned 标识符生成器(常用

3、hibernate映射类型

  • Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型

  • Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型. 基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型

    在这里插入图片描述

  • 时间和日期类型映射

    • 在 Java 中, 代表时间和日期的类型包括: java.util.Date 和 java.util.Calendar. 此外, 在 JDBC API 中还提供了 3 个扩展了 java.util.Date 类的子类: java.sql.Date, java.sql.Time 和 java.sql.Timestamp, 这三个类分别和标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP 类型对应
    • 在标准 SQL 中, DATE 类型表示日期, TIME 类型表示时间, TIMESTAMP 类型表示时间戳, 同时包含日期和时间信息.
    • 由于sql包中的三个日期时间类型都继承java.util.Date,所以pojo中可以使用java.util.Date来映射这三个日期时间类型
    • 当使用hibernate映射类型自动生成的表字段类型不精确时(即不是自己想要的类型),则可以不指定hibernate映射类型,而直接指定sql-type类型

在这里插入图片描述

4、映射组件

  • Hibernate 把持久化类的属性分为两种:

    • 值(value)类型: 没有 OID, 不能被单独持久化, 生命周期依赖于所属的持久化类的对象的生命周期
    • 实体(entity)类型: 有 OID, 可以被单独持久化, 有独立的生命周期
  • 假如Person类中有Address类类型的属性address,显然无法直接用 property 映射 address 属性

    • Hibernate 使用 <component> 元素来映射组成关系, 该元素表明address属性是 Person类一个组成部分, 称之为组件
  • demo

    package cn.freyfang.hibernate.a_component;
    public class PersonA {
        private Integer id;
        private String name;
        private AddressA address;//映射组件
    	//有参无参构造、setter&getter
    }
    package cn.freyfang.hibernate.a_component;
    public class AddressA {
        private String province;
        private String city;
        private String region;
        //有参无参构造、setter&getter
    //    private Person person;
    //    public Person getPerson() {
    //        return person;
    //    }
    //    public void setPerson(Person person) {
    //        this.person = person;
    //    }
    }
    package cn.freyfang.hibernate.a_component;
    public class TestComponent {
       //init()/destroy()...
        @Test
        public void testComponent() {
            PersonA person = new PersonA("li4");
            AddressA address = new AddressA("shanghai", "shanghai", "changning");
            person.setAddress(address);
            session.save(person);
        }
    }
    
    <!-- PersonA.hbm.xml -->
    <hibernate-mapping package="cn.freyfang.hibernate.a_component">
        <class name="PersonA" table="person_a" dynamic-insert="true" dynamic-update="true" >
            <id name="id" type="int" column="id">
                <generator class="native"/>
            </id>
            <property name="name" column="name" type="string"/>
            <!--
            <component> 元素来映射组成关系(组件)
                class:设定组成关系属性的类型, 此处表明 address 属性为 Address 类型
                <parent> 元素指定组件属性所属的整体类
                    name: 整体类在组件类中的属性名
            -->
            <component name="address" class="AddressA">
                <!--设置parent的前提是Address类中定义有person属性-->
                <!--<parent name="person"></parent>-->
                <property name="province" column="province" type="string"/>
                <property name="city" column="city" type="string"/>
                <property name="region" column="region" type="string"/>
            </component>
        </class>
    </hibernate-mapping>
    

5、映射单向n-1

  • 单向 n-1 关联只需从 n 的一端可以访问 1 的一端

  • 域模型:

    • 从 Employee到 Department 的多对一单向关联需要在Employee类中定义一个 Department 属性, 而在 Department 类中无需定义存放 Employee对象的集合属性
  • 关系数据模型:

  • Employee表中的 department_id参照 Department 表的主键

  • 显然无法直接用 property 映射 department 属性,Hibernate 使用 <many-to-one> 元素来映射多对一关联关系

  • demo

    • 持久化类

      package cn.freyfang.hibernate.b_many2one;
      public class EmployeeB {
          private Integer id;
          private String name;
          private DepartmentB department;//映射单向n-1
      }
      public class DepartmentB {
          private Integer deptId;
          private String deptName;
      }
      
    • 映射文件

      <!-- EmployeeB.hbm.xml -->
      <hibernate-mapping package="cn.freyfang.hibernate.b_many2one">
          <class name="EmployeeB" table="employee_b" dynamic-insert="true" dynamic-update="true" >
              <id name="id" type="int" column="id">
                  <generator class="native"/>
              </id>
              <property name="name" column="name" type="string"/>
              <!--
              <many-to-one> 元素来映射单向n-1
                  name: 设定待映射的持久化类的属性的名字
                  column: 设定和持久化类的属性对应的表的外键
                  class:设定待映射的持久化类的属性的类型
              -->
              <many-to-one name="department" column="department_id" class="DepartmentB">
              </many-to-one>
          </class>
      </hibernate-mapping>
      <!-- DepartmentB.hbm.xml -->
      <hibernate-mapping>
          <class name="cn.freyfang.hibernate.b_many2one.DepartmentB" table="department_b" dynamic-update="true">
              <id name="deptId" type="int" column="dept_id">
                  <generator class="native"/>
              </id>
              <property name="deptName" column="dept_name" type="string"/>
          </class>
      </hibernate-mapping>
      
    • 测试类

      package cn.freyfang.hibernate.b_many2one;
      public class TestManyToOne {
          @Test
          public void testSave() {
              EmployeeB employee1 = new EmployeeB("li4");
              EmployeeB employee2 = new EmployeeB("wang5");
              DepartmentB department = new DepartmentB("销售部");
              employee1.setDepartment(department);
              employee2.setDepartment(department);
              //1、先插入 1 的一端, 再插入 n 的一端, 3条 INSERT 语句.
              session.save(department);
              session.save(employee1);
              session.save(employee2);
              //2、先插入 n 的一端, 再插入 1 的一端, 3条 Insert,2条 UPDATE 语句!
              //因为在插入n端时, 无法确定 1 端的外键值. 所以只能等 1端插入后, 再额外发送 UPDATE 语句.
      //        session.save(employee1);
      //        session.save(employee2);
      //        session.save(department);
              //3、推荐先插入 1 的一端, 后插入 n 的一端,不需要update语句
          }
          @Test
          public void testGet() {
              //1、查询n端的对象时,并没有立即查询关联的1端的对象
              EmployeeB employee = (EmployeeB) session.get(EmployeeB.class, 1);
              //2、延迟加载1端的数据,在未使用1端数据时,是代理对象cn.freyfang.hibernate.b_many2one.DepartmentB_$$_javassist_1
              System.out.println(employee.getDepartment().getClass().getName());
              //如果此时session关闭,再加载1端数据,则懒加载LazyInitializationException异常,类似于load()
      //        session.close();
              //当使用到1端数据时,才发送sql
              System.out.println(employee.getDepartment());
          }
          @Test
          public void testUpdate() {
              //查询n端数据,改1端数据(会先查询1端数据):两条select,一条update
              EmployeeB EmployeeB = (EmployeeB) session.get(EmployeeB.class, 1);
              EmployeeB.getDepartment().setDeptName("销售部1");
          }
          @Test
          public void testDelete() {
              //1、删除n端数据,可以直接删除
      //        EmployeeB employee = (EmployeeB) session.get(EmployeeB.class, 1);
      //        session.delete(employee);
              //2、删除1端数据:在不设置级联的情况下,且n端有数据关联1端数据,则删除1端报约束异常ConstraintViolationException。
              DepartmentB department = (DepartmentB) session.get(DepartmentB.class,1);
              session.delete(department);
          }
      }
      

6、映射双向n-1/1-n

  • 双向 n-1 与 双向 1-n 是完全相同的两种情形

  • 双向n-1 需要在 1 的一端可以访问 n 的一端, 反之依然.

  • 域模型:

    • 从 Employee到 Department 的多对一双向关联需要在Employee类中定义一个 Department 属性, 而在 Department 类中需定义存放 Employee对象的集合属性
  • 关系数据模型:

  • Employee表中的 depart_id参照 Department 表的主键

  • 当 Session 从数据库中加载 Java 集合时, 创建的是 Hibernate 内置集合类的实例, 因此在持久化类中定义集合属性时必须把属性声明为 Java 接口类型

    • Hibernate 的内置集合类具有集合代理功能, 支持延迟检索策略
    • 事实上, Hibernate 的内置集合类封装了 JDK 中的集合类, 这使得 Hibernate 能够对缓存中的集合对象进行脏检查, 按照集合对象的状态来同步更新数据库。
  • 在定义集合属性时, 通常把它初始化. 这样可以提高程序的健壮性, 避免应用程序访问取值为 null 的集合的方法抛出NPE

  • Hibernate 使用 <set> 元素来映射 set 类型的属性

  • demo

    • 持久化类

      package cn.freyfang.hibernate.c_many2one_both;
      public class EmployeeC {
          private Integer id;
          private String name;
          private DepartmentC department;//映射单向n-1
      }
      public class DepartmentC {
          private Integer deptId;
          private String deptName;
          /*
           * 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取
           * 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的
           * 集合实现.
           * 2. 需要把集合进行初始化, 可以防止发生空指针异常
           */
          private Set<EmployeeC> employees = new HashSet<>();//映射单向1-n
      }
      
    • 映射文件

      <!-- EmployeeC.hbm.xml -->
      <hibernate-mapping package="cn.freyfang.hibernate.c_many2one_both">
          <class name="EmployeeC" table="employee_c" dynamic-insert="true" dynamic-update="true" >
              <id name="id" type="int" column="id">
                  <generator class="native"/>
              </id>
              <property name="name" column="name" type="string"/>
              <!--
              <many-to-one> 元素来映射单向n-1
                  name: 设定待映射的持久化类的属性的名字
                  column: 设定和持久化类的属性对应的表的外键
                  class:设定待映射的持久化类的属性的类型
              -->
              <many-to-one name="department" column="department_id" class="DepartmentC">
              </many-to-one>
          </class>
      </hibernate-mapping>
      <!-- DepartmentC.hbm.xml -->
      <hibernate-mapping package="cn.freyfang.hibernate.c_many2one_both">
          <class name="DepartmentC" table="department_c" dynamic-update="true" >
              <id name="deptId" type="int" column="dept_id">
                  <generator class="native"/>
              </id>
              <property name="deptName" column="dept_name" type="string"/>
              <!--
              set 映射 1-n
              <set> 元素来映射持久化类的 set 类型的属性
                  name: 设定待映射的持久化类的属性的
                  table: 指定集合中存放哪个表的数据
                  inverse: 指定由双向关联的哪一方来维护表和表之间关联关系. 通常设置为 true, 以指定由多的一端来维护关联关系
                      inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
                      在没有设置 inverse的情况下,父子两边都维护父子关系
                      在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
                      在 1-N 关系中,若将 1 方设为主控方
                          会额外多出 update 语句。
                          插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
                  cascade 设定级联操作. 开发时不建议设定该属性. 建议使用手工的方式来处理
                      <set>, <many-to-one> 和 <one-to-one> 都有一个 cascade 属性, 它用于指定如何操纵与当前对象关联的其他对象.
                      cascade="delete" 级联删除
                      cascade="save-update" save时级联保存关联的临时对象;update时级联保存关联的游离对象
                  order-by 在查询时对集合中的元素进行排序, order-by 中使用的是表的字段名, 而不是持久化类的属性名
              -->
              <set name="employees" table="employee_c" inverse="true" order-by="name asc" cascade="delete">
                  <!--
                  <key> 元素设定与所关联的持久化类对应的表的外键
                      column: 指定关联表的外键名,和many-to-one的column名称一致
                  -->
                  <key column="department_id"></key>
                  <!--
                  <one-to-many> 元素设定集合属性中所关联的持久化类
                      class: 指定关联的持久化类的类名
                  -->
                  <one-to-many class="EmployeeC"></one-to-many>
              </set>
          </class>
      </hibernate-mapping>
      
    • 测试类

      package cn.freyfang.hibernate.c_many2one_both;
      public class TestManyToOneBoth {
          @Test
          public void testSave() {
              EmployeeC employee1 = new EmployeeC("li4");
              EmployeeC employee2 = new EmployeeC("wang5");
              DepartmentC department = new DepartmentC("销售部");
              employee1.setDepartment(department);
              employee2.setDepartment(department);
              department.getEmployees().add(employee1);
              department.getEmployees().add(employee2);
              //1、先插入 1 的一端, 再插入 n 的一端, 3条INSERT 语句.多出2条update语句
              //因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE
              //去掉update语句:可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系
      //        session.save(department);
      //        session.save(employee1);
      //        session.save(employee2);
              //2、先插入 n 的一端, 再插入 1 的一端, 3条Insert,4条 UPDATE 语句(多出2条update)!
              //因为在插入多的一端时, 无法确定 1 的一端的外键值. 所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句.
              session.save(employee1);
              session.save(employee2);
              session.save(department);
              //3、推荐先插入 1 的一端, 后插入 n 的一端
          }
          @Test
          public void testGet() {
              //1、查询n端的对象时. 没有立即查询关联的1 端的对象
      //        EmployeeC employee = (EmployeeC) session.get(EmployeeC.class, 1);
              //延迟加载1端的数据,在未使用1端数据时,是代理对象cn.freyfang.hibernate.c_many2one_both.DepartmentC_$$_javassist_4
      //        System.out.println(employee.getDepartment().getClass().getName());
              //如果此时session关闭,再查询加载1端数据,则懒加载LazyInitializationException异常,类似于load()
      //        session.close();
              //当使用到1端数据时,才发送sql去查询
      //        System.out.println(employee.getDepartment());
      
              //2、查询1端的对象时. 没有立即查询关联的n端的对象
              DepartmentC department = (DepartmentC) session.get(DepartmentC.class, 1);
              //返回n端的集合是 Hibernate 内置的集合类型. 该类型具有延迟加载和存放代理对象的功能.
              //org.hibernate.collection.PersistentSet
              System.out.println(department.getEmployees().getClass().getName());
      //        session.close();//LazyInitializationException
              //当使用到n端数据时,才发送sql去查询
              System.out.println(department.getEmployees().size());
          }
          @Test
          public void testUpdate() {
              //1、查n端数据,改1端数据(会先查1端数据):2条select,1条update
      //        EmployeeC employee = (EmployeeC) session.get(EmployeeC.class, 1);
      //        employee.getDepartment().setDeptName("销售部1");
              //2、查1端数据,改n端数据(会先查n端数据):2条select,1条update
              DepartmentC department = (DepartmentC) session.get(DepartmentC.class, 1);
              department.getEmployees().iterator().next().setName("zhao6");
          }
          @Test
          public void testDelete() {
              //1、删除n端,可以直接删除
      //        EmployeeC employee = (EmployeeC) session.get(EmployeeC.class, 1);
      //        session.delete(employee);
              //2、删除1端,在不设置级联的情况下,且n端有数据关联1端数据,则删除1端报约束异常ConstraintViolationException。
              //级联删除:为set设置cascade="delete",则删除1端时,会先删除关联的n端数据
              DepartmentC department = (DepartmentC) session.get(DepartmentC.class,1);
              session.delete(department);
              //clear不会执行删除
      //        department.getEmployees().clear();
          }
      }
      

7、映射1-1

  • 基于外键的1-1关联

    • 其外键可以存放在任意一边,在需要存放外键一端,增加many-to-one元素。为many-to-one元素增加unique=“true” 属性来表示为1-1关联;另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
  • 注意:两边都使用外键映射1-1可以吗?不可以,因为可能A映射AA,但AA映射B

    • demo

      • 持久化类

        package cn.freyfang.hibernate.d_one2one_fk;
        public class DepartmentD {
            private Integer deptId;
            private String deptName;
            private ManagerD manager;//映射1-1
        }
        public class ManagerD {
            private Integer mgrId;
            private String mgrName;
            private DepartmentD department;//映射1-1
        }
        
      • 映射文件

        <!-- DepartmentD.hbm.xml --> 
        <hibernate-mapping package="cn.freyfang.hibernate.d_one2one_fk">
            <class name="DepartmentD" table="department_d" dynamic-update="true" >
                <id name="deptId" type="int" column="dept_id">
                    <generator class="native"/>
                </id>
                <property name="deptName" column="dept_name" type="string"/>
                <!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
                <many-to-one name="manager" column="manager_id" class="ManagerD"
                             unique="true"></many-to-one>
            </class>
        </hibernate-mapping>
        <!-- ManagerD.hbm.xml --> 
        <hibernate-mapping package="cn.freyfang.hibernate.d_one2one_fk">
            <class name="ManagerD" table="manager_d" dynamic-update="true" >
                <id name="mgrId" type="int" column="mgr_id">
                    <generator class="native"/>
                </id>
                <property name="mgrName" column="mgr_name" type="string"/>
                <!--
                    映射 1-1 的关联关系:
                        在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射
                	    没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
                 -->
                <one-to-one name="department" class="DepartmentD" property-ref="manager" cascade="delete"></one-to-one>
            </class>
        </hibernate-mapping>
        
      • 测试类

        package cn.freyfang.hibernate.d_one2one_fk;
        public class TestOneToOneBaseFK {
            @Test
            public void testSave() {
                ManagerD ManagerD = new ManagerD("zhang3");
                DepartmentD DepartmentD = new DepartmentD("市场部");
                DepartmentD.setManager(ManagerD);
                ManagerD.setDepartment(DepartmentD);
                //建议先保存没有外键列的那个对象. 这样不会有UPDATE 语句
                session.save(ManagerD);
                session.save(DepartmentD);
            }
            @Test
            public void testGet() {
                //1、查询有外键的一端的对象时. 没有立即查询关联的那一端的对象
        //        DepartmentD DepartmentD = (DepartmentD) session.get(DepartmentD.class, 1);
                //cn.freyfang.hibernate.d_one2one_fk.ManagerD_$$_javassist_2
        //        System.out.println(DepartmentD.getManager().getClass().getName());
        //        session.close();//LazyInitializationException
                //当使用到没有外键端的对象时,才发送sql查询,(使用的时left join) 2条select?
                //但默认sql错误on ManagerD0_.mgr_id=DepartmentD1_.dept_id ,
                //需要在没有外键的一端的one-to-one配置property-ref属性:on ManagerD0_.mgr_id=DepartmentD1_.ManagerD_id
        //        System.out.println(DepartmentD.getManager().getMgrName());
        
                //2、查询没有外键端的对象. 使用的左外连接查询, 一并查询出其关联的对象,并已经进行初始化.
                ManagerD ManagerD = (ManagerD) session.get(ManagerD.class, 1);
                //cn.freyfang.hibernate.d_one2one_fk.DepartmentD
                System.out.println(ManagerD.getDepartment().getClass().getName());
            }
            @Test
            public void testUpdate() {
                //1、查没有外键端数据,改有外键端数据 1条select(left join),1条update
        //        ManagerD ManagerD = (ManagerD) session.get(ManagerD.class, 1);
        //        ManagerD.getDepartment().setDeptName("市场部1");
                //2、查有外键端数据,改没有外键端数据(会先查n端数据):3条select,1条update
                DepartmentD DepartmentD = (DepartmentD) session.get(DepartmentD.class, 1);
                DepartmentD.getManager().setMgrName("zhang33");
            }
            @Test
            public void testDelete() {
                //1、删除有外键的一端:可以直接删除
        //        DepartmentD DepartmentD = (DepartmentD) session.get(DepartmentD.class, 1);
        //        session.delete(DepartmentD);
        
                //2、删除没有外键的一端:在不设置级联的情况下,且有外键端有数据关联没有外键端的数据,则删除没有外键端数据报约束异常ConstraintViolationException。
                //级联删除:为one-to-one设置cascade="delete"
                ManagerD ManagerD = (ManagerD) session.get(ManagerD.class, 1);
                session.delete(ManagerD);
            }
        }
        
  • 基于主键的1-1关联

    • 指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键.<param> 子元素指定使用当前持久化类的哪个属性作为 “对方”

    • 采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性。

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

    • demo

      • 持久化类

        package cn.freyfang.hibernate.e_one2one_pk;
        public class DepartmentE {
            private Integer deptId;
            private String deptName;
            private ManagerE manager;//映射1-1
        }
        public class ManagerE {
            private Integer mgrId;
            private String mgrName;
            private DepartmentE department;//映射1-1
        }
        
      • 映射文件

        <!-- DepartmentE.hbm.xml -->
        <hibernate-mapping package="cn.freyfang.hibernate.e_one2one_pk">
            <class name="DepartmentE" table="department_e" dynamic-update="true" >
                <id name="deptId" type="int" column="dept_id">
                    <generator class="native"/>
                </id>
                <property name="deptName" column="dept_name" type="string"/>
                <one-to-one name="manager" class="ManagerE" cascade="delete"></one-to-one>
            </class>
        </hibernate-mapping>
        <!-- ManagerE.hbm.xml -->
        <hibernate-mapping package="cn.freyfang.hibernate.e_one2one_pk">
            <class name="ManagerE" table="manager_e" dynamic-update="true" >
                <id name="mgrId" type="int" column="mgr_id">
                    <!-- 使用外键的方式来生成当前的主键 -->
                    <generator class="foreign">
                        <!-- property 属性指定使用当前持久化类的哪个属性作为 “对方” -->
                        <param name="property">department</param>
                    </generator>
                </id>
                <property name="mgrName" column="mgr_name" type="string"/>
                <!--
                    采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性,
        		    其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束
                 -->
                <one-to-one name="department" class="DepartmentE" constrained="true"></one-to-one>
            </class>
        </hibernate-mapping>
        
      • 测试类

        package cn.freyfang.hibernate.e_one2one_pk;
        public class TestOneToOneBasePK {
            @Test
            public void testSave() {
                ManagerE ManagerD = new ManagerE("zhang3");
                DepartmentE DepartmentD = new DepartmentE("市场部");
                DepartmentD.setManager(ManagerD);
                ManagerD.setDepartment(DepartmentD);
                //先save哪一个都不会有多余的 UPDATE,因为总是先插入department
        //        session.save(DepartmentD);
                session.save(ManagerD);
            }
            @Test
            public void testGet() {
                //1、查询被参照表时,会立即查询参照表,使用(left join)
                DepartmentE department = (DepartmentE) session.get(DepartmentE.class, 1);
                //cn.freyfang.hibernate.e_one2one_pk.ManagerE
                System.out.println(department.getManager().getClass().getName());
                //cn.freyfang.hibernate.e_one2one_pk.ManagerE@23e84203
                System.out.println(department.getManager());
        
                //2、查询参照表时,不会立即查询被参照表
        //        ManagerE manager = (ManagerE) session.get(ManagerE.class, 1);
                //cn.freyfang.hibernate.e_one2one_pk.DepartmentE_$$_javassist_6
        //        System.out.println(manager.getDepartment().getClass().getName());
        //        session.close();//LazyInitializationException
        //        System.out.println(manager.getDepartment());
            }
            @Test
            public void testUpdate() {
                //1、查参照表数据,改被参照表数据 2条select,1条update
                ManagerE manager = (ManagerE) session.get(ManagerE.class, 1);
                manager.getDepartment().setDeptName("市场部1");
                //2、查被参照表数据,改参照表数据:1条select(left join),1条update
        //        DepartmentE department = (DepartmentE) session.get(DepartmentE.class, 1);
        //        department.getManager().setMgrName("zhang33");
            }
            @Test
            public void testDelete() {
                //1、删除被参照表:在不设置级联的情况下,且有参照表的数据关联被参照表数据,则删除被参照表数据报约束异常ConstraintViolationException。
                //级联删除:在被参照表端为one-to-one设置cascade="delete"
        //        DepartmentE department = (DepartmentE) session.get(DepartmentE.class, 1);
        //        session.delete(department);
        
                //2、删除参照表的数据:可以直接删除
                ManagerE manager = (ManagerE) session.get(ManagerE.class, 2);
                session.delete(manager);
            }
        }
        

8、映射n-n

  • 单向

    • 必须使用连接表
    • 与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID. 与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-many. many-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象, column 属性指定 CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 ITEM_ID
  • 双向

    • 必须使用连接表,两端都使用集合属性

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

    • 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.

    • 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突.

    • demo

      • 持久化类

        package cn.freyfang.hibernate.f_many2many_both;
        public class CategoryF {
            private Integer cId;
            private String cName;
            private Set<ItemF> items = new HashSet<>();
        }
        public class ItemF {
            private Integer iId;
            private String iName;
            private Set<CategoryF> categorys = new HashSet<>();
        }
        
      • 映射文件

        <!-- CategoryF.hbm.xml -->
        <hibernate-mapping package="cn.freyfang.hibernate.f_many2many_both">
            <class name="CategoryF" table="category_f" dynamic-update="true" >
                <id name="cId" type="int" column="id">
                    <generator class="native"/>
                </id>
                <property name="cName" column="name" type="string"/>
                <!-- table: 指定中间表
                    对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突
                 -->
                <set name="items" table="category_item_f" inverse="true">
                    <key column="c_id"></key>
                    <!-- 使用 many-to-many 指定多对多的关联关系. column 执行 Set 集合中的持久化类在中间表的外键列的名称  -->
                    <many-to-many class="ItemF" column="i_id"></many-to-many>
                </set>
            </class>
        </hibernate-mapping>
        <!-- ItemF.hbm.xml -->
        <hibernate-mapping package="cn.freyfang.hibernate.f_many2many_both">
            <class name="ItemF" table="item_f" dynamic-update="true" >
                <id name="iId" type="int" column="id">
                    <generator class="native"/>
                </id>
                <property name="iName" column="name" type="string"/>
                <set name="categorys" table="category_item_f">
                    <key column="i_id"></key>
                    <many-to-many class="CategoryF" column="c_id"></many-to-many>
                </set>
            </class>
        </hibernate-mapping>
        
      • 测试类

        package cn.freyfang.hibernate.f_many2many_both;
        public class TestManyToMany {
            @Test
            public void testSave() {
                CategoryF category1 = new CategoryF("category-A");
                CategoryF category2 = new CategoryF("category-B");
                ItemF item1 = new ItemF("item-A");
                ItemF item2 = new ItemF("item-B");
                category1.getItems().add(item1);
                category1.getItems().add(item2);
                category2.getItems().add(item1);
                category2.getItems().add(item2);
                item1.getCategorys().add(category1);
                item1.getCategorys().add(category2);
                item2.getCategorys().add(category1);
                item2.getCategorys().add(category2);
                //ConstraintViolationException:不能在对象“dbo.category_item_f”中插入重复联合主键
                // 对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突
                session.save(category1);
                session.save(category2);
                session.save(item1);
                session.save(item2);
            }
            @Test
            public void testGet() {
                CategoryF category = (CategoryF) session.get(CategoryF.class, 1);
                //org.hibernate.collection.PersistentSet
                System.out.println(category.getItems().getClass().getName());
                //延迟加载,inner join
                System.out.println(category.getItems().size());
            }
            @Test
            public void testUpdate() {
        //        CategoryF category = (CategoryF) session.get(CategoryF.class, 1);
        //        category.getItems().iterator().next().setiName("item11");
                ItemF item = (ItemF) session.get(ItemF.class, 1);
                item.getCategorys().iterator().next().setcName("category111");
            }
            @Test
            public void testDelete() {
                //1、删除没有维护关联关系的一方:在不设置级联的情况下,且被中间表的数据关联,则删除报约束异常ConstraintViolationException。
                //级联删除:在没有维护关联关系的一方的set设置cascade="delete",会删除三表的数据,不建议设置
                CategoryF category = (CategoryF) session.get(CategoryF.class, 1);
                session.delete(category);
        
                //2、删除维护关联关系的一方:先删除中间表,再删除item
        //        ItemF item = (ItemF) session.get(ItemF.class, 1);
        //        session.delete(item);
            }
        }
        

9、映射继承关系

  • Hibernate 的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系。学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。

  • Hibernate支持三种继承映射策略:

    • 使用 subclass 进行映射:

      • 对于继承关系中父类和子类使用同一张表,因此需要在该表内增加一列,使用该列来区分每行记录到底是哪个类的实例----这个列被称为辨别者列(discriminator).

      • 在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值

      • 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中

      • 缺点:

        • 使用了辨别者列.
        • 子类独有的字段不能添加非空约束.
        • 若继承层次较深, 则数据表的字段也会较多.
      • demo

        • 持久化类

          package cn.freyfang.hibernate.g_extends_subclass;
          public class PersonG {
              private Integer id;
              private String name;
          }
          public class StudentG extends PersonG {
              private String school;
          }
          
        • 映射文件

          <!-- PersonG.hbm.xml -->
          
        ```
        • 测试类

          package cn.freyfang.hibernate.g_extends_subclass;
          public class TestExtendsSubclass {
              @Test
              public void testSave() {
                  //辨别者列由 Hibernate 自动维护.
                  PersonG person = new PersonG("zhang3");
                  StudentG student = new StudentG("li4", "jiaoda");
                  session.save(person);
                  session.save(student);
              }
              @Test
              public void testGet() {
                  //1、使用Person查询1-Person、查询2-School正常
                  PersonG person1 = (PersonG) session.get(PersonG.class, 1);
                  System.out.println(person1.getName());
                  PersonG person2 = (PersonG) session.get(PersonG.class, 2);
                  System.out.println(person2.getName());
                  //1、使用Student查询1-Person报错NullPointerException、查询2-School正常
          //        StudentG student1 = (StudentG) session.get(StudentG.class, 1);
          //        System.out.println(student1.getName());
                  StudentG student2 = (StudentG) session.get(StudentG.class, 2);
                  System.out.println(student2.getName());
              }
              @Test
              public void testQuery() {
                  Query from_personG = session.createQuery("from PersonG");
                  List<PersonG> list = from_personG.list();
                  System.out.println(list.size());//2
                  System.out.println(list.get(0).getName());
                  List<StudentG> list2 = session.createQuery("from StudentG").list();
                  System.out.println(list2.size());//1
                  System.out.println(list2.get(0).getName());
              }
          }
          
    • 使用 joined-subclass 进行映射:

      • **父类实例保存在父类表中,子类实例由父类表和子类表共同存储。**因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。

      • 无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键。

      • 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中

      • demo

      • 持久化类

        package cn.freyfang.hibernate.h_extends_joined;
        public class PersonH {
            private Integer id;
            private String name;
        }
        public class StudentH extends PersonH {
            private String school;
        }
        
      • 映射文件

        <!-- PersonH.hbm.xml -->
        <hibernate-mapping package="cn.freyfang.hibernate.h_extends_joined">
            <class name="PersonH" table="person_h" dynamic-update="true">
                <id name="id" type="int" column="id">
                    <generator class="native"/>
                </id>
                <property name="name" column="name" type="string"/>
        
                <joined-subclass name="StudentH" table="student_h">
                    <key column="student_id"></key>
                    <property name="school" column="school" type="string"></property>
                </joined-subclass>
            </class>
        </hibernate-mapping>
        
      • 测试类

        package cn.freyfang.hibernate.h_extends_joined;
        public class TestExtendsJoinedSubclass {
            @Test
            public void testSave() {
                PersonH person = new PersonH("zhang3");
                StudentH student = new StudentH("li4", "jiaoda");
                session.save(person);
                //对于子类对象至少需要插入到两张数据表中.
                session.save(student);
            }
            @Test
            public void testGet() {
                //1、使用Person查询1-Person、查询2-School正常
                PersonH person1 = (PersonH) session.get(PersonH.class, 1);
                System.out.println(person1.getName());
                PersonH person2 = (PersonH) session.get(PersonH.class, 2);
                System.out.println(person2.getName());
                //1、使用Student查询1-Person报错NullPointerException、查询2-School正常
        //        StudentH student1 = (StudentH) session.get(StudentH.class, 1);
        //        System.out.println(student1.getName());
                StudentH student2 = (StudentH) session.get(StudentH.class, 2);
                System.out.println(student2.getName());
            }
            @Test
            public void testQuery() {
                //查询父类记录, 做一个左外连接查询
                Query from_person = session.createQuery("from PersonH");
                List<PersonH> list = from_person.list();
                System.out.println(list.size());//2
                System.out.println(list.get(0).getName());
                //查询子类记录, 做一个内连接查询.
                List<StudentH> list2 = session.createQuery("from StudentH").list();
                System.out.println(list2.size());//1
                System.out.println(list2.get(0).getName());
            }
        }
        
    • 使用 union-subclass 进行映射:

      • 每一个实体对象映射到一个独立的表中

      • 子类增加的属性可以有非空约束 — 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。

      • 子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录

      • 子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和

      • 既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键.

      • 使用 union-subclass 映射策略是不可使用 identity 的主键生成策略, 因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的. 受此影响, 也不该使用 native 主键生成策略, 因为 native 会根据数据库来选择使用 identity 或 sequence.

      • 缺点:

        • 存在冗余的字段
        • 若更新父表的字段, 则更新的效率较低
      • demo

        • 持久化类

          package cn.freyfang.hibernate.i_extends_union;
          public class PersonI {
              private Integer id;
              private String name;
          }
          public class StudentI extends PersonI {
              private String school;
          }
          
          
  • 映射文件

            <!-- PersonI.hbm.xml -->
            <hibernate-mapping package="cn.freyfang.hibernate.i_extends_union">
                <class name="PersonI" table="person_i" dynamic-update="true">
                    <id name="id" type="int" column="id">
                        <!--使用 union-subclass 映射策略是不可使用 native 的主键生成策略
                            实际开发中,也不使用increment,因为会有并发问题
                        -->
                        <generator class="increment"/>
                    </id>
                    <property name="name" column="name" type="string"/>
                    <union-subclass name="StudentI" table="student_i">
                        <property name="school" column="school" type="string"></property>
                    </union-subclass>
                </class>
            </hibernate-mapping>
    
  • 测试类

            package cn.freyfang.hibernate.i_extends_union;
            public class TestExtendsUnionSubclass {
                @Test
                public void testSave() {
                    PersonI person = new PersonI("zhang3");
                    StudentI student = new StudentI("li4", "jiaoda");
                    session.save(person);
                    //对于子类对象只需把记录插入到一张数据表中.
                    session.save(student);
                }
                @Test
                public void testGet() {
                    //1、使用Person查询1-Person、查询2-School正常
                    PersonI person1 = (PersonI) session.get(PersonI.class, 1);
                    System.out.println(person1.getName());
                    PersonI person2 = (PersonI) session.get(PersonI.class, 2);
                    System.out.println(person2.getName());
                    //1、使用Student查询1-Person报错NullPointerException、查询2-School正常
                    StudentI student1 = (StudentI) session.get(StudentI.class, 1);
                    System.out.println(student1.getName());
                    StudentI student2 = (StudentI) session.get(StudentI.class, 2);
                    System.out.println(student2.getName());
                }
                @Test
                public void testQuery() {
                    //查询父类记录, 需把父表和子表记录汇总到一起再做查询. 性能稍差.
                    Query from_person = session.createQuery("from PersonI");
                    List<PersonI> list = from_person.list();
                    System.out.println(list.size());//2
                    System.out.println(list.get(0).getName());
                    //查询子类记录, 也只需要查询一张数据表
                    List<StudentI> list2 = session.createQuery("from StudentI").list();
                    System.out.println(list2.size());//1
                    System.out.println(list2.get(0).getName());
                }
                @Test
                public void testUpdate(){
                    //更新父类表,也会自动更新子类表
                    String hql = "UPDATE PersonI p SET p.name = 'wang'";
                    session.createQuery(hql).executeUpdate();
                }
            }
    
  • 三种继承映射策略比较

    在这里插入图片描述

四、检索策略

  • 检索数据时的 2 个问题:
    • 不浪费内存:当 Hibernate 从数据库中加载 Customer 对象时, 如果同时加载所有关联的 Order 对象, 但程序实际上仅仅需要访问 Customer 对象, 那么这些关联的 Order 对象就白白浪费了许多内存.
    • 更高的查询效率:发送尽可能少的 SQL 语句

1、类级别的检索策略

  • 类级别可选的检索策略包括立即检索和延迟检索, 默认为延迟检索
    • 立即检索: 立即加载检索方法指定的对象
      • 如果程序加载一个对象的目的是为了访问它的属性, 可以采取立即检索.
    • 延迟检索: 延迟加载检索方法指定的对象。在使用具体的属性时,再进行加载
      • 如果程序加载一个持久化对象的目的是仅仅为了获得它的引用, 可以采用延迟检索。注意出现懒加载异常!
  • 类级别的检索策略可以通过 <class> 元素的 lazy 属性进行设置
    • 类级别检索策略设置只对load()方法有效;Session 的 get() 方法及 Query 的 list() 方法在类级别总是使用立即检索策略
    • lazy 属性为 true 或取默认值, Session 的 load() 方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例, 该代理类实例有如下特征:
      • 由 Hibernate 在运行时采用 CGLIB 工具动态生成
      • Hibernate 创建代理类实例时, 仅初始化其 OID 属性(即获取Oid也不会初始化代理类实例)
      • 在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例

2、一对多和多对多的检索策略

  • <set> 元素来配置一对多关联及多对多关联关系. <set>元素有 lazy 、 fetch、batch-size 属性
  • lazy:
    • 主要决定 orders 集合被初始化的时机. 有true(默认值),false,extra三种取值
  • fetch:
    • 取值为 “select” (默认值)或 “subselect” 时, 决定初始化 orders 的查询语句的形式; 若取值为”join”, 则决定 orders 集合被初始化的时机
    • 当 fetch 属性为 “subselect” 时
      • 假定 Session 缓存中有 n 个 orders 集合代理类实例没有被初始化, Hibernate 能够通过带子查询的 select 语句, 来批量初始化 n 个 orders 集合代理类实例;
      • batch-size 属性将被忽略
      • 子查询中的 select 语句为查询 CUSTOMERS 表 OID 的 SELECT 语句
    • 当 fetch 属性为 “join” 时
      • 检索 Customer 对象时, 会采用迫切左外连接(通过左外连接加载与检索指定的对象关联的对象)策略来检索所有关联的 Order 对象
      • lazy 属性将被忽略
      • Query 的list() 方法会忽略迫切左外连接检索策略, 而依旧采用延迟加载策略
  • batch-size :
  • 用来为延迟检索策略或立即检索策略设定批量检索的数量. 批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能.

3、多对一和一对一的检索策略

  • many-to-one元素也有一个 lazy、fetch属性.
    • lazy
      • 三种取值:proxy(延迟检索,默认值)、no-proxy(无代理延迟检索)、false(立即检索)
    • fetch
      • 两种取值:select(默认)、join(迫切左外连接)

      • 若 fetch 属性设为 join, 那么 lazy 属性被忽略

      • 迫切左外连接检索策略的优点在于比立即检索策略使用的 SELECT 语句更少.

      • 无代理延迟检索需要增强持久化类的字节码才能实现

      • Query 的 list 方法会忽略映射文件配置的迫切左外连接检索策略, 而采用延迟检索策略

  • 如果在关联级别使用了延迟加载或立即加载检索策略, 可以设定批量检索的大小, 以帮助提高延迟检索或立即检索的运行性能.
    • batch-size, 该属性需要设置在 1 那一端的 class 元素中
    • 作用: 一次初始化 1 端代理对象的个数.
  • Hibernate 允许在应用程序中覆盖映射文件中设定的检索策略.

4、demo

  • 持久化类

    package cn.freyfang.hibernate.j_search_strategy;
    public class CustomerJ {
        private Integer id;
        private String name;
        private Set<OrderJ> orders = new HashSet<>();
    }
    public class OrderJ {
        private Integer id;
        private String name;
        private CustomerJ customer;
    }
    
  • 映射文件

    <!-- CustomerJ.hbm.xml -->
    <hibernate-mapping package="cn.freyfang.hibernate.j_search_strategy">
        <class name="CustomerJ" table="customer_j" dynamic-update="true" lazy="true" batch-size="3">
            <id name="id" type="int" column="id">
                <generator class="native"/>
            </id>
            <property name="name" column="name" type="string"/>
            <set name="orders" table="order_j" inverse="true" lazy="true" batch-size="2" fetch="subselect">
                <key column="customer_id"></key>
                <one-to-many class="OrderJ"></one-to-many>
            </set>
        </class>
    </hibernate-mapping>
    <!-- OrderJ.hbm.xml -->
    <hibernate-mapping package="cn.freyfang.hibernate.j_search_strategy">
        <class name="OrderJ" table="order_j" dynamic-insert="true" dynamic-update="true" >
            <id name="id" type="int" column="id">
                <generator class="native"/>
            </id>
            <property name="name" column="name" type="string"/>
            <many-to-one name="customer" column="customer_id" class="CustomerJ" lazy="false" fetch="join">
            </many-to-one>
        </class>
    </hibernate-mapping>
    
  • 测试类

    package cn.freyfang.hibernate.j_search_strategy;
    public class TestSearchStrategy {
        @Test
        public void initDate() {
            CustomerJ customer = new CustomerJ("zhang3");
            OrderJ order1 = new OrderJ("Order-A");
            OrderJ order2 = new OrderJ("Order-B");
            order1.setCustomer(customer);
            order2.setCustomer(customer);
            session.save(customer);
            session.save(order1);
            session.save(order2);
        }
        @Test
        public void testClassLevelStrategy() {
            /* class的lazy
                1、默认为true延迟加载;当使用到代理对象的非oid属性时,才会发送sql
                    输出:cn.freyfang.hibernate.j_search_strategy.CustomerJ_$$_javassist_2
                    输出:1
                    输出:zhang3
                2、设置为false,立即检索
                    输出:cn.freyfang.hibernate.j_search_strategy.CustomerJ
                    输出:1
                    输出:zhang3
             */
            CustomerJ customer = (CustomerJ) session.load(CustomerJ.class, 1);
            System.out.println(customer.getClass().getName());
            System.out.println(customer.getId());
            System.out.println(customer.getName());
        }
    
        @Test
        public void testOne2ManyStrategy_Lazy() {
            /*  1-n、n-n的set的lazy属性
                默认为true,延迟加载,当使用到代理对象的属性时,才会查询
                设置为false,立即检索;不建议设置为  false.
                设置为extra,加强延迟检索策略,尽可能推迟集合被初始化的时机
                    访问集合属性的 size(), contains() 和 isEmpty() 方法时, 不会初始化, 仅通过特定的 select 语句查询必要的信息
             */
            CustomerJ customer = (CustomerJ) session.get(CustomerJ.class,1);
            //org.hibernate.collection.PersistentSet
            System.out.println(customer.getOrders().getClass().getName());
            System.out.println(customer.getOrders().size());//false,则加载;extra,则不加载
            OrderJ order = new OrderJ();
            order.setId(1);
            System.out.println(customer.getOrders().contains(order));
        }
        @Test
        public void testOne2ManyStrategy_BatchSize() {
            /* set 元素的 batch-size 属性
                设定一次初始化 set 集合的数量.
                    如batch-size=3,表里有7个customer,则初始化对应order集合时,分3次
                     ... from order_j where customer_id in (?, ?, ?)
             */
            List<CustomerJ> list = session.createQuery("from CustomerJ").list();
            for (CustomerJ customer : list) {
                if(customer.getOrders() != null) {
                    System.out.println(customer.getOrders().size());
                }
            }
        }
        @Test
        public void testOne2ManyStrategy_Fetch() {
            /* set 集合的 fetch 属性
                默认值为 select. 通过正常的方式来初始化 set 元素
                取值为 subselect. 通过子查询的方式来初始化所有的 set 集合.
                    子查询作为 where 子句的 in 的条件出现, 子查询查询所有 1 的一端的 ID. 此时 lazy 有效.
                    但 batch-size 失效.
                取值为 join. 则
                    在加载 1 的一端的对象时, 使用迫切左外连接(使用左外链接进行查询, 且把集合属性进行初始化)的方式检索 n 的一端的集合属性
                    忽略 lazy 属性.
                    HQL 查询忽略 fetch=join 的取值
             */
            //测试 subselect
    //        List<CustomerJ> list = session.createQuery("from CustomerJ").list();
    //        for (CustomerJ customer : list) {
    //            if(customer.getOrders() != null) {
    //                System.out.println(customer.getOrders().size());
    //            }
    //        }
            //测试 join
            CustomerJ customer = (CustomerJ) session.get(CustomerJ.class, 1);
            System.out.println(customer.getOrders().size());
        }
        @Test
        public void testMany2OneStrategy(){
            /*
            lazy
                取值为 proxy 和 false 分别代表对应对应的属性采用延迟检索和立即检索
            fetch
                取值为 join, 表示使用迫切左外连接的方式初始化 n 关联的 1 的一端的属性
                忽略 lazy 属性.
                Query 的 list 方法会忽略映射文件配置的迫切左外连接检索策略, 而采用延迟检索策略
            batch-size, 该属性需要设置在 1 那一端的 class 元素中:
                作用: 一次初始化 1 端代理对象的个数.
             */
            //测试join
    //		OrderJ order1 = (OrderJ) session.get(OrderJ.class, 1);
    //		System.out.println(order1.getCustomer().getName());
            //测试lazy、batch-size
            List<OrderJ> orders = session.createQuery("FROM OrderJ").list();
            for(OrderJ order: orders){
                if(order.getCustomer() != null){
                    System.out.println(order.getCustomer().getName());
                }
            }
        }
    }
    

五、检索方式

  • Hibernate 提供了以下几种检索对象的方式
    • 导航对象图检索方式: 根据已经加载的对象导航到其他对象
    • OID 检索方式: 按照对象的 OID 来检索对象
    • HQL 检索方式: 使用面向对象的 HQL 查询语言
    • QBC 检索方式: 使用 QBC(Query By Criteria) API 来检索对象. 这种 API 封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口.
    • 本地 SQL 检索方式: 使用本地数据库的 SQL 查询语句

1、HQL 检索方式

  • HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 它有如下功能:

    • 在查询语句中设定各种查询条件
    • 支持投影查询, 即仅检索出对象的部分属性
      • 查询结果仅包含实体的部分属性. 通过 SELECT 关键字实现.
      • Query 的 list() 方法返回的集合中包含的是数组类型的元素, 每个对象数组代表查询结果的一条记录
      • 可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录, 使程序代码能完全运用面向对象的语义来访问查询结果集.
      • 可以通过 DISTINCT 关键字来保证查询结果不会返回重复元素
    • 支持分页查询
      • setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult 表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0. 默认情况下, Query 从查询结果中的第一个对象开始检索
      • setMaxResults(int maxResults): 设定一次最多检索出的对象的数目. 在默认情况下, Query 和 Criteria 接口检索出查询结果中所有的对象
    • 支持连接查询
    • 支持报表查询
      • 报表查询用于对数据分组和统计
      • 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
      • 提供内置聚集函数, 如 sum(), min() 和 max()
    • 支持子查询
    • 支持动态绑定参数
      • Hibernate 的参数绑定机制依赖于 JDBC API 中的 PreparedStatement 的预定义 SQL 语句功能.
      • HQL 的参数绑定由两种形式:
        • 按参数名字绑定: 在 HQL 查询语句中定义命名参数, 命名参数以 “:” 开头.
        • 按参数位置绑定: 在 HQL 查询语句中用 “?” 来定义参数位置
      • 相关方法:
        • setEntity(): 把参数与一个持久化类绑定
        • setParameter(): 绑定任意类型的参数. 该方法的第三个参数显式指定 Hibernate 映射类型
    • HQL采用 ORDER BY 关键字对查询结果排序
    • 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
  • 支持在映射文件中定义命名查询语句

    • Hibernate 允许在映射文件中定义字符串形式的查询语句.
    • <query> 元素用于定义一个 HQL 查询语句, 它和<class> 元素并列.
    • 在程序中通过 Session 的 getNamedQuery() 方法获取查询语句对应的 Query 对象.
    • HQL (迫切)左外连接
      • 迫切左外连接:
        • LEFT JOIN FETCH 关键字表示迫切左外连接检索策略.
        • list() 方法返回的集合中存放实体对象的引用, 每个 Department 对象关联的 Employee 集合都被初始化, 存放所有关联的 Employee 的实体对象.
        • 查询结果中可能会包含重复元素, 可以通过一个 HashSet 来过滤重复元素
      • 左外连接:
        • LEFT JOIN 关键字表示左外连接查询.
        • list() 方法返回的集合中存放的是对象数组类型
        • 根据配置文件来决定 Employee 集合的检索策略.
        • 如果希望 list() 方法返回的集合中仅包含 Department 对象, 可以在HQL 查询语句中使用 SELECT 关键字
    • HQL (迫切)内连接
      • 迫切内连接:
        • INNER JOIN FETCH 关键字表示迫切内连接, 也可以省略 INNER 关键字
        • list() 方法返回的集合中存放 Department 对象的引用, 每个 Department 对象的 Employee 集合都被初始化, 存放所有关联的 Employee 对象
      • 内连接:
        • INNER JOIN 关键字表示内连接, 也可以省略 INNER 关键字
        • list() 方法的集合中存放的每个元素对应查询结果的一条记录, 每个元素都是对象数组类型
        • 如果希望 list() 方法的返回的集合仅包含 Department 对象, 可以在 HQL 查询语句中使用 SELECT 关键字
  • HQL 检索方式包括以下步骤:

    • 通过 Session 的 createQuery() 方法创建一个 Query 对象, 它包括一个 HQL 查询语句. HQL 查询语句中可以包含命名参数或占位符
    • 动态绑定参数
    • 调用 Query 相关方法执行查询语句.
  • Qurey 接口支持方法链编程风格, 它的 setXxx() 方法返回自身实例, 而不是 void 类型

  • HQL vs SQL:

    • HQL 查询语句是面向对象的, Hibernate 负责解析 HQL 查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句. HQL 查询语句中的主体是域模型中的类及类的属性
    • SQL 查询语句是与关系数据库绑定在一起的. SQL 查询语句中的主体是数据库表及表的字段.
  • 检索策略

    • 如果在 HQL 中没有显式指定检索策略, 将使用映射文件配置的检索策略.
    • HQL 会忽略映射文件中设置的迫切左外连接检索策略, 如果希望 HQL 采用迫切左外连接策略, 就必须在 HQL 查询语句中显式的指定它
    • 若在 HQL 代码中显式指定了检索策略, 就会覆盖映射文件中配置的检索策略

2、QBC 检索和本地 SQL 检索

  • QBC 查询就是通过使用 Hibernate 提供的 Query By Criteria API 来查询对象,这种 API 封装了 SQL 语句的动态拼装,对查询提供了更加面向对象的功能接口
  • 本地SQL查询可以用来完善HQL不能涵盖的查询特性

3、demo

  • 持久化类

    package cn.freyfang.hibernate.k_search_way;
    public class DepartmentK {
        private Integer deptId;
        private String deptName;
        private Set<EmployeeK> employees = new HashSet<>();
        @Override
        public String toString() {
            return "DepartmentK{" +
                    "deptId=" + deptId +
                    ", deptName='" + deptName + "'}";
        }
    }
    public class EmployeeK {
        private Integer id;
        private String name;
        private BigDecimal salary;
        private DepartmentK department;
        @Override
        public String toString() {
            return "EmployeeK{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", salary=" + salary +
                    ", department=" + department +
                    '}';
        }
    }
    
  • 映射文件

    <!-- DepartmentK.hbm.xml -->
    <hibernate-mapping package="cn.freyfang.hibernate.k_search_way">
        <class name="DepartmentK" table="department_k" dynamic-update="true" >
            <id name="deptId" type="int" column="dept_id">
                <generator class="native"/>
            </id>
            <property name="deptName" column="dept_name" type="string"/>
            <set name="employees" table="employee_k" inverse="true">
                <key column="department_id"></key>
                <one-to-many class="EmployeeK"></one-to-many>
            </set>
        </class>
    </hibernate-mapping>
    <!-- EmployeeK.hbm.xml -->
    <hibernate-mapping package="cn.freyfang.hibernate.k_search_way">
        <class name="EmployeeK" table="employee_k" dynamic-insert="true" dynamic-update="true" >
            <id name="id" type="int" column="id">
                <generator class="native"/>
            </id>
            <property name="name" column="name" type="string"/>
            <property name="salary" column="salary" type="big_decimal"/>
            <many-to-one name="department" column="department_id" class="DepartmentK">
            </many-to-one>
        </class>
        <!--在xml中使用CDATA包裹sql,防止<>冲突-->
        <query name="salaryEmps"><![CDATA[from EmployeeK where salary>:minSal and salary<:maxSal]]></query>
    </hibernate-mapping>
    
  • 测试类

    package cn.freyfang.hibernate.k_search_way;
    public class TestSearchWay_HQL {
        //测试 HQL动态绑定参数
        @Test
        public void testHQLParameter(){
            //1.基于位置的参数.
            String hql = "FROM EmployeeK WHERE id > ? AND name LIKE ? AND department = ? "
                    + "ORDER BY name";
            //创建 Query 对象
            Query query = session.createQuery(hql);
            //绑定参数:Query 对象调用 setXxx 方法支持方法链的编程风格.
            DepartmentK dept = new DepartmentK();
            dept.setDeptId(2);
            query.setInteger(0, 1)
                    .setString(1, "%a%")
                    .setEntity(2, dept);
            //执行查询
            List<Employee> emps = query.list();
            System.out.println(emps.size());
    
            //2、基于命名参数.
            String hql1 = "FROM EmployeeK WHERE id > :id AND name LIKE :name";
            //创建 Query 对象
            Query query1 = session.createQuery(hql1);
            //绑定参数
            query1.setInteger("id", 3)
                    .setString("name", "%wang%");
            //执行查询
            emps = query1.list();
            System.out.println(emps.size());
        }
        //测试 HQL分页查询 top
        @Test
        public void testPageQuery(){
            String hql = "FROM EmployeeK";
            Query query = session.createQuery(hql);
            int pageNo = 3;
            int pageSize = 2;
            List<Employee> emps =
                    query.setFirstResult((pageNo - 1) * pageSize)
                            .setMaxResults(pageSize)
                            .list();
            System.out.println(emps);
        }
        //测试 HQL投影查询
        @Test
        public void testFieldQuery(){
            //不加别名的话,报SQLServerException: 列名 'department' 无效。??
            String hql = "SELECT name,e.department FROM EmployeeK e WHERE e.department = :dept";
            Query query = session.createQuery(hql);
            DepartmentK dept = new DepartmentK();
            dept.setDeptId(2);
            List<Object[]> result = query.setEntity("dept", dept)
                    .list();
            for(Object [] objs: result){
                System.out.println(Arrays.asList(objs));
            }
        }
        @Test
        public void testFieldQuery2(){
            String hql = "SELECT new EmployeeK (name,e.department) "
                    + "FROM EmployeeK e WHERE e.department = :dept";
            Query query = session.createQuery(hql);
            DepartmentK dept = new DepartmentK();
            dept.setDeptId(2);
            List<EmployeeK> result = query.setEntity("dept", dept)
                    .list();
            for(EmployeeK emp: result){
                System.out.println(emp.getId() + ", " + emp.getName() + ", " + emp.getDepartment());
            }
        }
        //测试 HQL报表查询
        @Test
        public void testGroupBy(){
            String hql = "SELECT e.department.deptId,min(e.salary), max(e.salary) "
                    + "FROM EmployeeK e "
                    + "GROUP BY e.department "
                    + "HAVING min(salary) > :minSal";
            Query query = session.createQuery(hql)
                    .setFloat("minSal", 10);
            List<Object []> result = query.list();
            for(Object [] objs: result){
                System.out.println(Arrays.asList(objs));
            }
        }
        //测试 HQL在映射文件中定义HQL语句
        @Test
        public void testNamedQuery(){
            Query query = session.getNamedQuery("salaryEmps");
            List<Employee> emps = query.setBigDecimal("minSal", new BigDecimal(100))
                    .setBigDecimal("maxSal", new BigDecimal(1000))
                    .list();
            System.out.println(emps.size());
        }
        @Test
        public void testLeftJoinFetch(){
            //只查询一次,查询deparment和employee的所有属性
            String hql = "FROM DepartmentK d LEFT JOIN FETCH d.employees";
            Query query = session.createQuery(hql);
    
            List<DepartmentK> depts = query.list();
            //查询结果中可能会包含重复元素, 可以通过一个 HashSet 来过滤重复元素
            //要重写Department的hashcode()和equals()方法
            //但此处不重写也可以?因为hibernate查询出来的相同对象,引用同一个对象,调用object的hashcode()返回值都一样
            HashSet hashSet = new HashSet(depts);
            depts = new ArrayList<>(hashSet);
            System.out.println(depts.size());
            for(DepartmentK dept: depts){
                System.out.println(dept.getDeptName() + ":" + dept.getEmployees().size());
            }
        }
        @Test
        public void testLeftJoin(){
            String hql = "SELECT DISTINCT d FROM DepartmentK d LEFT JOIN d.employees";
            Query query = session.createQuery(hql);
            //虽然是左连接,但是只查询了department的属性
            List<DepartmentK> depts = query.list();
            System.out.println(depts.size());
            for(DepartmentK dept: depts){
                //每次都根据department_id去查询employee
                System.out.println(dept.getDeptName() + ":" + dept.getEmployees().size());
            }
        }
        @Test
        public void testInnerJoin(){
            String hql = "SELECT e FROM EmployeeK e INNER JOIN e.department";
            Query query = session.createQuery(hql);
            List<EmployeeK> emps = query.list();
            System.out.println(emps.size());
            for(EmployeeK emp: emps){
                System.out.println(emp.getName() + ", " + emp.getDepartment().getDeptName());
            }
        }
    }
    public class TestSearchWay_QBC_SQL {
        @Test
        public void testQBC(){
            //1. 创建一个 Criteria 对象
            Criteria criteria = session.createCriteria(EmployeeK.class);
            //2. 添加查询条件: 在 QBC 中查询条件使用 Criterion 来表示
            //Criterion 可以通过 Restrictions 的静态方法得到
            criteria.add(Restrictions.like("name", "%a%"));
            criteria.add(Restrictions.gt("salary", new BigDecimal(100)));
            //3. 执行查询
            List<EmployeeK> list = criteria.list();
            System.out.println(list);
        }
        @Test
        public void testQBC2(){
            Criteria criteria = session.createCriteria(EmployeeK.class);
            //1. AND: 使用 Conjunction 表示
            //Conjunction 本身就是一个 Criterion 对象
            //且其中还可以添加 Criterion 对象
            Conjunction conjunction = Restrictions.conjunction();
            conjunction.add(Restrictions.like("name", "a", MatchMode.ANYWHERE));
            DepartmentK dept = new DepartmentK();
            dept.setDeptId(2);
            conjunction.add(Restrictions.eq("department", dept));
            System.out.println(conjunction);
            //2. OR  使用Disjunction表示
            Disjunction disjunction = Restrictions.disjunction();
            disjunction.add(Restrictions.lt("salary", new BigDecimal(100)));
            disjunction.add(Restrictions.isNotNull("name"));
            criteria.add(disjunction);
            criteria.add(conjunction);
            criteria.list();
        }
        @Test
        public void testQBC3(){
            Criteria criteria = session.createCriteria(EmployeeK.class);
            //统计查询: 使用 Projection 来表示: 可以由 Projections 的静态方法得到
            criteria.setProjection(Projections.max("salary"));
            System.out.println(criteria.uniqueResult());
        }
        @Test
        public void testQBC4(){
            Criteria criteria = session.createCriteria(EmployeeK.class);
            //1. 测试排序
            criteria.addOrder(Order.asc("salary"));
            criteria.addOrder(Order.desc("name"));
            //2. 测试分页
            int pageSize = 2;
            int pageNo = 3;
            criteria.setFirstResult((pageNo - 1) * pageSize)
                    .setMaxResults(pageSize)
                    .list();
        }
        //测试 SQL
        @Test
        public void testNativeSQL(){
            String sql = "INSERT INTO department_k(dept_name) VALUES(?)";
            Query query = session.createSQLQuery(sql);
            query.setString(0, "aaa")
                    .executeUpdate();
        }
        @Test
        public void testHQLUpdate(){
            String hql = "DELETE FROM DepartmentK d WHERE d.id = :id";
            session.createQuery(hql).setInteger("id", 6)
                    .executeUpdate();
        }
    }
    

六、Hibernate 二级缓存

1、Hibernate一级和二级缓存

  • 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的
  • 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
    • 适合放入二级缓存中的数据:读多写少,允许出现偶尔的并发问题
    • 不适合放入二级缓存中的数据:写多,绝对不允许出现并发问题
    • SessionFactory 的缓存可以分为两类:
      • 内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据(.hbm.xml 文件中的数据)的复制. 该内置缓存是只读的.
      • 外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘

2、二级缓存的并发访问策略

  • 两个并发的事务同时访问持久层的缓存的相同数据时, 也有可能出现各类并发问题.
  • 二级缓存可以设定以下 4 种类型的并发访问策略, 每一种访问策略对应一种事务隔离级别
    • 非严格读写(Nonstrict-read-write): 不保证缓存与数据库中数据的一致性. 提供 Read Uncommited 事务隔离级别, 对于极少被修改, 而且允许脏读的数据, 可以采用这种策略
    • 读写型(Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读
    • 事务型(Transactional): 仅在受管理环境下适用. 它提供了 Repeatable Read 事务隔离级别. 对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读和不可重复读
    • 只读型(Read-Only):提供 Serializable 数据隔离级别, 对于从来不会被修改的数据, 可以采用这种访问策略

3、二级缓存架构

在这里插入图片描述

  • 时间戳缓存区域
    • 时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
      • T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
      • T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
      • T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果

4、使用EHCACHE缓存插件

  • Hibernate 的二级缓存是进程或集群范围内的缓存,二级缓存是可配置的插件

  • EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 对 Hibernate 的查询缓存提供了支持

    • 加入二级缓存插件的 jar 包

      <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-ehcache</artifactId>
          <version>3.6.8.Final</version>
      </dependency>
      
    • 加入配置文件ehcache.xml

      <ehcache>
          <!--  
          	指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
          -->     
          <diskStore path="d:\\tempDirectory"/>
          <!--  
          	设置缓存的默认数据过期策略 
          -->    
          <defaultCache
              maxElementsInMemory="10000"
              eternal="false"
              timeToIdleSeconds="120"
              timeToLiveSeconds="120"
              overflowToDisk="true"
              />
         	<!--  
         		设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
         		缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
         		如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
         		Hibernate 在不同的缓存区域保存不同的类/集合。
      			对于类而言,区域的名称是类名。如:cn.freyfang.hibernate.Customer
      			对于集合而言,区域的名称是类名加属性名。如cn.freyfang.hibernate.Customer.orders
         	-->
         	<!--  
         		name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字 
      		maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目 
      		
      		eternal: 设置对象是否为永久的, true表示永不过期,
      		此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false 
      		timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。
      		当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。 
      		timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
      		如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值 
      		
      		overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 
         	-->
          <cache name="cn.freyfang.hibernate.l_cache.EmployeeL"
              maxElementsInMemory="1"
              eternal="false"
              timeToIdleSeconds="300"
              timeToLiveSeconds="600"
              overflowToDisk="true"
              />
      
          <cache name="cn.freyfang.hibernate.l_cache.DepartmentL.employees"
              maxElementsInMemory="1000"
              eternal="true"
              timeToIdleSeconds="0"
              timeToLiveSeconds="0"
              overflowToDisk="false"
              />
      </ehcache>
      
    • 配置 hibernate.cfg.xml

      • 启用 hibernate 的二级缓存
        • <property name=“hibernate.cache.use_second_level_cache”>true</property>
      • 配置hibernate二级缓存使用的产品
        • <property name=“hibernate.cache.provider_class”>org.hibernate.cache.EhCacheProvider</property>
      • 配置对哪些类使用 hibernate 的二级缓存(类级别缓存)
        • <class-cache class=“cn.freyfang.hibernate.l_cache.EmployeeL” usage=“read-write”></class-cache>
        • 也可以在 .hbm.xml 文件中配置对哪些类使用二级缓存, 及二级缓存的策略是什么.
      • 配置集合级别的二级缓存
        • 配置对集合使用二级缓存
          • <collection-cache collection=“cn.freyfang.hibernate.l_cache.DepartmentL.employees” usage=“read-write”></collection-cache>
          • 也可以在 .hbm.xml 文件中set元素中配置<cache>
        • 注意: 还需要配置集合中的元素对应的持久化类也使用二级缓存! 否则将会多出 n 条 根据emp_id查询emp的语句,因为只会缓存emp_id,然后根据id去查emp
      • 配置查询缓存
        • 查询缓存依赖于二级缓存,所以先配置二级缓存
        • 默认情况下, 设置的缓存对 HQL 及 QBC 查询时无效的, 但可以通过以下方式使其是有效的
        • . 在 hibernate 配置文件中开启查询缓存
          • <property name=“hibernate.cache.use_query_cache”>true</property>
        • 调用 Query 或 Criteria 的 setCacheable(true) 方法
  • demo

    • 持久化类

      package cn.freyfang.hibernate.l_cache;
      public class DepartmentL {
          private Integer deptId;
          private String deptName;
          private Set<EmployeeL> employees = new HashSet<>();
      }
      public class EmployeeL {
          private Integer id;
          private String name;
          private BigDecimal salary;
          private DepartmentL department;
      }
      
    • 映射文件

      <!-- DepartmentL.hbm.xml -->
      <hibernate-mapping package="cn.freyfang.hibernate.l_cache">
          <class name="DepartmentL" table="department_l" dynamic-update="true" >
              <id name="deptId" type="int" column="dept_id">
                  <generator class="native"/>
              </id>
              <property name="deptName" column="dept_name" type="string"/>
              <set name="employees" table="employee_l" inverse="true">
                  <cache usage="read-write"/>
                  <key column="department_id"></key>
                  <one-to-many class="EmployeeL"></one-to-many>
              </set>
          </class>
      </hibernate-mapping>
      <!-- EmployeeL.hbm.xml -->
      <hibernate-mapping package="cn.freyfang.hibernate.l_cache">
          <class name="EmployeeL" table="employee_l" dynamic-insert="true" dynamic-update="true" >
              <cache usage="read-write"/>
              <id name="id" type="int" column="id">
                  <generator class="native"/>
              </id>
              <property name="name" column="name" type="string"/>
              <property name="salary" column="salary" type="big_decimal"/>
              <many-to-one name="department" column="department_id" class="DepartmentL">
              </many-to-one>
          </class>
      </hibernate-mapping>
      
    • 测试类

      package cn.freyfang.hibernate.l_cache;
      public class TestCache {
          //测试 类级别缓存
          @Test
          public void testHibernateSecondLevelCache(){
              EmployeeL employee = (EmployeeL) session.get(EmployeeL.class, 2);
              System.out.println(employee.getName());
              tx.commit();
              session.close();
              session = sessionFactory.openSession();
              tx = session.beginTransaction();
              EmployeeL employee2 = (EmployeeL) session.get(EmployeeL.class, 2);
              System.out.println(employee2.getName());
          }
          //测试 集合级别缓存
          @Test
          public void testCollectionSecondLevelCache(){
              DepartmentL dept = (DepartmentL) session.get(DepartmentL.class, 1);
              System.out.println(dept.getDeptName());
              System.out.println(dept.getEmployees().size());
              tx.commit();
              session.close();
              session = sessionFactory.openSession();
              tx = session.beginTransaction();
              DepartmentL dept2 = (DepartmentL) session.get(DepartmentL.class, 1);
              System.out.println(dept2.getDeptName());
              System.out.println(dept2.getEmployees().size());
          }
          //测试 查询缓存
          @Test
          public void testQueryCache(){
              //HQL
              Query query = session.createQuery("FROM EmployeeL");
              query.setCacheable(true);
              List<EmployeeL> emps = query.list();
              System.out.println(emps.size());
              emps = query.list();
              System.out.println(emps.size());
              //QBC
              Criteria criteria = session.createCriteria(EmployeeL.class);
              criteria.setCacheable(true);
              List list = criteria.list();
              System.out.println(list.size());
              list = criteria.list();
              System.out.println(list.size());
          }
          @Test
          public void testUpdateTimeStampCache(){
              Query query = session.createQuery("FROM EmployeeL");
              query.setCacheable(true);
              List<EmployeeL> emps = query.list();
              System.out.println(emps.size());
              EmployeeL employee = (EmployeeL) session.get(EmployeeL.class, 2);
              employee.setSalary(new BigDecimal(100));
              emps = query.list();
              System.out.println(emps.size());
          }
          /*
          iterator()
              同 list() 一样也能执行查询操作
              list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
              Iterator() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
                  当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID 的对象,
                   如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select 语句到数据库中加载特定的实体对象
          */
          @Test
          public void testQueryIterate(){
              DepartmentL dept = (DepartmentL) session.get(DepartmentL.class, 1);
              System.out.println(dept.getDeptName());
              System.out.println(dept.getEmployees().size());
              Query query = session.createQuery("FROM EmployeeL e WHERE e.department.id = 1");
              //查询employee的所有属性
      //		List<EmployeeL> emps = query.list();
      //		System.out.println(emps.size());
              //仅查询employee的id属性
              Iterator<EmployeeL> empIt = query.iterate();
              while(empIt.hasNext()){
                  System.out.println(empIt.next().getName());
              }
          }
          //自定义缓存
          @Test
          public void test() throws InterruptedException {
              CacheManager cacheManager = CacheManager.create();
              Cache cache = new Cache("diy.param", 10000, false, false, 10, 10);
              cacheManager.addCache(cache);
              cache.put(new Element("url","http://baidu.com"));
      //      [ key = url, value=http://baidu.com, version=1, hitCount=1, CreationTime = 1615531754549, LastAccessTime = 1615531754551 ]
              System.out.println("刚存:" + cache.get("url"));
              Element url = cache.get("url");
              System.out.println(url.getValue());//http://baidu.com
              Thread.sleep(1000*3);
              System.out.println("3s后:" + cache.get("url").getValue());//http://baidu.com
              Thread.sleep(1000*3);
              System.out.println("6s后:" + cache.get("url").getValue());//http://baidu.com
              Thread.sleep(1000*3);
              System.out.println("9s后:" + cache.get("url").getValue());//http://baidu.com
              Thread.sleep(1000*3);
              System.out.println("12s后:" + cache.get("url").getValue());//NullPointerException
          }
      }
      

七、批处理

  • 批量处理数据是指在一个事务中处理大量数据.在应用层进行批量操作, 主要有以下方式:
    • 通过 Session
      • Session 的 save() 及 update() 方法都会把处理的对象存放在自己的缓存中. 如果通过一个 Session 对象来处理大量持久化对象, 应该及时从缓存中清空已经处理完毕并且不会再访问的对象. 具体的做法是在处理完一个对象或小批量对象后, 立即调用 flush() 方法刷新缓存, 然后再调用 clear() 方法清空缓存
      • 通过 Session 来进行处理操作会受到以下约束
        • 需要在 Hibernate 配置文件中设置 JDBC 单次批量处理的数目, 应保证每次向数据库发送的批量的 SQL 语句数目与 batch_size 属性一致
        • 若对象采用 “identity” 标识符生成器, 则 Hibernate 无法在 JDBC 层进行批量插入操作
        • 进行批量操作时, 建议关闭 Hibernate 的二级缓存
      • 批量更新: 在进行批量更新时, 如果一下子把所有对象都加载到 Session 缓存, 然后再缓存中一一更新, 显然是不可取的
      • 使用可滚动的结果集 org.hibernate.ScrollableResults, 该对象中实际上并不包含任何对象, 只包含用于在线定位记录的游标. 只有当程序遍历访问 ScrollableResults 对象的特定元素时, 它才会到数据库中加载相应的对象.
        org.hibernate.ScrollableResults 对象由 Query 的 scroll 方法返回
    • 通过 HQL
      • 注意: HQL 只支持 INSERT INTO … SELECT 形式的插入语句, 但不支持 INSERT INTO … VALUES 形式的插入语句. 所以使用 HQL 不能进行批量插入操作.
    • 通过 StatelessSession
      • 从形式上看,StatelessSession与session的用法类似。StatelessSession与session相比,有以下区别:
        • StatelessSession没有缓存,通过StatelessSession来加载、保存或更新后的对象处于游离状态。
        • StatelessSession不会与Hibernate的第二级缓存交互。
        • 当调用StatelessSession的save()、update()或delete()方法时,这些方法会立即执行相应的SQL语句,而不会仅计划执行一条SQL语句
        • StatelessSession不会进行脏检查,因此修改了Customer对象属性后,还需要调用StatelessSession的update()方法来更新数据库中数据。
        • StatelessSession不会对关联的对象进行任何级联操作。
        • 通过同一个StatelessSession对象两次加载OID为1的Customer对象,得到的两个对象内存地址不同。
        • StatelessSession所做的操作可以被Interceptor拦截器捕获到,但是会被Hibernate的事件处理系统忽略掉。
    • 通过 JDBC API
      • 建议使用原生JDBC操作,速度最快

八、Hibernate配置文件

  • Hibernate配置文件可以有两种格式:hibernate.properties或hibernate.cfg.xml

  • Hibernate 配置文件主要用于配置数据库连接和 Hibernate 运行时所需的各种属性

  • 每个 Hibernate 配置文件对应一个 Configuration 对象

  • demo

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE hibernate-configuration SYSTEM
         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
     <session-factory>
         <!--jdbc连接属性-->
         <property name="hibernate.connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
         <property name="hibernate.connection.url">jdbc:sqlserver://localhost:1433;DatabaseName=hibernate3</property>
         <property name="hibernate.connection.username">sa</property>
         <property name="hibernate.connection.password">123456</property>
         <!--使用c3p0数据源
             1、引入jar
             <dependency>
                 <groupId>org.hibernate</groupId>
                 <artifactId>hibernate-c3p0</artifactId>
                 <version>3.6.8.Final</version>
             </dependency>
             2、配置连接信息
                 max_size: 数据库连接池的最大连接数
                 min_size: 数据库连接池的最小连接数
                 timeout:   数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
                 max_statements:  缓存 Statement 对象的数量
                 idle_test_period:  表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
                 acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
             3、可以在session.dowork()方法中打印connecton:com.mchange.v2.c3p0.impl.NewProxyConnection@2fb3536e判断是否生效
         -->
         <property name="hibernate.c3p0.max_size">10</property>
         <property name="hibernate.c3p0.min_size">5</property>
         <property name="hibernate.c3p0.acquire_increment">2</property>
         <property name="hibernate.c3p0.idle_test_period">2000</property>
         <property name="hibernate.c3p0.timeout">2000</property>
         <property name="hibernate.c3p0.max_statements">10</property>
         <!--配置数据库的方言,根据底层的数据库不同产生不同的 sql 语句,Hibernate 会针对数据库的特性在访问时进行优化-->
         <property name="hibernate.dialect">
             org.hibernate.dialect.SQLServerDialect
         </property>
         <!--是否将运行期生成的SQL输出到日志以供调试-->
         <property name="hibernate.show_sql">true</property>
         <!--是否将 SQL 转化为格式良好的 SQL -->
         <property name="hibernate.format_sql">true</property>
         <!--
         hbm2ddl.auto:该属性可帮助程序员实现正向工程, 即由 java 代码生成数据库脚本, 进而生成具体的表结构. 。
             取值 create | update | create-drop | validate
                 create : 会根据 .hbm.xml  文件来生成数据表, 但是每次运行都会删除上一次的表 ,重新生成表, 哪怕二次没有任何改变
                 create-drop : 会根据 .hbm.xml 文件生成表,但是SessionFactory一关闭, 表就自动删除
                 update : 最常用的属性值,也会根据 .hbm.xml 文件生成表, 但若 .hbm.xml  文件和数据库中对应的数据表的表结构不同, Hiberante  将更新数据表结构,但不会删除已有的行和列
                 validate : 会和数据库中的表进行比较, 若 .hbm.xml 文件中的列在数据表中不存在,则抛出异常
         -->
         <!--<property name="hibernate.hbm2ddl.auto">validate</property>-->
         <property name="hibernate.hbm2ddl.auto">update</property>
         <!--隔离级别-->
         <!--<property name="hibernate.connection.isolation">2</property>-->
         <!--删除对象后,将对象变为临时对象-->
         <!--<property name="hibernate.use_identifier_rollback">true</property>-->
         <!--
             实质是调用 Statement.setFetchSize() 方法设定 JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数。
             Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢
             mysql不支持此属性
         -->
         <property name="hibernate.jdbc.fetch_size">100</property>
         <!--
             设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。
             batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。
         -->
         <property name="hibernate.jdbc.batch_size">30</property>
    
         <!-- hibernate 二级cache-->
         <!--启用 hibernate 的二级缓存-->
         <property name="hibernate.cache.use_second_level_cache">true</property>
         <!--配置hibernate二级缓存使用的产品-->
         <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
         <!--启用查询缓存-->
         <property name="hibernate.cache.use_query_cache">true</property>
    
         <!-- 配置管理 Session 的方式
             - thread: Session 对象的生命周期与本地线程绑定
             - jta*: Session 对象的生命周期与 JTA 事务绑定
             - managed: Hibernate 委托程序来管理 Session 对象的生命周期
         -->
         <property name="hibernate.current_session_context_class">thread</property>
    
         <!-- List of XML mapping files -->
         <mapping resource="cn/freyfang/hibernate/_helloworld/Employee.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/a_component/PersonA.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/b_many2one/EmployeeB.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/b_many2one/DepartmentB.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/c_many2one_both/EmployeeC.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/c_many2one_both/DepartmentC.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/d_one2one_fk/DepartmentD.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/d_one2one_fk/ManagerD.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/e_one2one_pk/DepartmentE.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/e_one2one_pk/ManagerE.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/f_many2many_both/CategoryF.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/f_many2many_both/ItemF.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/g_extends_subclass/PersonG.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/h_extends_joined/PersonH.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/i_extends_union/PersonI.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/j_search_strategy/CustomerJ.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/j_search_strategy/OrderJ.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/k_search_way/EmployeeK.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/k_search_way/DepartmentK.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/l_cache/EmployeeL.hbm.xml"/>
         <mapping resource="cn/freyfang/hibernate/l_cache/DepartmentL.hbm.xml"/>
    
         <!-- 配置对哪些类使用 hibernate 的二级缓存
                 也可以在 .hbm.xml 文件中配置对哪些类使用二级缓存, 及二级缓存的策略是什么.
         -->
         <!--<class-cache class="cn.freyfang.hibernate.l_cache.EmployeeL" usage="read-write"></class-cache>-->
         <!--配置**集合级别**的二级缓存
                 也可以在 .hbm.xml 文件中set元素中配置<cache>
         -->
         <!--<collection-cache collection="cn.freyfang.hibernate.l_cache.DepartmentL.employees" usage="read-write"></collection-cache>-->
     </session-factory>
    </hibernate-configuration>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值