- 观察问题:
- 进行两次次查询操作,查询的都是同一个对象,打印两次查询出的对象
@Test
public void testCache(){
Employee employee = (Employee) this.session.get(Employee.class,310);
Employee employee2 = (Employee) this.session.get(Employee.class,310);
System.out.println(employee);
System.out.println(employee2);
}
- 从结果中可以发现程序只执行了一次查询操作,这是因为同一个会话中相同id的持久化类对象会保存在Session的缓存中,所以第二次打印的时候,就不需要在此查询数据库了.
- 如果在第一次打印完之后,关闭了Session对象,那第二次在打印的时候会执行几次查询?
@Test
public void testCache(){
Employee employee = (Employee) this.session.get(Employee.class,310);
System.out.println(employee);
//提交事务和关闭session
this.transaction.commit();
this.session.close();
//重新获取session
this.session = this.sessionFactory.openSession();
this.transaction = this.session.beginTransaction();
Employee employee2 = (Employee) this.session.get(Employee.class,310);
System.out.println(employee2);
}
- 从结果中的值进行了两次的查询操作
- 缓存的作用
- 缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
- Hibernate中提供了两个级别的缓存
- 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的
- 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
- SessionFactory 的缓存可以分为两类:
- 内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据(.hbm.xml 文件中的数据)的复制. 该内置缓存是只读的.
- 外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘
什么数据适合放到二级缓存中?
- 适合放入二级缓存中的数据:
- 很少被修改
- 不是很重要的数据, 允许出现偶尔的并发问题
- 不适合放入二级缓存中的数据:
- 经常被修改
- 财务数据, 绝对不允许出现并发问题
- 与其他应用程序共享的数据
- Hibernate 二级缓存的架构
- Hibernate本身并没有实现二级缓存,需要使用插件来进行二级缓存管理
管理 Hibernate 的二级缓存
- Hibernate 的二级缓存是进程或集群范围内的缓存
- 二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
- EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 对 > Hibernate 的查询缓存提供了支持
- OpenSymphony OSCache:可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
- SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
- JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
- 4种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)
- 使用EHCache插件配置二级缓存.
- 加入插件jar包
- 配置ehcache.xml文件:在Hibernate下载的开发包中 \hibernate-orm\etc\ehcache.xml
3. 配置hibernate.cfg.xml文件
- 配置启用的hibernate的二级缓存
<!--启用二级缓存-->
<property name="cache.use_second_level_cache">true</property>
- 配置二级缓存使用的产品
<!--配置而二级缓存使用的产品-->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
- 配置那些类要使用二级缓存
- 在Employee.hbm.xml文件中配置二级缓存
<!--配置使用二级缓存-->
<cache usage="read-write"/>
- 再次启动测试类
- 此时只发送了一条查询语句
设置集合使用二级缓存
- 默认情况下,设置了类级别的二级缓存,但是与之关联的集合属性中的内容是不会保存到二级缓存中的.
- 示例:Department类中有一个emps集合保存的是Employess类实例化对象集合
@Test
public void testSetCache(){
Department department = (Department) this.session.get(Department.class,287);
System.out.println(department.getName());
System.out.println(department.getEmps().size());
//提交事务和关闭session
this.transaction.commit();
this.session.close();
//重新获取session
this.session = this.sessionFactory.openSession();
this.transaction = this.session.beginTransaction();
Department department2 = (Department) this.session.get(Department.class,287);
System.out.println(department2.getName());
System.out.println(department2.getEmps().size());
}
- 执行结果:
- 从执行结果中可以发现,在第一次查询过后第二次查询emps集合数量时,又进行了一次查询操作.
Hibernate:
select
department0_.ID as ID0_0_,
department0_.NAME as NAME0_0_
from
GG_DEPARTMENT department0_
where
department0_.ID=?
部门 = 6
Hibernate:
select
emps0_.DEPT_ID as DEPT5_0_1_,
emps0_.ID as ID1_,
emps0_.ID as ID1_0_,
emps0_.NAME as NAME1_0_,
emps0_.EMAIL as EMAIL1_0_,
emps0_.SALARY as SALARY1_0_,
emps0_.DEPT_ID as DEPT5_1_0_
from
GG_EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
10
部门 = 6
Hibernate:
select
emps0_.DEPT_ID as DEPT5_0_1_,
emps0_.ID as ID1_,
emps0_.ID as ID1_0_,
emps0_.NAME as NAME1_0_,
emps0_.EMAIL as EMAIL1_0_,
emps0_.SALARY as SALARY1_0_,
emps0_.DEPT_ID as DEPT5_1_0_
from
GG_EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
10
- 如果要配置集合的二级缓存有两种方式
- 在ehcache.xml文件中配置
<collection-cache
usage="read-write"
collection="com.atguigu.hibernate.entities.Department.emps"/>
- 在Department.hbm.xml文件中配置
<!--设置集合二级缓存-->
<cache usage="read-write"/>
- 设置完之后再次执行测试方法
- 发现在第二次查询集合数量时就直接输出了,没有再次执行查询操作
ehcache.xml配置文件
- 当缓存过多的时候,会占满内存空间,因此可以配置将缓存储存到磁盘中
- 修改ehcache.xml文件中的<diskStore>标签
<diskStore path="d:\\tempDirectory"/>
- Hibernate在Session的关闭时会自动清楚掉磁盘中的缓存文件
- ehcache配置文件中有一个默认的缓存配置,当没有自定义配置缓存的时候就会使用默认的缓存
- 如果要自定义缓存策略可以使用<cache>标签
- <cache> 设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache…/>- Hibernate在不同的缓存区域保存不同的类/集合。
- 对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
- 对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders
<cache name="mao.shu.vo.Department"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="60"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
查询缓存
- 默认情况下,设置的缓存对HQL和QBC查询时是无效的,但可以通过以下方式使其有效:
- 在hibernate配置文件中声明开启查询缓存
<!--开启查询缓存-->
<property name="cache.use_query_cache">true</property>
- 调Query.setCacheable()方法
@Test
public void testQueryCache(){
String hql = "FROM Employee";
Query query = this.session.createQuery(hql);
query.setCacheable(true);
System.out.println(query.list().size());
Criteria criteria = this.session.createCriteria(Department.class);
criteria.setCacheable(true);
}
- 查询缓存依赖于二级缓存.
更新时间戳缓存
- 时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
- T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
- T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
- T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 - UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果
Query的iterator方法
- Query 接口的 iterator() 方法
- 同 list() 一样也能执行查询操作
- list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
- Iterator() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
- 当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID 的对象, 如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select 语句到数据库中加载特定的实体对象
- 大多数情况下, 应考虑使用 list() 方法执行查询操作. iterator() 方法仅在满足以下条件的场合, 可以稍微提高查询性能:
要查询的数据表中包含大量字段
启用了二级缓存, 且二级缓存中可能已经包含了待查询的对象
- 该方法存在很多特定条件,不建议使用
@Test
public void tesetQueryIterator(){
Query queryTest = this.session.createQuery("FROM Employee ");
List<Employee> test = queryTest.list();
Query query = this.session.createQuery("FROM Employee ");
Iterator<Employee> list = query.iterate();
while(list.hasNext()){
System.out.println(list.next().getName());
}
}
- 当第一次使用查询已经将集合数据保存到了二级缓存之中,所以第二次使用iterator()方法查询的时候,并没有触发"select"语句,而是直接打印输出了.