Session管理
分为两种方式:本地线程管理(必须配置开启本地线程),一种是自己hibernate管理(如果不配置)区别:本地线程管理的session的生命周期与本地线程绑定,线程结算session销毁(当然也可以手动关闭,一般都是需要的时候开启,执行完以后关闭),只要session开启以后直接中线程中获取即可(线程执行到哪里,session就跟随到哪里,很方便),用完以后关闭,以后的线程中没有了session,如果重新获取则是一个新的session,其实hibernate管理也是线程管理,当一个线程第一次调用session工厂(sessionFactory) 的getCurrentSession的方法时候会把session对象和线程绑定,并同时返回一个session对象,当本地线程再次用sessionFactory在次调用getCurrentSession的方法的时候,该方法会自动重线程中获取上一次的session对象,通过程序调试发现,两次获取的对象是同一个对象(内存地址一模一样),只有不手动关闭线程中的session,并且线程存在,无论获取多少次,都是同一个对象。这样一个线程(sessionn必须没有关闭和清空的情况)就可以共享一级缓存里面的数据了。但事物一旦提交(commit)session会自动清空一级缓存,但session并没有销毁,
二级缓存
区别:这里主要是和一级缓存的区别,本质和一级缓存一样都是一个map集合,一级缓存是session级别的存在,而二级缓存是sessionFactory级别的存在,一级缓存session产生一级缓存产生,session关闭一级缓存销毁,虽然本地线程管理session用的是同一个session,但是每次事务一提交,一级缓存就被清空了,所以就算是同一个session,数据无法再多个事务间共享,那就更别说多个线程之间共享了。而二级缓存是回话工厂级别的缓存,是属于进程级别的缓存,所以可以被多个线程共享,一般一个程序只会有一个会话工厂,所以2级缓存是应用级别的缓存,是被整个项目共享的,实现多用户之间的数据共享,一级缓存是hibernate的内置缓存,有hibernate自己进行管理,不能手动的进行干预。而二级缓存是有程序(项目)来管理的,我们可以手动的对二级缓存的数据进行更新和读写。
缓存分类:回话工厂级别的缓存(二级缓存)也分为两中,一种是hibernate自带的内置缓存,不可卸载,在hibernate初始化的时候加载一些映射元素数据和预定义的sql语句放到sessionFactory中的缓存中,映射元数据是映射文件中数据的赋值,而预定义sql语句时hibernate更具映射元数据推到出来的(比如查询该映射元素的对应的表结构,字段的多少,字段的类型),次缓存为只读缓存,不能修改。第二种是外置缓存,一个可以配置的缓存插件,默认情况下会话工厂不开启次缓存,区别是内置缓存是数据库数据(结构信息)的复制,外置缓存是需要有物理介质的比如内存和硬盘(一般是内存,硬盘就没意义了)。
简单结构:其实(外置)二级缓存也分为两种一直是普通二级缓策略存和查询二级缓存策略,我们一般说的二级缓存指的就是普通二级缓存策略。内存中大致上分为:类级别缓存区,集合缓存区,跟新时间戳缓存区(前三种是普通二级缓存策略),查询缓存区(属于查询缓存策略),要使用二级缓存必须要在hibernate核心配置文件中做对应的配置
读写简介:当用户吧数据放入一级缓存的时候,其实默认同时也就放入了二级缓存,当一级缓存销毁是,二级缓存中的数据还存在,所以当一级缓存不存在的时候如果查询某个之前查询过的对象,会自动重二级缓存中获取,而不会直接向数据库发送sql语句,和一级缓存的区别是重一级缓存中获取的是同一个对象(内存地址相同),但重二级缓存中获取的却不是同一个对象,但确实没有向数据库发送sql语句(内存地址不同),
存储结构: 其实二级缓存在存储数据的时候,做了一些特殊的处理,当数据从数据库加载到内存的时候,放入一级缓存(之前一级缓存的原理就不细说了),也罢数据放入二级缓存了,但是放入二级缓存的数据却不是对象,这一点和一级缓存有区别,一级缓存放入的是一个具体的对象,对象的属性是有值的,所以可以直接获取(并且是同一个对象),但数据哎放入二级缓存的时候,hibernate内部吧该对象装箱了(吧具体对象转化成了object对象),但该object对象依然具有和和原始对象一样的属性只不过类型却是object类型(也称散装数据),装箱的同时也把该对象的类型字符串(包名加类型)也保存了一份,具体结构是,吧该对象和该对象的类型字符串通过键值对保存了二级缓存(本质是一个map集合所以可以通过键值对保存),但键却很特殊的字符串,键是该类型字符串和该对象的oid(对呀数据主键)的拼接,通过”#”分割,具体是类字符串#oid,对应该对象(objec)。当重二级缓存中获取该数据(不能说是对象)的时候,会更具id和类字符去匹配二级缓存的所有key,如果能找到,则通过反射生产一个对象,并返回给用户(程序),这就很合理的解释了为什么不是同一个对象,因为是反射生产的,和很好的解释了为什么要保存该对象的类字符串,那么为什么要反射,而不直接吧该对象拆箱呢?,个人认为:如果拆箱(吧object转化为具体的po对象)就是一个对象,二级缓存是多个线程共享,如果多个用户同时公用同一个对象(内存地址的指向相同),会引发安全问题(反正这样不好),
缓存的同步:主要是讲一级缓存和二级缓存直接的同步,一级缓存可以同步二级缓存,二级缓存也可以同步一级缓存,当一个po对象被放入一级缓存(同时也放入二级缓存)如果修改该po对象的的属性,此时一级缓存(快照)的值会立刻别修改,但此时二级缓存的值不会立刻被修改,只有当程序commit的时候才会去跟新二级缓存的值,当二级缓存的值被修改的时候,会覆盖一级缓存里面的值,但必须重新开启一个session(其实没多大意义),其实想表达,一个线程修改2级缓存,会把最新的值同步到另一个线程的一级缓存中(但是该一级缓存在获取之前必须没有该对象的缓存)
更新数据:主要是跟新二级缓存里面的对象object(op)的属性的跟新,时间戳概念和用处,记录hibernate对对象数据的最后跟新时间,当有任何的动作(跟新和查询),然后判断要不要跟新二级缓存的数据,当数据被加入到二级缓存的时候,跟新该对象(object)的时间戳(第一次设置时间戳)t1,当以后有update的动作的时候,跟新时间戳t2,当再次查询,这里主要是针对别的线程,自己的线程(修改数据的那个线程)数据肯定是最新的,获取的时候也不会到二级缓存中获取(直接重一级缓存获取),检测两个时间戳的大小,如果t2>t1(更新时间戳大于查询时间戳),则表示该数据已经被修改过了,必须向数据库发sql语句,如果检测到t1>t2(查询时间戳大于跟新时间戳),表名该数据只被查询过,没有做任何的跟新,可以直接获取,而不必想数据库发sql语句,
读写机制:主要是对上面读写简介的加强和原理分析,list,get。Load(必须访问其属性)等,,只要是能把数据重数据库加载到内存中的方式都能把数据写入二级缓存,但list却不能重二级缓存中读数据,只有get和load才能冲二级缓存中读取数据,只能通过id(应该说oid,就是主键)才能获取,原因是内存里面的对象都有持久化对象(是以对象的形式存在的,而且还是键值对,没有什么规律). 所以必须用该对象的id(其实类字符串和id的拼接)才能到二级缓存中获取该对象,因为是以对象形式的存在,所以不能像写sql语句那样构造条件去查询一批的对象,就好比你不能通过构造条件在不遍历的情况下从一个map集合里面获取一批对象(当然获取一些没有规律的一批对象是可以的,比如,获取前10条),所以内存中的查找机制就是只能通过id(其实是类字符串和id的拼接)去获取。所以就算就构造了 where id=?,也是不行的(就算行,也是hibernate在内部做了处理,以为查找机制决定不能这样写),所以list不能重缓存中获取。
关联集合缓存:之前说的缓存都是针对类级别的缓存,在内存中其实出了类级别的缓存意外还有关联级别的缓存(要使用关联集合的缓存必须配置),次区域的缓存主要是放与缓存中关联数据的id(oid),关联级别的缓存必须依赖类级别的缓存区,区别是类级别的缓存区主要是放置的一些对象(po对象装箱而成的object对象),而关联集合的缓存区主要是关联数据的id,因为内存(缓存)只能通过oid去查找,所以关联结合只保存了id,其实最后在获取数据的时候,也是通过关联集合的id去二级缓存中通过id(id和类字符串的拼接,因为是关联数据,所以累字符串能通过被关联者获取)获取数据并返回的,说道这里要提一个方法iterator,iterator 是重缓存中获取一批数据,可以写sql语句,可以达到和list相同的功能(list,是重数据库,iterator是重缓存中获取),起iterator的原理也是重数据库获取,只不过iterator是重数据库获取满足条件的id(省区了很多字段,值保留id),然后把这一批id放入关联集合里面,然后通过next方法去二级缓存中获取(通过oid),其实也是通过id获取的,但是如果放入关联集合的id在二级缓存中没有与之对应的oid最后才会向数据库发送sql语句。随带提一下,所有的查询满足这样的规律,通过id到一级缓存中获取,如果有则返回,一级缓存没有就到二级缓存查找,如果有就返回,二级缓存没有到数据库,如果有则返回对象,数据库没有返回空。
其实第一次吧数据加入缓存,它也是遵循这样的规律的,程序是不知道你到底是不是第一次,只不过在一级缓存没找到,二级缓存也没找到,只好打数据找洛。
查询缓存 :可以通过自定义sql语句到缓存中查询一批数据(对象),但他的本质是保持sql语句和”结果”,查询缓存的key是sql语句,值是sql语句对应的结果集(有可能是对象,也有可能是集合),有局限性(sql语句是不固定的,)所以基本不用,因为基本在实际情况中用同一条(一模一样)的比较少,返回的结果也是固定的。