Hibernate框架——二级缓存(一)

性能分析

抓取策略

研究对象

研究怎么样提取集合,该策略应该作用于set元上
研究从少的一方加载多的一方

案例

查询id为1 的班级所有的学生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取cid为1的班级,再获取cid为1的班级的所有的学生
*/
@Test
public void testGetClassesAndStudent(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Classes classes = (Classes)session.get(Classes.class, 1L);
Set<Student> students = classes.getStudents();
for(Student student:students){
System.out.println(student.getName());
}
transaction.commit();
}

Classes中的set节点:

1
<set name = "studnet" cascade="all" fetch="join"></set>

执行上述的代码,在fetch=”join”的配置下,hibernate执行如下的SQL语句:
fetch="join"

说明: 通过一条SQL语句:做外连接,把classes与student表的数据全部提取出来。

如果是如下的策略:

1
<set name="students" cascade="all" fetch="select"></set>

先查询classes,再查询student如下图:
fetch=select

查询所有的班级的所有的学生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 获取到所有的班级的所有的学生
* 如果采用默认的策略,会导致n+1条sql语句,n为classes表中的行数
*/
@Test
public void testGetAllClassesAndStudent(){
Session session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
List<Classes> classesList = session.createQuery("from Classes").list();
for(Classes classes:classesList){
Set<Student> students = classes.getStudents();
for(Student student:students){
System.out.println(student.getName());
}
}
transaction.commit();
}

该需求翻译过来含有子查询
select s.* from student s where s.cid in(select cid from classes)
如果含有子查询,必须用subselect
改进:

1
<set name="students" cascade="all" fetch="subselect"></set>

将Classes配置文件中的set节点配置成如上后,hibernate输出如下的SQL信息:
fetch=subselect

查询班级为1,2,3,4的所有的学生

Classes配置文件中的set节点:

1
<set name="studnets" cascade="all" fetch="subselect"></set>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 查询cid为1,2,3,4的班级的学生
*/
@Test
public void testQueryStudentFromClassesIn(){
Session session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
List<Classes> classesList = session.createQuery("from Classes where cid in(1,2,3,4)").list();
for(Classes classes:classesList){
Set<Student> students = classes.getStudents();
for(Student student:students){
System.out.println(student.getName());
}
}
transaction.commit();
}

总结

  1. 研究对象是集合。
  2. 经过分析,如果SQL语句中包含有了子查询,则用subselect效率比较高。
  3. 如果页面上需要一次性把两张表的数据全部提取出来,用join效率比较高。
  4. 如果select,先查询学生,如果查询班级的个数超过1个,会导致n + 1条SQL语句。
  5. 抓取策略是hibernate提供的一种优化方式而已。

延迟加载(懒加载)

概念

需要用到数据的时候才要加载。延迟加载的优化措施是优化SQL语句的发出时间。

种类

类的延迟加载

1
2
3
4
5
6
7
8
9
10
@Test
public void testLoad(){
Session session = HibernateUtils.sessionFactory.openSession();
Classes classes = (Classes) session.load(Classes.class, 1L);
//没有发出SQL语句
System.out.println(classes.getCid());
//发出了SQL语句
System.out.println(classes.getName());
session.close();
}

hibernate执行的SQL的输出:
延迟加载

说明:

  1. 执行第一条输出classes.getCid()的时候,不发出SQL语句,说明类的延迟加载和主键没有关系。
  2. 执行第二条输出classes.getName()的时候,发出了SQL语句,说明在得到了具体的属性的时候才要发出SQL语句。
    3.Session.load()方法返回的时候,返回的对象是classes=Classes_$$_javassist_1,是一个代理对象,而该对象是由javassist的jar包生成的,从代码结构可以看出该代理对象是持久化类的子类。
  3. 在Classes.hbm.xml文件中
<class nam="com.wcc.hibernate.domain.Classes"  lazy="true">
<!--lzay的属性默认是true-->
  1. 如果把上述的lazy改成false,则类的延迟加载不起作用了,默认为延迟加载。

集合的延迟加载

案例一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 集合 的延迟加载
* < set name="students" cascade="all" fetch="subselect" lazy="true">
*/
@Test
public void testCollection(){
Session session = HibernateUtils.sessionFactory.openSession();
Classes classes = (Classes) session.get(Classes.class, 2L);
//在得到学生集合的时候是没有发出SQL语句的
Set<Student> set = classes.getStudnets();
//在具体的getDescription 才发出了SQL语句
for (Student student : set) {
System.out.println(student.getDescription());
}
}

说明:
1.lazy的取值有三种情况:extra、false、true。

  1. 默认情况下,lazy属性的默认值是true, 此时,集合的延迟加载只有在遍历集合的时候才会发出SQL语句
  2. lazy的值为false,当加载classes的时候就把student加载出来了
  3. lazy的值是Extra:是更进一步延迟加载策略,如果求大小,平均数、和等。

案例二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
*集合的延迟加载
*<set name="students" cascade="all" fetch="subselect" lazy="extra">
*/
@Test
public void testLayzyExtre(){
Session session = HibernateUtils.sessionFactory.openSession();
Classes classes = (Classes) session.get(Classes.class, 2L);
//得到学生的集合
Set<Student> set = classes.getStudnets();
//只加载了大小没有加载数据
System.out.println(set.size());
session.close();
}

