摘要:前言本文介紹一種緩存的實現方法,使用實例如下通過使用注解來標注哪些數據庫訪問需要緩存,屬性設置前綴,這樣做的好處是將緩存的實現和業務邏輯分開,可擴展性強實現如上所述,注解用于注釋需要緩存的接口方法在容器中通常都會配置一個用來指定的位置包名,
前言
本文介紹一種 mybatis redis 緩存的實現方法,使用實例如下:
@Repository public interface UserDao { @Cache(prefix="user:") @Select(...) public User findUserById(int userId); }
通過使用 Cache 注解來標注哪些數據庫訪問(select)需要緩存,prefix 屬性設置 Redis key 前綴,這樣做的好處是將緩存的實現和業務邏輯分開,可擴展性強
實現 Cache如上所述,Cache 注解用于注釋需要緩存的 mapper 接口方法
public @interface Cache { long expire() default DEFAULT_EXPIRE_TIME; String prefix(); }MapperScannerConfigurer
在 spring 容器中 mybatis 通常都會配置一個 MapperScannerConfigurer 用來指定 mapper(ORM)的位置(包名),通過閱讀相關源代碼可以知道 MapperScannerConfigurer 會為每個 mapper 接口生成一個動態代理,我們要做的就是擴展 MapperScannerConfigurer,給 mybatis 提供的動態代理提供一個 wrapper 包裝
public class MapperScannerConfigurerProxy extends MapperScannerConfigurer { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { super.postProcessBeanDefinitionRegistry(registry); String[] beanDefinitionNames = registry.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName); if (!(beanDefinition instanceof GenericBeanDefinition)) { continue; } GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) beanDefinition; if (!genericBeanDefinition.hasBeanClass()) { continue; } if (genericBeanDefinition.getBeanClass() != MapperFactoryBean.class) { continue; } genericBeanDefinition.setBeanClass(MapperFactoryBeanProxy.class); genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
postProcessBeanDefinitionRegistry 是 spring bean 生命周期回調方法,這里先調用父類方法,然后遍歷 bean registry,找到 MapperFactoryBean(mapper 動態代理工廠 bean),將它的 bean class 修改成我們提供的 MapperFactoryBeanProxy
MapperFactoryBeanProxyMapperFactoryBeanProxy 是對 mybatis 提供的 MapperFactoryBean 的代理,它有三個屬性(字段)
public class MapperFactoryBeanProxy implements FactoryBean { private Class> mapperInterface; @Autowired private MapperCacheStrategy mapperCacheStrategy; private MapperFactoryBean mapperFactoryBean; }
mapperInterfacemapper, mapper 接口類(例如 UserDao)
mapperCacheStrategy, 具體的緩存策略(模式)
mapperFactoryBean, mybatis 提供的默認的 mapper factory bean
構造方法保存 mapper interface 的引用以及創建 mybatis MapperFactoryBean 對象
public MapperFactoryBeanFactory(Class> mapperInterface) { this.mapperInterface = mapperInterface; mapperFactoryBean = new MapperFactoryBean<>(mapperInterface); }getObject
spring 通過調用 FactoryBean 的 getObject 方法創建 bean 對象,這里先調用 mybatis MapperFactoryBean 的 getObject 方法獲取 mybatis 創建的動態代理,然后調用 Proxy.newProxyInstance 方法基于 mapper interface 再創建一個動態代理,handler 為 MapperProxy
@Override public Object getObject() throws Exception { mapperFactoryBean.afterPropertiesSet(); Object object = mapperFactoryBean.getObject(); return Proxy.newProxyInstance(getClass().getClassLoader(), new Class>[]{mapperFactoryBean.getMapperInterface()}, new MapperProxy(object, mapperCacheStrategy)); }MapperProxy
MapperProxy 動態代理 handler 用于實現具體的緩存策略
public class MapperProxy implements InvocationHandler { private Object target; private MapperCacheStrategy mapperCacheStrategy; public MapperProxy(Object target, MapperCacheStrategy mapperCacheStrategy) { this.target = target; this.mapperCacheStrategy = mapperCacheStrategy; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... } }
invoke 方法基本流程:
如果是 Object 中定義的方法直接返回
判斷方法時候有 Cache 注解,如果沒有,表明不需要緩存,直接返回
調用 mapper cache strategy 類的 get 方法獲取緩存,如果命中直接返回
調用 原始方法(查庫)并更新緩存
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } Cache annotation = method.getAnnotation(Cache.class); if (annotation == null) { return method.invoke(target, args); } long expire = annotation.expire(); String prefix = annotation.prefix(); String key = getKey(prefix, args); Object object = null; try { object = mapperCacheStrategy.get(key, method.getGenericReturnType()); } catch (Exception e) { logger.error("mapperCacheStrategy.get " + key, e); } if (object != null) { return object; } object = method.invoke(target, args); if (!isBlank(object)) { mapperCacheStrategy.set(key, object, expire); } return object; }
這里沒有考慮諸如 緩存穿透 之類的問題,讀者可以自行擴展
總結本文介紹了一種通過 擴展 mybatis,增加自定義注解來實現數據庫緩存的方法,該方法不僅可以用于緩存處理,稍微修改一下就可以實現數據的讀寫分離,例如定義一個 DataSource 注解注釋 mapper 方法需要連接哪個數據源~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67002.html
摘要:緩存介紹正如大多數持久層框架一樣,同樣提供了一級緩存和二級緩存的支持一級緩存基于的本地緩存,其存儲作用域為,當或之后,該中的所有就將清空。一級緩存實現對的操作內部都是通過來執行的。 MyBatis緩存介紹 正如大多數持久層框架一樣,MyBatis 同樣提供了一級緩存和二級緩存的支持 一級緩存: 基于PerpetualCache 的 HashMap本地緩存,其存儲作用域為 Se...
摘要:本文章的源碼再文章末尾什么是查詢緩存有一級緩存和二級緩存。默認開啟一級緩存。證明了一級緩存只是在數據庫會話內部共享的。但是,整合到中后,一級緩存就會被關閉。根據時間表比如沒有刷新間隔緩存不會以任何時間順序來刷新。 學習SpringBoot集成Mybatis的第二章,了解到Mybatis自帶的緩存機制,在部署的時候踩過了一些坑。在此記錄和分享一下Mybatis的緩存作用。 本文章的源碼再...
閱讀 3096·2021-11-22 09:34
閱讀 598·2021-11-22 09:34
閱讀 2446·2021-10-08 10:18
閱讀 3380·2021-09-22 15:57
閱讀 2592·2021-09-22 15:25
閱讀 2407·2019-08-30 15:54
閱讀 2114·2019-08-30 15:44
閱讀 1805·2019-08-29 11:18