WWWH学习法理解
1.W----->What:是什么?(首先引入问题,该知识点的初始,这是一个什么技术?)
Hibernate缓存机制分为一级缓存(session级缓存),二级缓存(sessionfactory)
缓存的作用主要用来提高性能,可以简单的理解成一个Map,使用缓存涉及到三个操作,把数据放入缓存,从缓存中获取数据,删除缓存中的无效数据。
2.W----->When:什么时候用?(接着利用该技术我们能够做什么?什么时候该用,什么时候不该用?)
· 一级缓存:
hibernate内部实现,不用手动配置。使用evict(),clear()方法可以清除session缓存里的对象。
· 二级缓存:
因为一级缓存内存有限,而且生命周期在session中有效,很短。所以这时会按需要配置二级缓存(sessionFactory级缓存)。
3.H------->How:怎么用?(然后就是该技术的用法,我们应该注意什么小细节?)
·一级缓存(session缓存):
该技术是hibernate内部实现,我们只需要了解该技术的实现原理就可以了。无需手动实现改技术。
A.什么时候向一级缓存中存放数据
save,update,saveOrUpdate,load,get,list,iterate,lock
这些方法会将对象放在一级缓存中,一级缓存不能控制缓存数量,所以要注意大批量操作数据可能造成内存溢出,可以使用evict,clear方法清除缓存中的内容(对象)。
·从hibernate缓存原理图,我们可以看出,当我们去查询对象的时候,首先到一级缓存取数据,如果有则不到数据库里面去取数据,同时在一级缓存中放入对象。
·一级缓存原理:
当第一次查询一条记录的时候Student stu1=(Student)session.get(Student.class, 2);首先会导致hibernate到一级缓存(session缓存)里面去查找数据,有没有id为2的学生在一级缓存里面存在,如果hibernate找不到改条记录,hibernate会向数据库里面去查询。这时找到2号学生的信息了。当找到2号学生以后,则放入一级缓存。当你使用对象2,Student stu2=(Student) session.get(Student.class, 2);查询id为2的学生时,hibernate会向一级缓存里面去查找,如果hibernate在一级缓存里面找到了id为2的学生信息,hibernate就不会向数据库里面去查找了,前提session是open的。只有查询一条新的记录时,hibernate才会向数据库发出sql语句进行查询。这就算是hibernate的一级缓存机制。
·一级缓存原理图:
·一级缓存的细节:
什么操作向一级缓存存取数据。
save,update,saveOrUpdate,load,get,list,iterate,lock
·save操作向一级缓存存入数据:
Student s=new Student();
s.setSaddress("成都");
s.setSage(21);
s.setSdept("化学系");
s.setSname("小小");
s.setSsex("M");
session.save(s);(放入一级缓存)
System.out.println("******************************");
马上查询
Studentss=(Student) session.get(Student.class, s.getSid());
System.out.println(ss.getSname());
ts.commit();
·控制台打印结果如下:
Hibernate:
insert
into
studentclass.dbo.student
(sname, ssex, sdept, sage, saddress)
values
(?, ?, ?, ?, ?)
******************************
小小
·理解:首先当save一个对象时hibernate会将该对象加入一级缓存(session级缓存),然后马上我有查询该对象,此时hibernate会向一级缓存里面查询数据,一旦发现该数据就不会向数据库发出sql语句查询了。当commit 的时候才向数据库保存该条记录。
B.什么操作会从一级缓存取出数据:
get load list
·get/load:
会首先从一级缓存中取数据,如果没有,再有不同的操作,get会立即向数据库发请求,而load会返回一个代理对象,知道用户真的使用数据时,才会向数据库发出请求。
·List:代码如下
Student s1=(Student)session.get(Student.class, 4);
System.out.println(s1.getSname());
System.out.println("------------------------------------------");
//这里使用uniqueResult(),因为返回的是一条记录。
Students2=(Student) session.createQuery("from Student wheresid=4").uniqueResult();
System.out.println(s2.getSname());
控制台打印结果:
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
周星驰
------------------------------------------
Hibernate:
select
student0_.sid as sid1_,
student0_.sname as sname1_,
student0_.ssex as ssex1_,
student0_.sdept as sdept1_,
student0_.sage as sage1_,
student0_.saddress as saddress1_
from
studentclass.dbo.student student0_
where
student0_.sid=4
周星驰
理解:由此可见query.list(),query.uniqueResult()不会从一级缓存中取出数据,但会放入数据。
·list会向一级缓存存放数据:
Student s2=(Student)session.createQuery("from Student where sid=4").uniqueResult();
System.out.println(s2.getSname());
System.out.println("------------------------------------------");
Students1=(Student) session.get(Student.class, 4);
System.out.println(s1.getSname());
控制台打印结果:
Hibernate:
select
student0_.sid as sid1_,
student0_.sname as sname1_,
student0_.ssex as ssex1_,
student0_.sdept as sdept1_,
student0_.sage as sage1_,
student0_.saddress as saddress1_
from
studentclass.dbo.student student0_
where
student0_.sid=4
周星驰
------------------------------------------
周星驰
理解:由此可见query.list(),query.uniqueResult()会向一级缓存存放数据。(因为控制台只打印了一条sql语句)
总结:get,load即向一级缓存存入数据,又向一级缓存取出数据,list只会想一级缓存存入数据,而不会向一级缓存取出数据。
C.一级缓存无需配置就可以使用,一级缓存本身没有保护机制,所以需要程序员需要通过evict或者clear来清除一级缓存(session缓存)中的对象。
·evict:
Studentstu1=(Student) session.get(Student.class, 2);
System.out.println(stu1.getSname());
session.evict(stu1);//这句话的意思是,在一级缓存中清除stu1这个对象
System.out.println("*************************");
Studentstu2=(Student) session.get(Student.class, 2);
System.out.println(stu2.getSname());
·控制台打印结果:
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
林青霞
*************************
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
林青霞
理解:由此可见当使用session.evict()方法时,将会在一级缓存中清除一个对象。而session.clear()则会清除所以session缓存对象。
D.session缓存中对象的生命周期,当session关闭后,就自动销毁。
Session session=null;
Transactionts=null;
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu1=(Student) session.get(Student.class, 7);
System.out.println(stu1.getSname());
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
System.out.println("------------------------------------------------");
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu2=(Student) session.get(Student.class, 7);
System.out.println(stu2.getSname());
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
·控制台打印结果:
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
王天鑫
------------------------------------------------
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
王天鑫
理解:由此可见对象的生命周期,在session关闭时被销毁。
E.利用HashMap模式一个session缓存。加深理解。
·代码如下:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 模拟缓存机制
*@author Administrator
*
*/
public class MyCache {
//使用map模拟缓存
staticMap<Integer, Student> map=new HashMap<Integer, Student>();
publicstatic void main(String[] args) {
getStudent(1);
getStudent(1);
getStudent(2);
getStudent(3);
getStudent(2);
}
publicstatic Student getStudent(Integer sid){
Studentstu=null;
//先到缓存取
if(map.containsKey(sid)){
//缓存中有
System.out.println("从缓存中取出了!");
}else{
System.out.println("从数据库中取出!");
//到数据库中取
stu=Mydb.getStudentFormDB(sid);
//放入到缓存中,这句话很重要,其重要是当到数据库中取出记录后马上就该记录放入缓存。
map.put(sid,stu);
}
returnstu;
}
}
//db
class Mydb{
staticList<Student> list=new ArrayList<Student>();
//程序会首先执行静态块,而且只执行一次
static{
//假如有三个学生
Studentstu1=new Student();
stu1.setSid(1);
stu1.setSname("aa");
Studentstu2=new Student();
stu2.setSid(2);
stu2.setSname("bb");
Studentstu3=new Student();
stu3.setSid(3);
stu3.setSname("cc");
//将学生放入集合
list.add(stu1);
list.add(stu2);
list.add(stu3);
}
//提供一个利用id查询一个学生的方法
publicstatic Student getStudentFormDB(Integer sid){
for(Studentstudent:list){
if(student.getSid().equals(sid))
returnstudent;
}
returnnull;//没有这个学生
}
}
//domain
class Student{
privateInteger sid;
privateString sname;
publicInteger getSid() {
returnsid;
}
publicvoid setSid(Integer sid) {
this.sid= sid;
}
publicString getSname() {
returnsname;
}
publicvoid setSname(String sname) {
this.sname= sname;
}
}
·控制台打印结果:
从数据库中取出!
从缓存中取出了!
从数据库中取出!
从数据库中取出!
从缓存中取出了!
执行原理:首先会到map集合(缓存)中取,如果发现没有与之对应的记录,就会到数据库取出数据。第二次也是先到map集合中取出记录,发现有与之对应的记录,就不会到数据库去查询记录了。
·二级缓存(sessionFactory缓存):
A.为什么需要二级缓存(sessionFactory级缓存)?
因为一级缓存内存有限,并且生命周期在一个session中有效(很短),所以需要二级缓存来弥补这个问题。
·需要配置二级缓存。
·二级缓存由第三方处理,常见的hashtable,OSCache,EHCache。
·二级缓存原理图:
由上图可见:当用户要查询一个对象的时候,先到一级缓存里面去获取,发现一级缓存里面没有要查询到该对象。就会到二级缓存里面去查询,这时发现二级缓存里面也没有查询到该对象,就会发出select语句向数据库里面去查询。此时查询到了结果。第二次继续查询该对象,到先一级缓存里面去查找数据,发现一级缓存evict,clear或者session是close的,所以在一级缓存里面没有查到该数据,就会到二级缓存里面去查询,因为二级缓存又叫sessionFactory级缓存,它的生命周期在sessionFactory里是有效的,所以此时就会查询到该对象了。
这就是hibernate的二级缓存机制(sessionFactory级缓存)。
B.二级缓存的对象可能放在内存,也可能放在磁盘。
·使用OSCache来演示二级缓存的使用:
a.将OSCache-2.1.jar文件add build path进项目中。
b.配置二级缓存:
<!-- 启用二级缓存-->
<propertyname="cache.use_second_level_cache">true</property>
<!-- 指定使用OSCache作为二级缓存-->
<propertyname="cache.provider_class">org.hibernate.cache.OSCacheProvider</property>
<!-- 指定对那个domain对象启用二级缓存-->
<class-cacheusage="read-write"class="com/wtx/domain/Student.hbm.xml"/>
·Hibernate二级缓存策略:
☞只读缓存(read-only)
☞读写缓存(read-write)【银行,财务软件】
☞不严格读写缓存(nonstrict-read-write)【bbs,被浏览了多少次,网站统计量】
☞事务缓存(transactional)
☞统计消息打开generate_statustucs;用户sessionFactory.getSatistics()获取统计信息。
案例演示:
Session session=null;
Transactionts=null;
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu1=(Student) session.get(Student.class, 7);
System.out.println(stu1.getSname());
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
System.out.println("------------------------------------------------");
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu2=(Student) session.get(Student.class, 7);
System.out.println(stu2.getSname());
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
控制台打印结果:
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
王天鑫
------------------------------------------------
王天鑫
理解:由此可见即便关闭了一级缓存(session级缓存),也只是向数据库发送了一条select语句,这就是hibernate的二级缓存机制。
C.二级缓存的命中率:
案例演示:
Session session=null;
Transactionts=null;
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu1=(Student) session.get(Student.class, 7);//1,1,0
System.out.println(stu1.getSname());
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
System.out.println("------------------------------------------------");
try{
//获取一个新的session
session=MySessionFactory.openSession();
ts=session.beginTransaction();
Studentstu2=(Student) session.get(Student.class, 7);//1,1,1
System.out.println(stu2.getSname());
Studentstu3=(Student) session.get(Student.class, 8);//2,2,1
System.out.println(stu3.getSname();
}catch (Exception e) {
if(ts!=null){
ts.rollback();
}
}finally{
if(session!=null&&session.isOpen()){
session.close();
}
}
//完成统计命中率,sessionFactory中完成
Statisticssti=MySessionFactory.getSessionFactory().getStatistics();
System.out.println("二级缓存放入 "+sti.getSecondLevelCachePutCount()+" 次");
System.out.println("二级缓存错过 "+sti.getSecondLevelCacheMissCount()+" 次");
System.out.println("二级缓存命中 "+sti.getSecondLevelCacheHitCount()+" 次");
·控制台打印结果:
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
王天鑫
------------------------------------------------
王天鑫
Hibernate:
select
student0_.sid as sid1_0_,
student0_.sname as sname1_0_,
student0_.ssex as ssex1_0_,
student0_.sdept as sdept1_0_,
student0_.sage as sage1_0_,
student0_.saddress as saddress1_0_
from
studentclass.dbo.student student0_
where
student0_.sid=?
张小小
二级缓存放入 2 次
二级缓存错过 2 次
二级缓存命中 1 次
D.在配置了二级缓存后,注意可以通过Statistics去查看命中率,失误越低(miss),命中越高(hit),说明配得越好。
·可以将oscache.properties文件放在src目录下,这样可以指定放入二级缓存对象capacity(容量)大小,默认1000个。
4.W----->Why:为什么要这么用?(最后,深入理解该技术为我们带来了什么优点,又有什么缺点。一门新技术的产生,必然是旧技术不能解决或者不能更好的解决该问题,那么用了该技术能够解决现有的问题吗?不用改技术可以吗?)
·为什么要用hibernate的一级缓存机制:
因为缓存机制可以为我们提高性能,当我反复多次的查询一个对象时,hibernate只需要向数据库发出一次select请求就可以查出数据。即第一次向一级缓存取出数据,当一级缓存发现没有与之对应的数据会向数据库查询,然后放入一级缓存,第二次查询只需要在一级缓存中取出即可。所以提高了查询效率。
·为什么要用二级缓存:
因为一级缓存内存有限,而且生命周期在session中有效,很短。所以这时会按需要配置二级缓存(sessionFactory级缓存)。并且二级缓存将对象保存在磁盘中,这样就可以解决一级缓存内存有限的问题了。