当上述代码中在lazy=”true”的情况下运行的时候,执行set.size()的时候,会执行查询语句,但是我们只需要的是集合的size,当lazy=”extra”时,表示的是更进一步的延迟加载,需要什么就加载什么,当执行到上述的student.size() 的时候,发出如下的SQL语句:
Class_Lazy_extre_Select.png
该SQL语句只加载了大小,并没有加载具体的数据。

Many-to-one的延迟加载

总结

延迟加载是通过什么时候加载SQL语句来优化性能的

抓取策略和延迟加载的结合

研究对象

set集合

  1. 当fetch为join时,lazy失效。
  2. 当fetch为select时
  3. 如果lazy为true/extra,当遍历集合的时候,发出加载集合的SQL语句,如果lazy为false,当获取班级的时候,发出加载集合的SQL语句
  4. 当fetch为subselect时和上面的情况一致。

二级缓存

概念

  • 是sessionFactory级别的缓存
  • 存放的是公有数据:共享数据
  • 二级缓存的生命周期是随着hibernate容器启动就开了,hibernate销毁,结束。
  • hibernate本身对二级缓存没有提供实现,是借助第三方插件实现的。

特点

公有数据的特征:

  • 一般情况下保持不变
  • 所有的人都能访问
  • 访问的频率比较高
  • 安全性不是特别高的数据

配置

  1. 在hibernate的配置文件中

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 开启 二级缓存 -->
    <property name="cache.use_second_level_cache">true</property>
    <!-- 二级缓存的供应商 -->
    <property name="cache_provider_class">org.hibernate.cache.EhCacheProvider</property>
    <!-- 开启hibernate 的统计机制 -->
    <property name="hibernate.generate_statistics">true</property>
  2. 二级缓存分为类的二级缓存和集合的二级缓存

  3. 开启哪个类的二级缓存就是在哪一个类的映射文件中进行二级缓存的配置。例如在classes类中进行二级缓存
    1
    2
    3
    4
    <class name="com.wcc.hibernate.domain.Classes">
    <!--在class中增加cache结点-->
    <cache usage="read-only"/>
    </class>

案例

案例一

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testGet(){
Session session = HibernateUtils.sessionFactory.openSession();
Classes classes = (Classes) session.get(Classes.class, 1L);
//执行下面这条语句 ,session已经关闭了
session.close();
// 该session 是一个新session
session = HibernateUtils.sessionFactory.openSession();
//对同一个对象, 并没有发出SQL语句的
classes = (Classes) session.get(Classes.class, 1L);
session.close();
// 说明 当 第二次执行session.get()方法的时候,并没有执行SQL语句
}

说明: 当第二次执行session.get()方法的时候,并没有发出SQL语句。 Session.save(),Session.update(),方法不能让一个对象进入到二级缓存中。

案例二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testQuery(){
Session session = HibernateUtils.sessionFactory.openSession();
//把class表中的所有的数据放在二级缓存中
List<Classes> classes = session.createQuery("from Classes").list();
System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
session.close();
session = HibernateUtils.sessionFactory.openSession();
List<Classes> classes1 = session.createQuery("from classes where cid in(1, 2)").list();
for (Classes cl : classes1) {
Set<Student> set = cl.getStudnets();
for (Student student : set) {
//不能输出student的名字,获取不到
System.out.println(student.getName());
}
}
session.close();
}

说明:
1.执行List<Classes> classes = session.createQuery("from Classes").list();代码的时候,把classes表中的所有的对象进入到了二级缓存中。

  1. 执行List<Classes> classes1 = session.createQuery("from classes where cid in(1, 2)").list();的时候,重新从数据库中查找记录。
  2. 所以createQuery(hql).list();方法能把一个对象放入二级缓存中,但是不利于二级缓存获取对象。

案例三 硬盘缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="e:\\TEMP1"/>
<defaultCache
maxElementsInMemory="12"
eternal="false"
timeToIdleSeconds="1200"
timeToLiveSeconds="1200"
overflowToDisk="false"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<Cache
name="com.wcc.hibernate.domain.Classes"
maxElementsInMemory="3"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

从上面的配置可以看出,classes对象在内存中存放的数量最多为3个,多余的对象将存在磁盘上。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testOverToDIsk() throws Exception{
Session session = HibernateUtils.sessionFactory.openSession();
List<Classes> classes = session.createQuery("from Classes").list();
session.close();
Thread.sleep(1000l);
}
```查找classes表中所有的对象,在内存中放置3个对象,剩余的对象将被存放在磁盘上。
#### **案例:集合的二级缓存**
除了在配置文件中的class节点里面配置cache,在set节点中也是可以进行配置的,如下面的在Classeshm.xml文件中的Set节点中添加cache节点。
```xml
<cache usage="read-only"/>

上面相当于开启了classes类中的set集合的二级缓存。

读写策略

Usage
  • Ready-only :只能把一个对象放入到二级缓存中,不能修改。
  • Read-write :能把一个对象放入到二级缓存中,也能修改。
坚持原创技术分享,您的支持将鼓励我继续创作!