Hibernate(二)PO状态转换 & 一级缓存

Hibernate(二)PO状态转换 & 一级缓存.png

1.PO 状态及转换

PO(Persistent Object):持久化对象,主要作用是把内存中的数据保存到可永久保存的存储设备中。其主要应用是将内需中的对象存储在关系型数据库中。

  • 映射:Hibernate 采用 POJO(传统 Java 对象)充当持久化对象(PO),来与数据表进行映射(Map)

  • 编写规则:
    1.提供无参构造方法
    2.提供标识属性,用来映射书库表中的主键字段
    3.提供 set get 方法
    4.标识属性尽量使用基本数据类型的包装类
    5.PO 不能用 final 修饰(Hibernate 无法生成代理对象进行优化)

  • OID:对象标识符(Object identifier)用来标识一个唯一对象,在关系型数据库中,用主键来识别每条记录,在 Java 中通过比较引用地址来识别不同对象,而 Hibernate 为了解决两者之间的不同,使用 OID 来标识对象的唯一性。也就是说 OID 是关系型数据库中主键与 Java 对象模型中的等价物,其具有唯一性和不变性,一般由 Hibernate 或数据库来为其赋值。

1.1 - PO 三种状态

要想学习并理解 Hibernate 的方法及操作,必须要清楚 PO 的三种状态,下面是官方的定义,之后会进行更详细解释。

  • 瞬时态(Transient):由 new 操作符创建,且尚未与Hibernate Session 关联的对象被认为是瞬时态的,其不会被持久化到数据库中,也不会被赋予持久化标识符(identifier);如果瞬时台对象在程序中没有被引用,它将会被垃圾回收器(garbage collector) 销毁,使用 Hibernate Session 可以将其变为持久状态,并且 Hibernate 会自动执行必要的 SQL 操作。

  • 持久态(Persistent): 该实例 1.在数据库中有对应的记录,并且 2.拥有一个持久化标识符,持久态的实例可能是刚刚被保存的(save),或刚刚被加载(load)的,无论哪一种,按定义,它存在于相关联的 session 作用范围内,Hibernate 会检测到处于持久状态的对象的任何改动,在当前操作单元(unit of word) 执行完毕时将对象数据与数据库同步(syschronize)

  • 脱管态(Detached):与持久化对象关联的 session 被关闭后,对象就因为脱管态对象了,Hibernate 对脱管态对象的引用依然有效,对象可以继续被修改。脱管对象如果重新关联到某个新的 session,会再次转变为持久态(重要的:在脱管期间的改动在关联 session 后将被持久化到数据库中)。这使得程序事务编程称为可能,即中间会给用户思考时间(user think-time) 的长时间运行的操作单元(unit of work)。即从用户的角度看是一个操作单元(unit of word)。


  • 三种状态区别分析:
    • 持久态:其特点是与 session 进行关联,换句话说只要与 session 关联的 PO 就是持久态的。
    • 瞬时态与脱管态:脱管态具有 OID 而瞬时态不具有 OID。

  • 三种状态典型案例:
/* PO 对象的 3 种状态 */
@Test
public void testSave() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();

    /* 瞬时态:未与 session 关联,对象未持有 oid,数据库中不存在 */
    Customer customer = new Customer("Sawyer", 26);
    System.out.println(customer);

    /* 持久态:与 session 关联,对象持有 oid,数据库中存在 */
    session.save(customer);
    System.out.println(customer);

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

    /* 脱管态:与 session 失去联系,持有 oid,数据库中存在 */
    System.out.println(customer);
}

1.2 - PO 状态转换

