资讯专栏INFORMATION COLUMN

Mybatis:一种 Redis 缓存实现

caige / 2986人阅读

摘要:前言本文介绍一种缓存的实现方法,使用实例如下通过使用注解来标注哪些数据库访问需要缓存,属性设置前缀,这样做的好处是将缓存的实现和业务逻辑分开,可扩展性强实现如上所述,注解用于注释需要缓存的接口方法在容器中通常都会配置一个用来指定的位置包名,

前言

本文介绍一种 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

MapperFactoryBeanProxy

MapperFactoryBeanProxy 是对 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 方法需要连接哪个数据源~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67002.html

相关文章

  • MyBatis缓存介绍

    摘要:缓存介绍正如大多数持久层框架一样,同样提供了一级缓存和二级缓存的支持一级缓存基于的本地缓存,其存储作用域为,当或之后,该中的所有就将清空。一级缓存实现对的操作内部都是通过来执行的。 MyBatis缓存介绍   正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持   一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Se...

    mingde 评论0 收藏0
  • Spring Boot2(三):使用Spring Boot2集成Redis缓存

    摘要:本文章的源码再文章末尾什么是查询缓存有一级缓存和二级缓存。默认开启一级缓存。证明了一级缓存只是在数据库会话内部共享的。但是,整合到中后,一级缓存就会被关闭。根据时间表比如没有刷新间隔缓存不会以任何时间顺序来刷新。 学习SpringBoot集成Mybatis的第二章,了解到Mybatis自带的缓存机制,在部署的时候踩过了一些坑。在此记录和分享一下Mybatis的缓存作用。 本文章的源码再...

    NusterCache 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<