Hibernate学习笔记二:Hibernate缓存策略详解
一:为什么使用Hibernate缓存:
Hibernate是一个持久层框架,经常访问物理数据库。
为了降低应用程序访问物理数据库的频次,从而提高应用程序的性能。
缓存内的数据是对物理数据源的复制,应用程序在运行时从缓存中读取数据,在特定时间或事件会同步缓存和物理数据源的数据
二:什么是Hibernate缓存:
Hibernate缓存分为两种:一级缓存,二级缓存。
1.一级缓存:又称为Session缓存,
Session缓存是Hibernate内置的缓存,不能被卸载,生命周期也就是在open和close之间,也就是每打开一个Session,内存中就会创建一块Session缓存区。
Session缓存中,每个持久化类都拥有一个唯一的OID。(在Hibernate中,类对象分为四种状态:持久化,游离,临时,销毁)。
①.对于刚创建的一个对象,如果session中和数据库中都不存在该对象,那么该对象就是临时
对象(Transient)
②.临时对象调用save方法,或者游离对象调用update方法可以使该对象变成持久化对象,如果
对象是持久化对象时,那么对该对象的任何修改,都会在提交事务时才会与之进行比较,
如果不同,则发送一条update语句,否则就不会发送语句
③.游离对象就是,数据库存在该对象,但是该对象又没有被session所托管
④.销毁状态顾名思义,就是被Delete的数据,只有临时状态和游离状态才能转换为销毁状态
通过OID查询到数据都会存放在Session缓存(一级缓存)中。
2.二级缓存:又称为SessionFactory缓存。
SessionFactory缓存是用户可选的,默认情况下不会开启,可以选择不同的缓存提供商来进行配置。
SessionFactory缓存的声明周期是在应用程序运行到程序结束之间,就是说每一个程序只会拥有一个SessionFactory缓存,因为二级缓存是在进程范围或者
说集群范围,所以有可能出现并发问题,因此需要采用适当的并发缓存策略,该策略为被缓存的数据提供了事务隔离级别。
Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。
什么样的数据适合存放在二级缓存中
1>不会经常被修改的数据
2>常量数据
3>不是很重要的数据,允许偶尔出现并发的数据
4>不能被并发访问的数据,例如:银行账户
3.延迟加载
延迟加载是在用户查询某个实体时,加载这个实体的同时,并不会加载该实体所关联的其它对象,而是会产生一个代理对象,在真正使用到这个对象的时候,
才会通过在缓存中存放的该对象的OID去查询数据库,并返回查询结果,如果查询条件是除了主键OID外,都会直接去查询数据库,通过延迟加载也是大大
提高了应用程序的运行效率。
4.应用程序查询数据
在应用程序通过OID查询数据的时候,会先从一级缓存中查询,如果查不到就会从二级缓存中查询,都查不到才会从数据库中查找。
一级缓存和二级缓存的对比:
一级缓存 | 二级缓存 | |
存放数据的形式 | 相互关联的持久化对象 | 对象的散装数据 |
缓存的范围 |
由于每个事务都拥有单独的一级缓存 不会出现并发问题,因此无须提供并发访问策略 |
由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别 |
数据过期策略 |
处于一级缓存中的对象永远不会过期,除非应用程 序显示清空或者清空特定对象 |
必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间 |
物理介质 | 内存 |
内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中 |
缓存软件实现 | 在Hibernate的Session的实现中包含 |
由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中 |
启用缓存的方式 |
只要通过Session接口来执行保存,更新,删除, 加载,查询,Hibernate就会启用一级缓存,对 于批量操作,如不希望启用一级缓存,直接通过 JDBCAPI来执行 |
用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中 |
用户管理缓存的方式 |
一级缓存的物理介质为内存,由于内存的容量有限 ,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐 |
二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择 需要使用第二级缓存的持久化类,设置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不推荐 |
并发访问策略 | 由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略 |
由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别 |
接下来我们通过例子来讲解一下缓存的具体使用
1. 一级缓存的讲解
>>一级缓存主要分为两种:使用HQL语句操作和不使用HQL操作,两者差别主要在于是否是通过OID来操作物理数据库
先贴上代码:添加一个对象实体,以下Session对象都已经通过SessionFactory静态获取
public void addUser(){
Session session = null;
Transaction tran = null;
try{
session = sf.openSession(); // 创建一个Session
tran = session.beginTransaction(); //开启事务 User user = new User();
user.setName("王五");
session.save(user);
User user2 = (User) session.get(User.class, 4);
System.out.println(user2); tran.commit();//事务提交
}catch(Exception e){
tran.rollback(); //事务回滚
throw(e);
}finally{
session.close(); //关闭session
}
}
结果:
Hibernate: insert into user_1 (name) values (?)
User [id=4, name=王五]
结果很明显,数据库只有插入的操作,并没有查询的操作,之前有讲过,只要是通过OID操作的数据,都会保存在Session缓存中(即一级缓存),
既然缓存中有这条数据,那就没必要在数据库中找了
获得一个实体对象:获得对象Session中有get()和load()两个方法,load()方法比较特殊,会有延迟加载的特效(延迟加载在上面有讲)
@Test
public void getUser(){
Session session = null;
Transaction tran = null;
try{
session = sf.openSession(); // 创建一个Session
tran = session.beginTransaction(); //开启事务 /*这里指明你要获得哪个类型,Hibernate会根据类名查询映射配置文件到数据库查询哪张表,根据指定
* id查询实体,通过反射机制创建实体对象
*/
User user1 = (User) session.get(User.class, 1); //执行查询,get
System.out.println(user1);
User user2 = (User) session.get(User.class, 1);
System.out.println(user2); tran.commit();//事务提交
}catch(Exception e){
tran.rollback(); //事务回滚
throw(e);
}finally{
session.close(); //关闭session
}
}
>>结果:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=1, name=李四]
User [id=1, name=李四]
从结果可以看出:session通过OID查询只查询了一次,因为在第一次查询查询到的数据已经存放在了Session缓存中(一级缓存)中了,所以再次
获得该实体对象,应用程序会先查询Session缓存,既然查询到了,就不会再到数据库中查找,所以这里只有一条查询语句。
----以上的代码,我在获取两个User中间加上一行代码:
user1.setName("测试_2");
结果:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=1, name=测试_1]
User [id=1, name=测试_2]
Hibernate: update user_1 set name=? where id=?
从结果看出:在更新一条数据的时候,并不会立即去数据库进行更新操作,而是先更新Session缓存中的数据,在事务提交
或Session关闭的时候应用程序会发现Session缓存中的数据有改变,然后才进行更新数据库操作,在Session类中还有一个
flush()这个方法,和IO流相似,立即刷新数据到目的地,也就是立即把数据更新到数据库中,在这里我并没有使用。
更新操作和删除操作是一样的,就不多说了。。
>>使用Hibernate的HQL操作数据
HQL和SQL基本上一样的,区别:SQL是针对数据库表查询,而HQL是针对类查询;SQL不区分大小写,HQL对类名区分大小写。
使用HQL操作数据会把查询到的数据保存在缓存区中,但是不会从缓存中查找,而是直接到数据库查找
接下来使用HQL操作一下
List<User> list1 = session.createQuery("FROM User WHERE id=3").list();
System.out.println(list1);
List<User> list2 = session.createQuery("FROM User WHERE id=3").list();
System.out.println(list2);
结果:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
[User [id=3, name=张三]]
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
[User [id=3, name=张三]]
结果可以看出:这里进行了两次查询,说明第一次查询到的数据并没有进缓存,即使限定了id。
使用迭代器来查询
Iterator<User> it1 = session.createQuery("FROM User").iterate();
while(it1.hasNext()){
System.out.println(it1.next());
}
Iterator<User> it2 = session.createQuery("FROM User").iterate();
while(it2.hasNext()){
System.out.println(it2.next());
}
结果:
Hibernate: select user0_.id as col_0_0_ from user_1 user0_
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=2, name=张三]
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=3, name=张三]
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=4, name=王五]
Hibernate: select user0_.id as col_0_0_ from user_1 user0_
User [id=2, name=张三]
User [id=3, name=张三]
User [id=4, name=王五]
这里第一次是先查询到所有User的id,然后再根据id去查到所有实体对象,使用迭代器能够迫使程序能够通过id去查询
,只有通过OID操作的数据才会进缓存,尽管如此,通过这种方式提高的效率还是有限的,还是会产生大量的查询语句。
2. 二级缓存
二级缓存在Hibernate中默认是关闭的,需要在Hibernate.hbm.xml中配置开启,并配置缓存的提供商,除此之外还
要配置需要添加到缓存的类或集合(class-cache | collection-cache)
在hibernate.cfg.xml中的配置
<!-- 默认情况下二级缓存是关闭的 -->
<!-- 选择使用二级缓存的提供商 -->
<property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
<!-- 默认是false,这里选择的值是 查询缓存 -->
<property name="cache.use_query_cache">true</property>
前面我们使用HQL进行查询获得一个List集合,虽然也能缓存,但是又有局限性,接下来我们使用二级缓存进行查询
虽然配置了二级缓存,但是并没有指定要缓存的类,所以还要添加缓存类的配置,具体usage的值可以查询API文档
<!-- 在这里要指定缓存类的全限定名 -->
<class-cache usage="read-write" class="com.a_helloworld.User"/>
Session session1 = sf.openSession(); // 创建一个Session
Transaction tran1 = session1.beginTransaction(); //开启事务 List<User> list1 = session1.createQuery("FROM User WHERE id<10")
.setCacheable(true)
.list();
System.out.println(list1); tran1.commit();//事务提交
session1.close(); //关闭session
//第二个Session
Session session2 = sf.openSession(); // 创建一个Session
Transaction tran2 = session2.beginTransaction(); //开启事务 List<User> list2 = session2.createQuery("FROM User WHERE id<10")
.setCacheable(true)
.list();
System.out.println(list2); tran2.commit();//事务提交
session2.close(); //关闭session
结果:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id<10
[User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]
[User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]
结果看出只有一条查询语句,第二次查询并没有通过数据库查询,而是从缓存区直接拿到了数据。但是,如果
把第二次查询的条件修改一下,就需要从数据库查询,说明这里存储的只是HQL语句。
接下来就不贴代码了,说一下我自己经过测试的问题,大家可以自己亲身测试一下。
>>只要是经过配置的类,所有查询到的数据都会更新二级缓存中
>>进行更新或者删除操作,程序会通知缓存进行更新
>>现在很晚了,一点多了,有时间再改进吧。。。
如需转载,请说明出处:http://www.cnblogs.com/gudu1/p/6882155.html
Hibernate学习笔记二:Hibernate缓存策略详解的更多相关文章
- Redis学习笔记--Redis数据过期策略详解
本文对Redis的过期机制简单的讲解一下 讲解之前我们先抛出一个问题,我们知道很多时候服务器经常会用到redis作为缓存,有很多数据都是临时缓存一下,可能用过之后很久都不会再用到了(比如暂存sessi ...
- Redis学习笔记--Redis数据过期策略详解==转
本文对Redis的过期机制简单的讲解一下 讲解之前我们先抛出一个问题,我们知道很多时候服务器经常会用到redis作为缓存,有很多数据都是临时缓存一下,可能用过之后很久都不会再用到了(比如暂存sessi ...
- K8S学习笔记之Kubernetes 部署策略详解
0x00 概述 在Kubernetes中有几种不同的方式发布应用,所以为了让应用在升级期间依然平稳提供服务,选择一个正确的发布策略就非常重要了. 选择正确的部署策略是要依赖于我们的业务需求的,下面我们 ...
- 小程序学习笔记二:页面文件详解之 .json文件
页面配置文件—— pageName.json 每一个小程序页面可以使用.json文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项. 页面的 ...
- IP地址和子网划分学习笔记之《IP地址详解》
2018-05-03 18:47:37 在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文. ...
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- Hibernate学习笔记二
Hibernate持久化类的编写规则 Hibernate是持久层的ORM映射框架,专注于数据的持久化工作.所谓持久化,就是将内存中的数据永久存储到关系型数据库中. 持久化类 一个java类与数据库表建 ...
- Hibernate学习笔记(六) — Hibernate的二级缓存
我们知道hibernate的一级缓存是将数据缓存到了session中从而降低与数据库的交互.那么二级缓存呢? 一.应用场合 比方.在12306购票时.须要选择出发地与目的地,假设每点一次都与数据库交互 ...
- Hibernate:Hibernate缓存策略详解
一:为什么使用Hibernate缓存: Hibernate是一个持久层框架,经常访问物理数据库. 为了降低应用程序访问物理数据库的频次,从而提高应用程序的性能. 缓存内的数据是对物理数据源的复制,应用 ...
随机推荐
- 性能调优案例分享:Mysql的cpu过高
性能调优案例分享:Mysql的cpu过高 问题:一个系统,Mysql数据库,数据量变大之后.mysql的cpu占用率很高,一个测试端访问服务器时mysql的cpu占用率为15% ,6个测试端连服务 ...
- js全选checkbox框
html: <input type="checkbox" id="checkbox1" value="1" onclick=&quo ...
- Hibernate(三)之配置文件详解
一.核心配置文件(hibernate.cfg.xml) <?xml version="1.0" encoding="UTF-8"?> <!DO ...
- Android IPC机制全解析<一>
概要 多进程概念及多进程常见注意事项 IPC基础:Android序列化和Binder 跨进程常见的几种通信方式:Bundle通过Intent传递数据,文件共享,ContentProvider,基于Bi ...
- Java 基础知识总结
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.数据类型: 数据类型:1>.基本数据类型:1).数值型: 1}.整型类型(byte 8位 (by ...
- 设置ZooKeeper服务器地址列表源码解析及扩展
设置ZooKeeper服务器地址列表源码解析及扩展 ZooKeeper zooKeeper = new ZooKeeper("192.168.109.130:2181",SESSI ...
- C字符串处理函数
部分参考百科. C常用字符串函数:字符串输入函数,字符串输出函数,字符串处理函数,标准输入输出流 字符串处理函数: 1.字符串长度:strlen(str),返回字符串实际长度,不包括'\0',返回值类 ...
- Unity C# 多态 委托 事件 匿名委托 Lambda表达式 观察者模式 .NET 框架中的委托和事件
一.多态 里氏替换原则: 任何能用基类的地方,可以用子类代替,反过来不行.子类能够在基类的基础上增加新的行为.面向对象设计的基本原则之一. 开放封闭原则: 对扩展开放,意味着有新的需求或变化时,可以对 ...
- 业务订单号生成算法,每秒50W左右,不同机器保证不重复,包含日期可读性好
参考snowflace算法,基本思路: 序列12位(更格式化的输出后,性能损耗导致每毫秒生成不了这么多,所以可以考虑减少这里的位,不过留着也并无影响) 机器位10位 毫秒为左移 22位 上述几个做或运 ...
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(十二)数据层优化-explain关键字及慢sql优化
本文提要 从编码角度来优化数据层的话,我首先会去查一下项目中运行的sql语句,定位到瓶颈是否出现在这里,首先去优化sql语句,而慢sql就是其中的主要优化对象,对于慢sql,顾名思义就是花费较多执行时 ...