hibernate缓存
- session缓存(一级缓存),sql查询结果缓存,由hibernate管理
- sessionFactory内置缓存,内置缓存是hibernate自带的,用于存放预定义的sql以及hbm.xml描述的元数据,不可卸载
- sessionFactory外置缓存(二级缓存),由外部插件提供,外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。
缓存的分类
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
1. 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
2. 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3. 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
缓存并发
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
1. 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
2. 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
3. 非严格读写型:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
因此二级缓存的适用场景:
1. 很少被修改的数据
2. 不是很重要的数据,允许出现偶尔并发的数据
3. 不会被并发访问的数据
4. 参考数据
不适用场景:
不适合存放到第二级缓存的数据?
1. 经常被修改的数据
2. 财务数据,绝对不允许出现并发
hibernate二级缓存插件
- ehcache、opensymphony oscache:进程范围缓存
- swarmcache、jbosscache:集群缓存
ehcache为例:
- 类级别缓存
- 集合级别缓存(一定要配合类级别缓存一起使用,否则它只是缓存obj的oid,然后根据一个个的oid去数据库分别获取,发送的sql反而增多)
- hql查询需要启用查询缓存,否则缓存不会生效,setCacheable(true)
- 更新时间戳缓:记录所有操作时间戳,通过比较时间戳得知缓存数据是否过期而需要重新向数据库查询
- query.list():查询的sql返回包含表中所有字段
- query.iterate(),一般使用list而非iterate:查询的sql只包含id,然后再根据id去查需要字段(通过缓存再到数据库),适用场景:表包含大量字段且二级缓存有缓存对象
List()和Iterator()的区别
执行的查询不同
1. list()方法在执行时,是直接运行查询结果所需要的查询语句,而iterator()方法则是先执行得到对象ID的查询,然后再根据每个ID值去取得所要查询的对象。因此,对于list()方式的查询通常只会执行一个SQL语句,而对于iterator()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数)。iterator()方法只是可能执行N+1条数据,具体执行SQL语句的数量取决于缓存的情况以及对结果集的访问情况。
2. 缓存的使用,list()方法只能使用二级缓存中的查询缓存,而无法使用二级缓存对单个对象的缓存(但是会把查询出的对象放入二级缓存中)。所以,除非重复执行相同的查询操作,否则无法利用缓存的机制来提高查询的效率。iterator()方法则可以充分利用二级缓存,在根据ID检索对象的时候会首先到缓存中查找,只有在找不到的情况下才会执行相应的查询语句。所以,缓存中对象的存在与否会影响到SQL语句的执行数量。
3. 对于结果集的处理方法不同,list()方法会一次获得所有的结果集对象,而且它会依据查询的结果初始化所有的结果集对象。这在结果集非常大的时候必然会占据非常多的内存,甚至会造成内存溢出情况的发生。iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。因此在访问中可以控制缓存中对象的数量,以避免占用过多缓存,导致内存溢出情况的发生。使用iterator()方法的另外一个好处是,如果只需要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。所以,对结果集的访问情况也是调用iterator()方法时执行数据库SQL语句多少的一个因素。