(springboot)基于Redis实现Mybatis二级缓存(自定义缓存)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> (springboot)基于Redis实现Mybatis二级缓存(自定义缓存)

Springboot + Mybatis + Redis

Mybatis的二级缓存是多个SqlSession共享的,作用于是mapper配置文件中同一个namespace,不同的SqlSession两次执行相同namespace下的sql语句且参数如果也一样则最终执行的sql语句是相同的。每次查询都会先看看缓存中是否有对应查询结果,如果有就从缓存拿,如果没有就执行sql语句从数据库中读取,从而提高查询效率。Mybatis默认开启的是一级缓存,所以二级缓存需要自己手动开启。

ps: 本项目是基于springboot + mybatis 环境下配置Redis

环境

  • 开发环境:Window 10
  • IDE: Intellij 2017.2
  • JDK: 1.8
  • Redis:3.2.100
  • Oracle:12.1.0.2.0

application.properties

开启二级缓存

12345
#Mybatismybatis.mapper-locations=classpath:com/sunnada/hurd/*/dao/*.xmlmybatis.type-aliases-package=com.sunnada.hurd#开启MyBatis的二级缓存mybatis.configuration.cache-enabled=true

#Mybatis
mybatis.mapper-locations=classpath:com/sunnada/hurd//dao/.xml
mybatis.type-aliases-package=com.sunnada.hurd
#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true

配置redis

123456789
#redis#database namespring.redis.database=0#server hostspring.redis.host=192.168.168.9#server passwordspring.redis.password=#connection portspring.redis.port=6379

#redis
#database name
spring.redis.database=0
#server host
spring.redis.host=192.168.168.9
#server password
spring.redis.password=
#connection port
spring.redis.port=6379

pom.xml

添加redis依赖

1234
dependency    groupIdorg.springframework.boot/groupId    artifactIdspring-boot-starter-data-redis/artifactId/dependency

dependency
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-starter-data-redis/artifactId
/dependency

RedisConfig

这里Redis配置类重写了Redis序列化的方式,改用Json的数据结构传输数据。

配置RedisTemplate并定义Serializer方式。

12345678910111213141516171819202122232425262728293031323334353637383940414243
package com.sunnada.hurd.config; import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @program: HurdProject * @description: Redis配置类 * @author: linyh * @create: 2018-09-10 17:17 **/@Configurationpublic class RedisConfig {     @Bean    public RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplateString, Object redisTemplate = new RedisTemplate();        redisTemplate.setConnectionFactory(redisConnectionFactory);         Jackson2JsonRedisSerializerObject jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializerObject(Object.class);        ObjectMapper om = new ObjectMapper();        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);        jackson2JsonRedisSerializer.setObjectMapper(om);         // 设置值(value)的序列化采用Jackson2JsonRedisSerializer。        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);        // 设置键(key)的序列化采用StringRedisSerializer。        redisTemplate.setKeySerializer(new StringRedisSerializer());         redisTemplate.afterPropertiesSet();        return redisTemplate;    }  }

package com.sunnada.hurd.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**

  • @program: HurdProject
  • @description: Redis配置类
  • @author: linyh
  • @create: 2018-09-10 17:17

**/
@Configuration
public class RedisConfig {


@Bean
public RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplateString, Object redisTemplate = new RedisTemplate();
    redisTemplate.setConnectionFactory(redisConnectionFactory);

    Jackson2JsonRedisSerializerObject jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializerObject(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    // 设置值(value)的序列化采用Jackson2JsonRedisSerializer。
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    // 设置键(key)的序列化采用StringRedisSerializer。
    redisTemplate.setKeySerializer(new StringRedisSerializer());

    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}

}

SpringContextHolder

