缓存
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
缓存:程序<--(内存)-->硬盘
( 1 )类级别缓存
类级别的缓存区域
* 存放的是对象的散装数据,散装数据使用OID从新组织一个新的对象,散装数据中存放的是类中属性的值
(2)集合级别的缓存
集合级别的缓存区域
* 存放的是对象的OID,如果要想获取真正的实体对象,还要到类级别的二级缓存中获取
总结:集合级别的缓存和查询级别的缓存都依赖于类级别的缓存
(3)查询级别的缓存
查询级别的缓存区域(重点),查询缓存指Query接口,Query接口支持HQL语句
* 存放的是对象的OID,如果要想获取真正的实体对象,还要到类级别的二级缓存中获取
总结:集合级别的缓存和查询级别的缓存都依赖于类级别的缓存
(4)更新时间戳级别的缓存
更新时间戳级别的缓存区域
* (1)将查询的对象放置到类、集合、查询级别的二级缓存中一份,而且设置放置对象的时间T1
* (2)当执行增、删、改操作的时候,更新时间戳会记录一个时间T2
如果当T1>T2的话,说明查询在后,更新在前,说明二级缓存中存放的数据是最新数据,那么此时从二级缓存中获取数据,不会查询数据库
如果当T1<T2的话,说明查询在前,更新在后,说明二级缓存中存放的数据不是最新数据,那么此时从二级缓存中获取数据,会查询数据库,查询到最新的结果
作用:保证二级缓存中的数据是最新的数据
二级缓存并发策略
l 两个并发的事务同时访问持久层的缓存的相同数据时, 也有可能出现各类并发问题.
l 二级缓存可以设定以下 4 种类型的并发访问策略, 每一种访问策略对应一种事务隔离级别
• 非严格读写(Nonstrict-read-write): 不保证缓存与数据库中数据的一致性. 提供 Read Uncommited 事务隔离级别, 对于极少被修改, 而且允许脏读的数据, 可以采用这种策略
• 读写型(Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读
• 事务型(Transactional): 仅在受管理环境下适用. 它提供了 Repeatable Read 事务隔离级别. 对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读和不可重复读
• 只读型(Read-Only):提供 Serializable 数据隔离级别, 对于从来不会被修改的数据, 可以采用这种访问策略(很强,但是性能低
二级缓存的应用场景:(跟钱有关的都不行)
l 适合放入二级缓存中的数据:
• 很少被修改
• 不是很重要的数据, 允许出现偶尔的并发问题
l 不适合放入二级缓存中的数据:
• 经常被修改
• 财务数据, 绝对不允许出现并发问题
• 与其他应用数据共享的数据
二级缓存的供应商
提供缓存的供应商
l Hibernate 的二级缓存是进程或集群范围内的缓存, 缓存中存放的是对象的散装数据
l 二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
• EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
二级缓存的配置:
2.开启二级缓存
去hibernate-distribution-3.6.10.Final\project\etc中查找二级缓存配置项
1
2
|
<!-- 开启二级缓存 -->
<
property
name
=
"hibernate.cache.use_second_level_cache"
>true</
property
>
|
3.设置二级缓存提供商
1
2
|
<!-- 设置二级缓存提供商 -->
<
property
name
=
"hibernate.cache.provider_class"
>org.hibernate.cache.EhCacheProvider</
property
>
|
分别开启类缓存,集合缓存
1
2
3
|
<!-- 开启类缓存和集合缓存 -->
<
class-cache
usage
=
"read-write"
class
=
"类名"
/>
<
collection-cache
usage
=
"read-only"
collection
=
"集合名"
/>
|
步骤1:从jar包复制xml文件
步骤2:将xml重命名“ehcache.xml”
步骤3:将修改后的xml,拷贝到src下
6.证明二级缓存的存在
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
30
31
|
@Test
public
void
demo01(){
//1 证明二级缓存存在
// * 修改toString()
// * 如果二级缓存开启,查询3 没有select语句,表示从二级缓存获得的。
// * 将二级缓存关闭,查询3将触发select语句。
Session s1 = factory.openSession();
s1.beginTransaction();
//1 查询id=1 -- 执行select (查询后,将数据存放在一级缓存,之后由一级缓存同步到二级缓存)
Customer c1 = (Customer) s1.get(Customer.
class
,
1
);
System.out.println(c1);
//2 查询id=1 --从一级缓存获取
Customer c2 = (Customer) s1.get(Customer.
class
,
1
);
System.out.println(c2);
s1.getTransaction().commit();
s1.close();
System.out.println(
"----------"
);
Session s2 = factory.openSession();
s2.beginTransaction();
//3 查询id=1 -- 从二级缓存获取
Customer c3 = (Customer) s2.get(Customer.
class
,
1
);
System.out.println(c3);
s2.getTransaction().commit();
s2.close();
}
|
类缓存
l 类缓存:只存放数据
l 一级缓存:存放对象本身
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
|
/**
* 知识点12:测试二级缓存和散装数据(类级别的二级缓存)
* 如果去掉<class-cache usage="read-write" class="cn.itcast.o_secondCache.Customer"/>
* 此时二级缓存中不能存放数据了,此时c3应该查询数据库
*/
@Test
public
void
testSecondCache(){
Session s = sf.openSession();
Transaction tx = s.beginTransaction();
Customer c1 = (Customer)s.get(Customer.
class
,
1
);
//产生select语句
Customer c2 = (Customer)s.get(Customer.
class
,
1
);
//不产生select语句,从Session的一级缓存中读取
tx.commit();
s.close();
//一级缓存没有了
/*******************************************************/
s = sf.openSession();
tx = s.beginTransaction();
Customer c3 = (Customer)s.get(Customer.
class
,
1
);
//没有,从SessionFactory的二级缓存中获取数据
Customer c4 = (Customer)s.get(Customer.
class
,
1
);
//不产生select语句,从Session的一级缓存中读取
System.out.println(c1==c2);
System.out.println(c2==c3);
System.out.println(c3==c4);
tx.commit();
s.close();
//一级缓存没有了
}
|
集合缓存
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
30
31
32
33
34
35
36
37
38
|
@Test
public
void
demo03(){
//3 集合缓存:只存放关联对象OID的值,如果需要数据,从类缓存中获取。
// * 3.1 默认:第一条select 查询客户,第二天 select 查询客户所有订单
// * 3.2 操作:在hibernate.cfg.xml 将 Order 类缓存删除
// *** <!-- <class-cache usage="read-write" class="com.itheima.a_init.Order"/>-->
// *** 多了10条select,通过订单的id查询订单
Session s1 = factory.openSession();
s1.beginTransaction();
//1 查询id=1
Customer c1 = (Customer) s1.get(Customer.
class
,
1
);
System.out.println(c1);
//2 获得订单
for
(Order o1 : c1.getOrderSet()) {
System.out.println(o1);
}
s1.getTransaction().commit();
s1.close();
System.out.println(
"----------"
);
Session s2 = factory.openSession();
s2.beginTransaction();
//3 查询id=1
Customer c3 = (Customer) s2.get(Customer.
class
,
1
);
System.out.println(c3);
//4 获得订单
for
(Order o2 : c3.getOrderSet()) {
System.out.println(o2);
}
s2.getTransaction().commit();
s2.close();
}
|
时间戳
l 时间戳:任何操作都在时间戳中记录操作时间。
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
30
31
32
33
34
|
@Test
public
void
demo04(){
//4 时间戳: 所有的操作都会在时间戳中进行记录,如果数据不一致,将触发select语句进行查询
// * 修改toString()
Session s1 = factory.openSession();
s1.beginTransaction();
//1 查询id=1
Integer cid =
1
;
Customer c1 = (Customer) s1.get(Customer.
class
, cid);
System.out.println(c1);
//2 绕过一级和二级缓存,修改数据库,修改客户cname=田志成
s1.createQuery(
"update Customer set cname = ? where cid = ?"
)
.setString(
0
,
"田志成"
)
.setInteger(
1
, cid)
.executeUpdate();
//3打印
System.out.println(c1);
s1.getTransaction().commit();
s1.close();
System.out.println(
"----------"
);
Session s2 = factory.openSession();
s2.beginTransaction();
//4 查询id=1 -- ?
Customer c3 = (Customer) s2.get(Customer.
class
,
1
);
System.out.println(c3);
s2.getTransaction().commit();
s2.close();
}
|
查询缓存
l 查询缓存又称为三级缓存(民间)
l 查询缓存默认不使用。需要手动开启
l 查询缓存:将HQL语句与 查询结果进行绑定。通过HQL相同语句可以缓存内容。
默认情况Query对象只将查询结果存放在一级和二级缓存,不从一级或二级缓存获取。
查询缓存就是让Query可以从二级缓存获得内容。
步骤一:开启查询缓存
1
2
|
<!-- 开启查询缓存 -->
<
property
name
=
"hibernate.cache.use_query_cache"
>true</
property
>
|
步骤二:在查询query对象,设置缓存内容(注意:存放和查询 都需要设置)
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
30
31
32
33
34
35
36
37
38
39
40
|
@Test
public
void
demo05(){
//5 查询缓存
Session s1 = factory.openSession();
s1.beginTransaction();
//1 query查询
Query q1 = s1.createQuery(
"from Customer"
);
q1.setCacheable(
true
);
List<Customer> a1 = q1.list();
for
(Customer c1 : a1) {
System.out.println(c1);
}
//2 cid =1 -- 一级缓存获得
Customer customer = (Customer) s1.get(Customer.
class
,
1
);
System.out.println(customer);
s1.getTransaction().commit();
s1.close();
System.out.println(
"----------"
);
Session s2 = factory.openSession();
s2.beginTransaction();
//2 cid =1 -- 二级缓存获得
Customer customer2 = (Customer) s2.get(Customer.
class
,
1
);
System.out.println(customer2);
//3 query查询
Query q2 = s2.createQuery(
"from Customer"
);
q2.setCacheable(
true
);
List<Customer> a2 = q2.list();
for
(Customer c2 : a2) {
System.out.println(c2);
}
s2.getTransaction().commit();
s2.close();
|
ehcache配置文件
l <diskStore path="java.io.tmpdir"/> 设置临时文件存放位置。(缓存一般内存,一定程度时,写入硬盘。)
l 缓存详细设置
<defaultCache> 所有的缓存对象默认的配置
<cache name="类"> 指定对象单独配置
l 参数设置
maxElementsInMemory="10000" 内存最大数
eternal="false" 是否永久(内存常驻留)
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" 内存满了,是否写入到硬盘
maxElementsOnDisk="10000000" 硬盘最大数
diskPersistent="false" 关闭JVM,是否将内存保存硬盘中
diskExpiryThreadIntervalSeconds="120" 轮询
memoryStoreEvictionPolicy="LRU"
Least Recently Used (specified as LRU).
First In First Out (specified as FIFO)
Less Frequently Used (specified as LFU)