PO状态转换
  • 瞬时态对象:new
    • => 持久态:save()、saveOrUpdate() 保存及更新操作。
    • => 脱管态:setId() 该 id 必须与数据库中已经存在的对象 id 一致,即 OID。
  • 持久态对象:session 查询方法获得(get()、load()、find()、iterate()、etc.)
    • => 瞬时态:delete() 从数据表移除,此时 PO 对象的 id 不再是 OID 即成为瞬时态。
    • => 脱管态:与 session 失去关联(evict()、close()、clear())
  • 脱管态对象:无法直接获得。
    • => 瞬时态:id 置为 null、手动删除数据库对应数据。
    • => 持久态:将对象放入 session 并重新关联(update()、saveOrUpdate()、lock())

2.Session 的一级缓存

2.1 - 概述

在 session 接口实现类中包含一系列的 Java 集合,这个 Java 集合构成了 session 缓存。

  • 作用:将数据缓存到内存或硬盘上,当访问这些数据时,可以直接从内存或硬盘加载数据,无需到数据库中查询,降低数据库压力,提高效率。

  • 生命周期:session 中的对象集合(map)随着 session 的开启而创建,随着 session 的关闭而销毁。

  • 缓存内容:只要是持久态对象,都会保存在一级缓存中(与 session 关联的本质)只要通过 session 操作对象都会经过一级缓存,并且由 Hibernate 维护,无法手动关闭。

  • 一级缓存存在性断点测试:

public class Learn3_firstCache {

    /*
    *
    * 测试一级缓存的存在性
    *
    * 两次读取数据
    *
    * */

    @Test
    public void testFirstCacheExist() {
        /* 一级缓存打开,但为空
         *
         * PersistenceContext[entityKeys=[]]
         *
         * */
        Session session = HibernateUtils.openSession();
        session.beginTransaction();

        /*
        *
        * 第一次查询
        *
        * 自动发出 sql 语句,查询数据库,将结果封装回实体对象,将对象放入一级缓存中
        *
        * PersistenceContext[entityKeys=[EntityKey[domain.Customer#1]]
        *
        * */
        Customer customer1 = session.get(Customer.class, 1);
        /* 1533662221 */
        System.out.println(customer1.hashCode());

        /*
        *
        * 第二次查询
        *
        * 优先从一级缓存中获取对象,不发送 sql 语句查询数据库
        *
        * sessionImpl(<closed>)
        *
        * */
        Customer customer2 = session.get(Customer.class, 1);
        /* 1533662221 */
        System.out.println(customer2.hashCode());

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

        /*
        *
        * sessionImpl closed
        *
        * */
        System.out.println(customer2);
    }
}

2.2 - 一级缓存快照(snapshot)

  • 作用:在内存中,将 PO 数据保存一份副本,一旦改变一级缓存数据,导致与快找数据不一致,Hibernate 框架可以感知这个改动。

  • 优势:在执行 flush / get / update 等操作时,与一级缓存数据比对,可以实现局部更新等优化操作,提高效率。

  • 设计原则:PO 尽量与数据库数据保持一致。

  • 原理:以 commit() 为例。当一个对象变为持久态与 session 绑定时,会在一级缓存中初始化一个该对象映射并保存一份该对象的一级缓存快照,在事务执行完毕调用 commit() 提交之前,会自动调用 flush() 将 PO 刷出到数据库,此时 Hibernate 会自动对该 PO 的一级缓存与一级缓存快照进行比对,如果不一致,则 Hibernate 会自动发送 update() 将 PO 与数据库同步(更新为一级缓存中数据),再执行 flush() 操作,该操作执行完毕后再次刷新一级缓存快照,此时完成了一级缓存、一级缓存快照、数据库三者的同步。