组件,实现了Spring的ApplicationContextAware来获取ApplicationContext,从中获取容器的bean

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
package com.sunnada.hurd.cache; import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component; /** * @description: 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext * @author: linyh * @create: 2018-09-10 17:25 **/@Componentpublic class SpringContextHolder implements ApplicationContextAware{     private static ApplicationContext applicationContext;     /**     * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.     */    public void setApplicationContext(ApplicationContext applicationContext) {        SpringContextHolder.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(ClassT 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");        }    }}

package com.sunnada.hurd.cache;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**

  • @description: 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext
  • @author: linyh
  • @create: 2018-09-10 17:25

**/
@Component
public class SpringContextHolder implements ApplicationContextAware{


private static ApplicationContext applicationContext;

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

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

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

/**
 * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
 */
@SuppressWarnings("unchecked")
public static  T getBean(Class 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");
    }
}

}

MybatisRedisCache

Mybatis二级缓存默认使用的是其他的缓存,这里我们需要集成Redis就需要自己自定义写一个缓存类去实现二级缓存。

自定义缓存需要实现Mybatis的Cache接口。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
package com.sunnada.hurd.cache; import org.apache.ibatis.cache.Cache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.CollectionUtils; import java.util.Set;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @description: 使用Redis实现Mybatis二级缓存,实现Cache接口 * @author: linyh * @create: 2018-09-10 17:21 **/public class MybatisRedisCache implements Cache {     //private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);     // 读写锁    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);     private RedisTemplateString, Object redisTemplate = SpringContextHolder.getBean("redisTemplate");     private String id;     public MybatisRedisCache(final String id) {        if (id == null) {            throw new IllegalArgumentException("Cache instances require an ID");        }        //logger.info("Redis Cache id " + id);        this.id = id;    }     @Override    public String getId() {        return this.id;    }     @Override    public void putObject(Object key, Object value) {        if (value != null) {            // 向Redis中添加数据,有效时间是2天            redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);        }    }     @Override    public Object getObject(Object key) {        try {            if (key != null) {                Object obj = redisTemplate.opsForValue().get(key.toString());                return obj;            }        } catch (Exception e) {            //logger.error("redis ");        }        return null;    }     @Override    public Object removeObject(Object key) {        try {            if (key != null) {                redisTemplate.delete(key.toString());            }        } catch (Exception e) {        }        return null;    }     @Override    public void clear() {        //logger.debug("清空缓存");        try {            SetString keys = redisTemplate.keys("*:" + this.id + "*");            if (!CollectionUtils.isEmpty(keys)) {                redisTemplate.delete(keys);            }        } catch (Exception e) {        }    }     @Override    public int getSize() {        Long size = (Long) redisTemplate.execute(new RedisCallbackLong() {            @Override            public Long doInRedis(RedisConnection connection) throws DataAccessException {                return connection.dbSize();            }        });        return size.intValue();    }     @Override    public ReadWriteLock getReadWriteLock() {        return this.readWriteLock;    }}

package com.sunnada.hurd.cache;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**

  • @description: 使用Redis实现Mybatis二级缓存,实现Cache接口
  • @author: linyh
  • @create: 2018-09-10 17:21

**/
public class MybatisRedisCache implements Cache {


//private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

private RedisTemplateString, Object redisTemplate = SpringContextHolder.getBean("redisTemplate");

private String id;

public MybatisRedisCache(final String id) {
    if (id == null) {
        throw new IllegalArgumentException("Cache instances require an ID");
    }
    //logger.info("Redis Cache id " + id);
    this.id = id;
}

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

@Override
public void putObject(Object key, Object value) {
    if (value != null) {
        // 向Redis中添加数据,有效时间是2天
        redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);
    }
}

@Override
public Object getObject(Object key) {
    try {
        if (key != null) {
            Object obj = redisTemplate.opsForValue().get(key.toString());
            return obj;
        }
    } catch (Exception e) {
        //logger.error("redis ");
    }
    return null;
}

@Override
public Object removeObject(Object key) {
    try {
        if (key != null) {
            redisTemplate.delete(key.toString());
        }
    } catch (Exception e) {
    }
    return null;
}

@Override
public void clear() {
    //logger.debug("清空缓存");
    try {
        SetString keys = redisTemplate.keys("*:" + this.id + "*");
        if (!CollectionUtils.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    } catch (Exception e) {
    }
}

@Override
public int getSize() {
    Long size = (Long) redisTemplate.execute(new RedisCallbackLong() {
        @Override
        public Long doInRedis(RedisConnection connection) throws DataAccessException {
            return connection.dbSize();
        }
    });
    return size.intValue();
}

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

}

Mapper.xml

mapper映射配置文件,只需要引入刚刚配置好的自定义缓存类。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
?xml version="1.0" encoding="UTF-8"?!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" mapper namespace="com.sunnada.hurd.dictionary.dao.CertificationTypeMapper"    !--resultMap id="DemoResultMap" type="com.sunnada.hurd.demo.pojo.Demo"        id column="id" jdbcType="INT" property="id" /        result column="name" jdbcType="VARCHAR" property="name" /    /resultMap--     cache type="com.sunnada.hurd.cache.MybatisRedisCache"        property name="eviction" value="LRU" /        property name="flushInterval" value="6000000" /        property name="size" value="1024" /        property name="readOnly" value="false" /    /cache     insert id="insert" parameterType="CertificationType"         insert into DIS_CERTIFICATION_TYPE ( ID,NAME,CODE,PARENT_ID,SORT,STATUS,CREATE_TIME,MODIFY_TIME,OLD_SYSTEM_ID )        values (${id},#{name},#{code},#{parentID},#{sort},#{status},#{createTime},#{modifiedTime},#{oldSystemID})        selectKey keyProperty="id" order="BEFORE" resultType="int"            SELECT DIS_CERTIFICATION_TYPE_ID_SEQ.NEXTVAL FROM dual        /selectKey    /insert     delete id="delete" parameterType="java.lang.Integer"         delete from DIS_CERTIFICATION_TYPE where ID= #{id}    /delete     select id="get" parameterType="_int" resultType="CertificationType"        select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1    /select     update id="update" parameterType="CertificationType"         update DIS_CERTIFICATION_TYPE        set            if test="name != null and name.length()  0"NAME=#{name},/if            if test="code != null and code.length()  0"CODE=#{code},/if            if test="sort != 0"SORT=#{sort},/if            if test="createTime != null"CREATE_TIME=#{createTime},/if            if test="modifiedTime != null"MODIFY_TIME=#{modifiedTime},/if            STATUS=#{status}        /set         where ID=#{id}    /update     select id="list" parameterType="CertificationType" resultType="CertificationType"        select * from DIS_CERTIFICATION_TYPE        where            if test="name != null and name.length()  0"                bind name="likename" value="'%'+ name +'%'"/bind                and NAME like #{likename}            /if            and STATUS = 1        /where    /select/mapper

?xml version=”1.0” encoding=”UTF-8”?
!DOCTYPE mapper
PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN”
http://mybatis.org/dtd/mybatis-3-mapper.dtd"

mapper namespace=”com.sunnada.hurd.dictionary.dao.CertificationTypeMapper”
!–resultMap id=”DemoResultMap” type=”com.sunnada.hurd.demo.pojo.Demo”
id column=”id” jdbcType=”INT” property=”id” /
result column=”name” jdbcType=”VARCHAR” property=”name” /
/resultMap–


cache type="com.sunnada.hurd.cache.MybatisRedisCache"
    property name="eviction" value="LRU" /
    property name="flushInterval" value="6000000" /
    property name="size" value="1024" /
    property name="readOnly" value="false" /
/cache

insert id="insert" parameterType="CertificationType" 
    insert into DIS_CERTIFICATION_TYPE ( ID,NAME,CODE,PARENT_ID,SORT,STATUS,CREATE_TIME,MODIFY_TIME,OLD_SYSTEM_ID )
    values (${id},#{name},#{code},#{parentID},#{sort},#{status},#{createTime},#{modifiedTime},#{oldSystemID})
    selectKey keyProperty="id" order="BEFORE" resultType="int"
        SELECT DIS_CERTIFICATION_TYPE_ID_SEQ.NEXTVAL FROM dual
    /selectKey
/insert

delete id="delete" parameterType="java.lang.Integer" 
    delete from DIS_CERTIFICATION_TYPE where ID= #{id}
/delete

select id="get" parameterType="_int" resultType="CertificationType"
    select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1
/select

update id="update" parameterType="CertificationType" 
    update DIS_CERTIFICATION_TYPE
    set
        if test="name != null and name.length()  0"NAME=#{name},/if
        if test="code != null and code.length()  0"CODE=#{code},/if
        if test="sort != 0"SORT=#{sort},/if
        if test="createTime != null"CREATE_TIME=#{createTime},/if
        if test="modifiedTime != null"MODIFY_TIME=#{modifiedTime},/if
        STATUS=#{status}
    /set

    where ID=#{id}
/update

select id="list" parameterType="CertificationType" resultType="CertificationType"
    select * from DIS_CERTIFICATION_TYPE
    where
        if test="name != null and name.length()  0"
            bind name="likename" value="'%'+ name +'%'"/bind
            and NAME like #{likename}
        /if
        and STATUS = 1
    /where
/select

/mapper

cache标签内属性:

eviction:定义缓存移除机制(算法),默认为LRU(最近最少使用),它会清除最少使用的数据,还有一种FIFO(先进先出),它会清除最先进来的数据。

flushInterval:定义缓存刷新周期,单位为毫秒。

size:标识缓存cache中容纳的最大元素,默认为1024。

readOnly:默认为false,可配置为true缓存只读。

(虽然我的配置大部分都为默认值,但个人观点写出来的话看上去会更清楚一点,所以都写上吧)

对于有不需要用到二级缓存的语句可以在标签内写userCache=”false”,默认为true开启缓存。

123
select id="get" parameterType="_int" resultType="CertificationType" useCache="false"        select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1/select

select id=”get” parameterType=”_int” resultType=”CertificationType” useCache=”false”
select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1
/select

(select 默认useCache为true:使用缓存,flushCache为false:不清空缓存)

(insert、update、delete 默认flushCache为true:清空缓存)

其他的Mapper接口,Service类都照常编写即可

实体类

实体类需要实现Serializable

12345678910111213141516171819202122232425262728293031323334
package com.sunnada.hurd.dictionary.entity; import java.io.Serializable;import java.util.Date; /** * @Author:linyh * @Date: 2018/9/6 14:34 * @Modified By: */public class CertificationType implements Serializable{     private static final long serialVersionUID = 1L;     private int id;    private String name;     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;    } }

package com.sunnada.hurd.dictionary.entity;

import java.io.Serializable;
import java.util.Date;

/**

  • @Author:linyh
  • @Date: 2018/9/6 14:34
  • @Modified By:

*/
public class CertificationType implements Serializable{


private static final long serialVersionUID = 1L;

private int id;
private String name;

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;
}

}

测试

这里使用Postman进行测试。

访问url 获取列表 (第一次)

(springboot)基于Redis实现Mybatis二级缓存(自定义缓存)

(第二次)

(springboot)基于Redis实现Mybatis二级缓存(自定义缓存)

速度明显提升。

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> (springboot)基于Redis实现Mybatis二级缓存(自定义缓存)


 上一篇
【加精】最全面的SpringBoot配置文件详解 【加精】最全面的SpringBoot配置文件详解
Spring Boot在工作中是用到的越来越广泛了,简单方便,有了它,效率提高不知道多少倍。Spring Boot配置文件对Spring Boot来说就是入门和基础,经常会用到,所以写下做个总结以便日后查看。 1、配置文件当我们构建完Spr
下一篇 
(SpringBoot)Shiro安全框架深入解析 (SpringBoot)Shiro安全框架深入解析
最近在学习Shiro安全框架的使用,深入研究其原理,让自己更得心应手的使用这个框架。 内容目录 Shiro的整体架构介绍 框架验证流程与原理分析 Url匹配模式 加密机制 缓存机制 1.Shiro的整体架构介绍1.1从使用者角度看Shir