e2

滴滴侠,fai抖

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

首先来了解下mybatis 缓存,mybatis缓存分为一级缓存和二级缓存。一级缓存是默认开启的,无需其他配置操作,二级缓存则需要手动设置开启。

一级缓存原理:

Mybatis的一级缓存是指同一个SqlSession中的操作。一级缓存的作用域是一个SqlSession。
在同一个SqlSession中,执行相同的查询SQL,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中取。当执行SQL时两次查询中间发生了增删改操作,则SqlSession的缓存清空。

在这里插入图片描述

二级缓存原理:

Mybatis的二级缓存是指mapper映射文件。二级缓存是多个sqlSession共享的,其作用域是mapper下的同一个namespace。
在不同的sqlSession中,相同的namespace下,相同的查询sql语句并且参数也相同的情况下,会命中二级缓存。如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

在这里插入图片描述

了解一些基本原理后,我们开始在springboot集成mybatis的情况下,开启二级缓存。

  1. 在pom.xml文件中引入mybatis和redis的依赖
        <!--mybatis 依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--redis lettuce-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
  1. 在application.yml文件中配置mybatis相关设置时,开启二级缓存
### mybatis相关配置
mybatis:
  configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      #开启MyBatis的二级缓存
      cache-enabled: true
  mapper-locations: classpath*:mappers/*Mapper.xml

### Redis 相关配置
redis:
  host: localhost
  port: 6379
  timeout: 10000
  database: 0
  lettuce:
    pool:
      max-active: 8
      max-wait: -1
      max-idle: 8
      min-idle: 0
  1. 实体类实现序列化

我们采用的redis序列化方式是默认的jdk序列化。所以数据库的查询对象(比如Student类)需要实现Serializable接口。

public class Student implements Serializable {
    //采用的redis序列化方式是默认的jdk序列化。所以数据库的查询对象实体需要实现Serializable接口。
    private static final long serialVersionUID = 1L;

    private int id;
    private String name;
    private int age;
    private String position;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getPosition() {
        return position;
    }
    public void setPosition(String position) {
        this.position = position;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", position='" + position + '\'' +
                '}';
    }
}
  1. 先看一下Redis的配置类(这里用的是lettuce)
@Configuration
public class RedisConfig {

    @Autowired
    private LettuceConnectionFactory connectionFactory;

    @Bean
    public RedisTemplate<String,Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate, connectionFactory);
        return redisTemplate;
    }

    /**
     * 设置数据存入 redis 的序列化方式
     * @param template
     * @param factory
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> template,LettuceConnectionFactory factory) {
        // 定义 key 的序列化方式为 string
        // 需要注意这里Key使用了 StringRedisSerializer,那么Key只能是String类型的,不能为Long,Integer,否则会报错抛异常。
        StringRedisSerializer redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        // 定义 value 的序列化方式为 json
        @SuppressWarnings({"rawtypes", "unchecked"})
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        //hash结构的key和value序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setEnableTransactionSupport(true);
        template.setConnectionFactory(factory);
    }
}
  1. 缓存配置类
public class MybatisRedisCache implements Cache {
    private static final Logger log = LoggerFactory.getLogger(MybatisRedisCache.class);
    private String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间


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

    private RedisTemplate<Object, Object> getRedisTemplate(){
        return ApplicationContextHolder.getBean("redisTemplate");
    }

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

    @Override
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.boundHashOps(getId()).put(key, value);
        log.info("[结果放入到缓存中: " + key + "=" + value+" ]");

    }

    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        Object value = redisTemplate.boundHashOps(getId()).get(key);
        log.info("[从缓存中获取了: " + key + "=" + value+" ]");
        return value;
    }

    @Override
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        Object value = redisTemplate.boundHashOps(getId()).delete(key);
        log.info("[从缓存删除了: " + key + "=" + value+" ]");
        return value;
    }

    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.delete(getId());
        log.info("清空缓存!!!");
    }

    @Override
    public int getSize() {
        RedisTemplate redisTemplate = getRedisTemplate();
        Long size = redisTemplate.boundHashOps(getId()).size();
        return size == null ? 0 : size.intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}

ps:
重点部分就是重写这个mybatis的cache类,它只会对配置文件类型的映射文件起作用。
该接口共有以下五个方法:
String getId():mybatis缓存操作对象的标识符。一个mapper对应一个mybatis的缓存操作对象。
void putObject(Object key, Object value):将查询结果塞入缓存。
Object getObject(Object key):从缓存中获取被缓存的查询结果。
Object removeObject(Object key):从缓存中删除对应的key、value。只有在回滚时触发。
void clear():发生更新时,清除缓存。
int getSize():可选实现。返回缓存的数量。
ReadWriteLock getReadWriteLock():可选实现。用于实现原子性的缓存操作。

上述重写cache类中有几个关键点:
  • 自定义实现的二级缓存,必须要有一个带id的构造函数,否则会报错。
  • 此处使用Spring封装的redisTemplate来操作Redis。很多都是直接用jedis库,但是现在springboot2.x 以上对lettuce的兼容更好。RedisTemplate封装了底层的实现,使用redisTemplate会更加方便,无论是使用jedis还是使用lettuce,我们可以直接更换底层的库,无需修改上层代码。
  • 这里不能通过@Autowire的方式引用redisTemplate,因为RedisCache并不是Spring容器里的bean。所以我们需要手动地去调用容器的getBean方法来拿到这个bean,那么这样,我们就需要引入ApplicationContextHolder这个类。
  1. ApplicationContextHolder.java (我们需要通过这个类得到RedisTemplate)
@Component
public class ApplicationContextHolder implements ApplicationContextAware{

    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        ApplicationContextHolder.applicationContext = applicationContext; // NOSONAR
    }

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        checkApplicationContext();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }

    /**
     * 清除applicationContext静态变量.
     */
    public static void cleanApplicationContext() {
        applicationContext = null;
    }

    private static void checkApplicationContext() {
        if (applicationContext == null) {
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
        }
    }
}
  1. 然后再映射文件中开启二级缓存(使用二级缓存)
