mybatis+redis实现自定义缓存

mybatis结合redis实现自定义缓存

  1. 缓存的应运而生

众所周知呢,在实际项目中,频繁操作数据库是十分耗费资源的。这个时候,缓存的出现就在一定程度上解决了这种问题。这里为什么说是一定程度上呢:因为缓存的主要优势体验在查询操作非常频繁的场景下[我们将一次查询的结果放入缓存中,当我们再次查询相同的数据的时候,直接走缓存,就不再走数据库了],如果一个场景修改数据非常频繁,那缓存就几乎起不到优势作用了。

  • 下面介绍一下什么是缓存:

缓存的英文是cache,一般是用于RAM存储器,用于存储临时数据,断电后存储的内容会消失。
缓存是临时文件交换区,电脑把最常用的文件从存储器里提出来临时放在缓存里,就像把工具和材料搬上工作台一样,这样会比用时现去仓库取更方便。
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上查询,而是直接从缓存中查询,从而提高了查询的效率,解决了高并发系统的性能问题。

  • 下面介绍mybatis的缓存:
    mybatis拥有一级缓存和二级缓存这两种机制:
    一级缓存是SqlSession级别的缓存
    在这里插入图片描述
    如上图代码所示:一次sqlSession的开与关就表示了一次缓存,在开与关之间进行多次查询的话,那么只会有一次查询数据库;如果两次查询之间夹杂着一次更新操作,那么这两次查询都会走数据库。虽然一级缓存是默认开启的,但是在我们实际的使用过程中并没有感受到其带来的方便之处。

二级缓存是mapper级别的缓存
二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
在这里插入图片描述
如上图所示,二级缓存在一个mapper的namespace下横跨在多个一级缓存上。

下面演示一下,二级缓存 在关闭和开启时的效果:
在没有开启二级缓存的时候,效果如下:
在这里插入图片描述
在同一个测试方法下,执行两次查询方法,那么这两次查询方法都会对数据库进行查询。

当开启二级缓存后:
效果如下:
在这里插入图片描述
这里需要强调几点问题:
以上测试都是在springboot中进行的,在yml文件中对mybatis进行配置的时候,需要注意,仅仅在yml中配置了cache-enabled: true 是不起作用的,需要在mapper.xml文件中同时加入标签,这样二级缓存才会开启。

server:
  port: 8081

# 数据库基本配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.31.100:3306/mybatis?useSSL=false&serverTimezone=UTC
    username: root
    password: xxxxxx
    type: com.alibaba.druid.pool.DruidDataSource

# mybatis相关配置
mybatis:
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
  type-aliases-package: com.sample.entity
  mapper-locations: classpath:mapper/*.xml

# 日志配置
logging:
  level:
    com.sample.dao: debug
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.sample.dao.IEmployeeDao">
    <cache/><!--开启二级缓存-->
    <select id="findAll" resultType="employee">
        select * from employee
    </select>
    <select id="findById" parameterType="integer" resultType="employee">
        select * from employee where id = #{id}
    </select>
</mapper>

虽然mybatis的二级缓存已经比较好用了,但是并没有解决断电数据消失的问题。只要电脑一断电,那么缓存中的数据就不存在了。
此外,当在集群环境下,应用部署在多个服务器上,那么根据负载均衡的原理,查询操作可能会请求不同的服务器,那么就会出现这样的问题,在服务器1上的查询已经放入了服务器1的缓存中,第二次查询可能请求服务器2,而服务器2就要先查询数据库,然后再把数据放入服务器2的缓存中。如此一来,十分的麻烦。所以这里就需要使用分布式缓存对这个问题进行解决。
在这里插入图片描述

所以我们要接触第三方工具去解决以上问题,本篇文章就来介绍mybatis配合redis进行二级缓存的使用。
由于redis拥有持久化机制,所以断电数据消失的问题就可以借助redis来解决。
mybatis提供了Cache接口,用于实现自定义的缓存策略。
在这里插入图片描述
在实现该接口前,我们首先看看mybatis的默认缓存实现是怎么样的:

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

从源码中我们不难看出,mybatis的默认缓存实现,内部维护了一个HashMap,最常用的方法是putObject和getObject
在这里插入图片描述
我们通过断点可以知道,在putObject中,key是mapper中namespace+标签的id
在这里插入图片描述
value是一个ArrayList集合,其中保存了我们查询到的结果。

接下来我们就针对这两个方法进行缓存的定制。

public class CustomRedisCache implements Cache {
    /*
    * id是必须的带上的,这里的id会指定当前放入缓存的mapper的namespace
    * 如这里的id就是com.sample.dao.IEmployeeDao
    */
    private final String id;


    public CustomRedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }
    ……
    }

