本文介绍一种 mybatis redis 缓存的实现方法,使用实例以下:redis
@Repository public interface UserDao { @Cache(prefix="user:") @Select(...) public User findUserById(int userId); }
经过使用 Cache 注解来标注哪些数据库访问(select)须要缓存,prefix 属性设置 Redis key 前缀,这样作的好处是将缓存的实现和业务逻辑分开,可扩展性强spring
如上所述,Cache 注解用于注释须要缓存的 mapper 接口方法数据库
public @interface Cache { long expire() default DEFAULT_EXPIRE_TIME; String prefix(); }
在 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 修改为咱们提供的 MapperFactoryBeanProxymybatis
MapperFactoryBeanProxy 是对 mybatis 提供的 MapperFactoryBean 的代理,它有三个属性(字段)app
public class MapperFactoryBeanProxy implements FactoryBean { private Class<?> mapperInterface; @Autowired private MapperCacheStrategy mapperCacheStrategy; private MapperFactoryBean mapperFactoryBean; }
mapperInterfacemapper, mapper 接口类(例如 UserDao)ide
mapperCacheStrategy, 具体的缓存策略(模式)post
mapperFactoryBean, mybatis 提供的默认的 mapper factory beanthis
保存 mapper interface 的引用以及建立 mybatis MapperFactoryBean 对象代理
public MapperFactoryBeanFactory(Class<?> mapperInterface) { this.mapperInterface = mapperInterface; mapperFactoryBean = new MapperFactoryBean<>(mapperInterface); }
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 动态代理 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 方法须要链接哪一个数据源~