<mapper namespace="com.example.demo.dao.StudentDao">

    <!-- 开启基于redis的二级缓存 -->
    <cache type="com.example.demo.redis.cache.MybatisRedisCache"/>
    <cache/>

    <insert id="insert" parameterType="com.example.demo.entity.Student" useGeneratedKeys="true" keyProperty="id">
        insert into
        students(name,age,position)
        values
        (#{name},#{age},#{position})
    </insert>

    <insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
        insert into
        students(name,age,position)
        values
        <foreach collection="studentList" item="item" index="index" open="" close="" separator=",">
            (
              #{item.name},
              #{item.age},
              #{item.position}
            )
        </foreach>
    </insert>
    
    <delete id="delete" parameterType="java.lang.String">
        delete from students where name = #{name}
    </delete>

    <!--并且在update语句中,设置flushCache为true,这样在更新信息时,能够自动失效缓存(本质上调用的是clear方法)-->
    <update id="update" parameterType="com.example.demo.entity.Student" flushCache="true">
        update students
          set students.position = #{position}
          where name = #{name}
    </update>
    
    <select id="findByName" resultMap="BaseResultMap">
        select *
        from students
        where name = #{name}
    </select>

    <select id="findAll" resultMap="BaseResultMap">
        select *
        from students
    </select>

    <resultMap id="BaseResultMap" type="com.example.demo.entity.Student">
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="position" property="position"/>
    </resultMap>
</mapper>

下面是我在实现二级缓存过程中一些报错问题:

  1. 在我修改了序列化问题后,报错消失。
    在这里插入图片描述

小白入门,在实践过程中参考了很多优秀的博客:
https://my.oschina.net/ljc94/blog/1504320
https://blog.csdn.net/qq_40008535/article/details/83446123

posted on 2019-09-06 19:47  纯黑Se丶  阅读(1009)  评论(0编辑  收藏  举报