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二级缓存相关推荐

  1. Hibernate学习——建立一个简单的Hibernate项目

    最近老师让做个web小应用,大三的时候学习过一点J2EE的东西,也做过一些web相关的XXX管理系统,都是用servlet,jsp这些完成的,虽然勉强能够完成任务,但其中各种代码掺杂在一起,不好看而且 ...

  2. 《Linux内核分析》 第三周 构造一个简单的Linux系统MenuOS

    Linux内核分析 第三周 构造一个简单的Linux系统MenuOS 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/ ...

  3. java 缓存方法_Java实现一个简单的缓存方法

    Java实现一个简单的缓存方法 发布时间:2020-09-07 21:39:55 来源:脚本之家 阅读:99 作者:BrightLoong 缓存是在web开发中经常用到的,将程序经常使用到或调用到的对 ...

  4. 小灰灰的APP学习之路(三)--创建一个简单的问答选择项目

    创建一个简单的问答选择项目 简介 这是一个简单的问答选择项目,界面上显示问题,然后点击"正确"或"错误"按钮,系统给出是否回答正确的提示. 例如: 问题:1+1 ...

  5. HTML5.0实例练习(三) --制作一个简单登陆界面

    今天分享一个简单的实例--制作一个简单登陆界面 代码如下: 代码执行结果如下: 这里面用到了<table><th><td><tr>这些表格标签,这些在这 ...

  6. VIPLE初学者日记(三)实现一个简单定时器

    目标 做一个简单的定时器,实现语音报数从0数到99. 实现过程 如下图: 结果 语音报数 0.1.2.3-99,数一百个数,到99后,语音输出 all done.

  7. struts+hibernate+oracle+easyui实现lazyout组件的简单案例——hibernate的config文件(hibernate.cfg.xml)...

    hibernate.cfg.xml文件,必不可少的一个xml文件,上面附有数据库的用户名,密码,链接字符串,方言等信息,还包含映射的文件路径: <?xml version='1.0' encod ...

  8. struts+hibernate+oracle+easyui实现lazyout组件的简单案例——hibernate的config文件(hibernate.cfg.xml)

    hibernate.cfg.xml文件,必不可少的一个xml文件,上面附有数据库的用户名,密码,链接字符串,方言等信息,还包含映射的文件路径: <?xml version='1.0' encod ...

  9. 自定义sql_一个简单易用的开源BI软件,专为SQL用户设计的开源库

    poli 一个易于使用的SQL报告应用程序,专为SQL爱好者而设计. SQL中的电源数据分析,可获得更快的业务洞察力. 特性 ⚡️ 自托管和轻松设置 平台独立的Web应用程序 单个JAR文件+单个SQ ...

最新文章

  1. python打包exe os模块_python打包成exe格式的方法求教
  2. 论文的写作要求、流程与写作技巧
  3. 1.MySQL数据库的介绍
  4. 解决oracle11g连接失败 ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist
  5. 桌面没有计算机图标6,我的电脑图标没了怎么办
  6. Aplication的意义和生命周期,与Context的关系,以及关于Aplication和Context相关问题的记录和解决办法...
  7. 【非原创】codeforces 1070C Cloud Computing 【线段树树状数组】
  8. 恢复Debian下root用户bash高亮显示
  9. [LTE] LTE基本架构
  10. 基于Android Studio和Gradle 的小米便签配置和安装
  11. Android中添加手心默认输入法,并能卸载
  12. paypal注册流程
  13. 攻防世界 reverse新手题 logmein
  14. 键盘调节台式计算机声音,台式电脑如何用键盘控制声音开关
  15. POJ 1088 滑雪(输出对比)
  16. (4.2.40)阿里开源路由框架ARouter的源码分析
  17. fedora34 不显示桌面图标
  18. SpringBoot mybatis多数据源配置,记录下我磕磕碰碰的三个月找工作经历
  19. centos离线配置yun源
  20. MATLAB糖葫芦哈哈哈

热门文章

  1. Java集合源码浅析(一) : ArrayList
  2. 长时长视频java存储及vue播放解决方法
  3. 计算机stem案例,【stem教育项目教学案例】_STEM教育理念下的“三维创意设计”课程教学案例...
  4. Python实战:批量修改文本文件
  5. 21天Java开发速成篇-Java从入门到大师01快速入门
  6. u盘自动运行病毒分析与解决方法
  7. Missing artifact com.oracle:ojdbc7:jar:10.2.0.7.0
  8. VC2008 不能将参数 从“CString”转换为“const wchar_t *” 问题
  9. 力软lrselect下拉框默认选择
  10. 禁止打开Word自动开启BQQ