Hibernate的缓存章节
1.什么是缓存:
缓存(Cache )是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝,应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内存的速度显然比读写硬盘的速度快,如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
缓存的实现不仅需要作为物理介质的硬件,同时还需要用于管理缓存的并发访问和过期等策略的软件。因此,缓存是通过软件和硬件共同实现的
- 2.为什么使用缓存:
1)ORM框架访问数据库的效率直接影响应用程序的运行速度,提升和优化ORM框架的执行效率至关重要。
2)Hibernate的缓存是提升和优化Hibernate执行效率的重要手段,所以学会Hibernate缓存的使用和配置是优化的关键。
3)评判一个ORM框架是否优秀,访问数据库的频次就一个重要的标准
而我们今天讲的缓存:并不是指计算机的内存或者CPU的一二级缓存;我们今天讲的是Hibernate所能操作和管理的缓存,并不是内存条中所有的缓存
备注: 1)Hibernate中的缓存: 用于临时存放数据的内存区域,目的是为了减少应用程序对物理数据源访问的次数,从而提高了程序的运行性能
2)hibernate查询数据时,首先是到缓存中查找,如果找到就直接使用,如果找不到就再从物理数据源中查找
3.Hibernate中的缓存管理机制:
Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;
第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存,但是有些人称之为叫查询缓存,或者三级缓存(一般极少用)
一级缓存
- 1)Hibernate一级缓存又称为"Session缓存",“会话级缓存”
- 2)通过Session从数据库查询实体时把实体在内存中存储起来,下一次查询同一实体时不再从数据库获取,而是从内存中获取,这就是缓存
- 3)一级缓存的生命周期和Session相同;Session销毁,他也销毁
- 4)一级缓存中的数据可适用范围在当前会话之内
- 2.Hibernate怎样管理一级缓存
一级缓存是Hibernate的默认缓存,无法取消,用两个方法管理:
1)evict():用于将某个对象从Session的一级缓存中清除。
2)clear():用于将一级缓存中的所有对象全部清除
- contains(Object obj) 判断指定的对象是否存在于一级缓存中。
4)flush() 刷新一级缓存区的内容,使之与数据库数据保持同步。
详细参考项目:Maven_HibernateLx3_Acche中的Test.java
-
3.什么样的数据放在一级缓存:
1) 经常被修改的数据
2) 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发
3) 与其他应用共享的数据 -
上代码
一级缓存实例
- 查询两条相同的数据,发现sql只执行了一次
- 一级缓存是默认开启的
清除全部一级缓存
- session.clear();
- 清除之后session执行了2次
清除某一个对象的一级缓存
- session.evict(student)
判断某个对象是否在一级缓存中
-
boolean contains = session.contains(student);
刷新缓存
- session.flush();
二级缓存
- 1.二级缓存:
Hibernate提供了基于应用程序级别的缓存即为二级缓存,可以跨多个session,即不同的session都可以访问缓存数据。 这个缓存也叫二级缓存。
Hibernate提供的二级缓存有默认的实现,且是一种可插配的缓存配置!如果用户想用二级缓存,只需要在hibernate.cfg.xml中配置即可; 不想用,直接移除,不影响代码
PS:Hbernate的二级缓存就是sessionFactory可以依靠Ehcache插件实现 - 2.什么样的数据放在二级缓存:
1) 很少被修改的数据
2) 不是很重要的数据,允许出现偶尔并发的数据
3) 不会被并发访问的数据
4) 常量数据
EhCache
- 1.EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider
- 2.主要的特性有:
1)快速:EhCache直接作用于堆内存和非堆内存所以速度是超级快的,比redis还要快
2)简单:因为EhCache所依赖的包只有一个SLF4j.jar你不用过多的去关注,而且只是需要简单的配置或者写少许的代码即可
3)多种缓存策略:
LRU(Least Recently Used):最近最少使用。当我们把一个元素储存到Cache中或者从Cache中取出时都会更新该元素的最后使用时间。当采用最近最少使用原则进行驱除时会优先把最后使用时间最早的 元素进行驱除。
LFU(Least Frequently Used):最不常使用的。每次我们从Cache中获取一个元素时都会更新该元素的hitCount属性值加1。当采用最不常使用原则进行驱除时hitCount属性值最小的元素将优先驱除。
FIFO(First In First Out):先进先出。当采用这种驱除原则时将优先驱除最先储存的元素 - 4)缓存数据有两级:内存和磁盘,因此无需担心容量问题
- 5)缓存数据会在虚拟机重启的过程中写入磁盘
- 6)可以通过RMI、可插入API等方式进行分布式缓存:意思就是它也支持分布式的实现
- 7)具有缓存和缓存管理器的侦听接口
- 8)支持多缓存管理器实例,以及一个实例的多个缓存区域
- 9)提供Hibernate的缓存实现
使用:在pom中引入依赖
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.5.2</version>
</dependency>
java代码的方式实现缓存
package bj.ft.an.EhCache;
import org.ehcache.UserManagedCache;
import org.ehcache.config.builders.UserManagedCacheBuilder;
/**
* @author LXY
* @desc
* @time 2022-10-15 14:48
*/
public class TestEacache {
public static void main(String[] args) {
//实例化UserManagedCacheBuilder
//我们的key是int类型的
//我们的value是String类型的
UserManagedCache<Integer, String> map = UserManagedCacheBuilder.newUserManagedCacheBuilder(Integer.class, String.class).build();
//初始化
map.init();
for (int i = 0; i < 20; i++) {
map.put(i,"这是第"+i+"条");
}
String s = map.get(10);
System.out.println(s);
map.close();
}
}
- 查看UserManagedCacheBuilder的源码,存储的方式是kv类型的数据格式
通过xml的方式使用缓存
- java代码
package bj.ft.an.EhCache;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.Configuration;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.xml.XmlConfiguration;
/**
* @author LXY
* @desc
* @time 2022-10-15 15:02
*/
public class TestEacache2 {
public static void main(String[] args) {
//把配置文件转为url对象
java.net.URL myUrl = TestEacache2.class.getClass().getResource("/ehcache.xml");
//加载配置文件
Configuration xmlConfig = new XmlConfiguration(myUrl);
//获取缓存管理器
CacheManager cacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
cacheManager.init();//初始化
Cache<String, String> myCache = cacheManager.getCache("myCache", String.class, String.class);
myCache.put("name","张三");
System.out.println(myCache.get("name"));
cacheManager.close();
}
}
- ehcache.xml
<?xml version='1.0' encoding='UTF-8'?>
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
<cache-template name="myTemplate">
<key-type>java.lang.Long</key-type>
<value-type>java.lang.String</value-type>
<heap unit="entries">200</heap>
</cache-template>
<!-- 使用模板的情况下,模板的属性是可以进行覆盖的 -->
<cache alias="myCache" uses-template="myTemplate">
<key-type>java.lang.String</key-type>
</cache>
<!-- 过期策略配置 -->
<cache alias="myCache1">
<key-type>java.lang.String</key-type>
<value-type>java.lang.String</value-type>
<!-- 缓存过期策略配置,只能配置tti(time to idle)、ttl(time to live)、none、class之中的一种 -->
<expiry>
<!-- 空闲指定时间回收 -->
<tti unit="seconds">5</tti>
<!-- 存在指定时间回收 -->
</expiry>
<resources>
<!-- 堆内存使用配置 -->
<!-- 堆内存配置的空间不能大于堆外内存的配置空间 -->
<heap unit="entries">100</heap>
<!-- 1、堆外内存使用限制配置 -->
<!-- 2、使用的空间达到此处配置后,进行回收 -->
<!-- 3、最小配置为1M,超出指定空间后,无法正常保存(获取的结果为null) -->
<offheap unit="MB">1</offheap>
</resources>
</cache>
</config>
测试EhCache的缓存过期策略
package bj.ft.an.EhCache;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.Configuration;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.xml.XmlConfiguration;
import java.security.PrivateKey;
/**
* @author LXY
* @desc
* @time 2022-10-15 15:13
*/
public class TestEach3 {
public static void main(String[] args) {
//把配置文件转为url对象
java.net.URL myUrl = TestEacache2.class.getClass().getResource("/ehcache.xml");
//加载配置文件
Configuration xmlConfig = new XmlConfiguration(myUrl);
//获取缓存管理器
CacheManager cacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
cacheManager.init();//初始化
Cache<String, String> myCache = cacheManager.getCache("myCache1", String.class, String.class);
String str = "空间大小";
for (;str.getBytes().length<1024*1024;) {
str += str;
///将str这个变量给累加成一个大小为1MB的变量,目的是为了要测试一个在ehcahe.xml中配置的内存空间
}
myCache.put("name",str);
System.out.println(myCache.get("name"));
cacheManager.close();
}
}
Hibernate使用 EhCache开启二级缓存
- 重构Pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>bj.sh.fy</groupId>
<artifactId>Hibernatemaven</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<hibernate.version>5.2.12.Final</hibernate.version>
<mysql.driver.version>8.0.19</mysql.driver.version>
<ehcache.version>2.10.0</ehcache.version>
</properties>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
</project>
在 ehcache.xml文件中
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存 -->
<!--path:指定在硬盘上存储对象的路径 -->
<!--java.io.tmpdir 是默认的临时文件路径。 可以通过如下方式打印出具体的文件路径 System.out.println(System.getProperty("java.io.tmpdir")); -->
<diskStore path="E://hibernate" />
<!--defaultCache:默认的管理策略 -->
<!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断 -->
<!--maxElementsInMemory:在内存中缓存的element的最大数目 -->
<!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上 -->
<!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false -->
<!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问 -->
<!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问 -->
<!--memoryStoreEvictionPolicy:缓存的3 种清空策略 -->
<!--FIFO:first in first out (先进先出) -->
<!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存 -->
<!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存 -->
<defaultCache eternal="false" maxElementsInMemory="1000"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />
<!--name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里) -->
<cache name="bj.ft.an.enity.StudentEntity" eternal="false"
maxElementsInMemory="1" overflowToDisk="true" diskPersistent="true"
timeToIdleSeconds="0" timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
- 配置开启缓存
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- hibernate 的方言,就是hibernate连接的哪个数据库-->
<property name="dialect"> org.hibernate.dialect.MySQLDialect </property>
<!-- 连库4要素-->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernates</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!--hibernate的属性配置 这行的意思就是控制台打印sql语句-->
<property name="show_sql">true</property>
<property name="cache.use_query_cache">true</property>
<!-- hibernate5<property name="cache.use_query_cache">true</property>
的二级缓存配置 -->
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- EhCache驱动 -->
<!-- 注意:此处必须用hibernate的5.2.12.Final 版本 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!--hibernate的实体映射文件-->
<mapping resource="ClassesEntity.hbm.xml"/>
<mapping resource="StudentEntity.hbm.xml"/>
<mapping resource="UserEntity.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 测试类
package bj.ft.an.test;
import bj.ft.an.enity.ClassesEntity;
import bj.ft.an.enity.StudentEntity;
import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import org.hibernate.sql.Select;
import java.util.*;
/**
* @author LXY
* @desc
* @time 2022-10-15 11:35
*/
public class test {
public static void main(String [] args){
//1.加载配置
//默认去加载Src下的名字为hibernate.cfg.xml的文件为了启动hibernate 框架
Configuration config= new Configuration().configure();
//2.创建sesionFactory对象
SessionFactory sesionFactory =config.buildSessionFactory();
//3.获取session
Session session = sesionFactory.openSession();
//4.开启事务
Transaction transation = session.beginTransaction();
//5.执行持久化操作
Query from_studen = session.createQuery("from StudentEntity");
from_studen.setCacheable(true);
List list1 = from_studen.list();
System.out.println(list1.size());
List list2 = from_studen.list();
System.out.println(list2.size());
List list3 = from_studen.list();
System.out.println(list3.size());
session.flush();
//6.提交事务
transation.commit();
//7:关闭相关对象
session.close();
sesionFactory.close();
}
}
- 开启二级缓存(生效)
from_studen.setCacheable(true);
把这行注释就不生效了