2.3 - 一级缓存刷出时机

  • 概述:session 能够在某些操作时,Hibernate 会比对一级缓存与一级缓存快照的差异性来自动执行相关的 SQL 语句,同步到数据库,这一操作被称为刷出缓存(flush)。

  • flush 时机:
    1.事务提交:执行 commit() 操作,会首先调用 flush() 刷出缓存,并提交事务将数据持久化到数据库。
    2.手动提交:调用 flush() 方法刷出数据库。
    3.查询操作:Query 查询(get、load 优先从一级缓存获取数据),如果快照与一级缓存不一致,则先执行 flush() 刷出到数据库(Hibernate 自动发送 update),再执行 select,保证查询到的结果能反应出持久化对象的最新状态。

  • 代码验证一级缓存刷出时机:

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

    /* 创建持久态对象 将 PO 放入一级缓存 */
    Customer customer1 = session.get(Customer.class, 1);
    System.out.println(customer1);

    /* 改变一级缓存数据 */
    /* 结论: 一级缓存数据被更新,等待 flush 进数据库并更新快照 */
    customer1.setName("Change");
    System.out.println(customer1);

    /* 通过 get() 获取 PO 对象 */
    Customer customer2 = session.get(Customer.class, 1);
    System.out.println(customer2);

    /* 通过 uniqueResult() / list() 查询 */
    /* 结论: 执行 update 语句将更新后的一级缓存数据 flush 到数据库中后,再执行 select 查询操作 */
    Customer customer3 = (Customer) session.createQuery("from domain.Customer where id = 1")
            .uniqueResult();
    System.out.println(customer3);

    /* 结论: 此时更新后的数据已经 flush 到数据库,并同步快照,一级缓存数据=快照数据 不会执行 update */
    session.flush();

    /* 结论: 一级缓存数据=快照数据 只提交 不 flush */
    session.getTransaction().commit();
    session.close();
}

2.4 - 一级缓存刷出模式

  • 作用:改变一级缓存的刷出时机
清理缓存模式 session 查询方法 tx 的 commit() session 的 flush()
FlushMode.AUTO(默认) 刷出 刷出 输出
FlushMode.COMMIT 不刷出 刷出 刷出
FlushMode.ALWAYS 刷出 刷出 刷出
FlushMode.MANUAL 不刷出 不刷出 刷出

2.5 - Session 常用 API

  • flush():刷出一级缓存到数据库。
    当一级缓存发生改变时,即和快照不一致,刷出一级缓存时,Hibernate 自动向数据库发送 update。

Reminder 💁‍♂️
MySQL 数据库:开启事务后,执行 flush() ,不执行 commit(),Hibernate 不会自动提交。
Oracle 数据库:开启事务后,执行 flush(),不执行 commit(),Hibernate 自动 commit()(Oracle 事务的提交:手动提交、正常关闭连接的时候也会隐式提交)

  • clear():清除一级缓存中所有对象。
    清除一级缓存中的所有对象,这些对象被清除后将由持久态 => 脱管态。

  • evict(Object object):清除一级缓存中指定对象。

  • refresh(Object object):将指定对象刷入一级缓存。
    刷入一级缓存中指定的对象,发送 select 语句将数据库的数据同步到一级缓存中,并同时更新一级缓存快照,即该对象从脱管态 => 持久态。

  • 代码示例:

public void method() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();

    /* 将 PO 放入一级缓存和快照 */
    Customer customer = session.get(Customer.class, 1);

    /* 将 session 中数据清空 其中 PO 变为脱管态 */
    session.clear();

    /* 将指定 PO 从 session 中清除 变为脱管态 */
    session.evict(customer);

    /* 脱管态 PO 不会被 flush 到数据库 */
    customer.setName("Change");

    /* 发送 select 语句,重新关联 session */
    session.refresh(customer);

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

3.CRUD 操作

3.1 - 保存操作

1.抢占主键问题?
2.持久态对象更改一级缓存对象 OID 问题?

  • 代码示例:
/* 保存操作 */
@Test
public void testSave() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();

    /* 瞬时态对象 */
    Customer customer = new Customer("Sawyer", 26);

    /*
    *
    * 调用保存方法 => 持久态
    *
    * 抢占主键问题:
    * 如果主键由数据库生成,则会发送 insert 去数据库抢占 id
    *
    * 如果主键策略是 assigned 则不需要 insert 抢占主键,而是 commit() 时提交
    * Hibernate 认为一级缓存中的数据已经存在于数据库中,这是持久态,但数据库中不存在该 id 行
    *
    * */
    session.save(customer);

    /* 更改一级缓存对象 OID 值则会抛出异常 */
    customer.setId(2);

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

