hibernate二级缓存(三) 自定义实现一个简单的hibernate二级缓存
hibernate二级缓存(三) 自定义实现一个简单的hibernate二级缓存
前面我们已经提及过hibernate内部为二级缓存的扩展做了很多的实现。我们只需要实现RegionFactoryTemplate和DomainDataStorageAccess即可自已实现hibernate缓存。在DomainDataStorageAccess实现中可以将缓存放置到redis或者memcache,来实现分布式的二级缓存。下面用hashmap实现了一个简单的二级缓存。
1. 实现RegionFactoryTemplate
RegionFactoryTemplate模版类为实现RegionFactory简化了很多操作。通常需要实现以下几个方法
//创建查询结果缓存,该缓存用于hibernate的查询缓存
StorageAccess createQueryResultsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory);
//创建时间戳缓存
StorageAccess createTimestampsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory);
//创建实体缓存
DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext);
//简单的理解为初始化之前需要做的一些准备工作
void prepareForUse(SessionFactoryOptions settings, Map configValues);
//释放缓存
void releaseFromUse() ;
从RegionFactoryTemplate的实现不难看出实际上是通过StorageAccess 来管理缓存的。简化了之前讲过的DomainDataRegion,EntityDataAccess等实现细节。
实现类如下:
public class MyRegionFactoryImpl extends RegionFactoryTemplate {private static final long serialVersionUID = -401479360060731148L;//缓存管理器private volatile CacheManager cacheManager;@Overrideprotected StorageAccess createQueryResultsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) {return new MyDomainDataStorageAccess(getOrCreateCache(regionName));}private Cache getOrCreateCache(String regionName) {Cache cache = cacheManager.getCache(regionName);if (cache == null) {return createCache(regionName);}return cache;}private Cache createCache(String regionName) {cacheManager.addCache(regionName);return cacheManager.getCache(regionName);}@Overrideprotected StorageAccess createTimestampsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) {return new MyDomainDataStorageAccess(getOrCreateCache(regionName));}/*** 该方法必须覆盖RegionFactoryTemplate的方法,RegionFactoryTemplate默认实现是抛出异常** @param regionConfig* @param buildingContext* @return*/@Overrideprotected DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) {return new MyDomainDataStorageAccess(getOrCreateCache(regionConfig.getRegionName()));}@Overrideprotected void prepareForUse(SessionFactoryOptions settings, Map configValues) {this.cacheManager = new CacheManager();//一个打印缓存内容的线程,用于观察缓存里面的内容/*Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true){System.out.println(cacheManager);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();*/}@Overrideprotected void releaseFromUse() {cacheManager = null;}
}
通过MyDomainDataStorageAccess内部包含一个cache对象,CacheManager来创建和管理cache对象。
2. 实现DomainDataStorageAccess
DomainDataStorageAccess内部通过Cache类来真正的访问缓存,这样不同的cache类就能访问不同的缓存。将缓存的访问和缓存的具体实现分离。
DomainDataStorageAccess的实现代码如下:
public class MyDomainDataStorageAccess implements DomainDataStorageAccess {private Cache cache;public MyDomainDataStorageAccess(Cache cache) {this.cache = cache;}@Overridepublic Object getFromCache(Object key, SharedSessionContractImplementor session) {return cache.get(key);}@Overridepublic void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) {cache.put(key, value);}@Overridepublic boolean contains(Object key) {return cache.contains(key);}@Overridepublic void evictData() {cache.clear();}@Overridepublic void evictData(Object key) {cache.remove(key);}@Overridepublic void release() {cache = null;}
}
代码非常简单,对于缓存的操作都通过cache来实现。
3. CacheManager 的实现
CacheManager 用来管理cache,实则是cache的一个ConcurrentHashMap,具体的实现代码如下:
public class CacheManager {private final Map<String, Cache> caches = new ConcurrentHashMap<>();public Cache getCache(String cacheName) {return caches.get(cacheName);}public void addCache(String cacheName, Cache cache) {caches.put(cacheName, cache);}public void addCache(String cacheName) {caches.put(cacheName, new Cache());}@Overridepublic String toString() {return "CacheManager{" +"caches=" + caches +'}';}
}
4. Cache实现
cache的代码和CacheManager 的代码有些类似,也是用ConcurrentHashMap来实现的
public class Cache {private final Map<Object, Object> cache = new ConcurrentHashMap<>();public Object get(Object key) {return cache.get(key);}public boolean contains(Object key) {return cache.containsKey(key);}public void clear() {cache.clear();}public void remove(Object key) {cache.remove(key);}public void put(Object key, Object value) {cache.put(key, value);}@Overridepublic String toString() {return "Cache{" +"cache=" + cache +'}';}
}
看到这里的朋友可能会有点晕了CacheManager 和 Cache 的代码十分相似啊,那么他们有什么实质的区别吗。了解redis的朋友们可以简单的理解为CacheManager 是redis里面的hash类型,Cache 就是hash类型的实际键值对对象。hibernate二级缓存的也是如此,CacheManager 管理不同的实体缓存(比如TeacherPo,StudentPo),CacheManager 里面存放的是不同实体的缓存对象,key可理解为实体的class类名。Cache 就是相同类型的不同对象,比如TeacherPo有id为1,2,3等不同的对象,Cache 里面的key可以理解为对象的主键id,值为真正的实体对象。如果将CacheManager 理解为数据库中的实体PO的表的map集合,那么Cache 就是某张表里面的每一行的map集合。
5. 配置及测试
实体缓存测试:
配置很简单,在配置文件中加上如下参数:
<!--是否启用二级缓存--><property name="hibernate.cache.use_second_level_cache">true</property><!--缓存的具体实现--><property name="hibernate.cache.region.factory_class">com.foo.cache.MyRegionFactoryImpl</property>
hibernate.cache.region.factory_class指定regionFactory的具体实现类。
下面是测试的代码:
public static void main(String[] args) {SessionFactoryUtil sessionFactoryUtil = new SessionFactoryUtil();SessionFactory sessionFactory = sessionFactoryUtil.getSessionFactory();//1.查询单个实体doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {Event event = session.get(Event.class, 7L);System.out.println(event);}});//2.查询单个实体doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {Event event = session.get(Event.class, 7L);System.out.println(event);}});}
public static void doExecute(SessionFactory sessionFactory, HibernateExecuteCallBack executeCallBack) {Session session = sessionFactory.openSession();session.beginTransaction();executeCallBack.execute(session);session.getTransaction().commit();session.close();}interface HibernateExecuteCallBack {void execute(Session session);}
在配置中我们开启了二级缓存,同时连续两次加载同一个实体,在每次加载后释放session,避免session级缓存。日志输出如下:
Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
可以看到对应的sql语句只打印了一次,同时如果debug,会发现第二次查询时直接通过缓存查询出来的。
上面我们用到的是实体缓存。下面测试一下查询缓存。
查询缓存配置:
配置文件添加如下配置
<!--是否使用查询缓存--><property name="hibernate.cache.use_query_cache">true</property>
同时在查询的时候手动设置query.setCacheable(true);如果不设置查询缓存不生效。
Iterator缓存测试
//3.Iterate查询集合public static void main(String[] args) {SessionFactoryUtil sessionFactoryUtil = new SessionFactoryUtil();SessionFactory sessionFactory = sessionFactoryUtil.getSessionFactory();doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {testIterateQuery(session,"from Event where id>=7");}});System.out.println("=====================================");doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {testIterateQuery(session,"from Event where id>=7");}});
}private static void testIterateQuery(Session session, String testHql) {Query query = session.createQuery(testHql);query.setCacheable(true);@SuppressWarnings("unchecked")Iterator<Event> list = (Iterator<Event>) query.iterate();while (list.hasNext()) {System.out.println("iterate语句测试query cache:" + list.next());}}
在上面我们使用Iterator来进行查询,同时设置 query.setCacheable(true)表明使用查询缓存(hibernate)。通过更改query.setCacheable(true)发现无论是否设置启用查询缓存,都会产生如下相同的日志:
Hibernate: select event0_.id as col_0_0_ from event event0_ where event0_.id>=7
Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
iterate语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
iterate语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
=====================================
Hibernate: select event0_.id as col_0_0_ from event event0_ where event0_.id>=7
iterate语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
iterate语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
CacheManager{caches={default-query-results-region=Cache{cache={}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
CacheManager{caches={default-query-results-region=Cache{cache={}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
分析上面的日志我们不难发现,在第一次查询的时候除了发送查询的sql语句,还另外发送了2条查询实体的sql语句,并且通过打印当前缓存内容发现,缓存里面确实只有实体缓存而没有查询缓存。
所以Iterate查询有如下特征:
- 1、Iterate是通过查询出实体的id,再通过id去查询实体,如果查询结果有n条数据,那么总共会发送n+1条sql
- 2、Iterate会用到实体缓存(二级缓存),(通过上面第二次查询结果表明只发送了一条查询id的语句,实体查询是直接从二级缓存获取)
- 3、Iterate不会使用查询缓存,(上面两次查询,发送了相同的sql语句,且缓存里面default-query-results-region查询缓存为空)
- 4、Iterate也会使用到session缓存,(此条结果可自行证明,证明方法:关闭二级缓存,然后在一个doExecute里面执行两条sql,和分别在两个doExecute执行一条sql可以得出)
list缓存测试
//4.List查询集合doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {testListQuery(session, "from Event where id>=7");}});System.out.println("=====================================");doExecute(sessionFactory, new HibernateExecuteCallBack() {@Overridepublic void execute(Session session) {testListQuery(session, "from Event where id>=7");}});private static void testListQuery(Session session, String testHql) {Query query = session.createQuery(testHql);//必须开启查询缓存并且,手动设置缓存为true,查询缓存才生效query.setCacheable(true);@SuppressWarnings("unchecked")List<Event> list = query.list();for (Event event : list) {System.out.println("list语句测试query cache:" + event);}}
同时开启查询缓存和二级缓存的测试结果如下:
Hibernate: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
=====================================
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
CacheManager{caches={default-query-results-region=Cache{cache={sql: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7; parameters: ; named parameters: {}; transformer: org.hibernate.transform.CacheableResultTransformer@110f2=org.hibernate.cache.internal.QueryResultsCacheImpl$CacheItem@64a87f32}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
CacheManager{caches={default-query-results-region=Cache{cache={sql: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7; parameters: ; named parameters: {}; transformer: org.hibernate.transform.CacheableResultTransformer@110f2=org.hibernate.cache.internal.QueryResultsCacheImpl$CacheItem@64a87f32}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
关闭查询缓存的测试结果如下(要关闭查询缓存只需设置query.setCacheable(false)即可):
Hibernate: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
=====================================
Hibernate: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
CacheManager{caches={default-query-results-region=Cache{cache={}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
CacheManager{caches={default-query-results-region=Cache{cache={}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={com.foo.model.Event#8=CacheEntry(com.foo.model.Event), com.foo.model.Event#7=CacheEntry(com.foo.model.Event)}}}}
开启查询缓存,关闭二级缓存测试结果如下:
Hibernate: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
=====================================
Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
Hibernate: select event0_.id as id1_0_0_, event0_._date as _date2_0_0_, event0_.title as title3_0_0_ from event event0_ where event0_.id=?
list语句测试query cache:Event{id=7, title='Our very first event!', date=2019-03-11 13:45:21.0}
list语句测试query cache:Event{id=8, title='A follow up event', date=2019-03-11 13:45:21.0}
CacheManager{caches={default-query-results-region=Cache{cache={sql: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7; parameters: ; named parameters: {}; transformer: org.hibernate.transform.CacheableResultTransformer@110f2=org.hibernate.cache.internal.QueryResultsCacheImpl$CacheItem@2ff501c3}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={}}}}
CacheManager{caches={default-query-results-region=Cache{cache={sql: select event0_.id as id1_0_, event0_._date as _date2_0_, event0_.title as title3_0_ from event event0_ where event0_.id>=7; parameters: ; named parameters: {}; transformer: org.hibernate.transform.CacheableResultTransformer@110f2=org.hibernate.cache.internal.QueryResultsCacheImpl$CacheItem@2ff501c3}}, default-update-timestamps-region=Cache{cache={}}, com.foo.model.Event=Cache{cache={}}}}
从上面的测试结果发现,在开启查询缓存后:第一次查询的时候只发送了一条sql,第二次查询没有发送任何的sql语句,并且CacheManager缓存了查询的sql语句,和相关的实体对象;在关闭查询缓存后,发送了2次sql,并且CacheManager里面没有缓存查询的sql语句。在开启查询缓存,关闭二级缓存后:查询sql只发送了一条,但是在第二次查询的时候单独发送了2条查询实体的sql语句。
综上可以得出list查询有如下特征:
- 1、list是通过一条sql查询出整个结果集,(Iterate是先查询id,再通过id查实体)
- 2、list能使用查询缓存和二级缓存
- 3、list也能使用到session缓存(证明同Iterate)
- 4、查询缓存生效必须在二级缓存开启的情况下才能发挥作用(查询缓存依赖于二级缓存,证明方法:开启查询缓存,关闭二级缓存),否则hibernate只会缓存查询结果(或者说只缓存了实体的id),而具体的实体还需要通过sql语句单独查询
6. 总结
通过上面的一系列讲解我们大致了解了要自己实现一个hibernate插件有哪些步骤,同时分析了查询缓存与二级缓存,Iterate和list的区别,大家有兴趣的话可以通过redis实现DomainDataStorageAccess ,使用redis作为hibernate的二级缓存。
以上完整的代码在:https://github.com/Json-Lin/hibernate-cache-test
hibernate二级缓存(三) 自定义实现一个简单的hibernate二级缓存相关推荐
- Hibernate学习——建立一个简单的Hibernate项目
最近老师让做个web小应用,大三的时候学习过一点J2EE的东西,也做过一些web相关的XXX管理系统,都是用servlet,jsp这些完成的,虽然勉强能够完成任务,但其中各种代码掺杂在一起,不好看而且 ...
- 《Linux内核分析》 第三周 构造一个简单的Linux系统MenuOS
Linux内核分析 第三周 构造一个简单的Linux系统MenuOS 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/ ...
- java 缓存方法_Java实现一个简单的缓存方法
Java实现一个简单的缓存方法 发布时间:2020-09-07 21:39:55 来源:脚本之家 阅读:99 作者:BrightLoong 缓存是在web开发中经常用到的,将程序经常使用到或调用到的对 ...
- 小灰灰的APP学习之路(三)--创建一个简单的问答选择项目
创建一个简单的问答选择项目 简介 这是一个简单的问答选择项目,界面上显示问题,然后点击"正确"或"错误"按钮,系统给出是否回答正确的提示. 例如: 问题:1+1 ...
- HTML5.0实例练习(三) --制作一个简单登陆界面
今天分享一个简单的实例--制作一个简单登陆界面 代码如下: 代码执行结果如下: 这里面用到了<table><th><td><tr>这些表格标签,这些在这 ...
- VIPLE初学者日记(三)实现一个简单定时器
目标 做一个简单的定时器,实现语音报数从0数到99. 实现过程 如下图: 结果 语音报数 0.1.2.3-99,数一百个数,到99后,语音输出 all done.
- struts+hibernate+oracle+easyui实现lazyout组件的简单案例——hibernate的config文件(hibernate.cfg.xml)...
hibernate.cfg.xml文件,必不可少的一个xml文件,上面附有数据库的用户名,密码,链接字符串,方言等信息,还包含映射的文件路径: <?xml version='1.0' encod ...
- struts+hibernate+oracle+easyui实现lazyout组件的简单案例——hibernate的config文件(hibernate.cfg.xml)
hibernate.cfg.xml文件,必不可少的一个xml文件,上面附有数据库的用户名,密码,链接字符串,方言等信息,还包含映射的文件路径: <?xml version='1.0' encod ...
- 自定义sql_一个简单易用的开源BI软件,专为SQL用户设计的开源库
poli 一个易于使用的SQL报告应用程序,专为SQL爱好者而设计. SQL中的电源数据分析,可获得更快的业务洞察力. 特性 ⚡️ 自托管和轻松设置 平台独立的Web应用程序 单个JAR文件+单个SQ ...
最新文章
- python打包exe os模块_python打包成exe格式的方法求教
- 论文的写作要求、流程与写作技巧
- 1.MySQL数据库的介绍
- 解决oracle11g连接失败 ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist
- 桌面没有计算机图标6,我的电脑图标没了怎么办
- Aplication的意义和生命周期,与Context的关系,以及关于Aplication和Context相关问题的记录和解决办法...
- 【非原创】codeforces 1070C Cloud Computing 【线段树树状数组】
- 恢复Debian下root用户bash高亮显示
- [LTE] LTE基本架构
- 基于Android Studio和Gradle 的小米便签配置和安装
- Android中添加手心默认输入法,并能卸载
- paypal注册流程
- 攻防世界 reverse新手题 logmein
- 键盘调节台式计算机声音,台式电脑如何用键盘控制声音开关
- POJ 1088 滑雪(输出对比)
- (4.2.40)阿里开源路由框架ARouter的源码分析
- fedora34 不显示桌面图标
- SpringBoot mybatis多数据源配置,记录下我磕磕碰碰的三个月找工作经历
- centos离线配置yun源
- MATLAB糖葫芦哈哈哈
热门文章
- Java集合源码浅析(一) : ArrayList
- 长时长视频java存储及vue播放解决方法
- 计算机stem案例,【stem教育项目教学案例】_STEM教育理念下的“三维创意设计”课程教学案例...
- Python实战:批量修改文本文件
- 21天Java开发速成篇-Java从入门到大师01快速入门
- u盘自动运行病毒分析与解决方法
- Missing artifact com.oracle:ojdbc7:jar:10.2.0.7.0
- VC2008 不能将参数 从“CString”转换为“const wchar_t *” 问题
- 力软lrselect下拉框默认选择
- 禁止打开Word自动开启BQQ