在mapper中需要改成下面这样

<cache type="com.sample.config.CustomRedisCache"/>

到这里我们就要注意了,在CustomRedisCache这个类中,我们该如何加入redis的实例。CustomRedisCache实例化并不是由工厂或者spring容器进行的,而是由mybatis进行的。因为在CustomRedisCache实例化时,mybatis需要向其构造函数中传入namespace。
所以在CustomRedisCache类中,不能使用注入的方式向其添加redis实例。

那么我们就要另辟蹊径了。在学习spring的时候,我们知道ApplicationContext是最大的容器,然后通过其getBean方法来获取redis实例。

下面我们写一个工具类来获取ApplicationContext.

@Configuration
public class ApplicationContextUtil implements ApplicationContextAware {
    //通俗来讲,这里的applicationContext就是spring为我们保留下来的工厂
    static ApplicationContext applicationContext;
    /*
    * 实现了ApplicationContextAware接口后,Spring容器会自动把上下文环境对象
    * 调用ApplicationContextAware接口中的setApplicationContext方法进行设置
    * */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext = applicationContext;
    }

    //通过在工厂中获取对象的方法
/*    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }*/
    //将上面的方法使用泛型进行优化;因为上面那个方法需要强制类型转换
    public static <T> T getBean(String beanName,Class<T> requiredType){
        return applicationContext.getBean(beanName,requiredType);
    }
}

下面是在CustomRedisCache添加redis及相关操作

public class CustomRedisCache implements Cache {
    /*
    * id是必须的带上的,这里的id会指定当前放入缓存的mapper的namespace
    * 如这里的id就是com.sample.dao.IEmployeeDao
    */
    private final String id;
    private final RedisTemplate redisTemplate;

    public CustomRedisCache(String id) {
//        获取redis实例
        redisTemplate = ApplicationContextUtil.getBean("redisTemplate",RedisTemplate.class);
//        指定key的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForHash().put(id.toString(),key.toString(),value);
    }

    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForHash().get(id.toString(),key.toString());
    }
    ...
}

运行测试案例
如下所示,结果显示成功
在这里插入图片描述
redis中保有数据
在这里插入图片描述
当我们再次执行刚才的测试案例,那么这两次数据查询都不会走数据库,而是都走缓存。
在这里插入图片描述

到此为止,mybatis+redis实现二级缓存的例子就结束了。下面附上关键代码:

CustomRedisCache.java

package com.sample.config;

import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

public class CustomRedisCache implements Cache {
    /*
    * id是必须的带上的,这里的id会指定当前放入缓存的mapper的namespace
    * 如这里的id就是com.sample.dao.IEmployeeDao
    */
    private final String id;
    private final RedisTemplate redisTemplate;

    public CustomRedisCache(String id) {
//        获取redis实例
        redisTemplate = ApplicationContextUtil.getBean("redisTemplate",RedisTemplate.class);
//        指定key的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForHash().put(id.toString(),key.toString(),value);
    }

    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForHash().get(id.toString(),key.toString());
    }

    @Override
    public Object removeObject(Object key) {
        return null;
    }

    @Override
    public void clear() {
        redisTemplate.delete(id.toString());
    }

    @Override
    public int getSize() {
        return redisTemplate.opsForHash().size(id.toString()).intValue();
    }
}

application.yml

server:
  port: 8081

# 数据库基本配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.31.100:3306/mybatis?useSSL=false&serverTimezone=UTC
    username: root
    password: xxxxxx
    type: com.alibaba.druid.pool.DruidDataSource
  redis:
    port: 6378
    host: 192.168.31.100
    database: 0

# mybatis相关配置
mybatis:
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
  type-aliases-package: com.sample.entity
  mapper-locations: classpath:mapper/*.xml

# 日志配置
logging:
  level:
    com.sample.dao: debug

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值