3.2 - 更新操作

1.脱管态对象更新方式?
2.持久态对象更新方式?
3.核心配置中 <select> 的 selsect-before-update 字段优化?

  • 代码示例:
public void testUpdate() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();
    
    /* 先创建瞬时态对象 */
    Customer customer = new Customer("Sawyer", 26);
    customer.setId(1);
    /* 脱管态 => 持久态 */
    session.update(customer);
    
    /*
    * 
    * 快照更新
    * 
    * 先查询
    * 
    * 区别:update() 只能更新脱管态对象,快照更新必须更新持久态对象
    * 快照更新方便更新多字段 PO 对象
    * 
    * */
    Customer customer1 = session.get(Customer.class, 1);
    customer1.setName("Change");

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

Reminder 💁‍♂️
update() 默认一定会更新该 PO 对象所有字段,select 标签设置 selsect-before-update 字段优化后,先查后改,如果没变化则不改,select 数据库性能高,增删改数据库性能低。
不能对持久态 PO 使用 update():一级缓存如果已经存在相同 OID 对象,无法使用 update() 方法,update() 是脱管态变持久态,如果成功则一级缓存中会存在两个 OID 相同的 PO 对象

3.3 - 根据 OID 查询

1.get()、load() 区别?

  • 代码示例:
public void testFindByID() {
    Session session = HibernateUtils.openSession();
    session.beginTransaction();

    /*  
    * 
    * 通过查询方法 直接获得持久态对象
    * 
    * 相同:都优先查询一级缓存
    * 不同:1.是否延迟加载: load() 使用延迟加载,对缓存中存在的对象使用动态代理
    *       在访问时发送 sql 加载 
    *       节约内存资源
    *      2.返回结果处理方式: get() 查询不到返回 null,load() 查询不到抛出异常
    * */
    Customer customer = session.get(Customer.class, 1);
    Customer load = session.load(Customer.class, 1);
    System.out.println(customer == load);

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

3.4 - 删除操作

1.持久态 delete() 与 脱管态 delete() 区别?

  • 代码示例:
public void testDelete() {

    /* 持久态/脱管态 => 瞬时态 */

    Session session = HibernateUtils.openSession();
    session.beginTransaction();
    
    /* 删除脱管态对象 */
    Customer customer = new Customer("Sawyer", 26);
    customer.setId(1);
    session.delete(customer);

    /* 删除持久态对象 */
    Customer customer1 = session.get(Customer.class, 1);
    session.delete(customer1);

    /* 删除单表操作的脱管态和持久态无区别 */
    
    session.getTransaction().commit();
    session.close();
}

悄悄话 🌈

  • 最近在学习 Hibernate 框架的使用,在学习过程中也出现过很多困惑的地方,当我试图向 Google 求助时发现这部分内容的学习资源很稀少,只能凭着官方文档和源码硬着头皮挖下去,在看不到我认为可以让我信服的学习教程时,我只能选择这种方式学习才能让我心安。
  • Hibernate 这个框架已经由来已久了,随着更多的轻量级的框架和一些非关系型数据库的崛起这个框架已经在慢慢被冷落,那么还为什么要花时间去学习这个框架呢,因为所有新生的框架都是会在借鉴老框架的基础上,为了迎合部分需求而做的一些改动,Hibernate 与这些新生的框架来比,还是比较庞大且复杂丰富的,所以如果能掌握了这个框架,之后学习一些轻量级框架时就会驾轻就熟了。

彩蛋 🐣

  • 本篇没彩蛋!本篇没没彩蛋!!本篇没没没彩蛋!!!🐷🐷🐷

如果你觉得我的分享对你有帮助的话,请在下面👇随手点个喜欢 💖,你的肯定才是我最大的动力,感谢。

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

推荐阅读更多精彩内容