热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)

本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。

eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存

  • 1、前言
  • 2、项目实践
    • 2.1 jar包拉取
    • 2.2 数据库结构
    • 2.3 创建配置文件hibernate.cfg.xml
    • 2.4 创建ehcache.xml
    • 2.5 创建Teacher类
    • 2.6 Teacher.hbm.xml
    • 2.7 创建测试CacheLearn
  • 3、1级2级缓存相关概念以及缓存策略
    • 3.1一级缓存
    • 3.2 二级缓存
    • 3.3 缓存的并发策略
    • 3.4 缓存提供者
  • 4、说到2级缓存,那就要说1+n问题(这个了解一下就好了)
    • 4.1 list方法的缓存
  • 5、说到缓存,就不得不说get和load方法的区别
    • 5.1 对于Hibernate的get方法。
    • 5.2 对于Hibernate的load方法。
    • 5.3 说完它们的不同工作流程,那么来说一下它们如果出错会报什么异常吧
  • 项目地址


1、前言

这个我在操作的过程中遇到了很多知识点,不只是一篇文章了,我都看了一下然后外加自己研究操作做了一个实践纪录。

2、项目实践

2.1 jar包拉取

在这里插入图片描述

2.2 数据库结构

在这里插入图片描述
在这里插入图片描述

SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (`id` int(11) NOT NULL,`name` varchar(255) NOT NULL,`subject` varchar(255) NOT NULL,`favorite_food` varchar(255) NOT NULL,`height` double(30,0) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('2', 'li', 'chinese', 'apple', '168');
INSERT INTO `teacher` VALUES ('3', '移动', '电子科技', '钢铁', '1000000');
INSERT INTO `teacher` VALUES ('10', '老王', '英文', '桃子', '175');

2.3 创建配置文件hibernate.cfg.xml

hibernate.cfg.xml,这里主要看缓存那里即可。这里的mapping我把class注释是为了先不使用注释的方法,如果使用注释的方法实现2级缓存也可以,不过到时候类上要加入@Cache注解


"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/student?serverTimezone=UTC
root
root
10
true
org.hibernate.dialect.MySQLDialect




updatethreadtrue
true
true
ehcache


2.4 创建ehcache.xml

ehcache.xml,这个文件可以不写,不写就是使用我这里的default缓存。
这里可能会有人不理解为什么空闲时间跟生存时间不一致,为什么要设置这两个?因为你很有可能生存了9分钟,然后被访问了一次,空闲时间重置了,这个时候就不需要挂掉被删掉,还是要缓存。同时成立才要被删除这种设计挺不错的。






-->


2.5 创建Teacher类

Teacher,
@org.hibernate.annotations.Cache标注的3个属性
usage,设置二级缓存的并发策略,并发策略后面单拎出来说
region,设置二级缓存的区域,自定义的cache名称
include,设置是否缓存lazy的数据,all表示缓存lazy的数据,non-lazy表示只缓存非lazy的数据

package com.czx.cache.pojo;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;@Entity
@Table(name = "teacher")
//@Cache( usage=CacheConcurrencyStrategy.READ_WRITE,region="sampleCache1")
public class Teacher {@Id@Column(name = "id")private Integer id;@Column(name = "name")private String name;@Column(name = "subject")private String subject;@Column(name = "favorite_food")private String favoriteFood;@Column(name = "height")private Double height;public Teacher(Integer id, String name, String subject, String favoriteFood, Double height) {super();this.id = id;this.name = name;this.subject = subject;this.favoriteFood = favoriteFood;this.height = height;}public Teacher() {super();}@Overridepublic String toString() {return "Teacher [id=" + id + ", name=" + name + ", subject=" + subject + ", favoriteFood=" + favoriteFood+ ", height=" + height + "]";}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getFavoriteFood() {return favoriteFood;}public void setFavoriteFood(String favoriteFood) {this.favoriteFood = favoriteFood;}public Double getHeight() {return height;}public void setHeight(Double height) {this.height = height;}}

2.6 Teacher.hbm.xml

Teacher.hbm.xml,
cache 的3个属性
usage,设置二级缓存的并发策略,并发策略后面单拎出来说
region,设置二级缓存的区域,自定义的cache名称
include,设置是否缓存lazy的数据,all表示缓存lazy的数据,non-lazy表示只缓存非lazy的数据


一个teacher类的描述处理

2.7 创建测试CacheLearn

CacheLearn,这里我加入线程休眠是为了测试2级缓存是否过期,没啥问题的,自己注释打开就能得到不一样的结果了

package com.czx.cache;import java.util.Date;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;import com.czx.cache.pojo.Teacher;public class CacheLearn {
private static SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();public static void main(String[] args) {CacheLearn cacheLearn = new CacheLearn();cacheLearn.cache007();}public void cache007() {Session session = sessionFactory.openSession();Transaction transaction = session.beginTransaction();Teacher t = session.load(Teacher.class, 3);System.out.println(t.getFavoriteFood());Thread tt =new Thread();transaction.commit();
// System.out.println(new Date());//弄个线程处理休眠下,这里毫秒单位来的
// try {
// tt.sleep(60000L);
// }catch (Exception e) {
// // TODO: handle exception
// }
// System.out.println(new Date());Session session2 = sessionFactory.openSession();Transaction transaction2 = session2.beginTransaction();Teacher t2 = session2.load(Teacher.class, 3);System.out.println(t2);transaction2.commit();session.close();}
}

3、1级2级缓存相关概念以及缓存策略

下面有个图我们客户端通过session进行查询操作,这个时候我们会先去找一级缓存有没有,如果有就直接返回,如果没有就去找2级缓存有没有,如果有也可以直接返回给客户端。都没有的情况下去找数据库做相关操作,操作完成数据存到session1级缓存中去,然后在存到2级缓存去,最后返回给客户端。

在这里插入图片描述

3.1一级缓存

第一级缓存是 Session 缓存并且是一种强制性的缓存,所有的要求都必须通过它。Session 对象在它自己的权利之下,在将它提交给数据库之前保存一个对象。

如果你对一个对象发出多个更新,Hibernate 会尝试尽可能长地延迟更新来减少发出的 SQL 更新语句的数目。如果你关闭 session,所有缓存的对象丢失,或是存留,或是在数据库中被更新。

3.2 二级缓存

第二级缓存是一种可选择的缓存并且第一级缓存在任何想要在第二级缓存中找到一个对象前将总是被询问。第二级缓存可以在每一个类和每一个集合的基础上被安装,并且它主要负责跨会话缓存对象。

3.3 缓存的并发策略

并发策略
一个并发策略是一个中介,它负责保存缓存中的数据项和从缓存中检索它们。如果你将使用一个二级缓存,你必须决定,对于每一个持久类和集合,使用哪一个并发策略。
缓存并发策略,可以得知这里面的级别是从高到低的,Transactional最高最严谨,阻止脏读不可重复读,遇到下面就越不严谨,根据项目的具体情况去选择合适的。

名称含义
Transactional(事务型)仅在受管理环境下适用.它提供了RepeatableRead事务隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读
Read-write(读写型)提供ReadCommited数据隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读
Nonstrict-read-write(非严格读写)不保证缓存与数据库中数据的一致性.提供ReadUncommited事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略
Read-only(只读型)提供Serializable数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略

3.4 缓存提供者

下面这个其实主要看他能否集群,支持什么集群就够了。

缓存名称描述
EHCache它能在内存或硬盘上缓存并且集群缓存,而且它支持可选的 Hibernate 查询结果缓存。
OSCache支持在一个单独的 JVM 中缓存到内存和硬盘,同时有丰富的过期策略和查询缓存支持。
warmCache一个基于 JGroups 的聚集缓存。它使用集群失效但是不支持 Hibernate 查询缓存。
JBoss Cache一个也基于 JGroups 多播库的完全事务性的复制集群缓存。它支持复制或者失效,同步或异步通信,乐观和悲观锁定。Hibernate 查询缓存被支持。

下面这个的话是不同的缓存提供者支持什么类型的缓存策略。每一个缓存提供者都不和每个并发策略兼容。以下的兼容性矩阵将帮助你选择一个合适的组合。

策略/提供者Read-onlyNonstrictread-writeRead-writeTransactional
EHCacheXXX
OSCacheXXX
SwarmCacheXX
JBoss CacheXX

4、说到2级缓存,那就要说1+n问题(这个了解一下就好了)

我先说一下1+n是什么问题吧。
网络上有的说是如果你查询一个集合,但是呢,由于你查询的只是一个集合,返回的是一个id集合并没有真的去细查,到了真正要使用的时候,又会再去查一次数据库。
举例说明:
如果你使用的是iterate这个方法,那么就会出现这个问题,结果如下,你可以看看。(这个iterate已经过期了,了解一下看看吧)

Iterator iterator = session.createQuery("from Teacher").iterate();for(;iterator.hasNext();) {System.out.println(iterator.next());}

在这里插入图片描述但是,当你使用查询的list方法不会有1+n问题,所以我感觉没啥用,而且iterate方法也已经过期。这个1+n问题也可以通过2级缓存解决。

4.1 list方法的缓存

list() 默认情况只会放入缓存,不会从一级缓存中取!
使用查询缓存,可以让list()查询从二级缓存中取!
1、开启查询缓存后查询的list()可以从二级缓存中拿数据(list()不能从一级缓存[session缓存]中拿数据),但是不主动去拿,需要设置setCacheable(true)

5、说到缓存,就不得不说get和load方法的区别

get和load都是通过id获取到对应的数据。
关于这个get说法众说纷纭,各有各的看法,我自己操作看看才能知道结果。百度有的说get可以查2级,有人说不行,我自己测试不就知道了。
在我配置好了2级缓存的情况下,执行以下代码(以下是方法,调用一下即可)
这里我一开始使用get获取数据,关闭session,在获取一次数据看看,结果如下,显而易见,这个get是可以查2级缓存的。

public void cacheTest() {Session session = sessionFactory.openSession();Transaction transaction = session.beginTransaction();Teacher t = session.get(Teacher.class, 3);System.out.println(t);transaction.commit();session.close();Session session2 = sessionFactory.openSession();Transaction transaction2 = session2.beginTransaction();Teacher t2 = session2.get(Teacher.class, 3);System.out.println(t2);transaction2.commit();session2.close();}

在这里插入图片描述

5.1 对于Hibernate的get方法。

这里说下其实本质就是如果要查数据,先查看一下session一级缓存有没有数据,如果没有就去2级缓存中找,都没有再去查找数据库。其中每一个session是独立开来的,如果你关闭了session,那么里面的缓存也会丢失,但是2级缓存的介质不一样所以还能存起来,所以如果加入了2级缓存那么只要查一次就算清空session之后也还能查到数据。
这里有一个特例,如果数据库没有数据,那么就没法缓存,每一次去查都要去到数据库查为null的数据,这样就会造成缓存穿透。
看到这里如果你对上面概念还是不了解在建议看看下面我举得例子。
第一次查数据的情况:
首先启动session---->
然后去session缓存(一级缓存)中找。第一次是没有的---->
所以去更下级缓存中找,2级缓存有没有呢?其实还能继续往下找更高级的缓存,但是这里以2级缓存为例就够了。---->
缓存中找不到了,那么查数据库去,如果没有对应的数据,那么也会返回null,正常有数据就能返回对应的对象。然后将数据缓存到session缓存中。


连续二次查数据的情况下(没有关闭session,在一个session中查数据,这里2级缓存用不到):
第一次查询结束---->
接着查相同的hql/sql语句---->
去session缓存中找,找到了---->
返回对应的对象即可


连读二次查数据的情况(在不同的session)
第一次查询数据结束---->
关闭session---->
新建一个新的sessionA---->
使用sessionA查数据---->
sessionA缓存中没有数据,去查找数据库---->
将数据库数据返回


连读二次查数据的情况(在不同的session,启用了2级缓存)
第一次查询数据结束---->
关闭session–>
新建一个新的sessionA---->
使用sessionA查数据----->
sessionA中没有数据,去2级缓存中找,找到了---->
将2级缓存中内容返回

5.2 对于Hibernate的load方法。

如果你使用了get方法返回数据那么必定会有是数据库的查询语句,但是如果你使用的是load方法,那么就未必是了。
当我们使用session.load()方法来加载一个对象时,此时并不会发出SQL语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,只有当我们要使用这个对象(例如要打印这个数据),得到其它属性时,这个时候才会发出SQL语句,从数据库中去查询对象。如果你只是要获取id值,那么也并不会访问数据库。
实际上工作流程是这样的,这类似懒加载,我们一开始加载数据都去缓存中找,先一级后2级,找不到了,那么也先会返回一个代理对象,里面只带有对象id。到了我们要使用该对象(不是id的其他属性)的时候,那么才会真正去向数据库发请求,然后将数据缓存到session中去。第二次查找如果缓存没了那么跟第一次查找一样。缓存还在进行第二次查找,加载数据去缓存中找,返回一个对象的引用,到了要使用该对象的时候可以直接从缓存中获取了

第一次查找数据
使用load方法获取数据----->
去缓存中找数据,没有就返回一个代理对象---->
该代理对象带id,如果你要使用该对象除了id以外的属性那么就要去查询数据库------>
数据库获取到了数据,返回并将数据存到session缓存中去

第二次查找数据,缓存在
使用load方法获取数据------>
去缓存中找数据,发现有数据,于是就返回该对象的引用----->
然后获取属性可以去缓存中拿了。

5.3 说完它们的不同工作流程,那么来说一下它们如果出错会报什么异常吧

上面流程说过了,get是会从数据库获取对象的,那么如果数据库没有这个对象,我还要调用这个对象的属性,那么会报什么异常呢?java.lang.NullPointerException,空指针异常,是null的。
load方法会返回一个代理对象,那么如果数据库没有,我还要调用这个对象的属性会报什么异常?org.hibernate.ObjectNotFoundException,因为我们的代理对象只有id,其他属性都是没有的呀。

项目地址

这篇不是很建议看项目,因为我在里面不停的做不同情况下的测试,不过如果你想要看,那也行吧。
https://gitee.com/mrchen13427566118/ssh_hibernate_learn.git里面的ssh_hibernate_cache。
打开方式可以看这篇
https://blog.csdn.net/weixin_43987277/article/details/116936221里面的第三点。


推荐阅读
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • 本文介绍了使用Python解析C语言结构体的方法,包括定义基本类型和结构体类型的字典,并提供了一个示例代码,展示了如何解析C语言结构体。 ... [详细]
  • 本文主要复习了数据库的一些知识点,包括环境变量设置、表之间的引用关系等。同时介绍了一些常用的数据库命令及其使用方法,如创建数据库、查看已存在的数据库、切换数据库、创建表等操作。通过本文的学习,可以加深对数据库的理解和应用能力。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
author-avatar
zhj808008
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有