这个我在操作的过程中遇到了很多知识点,不只是一篇文章了,我都看了一下然后外加自己研究操作做了一个实践纪录。
2、项目实践
SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (`id` int(11) NOT NULL,`name` varchar(255) NOT NULL,`subject` varchar(255) NOT NULL,`favorite_food` varchar(255) NOT NULL,`height` double(30,0) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('2', 'li', 'chinese', 'apple', '168');
INSERT INTO `teacher` VALUES ('3', '移动', '电子科技', '钢铁', '1000000');
INSERT INTO `teacher` VALUES ('10', '老王', '英文', '桃子', '175');
hibernate.cfg.xml,这里主要看缓存那里即可。这里的mapping我把class注释是为了先不使用注释的方法,如果使用注释的方法实现2级缓存也可以,不过到时候类上要加入@Cache注解
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
ehcache.xml,这个文件可以不写,不写就是使用我这里的default缓存。
这里可能会有人不理解为什么空闲时间跟生存时间不一致,为什么要设置这两个?因为你很有可能生存了9分钟,然后被访问了一次,空闲时间重置了,这个时候就不需要挂掉被删掉,还是要缓存。同时成立才要被删除这种设计挺不错的。
Teacher,
@org.hibernate.annotations.Cache标注的3个属性
usage,设置二级缓存的并发策略,并发策略后面单拎出来说
region,设置二级缓存的区域,自定义的cache名称
include,设置是否缓存lazy的数据,all表示缓存lazy的数据,non-lazy表示只缓存非lazy的数据
package com.czx.cache.pojo;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;@Entity
@Table(name = "teacher")
//@Cache( usage=CacheConcurrencyStrategy.READ_WRITE,region="sampleCache1")
public class Teacher {@Id@Column(name = "id")private Integer id;@Column(name = "name")private String name;@Column(name = "subject")private String subject;@Column(name = "favorite_food")private String favoriteFood;@Column(name = "height")private Double height;public Teacher(Integer id, String name, String subject, String favoriteFood, Double height) {super();this.id = id;this.name = name;this.subject = subject;this.favoriteFood = favoriteFood;this.height = height;}public Teacher() {super();}@Overridepublic String toString() {return "Teacher [id=" + id + ", name=" + name + ", subject=" + subject + ", favoriteFood=" + favoriteFood+ ", height=" + height + "]";}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getFavoriteFood() {return favoriteFood;}public void setFavoriteFood(String favoriteFood) {this.favoriteFood = favoriteFood;}public Double getHeight() {return height;}public void setHeight(Double height) {this.height = height;}}
Teacher.hbm.xml,
cache 的3个属性
usage,设置二级缓存的并发策略,并发策略后面单拎出来说
region,设置二级缓存的区域,自定义的cache名称
include,设置是否缓存lazy的数据,all表示缓存lazy的数据,non-lazy表示只缓存非lazy的数据
CacheLearn,这里我加入线程休眠是为了测试2级缓存是否过期,没啥问题的,自己注释打开就能得到不一样的结果了
package com.czx.cache;import java.util.Date;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;import com.czx.cache.pojo.Teacher;public class CacheLearn {
private static SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();public static void main(String[] args) {CacheLearn cacheLearn = new CacheLearn();cacheLearn.cache007();}public void cache007() {Session session = sessionFactory.openSession();Transaction transaction = session.beginTransaction();Teacher t = session.load(Teacher.class, 3);System.out.println(t.getFavoriteFood());Thread tt =new Thread();transaction.commit();
// System.out.println(new Date());//弄个线程处理休眠下,这里毫秒单位来的
// try {
// tt.sleep(60000L);
// }catch (Exception e) {
// // TODO: handle exception
// }
// System.out.println(new Date());Session session2 = sessionFactory.openSession();Transaction transaction2 = session2.beginTransaction();Teacher t2 = session2.load(Teacher.class, 3);System.out.println(t2);transaction2.commit();session.close();}
}
3、1级2级缓存相关概念以及缓存策略
下面有个图我们客户端通过session进行查询操作,这个时候我们会先去找一级缓存有没有,如果有就直接返回,如果没有就去找2级缓存有没有,如果有也可以直接返回给客户端。都没有的情况下去找数据库做相关操作,操作完成数据存到session1级缓存中去,然后在存到2级缓存去,最后返回给客户端。
第一级缓存是 Session 缓存并且是一种强制性的缓存,所有的要求都必须通过它。Session 对象在它自己的权利之下,在将它提交给数据库之前保存一个对象。
如果你对一个对象发出多个更新,Hibernate 会尝试尽可能长地延迟更新来减少发出的 SQL 更新语句的数目。如果你关闭 session,所有缓存的对象丢失,或是存留,或是在数据库中被更新。
第二级缓存是一种可选择的缓存并且第一级缓存在任何想要在第二级缓存中找到一个对象前将总是被询问。第二级缓存可以在每一个类和每一个集合的基础上被安装,并且它主要负责跨会话缓存对象。
并发策略
一个并发策略是一个中介,它负责保存缓存中的数据项和从缓存中检索它们。如果你将使用一个二级缓存,你必须决定,对于每一个持久类和集合,使用哪一个并发策略。
缓存并发策略,可以得知这里面的级别是从高到低的,Transactional最高最严谨,阻止脏读不可重复读,遇到下面就越不严谨,根据项目的具体情况去选择合适的。
名称 | 含义 |
---|---|
Transactional(事务型) | 仅在受管理环境下适用.它提供了RepeatableRead事务隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读 |
Read-write(读写型) | 提供ReadCommited数据隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读 |
Nonstrict-read-write(非严格读写) | 不保证缓存与数据库中数据的一致性.提供ReadUncommited事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略 |
Read-only(只读型) | 提供Serializable数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略 |
下面这个其实主要看他能否集群,支持什么集群就够了。
缓存名称 | 描述 |
---|---|
EHCache | 它能在内存或硬盘上缓存并且集群缓存,而且它支持可选的 Hibernate 查询结果缓存。 |
OSCache | 支持在一个单独的 JVM 中缓存到内存和硬盘,同时有丰富的过期策略和查询缓存支持。 |
warmCache | 一个基于 JGroups 的聚集缓存。它使用集群失效但是不支持 Hibernate 查询缓存。 |
JBoss Cache | 一个也基于 JGroups 多播库的完全事务性的复制集群缓存。它支持复制或者失效,同步或异步通信,乐观和悲观锁定。Hibernate 查询缓存被支持。 |
下面这个的话是不同的缓存提供者支持什么类型的缓存策略。每一个缓存提供者都不和每个并发策略兼容。以下的兼容性矩阵将帮助你选择一个合适的组合。
策略/提供者 | Read-only | Nonstrictread-write | Read-write | Transactional |
---|---|---|---|---|
EHCache | X | X | X | |
OSCache | X | X | X | |
SwarmCache | X | X | ||
JBoss Cache | X | X |
我先说一下1+n是什么问题吧。
网络上有的说是如果你查询一个集合,但是呢,由于你查询的只是一个集合,返回的是一个id集合并没有真的去细查,到了真正要使用的时候,又会再去查一次数据库。
举例说明:
如果你使用的是iterate这个方法,那么就会出现这个问题,结果如下,你可以看看。(这个iterate已经过期了,了解一下看看吧)
Iterator
但是,当你使用查询的list方法不会有1+n问题,所以我感觉没啥用,而且iterate方法也已经过期。这个1+n问题也可以通过2级缓存解决。
list() 默认情况只会放入缓存,不会从一级缓存中取!
使用查询缓存,可以让list()查询从二级缓存中取!
1、开启查询缓存后查询的list()可以从二级缓存中拿数据(list()不能从一级缓存[session缓存]中拿数据),但是不主动去拿,需要设置setCacheable(true)
get和load都是通过id获取到对应的数据。
关于这个get说法众说纷纭,各有各的看法,我自己操作看看才能知道结果。百度有的说get可以查2级,有人说不行,我自己测试不就知道了。
在我配置好了2级缓存的情况下,执行以下代码(以下是方法,调用一下即可)
这里我一开始使用get获取数据,关闭session,在获取一次数据看看,结果如下,显而易见,这个get是可以查2级缓存的。
public void cacheTest() {Session session = sessionFactory.openSession();Transaction transaction = session.beginTransaction();Teacher t = session.get(Teacher.class, 3);System.out.println(t);transaction.commit();session.close();Session session2 = sessionFactory.openSession();Transaction transaction2 = session2.beginTransaction();Teacher t2 = session2.get(Teacher.class, 3);System.out.println(t2);transaction2.commit();session2.close();}
这里说下其实本质就是如果要查数据,先查看一下session一级缓存有没有数据,如果没有就去2级缓存中找,都没有再去查找数据库。其中每一个session是独立开来的,如果你关闭了session,那么里面的缓存也会丢失,但是2级缓存的介质不一样所以还能存起来,所以如果加入了2级缓存那么只要查一次就算清空session之后也还能查到数据。
这里有一个特例,如果数据库没有数据,那么就没法缓存,每一次去查都要去到数据库查为null的数据,这样就会造成缓存穿透。
看到这里如果你对上面概念还是不了解在建议看看下面我举得例子。
第一次查数据的情况:
首先启动session---->
然后去session缓存(一级缓存)中找。第一次是没有的---->
所以去更下级缓存中找,2级缓存有没有呢?其实还能继续往下找更高级的缓存,但是这里以2级缓存为例就够了。---->
缓存中找不到了,那么查数据库去,如果没有对应的数据,那么也会返回null,正常有数据就能返回对应的对象。然后将数据缓存到session缓存中。
连续二次查数据的情况下(没有关闭session,在一个session中查数据,这里2级缓存用不到):
第一次查询结束---->
接着查相同的hql/sql语句---->
去session缓存中找,找到了---->
返回对应的对象即可
连读二次查数据的情况(在不同的session)
第一次查询数据结束---->
关闭session---->
新建一个新的sessionA---->
使用sessionA查数据---->
sessionA缓存中没有数据,去查找数据库---->
将数据库数据返回
连读二次查数据的情况(在不同的session,启用了2级缓存)
第一次查询数据结束---->
关闭session–>
新建一个新的sessionA---->
使用sessionA查数据----->
sessionA中没有数据,去2级缓存中找,找到了---->
将2级缓存中内容返回
如果你使用了get方法返回数据那么必定会有是数据库的查询语句,但是如果你使用的是load方法,那么就未必是了。
当我们使用session.load()方法来加载一个对象时,此时并不会发出SQL语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,只有当我们要使用这个对象(例如要打印这个数据),得到其它属性时,这个时候才会发出SQL语句,从数据库中去查询对象。如果你只是要获取id值,那么也并不会访问数据库。
实际上工作流程是这样的,这类似懒加载,我们一开始加载数据都去缓存中找,先一级后2级,找不到了,那么也先会返回一个代理对象,里面只带有对象id。到了我们要使用该对象(不是id的其他属性)的时候,那么才会真正去向数据库发请求,然后将数据缓存到session中去。第二次查找如果缓存没了那么跟第一次查找一样。缓存还在进行第二次查找,加载数据去缓存中找,返回一个对象的引用,到了要使用该对象的时候可以直接从缓存中获取了
第一次查找数据
使用load方法获取数据----->
去缓存中找数据,没有就返回一个代理对象---->
该代理对象带id,如果你要使用该对象除了id以外的属性那么就要去查询数据库------>
数据库获取到了数据,返回并将数据存到session缓存中去
第二次查找数据,缓存在
使用load方法获取数据------>
去缓存中找数据,发现有数据,于是就返回该对象的引用----->
然后获取属性可以去缓存中拿了。
上面流程说过了,get是会从数据库获取对象的,那么如果数据库没有这个对象,我还要调用这个对象的属性,那么会报什么异常呢?java.lang.NullPointerException,空指针异常,是null的。
load方法会返回一个代理对象,那么如果数据库没有,我还要调用这个对象的属性会报什么异常?org.hibernate.ObjectNotFoundException,因为我们的代理对象只有id,其他属性都是没有的呀。
这篇不是很建议看项目,因为我在里面不停的做不同情况下的测试,不过如果你想要看,那也行吧。
https://gitee.com/mrchen13427566118/ssh_hibernate_learn.git里面的ssh_hibernate_cache。
打开方式可以看这篇
https://blog.csdn.net/weixin_43987277/article/details/116936221里面的第三点。