前言

什么是mybatis二级缓存?

二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。

即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存。

第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存。

本文讲述的是使用Redis作为缓存,与springboot、mybatis进行集成的方法。

1、pom依赖

使用springboot redis集成包,方便redis的访问。redis客户端选用Jedis。

另外读写kv缓存会进行序列化,所以引入了一个序列化包。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.19</version>
        </dependency>

依赖搞定之后,下一步先调通Redis客户端。

2、Redis访问使用的Bean

增加Configuration,配置jedisConnectionFactory bean,留待后面使用。

一般来讲,也会生成了redisTemplate bean,但是在接下来的场景没有使用到。

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.pool.maxTotal}")
    private int maxTotal;
    @Value("${spring.redis.pool.maxWaitMillis}")
    private int maxWaitMillis;
    @Value("${spring.redis.pool.maxIdle}")
    private int maxIdle;
    @Value("${spring.redis.pool.minIdle}")
    private int minIdle;

    @Bean
    public JedisPoolConfig getRedisConfig(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(maxIdle);
        config.setMaxTotal(maxTotal);
        config.setMaxWaitMillis(maxWaitMillis);
        config.setMinIdle(minIdle);
        return config;
    }

    @Bean(name = "jedisConnectionFactory")
    public JedisConnectionFactory getConnectionFactory(){
        JedisConnectionFactory factory = new JedisConnectionFactory();
        JedisPoolConfig config = getRedisConfig();
        factory.setPoolConfig(config);
        factory.setHostName(host);
        factory.setPort(port);
        factory.setDatabase(database);
        factory.setPassword(password);
        factory.setTimeout(timeout);
        return factory;
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate<?, ?> getRedisTemplate(){
        RedisTemplate<?,?> template = new StringRedisTemplate(getConnectionFactory());
        return template;
    }
}

这里使用@Value读入了redis相关配置,有更简单的配置读取方式(@ConfigurationProperties(prefix=...)),可以尝试使用。

Redis相关配置如下

#redis
spring.redis.host=10.93.84.53
spring.redis.port=6379
spring.redis.password=bigdata123
spring.redis.database=15
spring.redis.timeout=0

spring.redis.pool.maxTotal=8
spring.redis.pool.maxWaitMillis=1000
spring.redis.pool.maxIdle=8
spring.redis.pool.minIdle=0

Redis客户端的配置含义,这里不再讲解了。pool相关的一般都和性能有关,需要根据并发量权衡句柄、内存等资源进行设置。

Redis客户端设置好了,我们开始配置Redis作为Mybatis的缓存。

3、Mybatis Cache

这一步是最为关键的一步。实现方式是实现Mybatis的一个接口org.apache.ibatis.cache.Cache。

Cache.java

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}

可以看到,这个接口设计了写缓存,读缓存,销毁缓存的方式,和访问控制读写锁。

我们实现实现Cache接口的类是MybatisRedisCache。

MybatisRedisCache.java

public class MybatisRedisCache implements Cache {

    private static JedisConnectionFactory jedisConnectionFactory;

    private final String id;

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

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

    @Override
    public void clear() {
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            connection.flushDb();
            connection.flushAll();
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

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

    @Override
    public Object getObject(Object key) {
        Object result = null;
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = serializer.deserialize(connection.get(serializer.serialize(key)));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

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

    @Override
    public int getSize() {
        int result = 0;
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            result = Integer.valueOf(connection.dbSize().toString());
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    @Override
    public void putObject(Object key, Object value) {
        RedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            connection.set(serializer.serialize(key), serializer.serialize(value));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public Object removeObject(Object key) {
        RedisConnection connection = null;
        Object result = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = connection.expire(serializer.serialize(key), 0);
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory;
    }

}

注意:

可以看到,这个类并不是由Spring虚拟机管理的类,但是,其中有一个静态属性jedisConnectionFactory需要注入一个Spring bean,也就是在RedisConfig中生成的bean。

在一个普通类中使用Spring虚拟机管理的Bean,一般使用Springboot自省的SpringContextAware。

这里使用了另一种方式,静态注入的方式。这个方式是通过RedisCacheTransfer来实现的。

4、静态注入

RedisCacheTransfer.java

可以看到RedisCacheTransfer是一个springboot bean,在容器创建之初进行初始化的时候,会注入jedisConnectionFactory bean给setJedisConnectionFactory方法的传参。

而setJedisConnectionFactory通过调用静态方法设置了类MybatisRedisCache的静态属性jedisConnectionFactory。

这样就把spring容器管理的jedisConnectionFactory注入到了静态域。

@Component
public class RedisCacheTransfer {

    @Autowired
    public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory);
    }

}

到这里,代码基本已经搞定,下面是一些配置。主要有(1)全局开关;(2)namespace作用域开关;(3)Model实例序列化。

5、Mybatis二级缓存的全局开关

前面提到过,默认二级缓存没有打开,需要设置为true。这是全局二级缓存的开关。

Mybatis的全局配置。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!-- 全局参数 -->
    <settings>
        <!-- 使全局的映射器启用或禁用缓存。 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>

全局配置的加载在dataSource中可以是这样的

    @Bean(name = "moonlightSqlSessionFactory")
    @Primary
    public SqlSessionFactory moonlightSqlSessionFactory(@Qualifier("moonlightData") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));
        bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return bean.getObject();
    }

6、配置mapper作用域namespace

前面提到过,二级缓存的作用域是mapper的namespace,所以这个配置需要到mapper中去写。

<mapper namespace="com.kangaroo.studio.moonlight.dao.mapper.MoonlightMapper">
  <cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>
  <resultMap id="geoFenceList" type="com.kangaroo.studio.moonlight.dao.model.GeoFence">
    <constructor>
      <idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
      <arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="type" javaType="java.lang.Integer" jdbcType="INTEGER" />
      <arg column="group" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="geo" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="createTime" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="updateTime" javaType="java.lang.String" jdbcType="VARCHAR" />
    </constructor>
  </resultMap>

<select id="queryGeoFence" parameterType="com.kangaroo.studio.moonlight.dao.model.GeoFenceQueryParam" resultMap="geoFenceList">
    select <include refid="base_column"/> from geoFence where 1=1
    <if test="type != null">
      and type = #{type}
    </if>
    <if test="name != null">
      and name like concat('%', #{name},'%')
    </if>
    <if test="group != null">
      and `group` like concat('%', #{group},'%')
    </if>
    <if test="startTime != null">
      and createTime &gt;= #{startTime}
    </if>
    <if test="endTime != null">
      and createTime &lt;= #{endTime}
    </if>
  </select>
</mapper>

注意:

namespace下的cache标签就是加载缓存的配置,缓存使用的正式我们刚才实现的MybatisRedisCache。

<cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>

7、Mapper和Model

读写缓存Model需要序列化:只需要类声明的时候实现Seriaziable接口就好了。

public class GeoFence implements Serializable {

    private Integer id;
    private String name;
    private String group;
    private Integer type;
    private String geo;
    private String createTime;
    private String updateTime;

    public GeoFence() {}

    public GeoFence(Integer id, String name, Integer type, String group, String geo, String createTime, String updateTime) {
        this.id = id;
        this.name = name;
        this.type = type;
        this.group = group;
        this.geo= geo;
        this.createTime = createTime;
        this.updateTime = updateTime;
    }
    // setter和getter省略
}

mapper就还是以前的写法

@Mapper
public interface MoonlightMapper {
    List<GeoFence> queryGeoFence(GeoFenceQueryParam geoFenceQueryParam);
}

到这里,所有的代码和配置都完成了,下面测试一下。

8、测试一下

Controller中实现一个这样的接口POST。

@RequestMapping(value = "/fence/query", method = RequestMethod.POST)
    @ResponseBody
    public ResponseEntity<Response> queryFence(@RequestBody GeoFenceQueryParam geoFenceQueryParam) {
        try {
            Integer pageNum = geoFenceQueryParam.getPageNum()!=null?geoFenceQueryParam.getPageNum():1;
            Integer pageSize = geoFenceQueryParam.getPageSize()!=null?geoFenceQueryParam.getPageSize():10;
            PageHelper.startPage(pageNum, pageSize);
            List<GeoFence> list = moonlightMapper.queryGeoFence(geoFenceQueryParam);
            return new ResponseEntity<>(
                    new Response(ResultCode.SUCCESS, "查询geoFence成功", list),
                    HttpStatus.OK);
        } catch (Exception e) {
            logger.error("查询geoFence失败", e);
            return new ResponseEntity<>(
                    new Response(ResultCode.EXCEPTION, "查询geoFence失败", null),
                    HttpStatus.INTERNAL_SERVER_ERROR);
        }

使用curl发送请求

curl -H "Content-Type:application/json" -XPOST http://bigdata.xiaojukeji.com/data_monitor/moonlight/fence/query -d '{
    "name" : "test",
    "group": "test",
    ,
    "startTime":"2017-12-06 00:00:00",
    "endTime":"2017-12-06 16:00:00",
    ,

}'

请求了三次,日志打印如下,

可以看到,只有第一次执行了sql模板查询,后面都是命中了缓存。

完毕。

Springboot Mybatis Redis 实现二级缓存的更多相关文章

  1. Spring + MySQL + Mybatis + Redis【二级缓存】

    一.Redis环境 Redis 官网 :http://redis.io/ windows下载:https://github.com/dmajkic/redis/downloads 1.文件解压缩 2. ...

  2. springboot+mybatis+redis实现分布式缓存

    大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...

  3. mybatis+redis实现二级缓存

    在网上看了很多资料,发现例子都是千篇一律的相互复制.而且,使用的都是jedis的客户端..我这里使用的是redistemplate类实现. 缓存的原理..实现cache类接口,当哪个类需要缓存的时候, ...

  4. Spring + MySQL + Mybatis + Redis【二级缓存】执行流程分析

    一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就 ...

  5. mybatis plus使用redis作为二级缓存

    建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现.为了方便,这里我们将缓存放到mapper层.mybatis-plus整合redi ...

  6. SpringMVC + MyBatis + Mysql + Redis(作为二级缓存) 配置

    2016年03月03日 10:37:47 标签: mysql / redis / mybatis / spring mvc / spring 33805 项目环境: 在SpringMVC + MyBa ...

  7. mybatis 使用redis实现二级缓存(spring boot)

    mybatis 自定义redis做二级缓存 前言 如果关注功能实现,可以直接看功能实现部分 何时使用二级缓存 一个宗旨---不常变的稳定而常用的 一级是默认开启的sqlsession级别的. 只在单表 ...

  8. SpringMVC +Spring + MyBatis + Mysql + Redis(作为二级缓存) 配置

    转载:http://blog.csdn.net/xiadi934/article/details/50786293 项目环境: 在SpringMVC +Spring + MyBatis + MySQL ...

  9. Mybatis的二级缓存、使用Redis做二级缓存

    目录 什么是二级缓存? 1. 开启二级缓存 如何使用二级缓存: userCache和flushCache 2. 使用Redis实现二级缓存 如何使用 3. Redis二级缓存源码分析 什么是二级缓存? ...

随机推荐

  1. LeetCode 219. Contains Duplicate II (包含重复项之二)

    Given an array of integers and an integer k, find out whether there are two distinct indices i and j ...

  2. LeetCode 191. Number of 1 bits (位1的数量)

    Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also know ...

  3. SimpleDateFormat 常规用法

    public class SimpleDateFormat extends DateFormat SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (d ...

  4. Java基础笔记11

    异常: 即java程序在运行时出现的意外情况.  java如何处理异常. try{ //可能发生异常的地方 }catch(异常类型 对象){  //异常处理处 }catch(异常类型 对象){ }.. ...

  5. django 实现同一个ip十分钟内只能注册一次

    很多小伙伴都会有这样的问题,说一个ip地址十分钟内之内注册一次,用来防止用户来重复注册带来不必要的麻烦 逻辑: 取ip,在数据库找ip是否存在,存在判断当前时间和ip上次访问时间之差,小于600不能注 ...

  6. Appium python自动化测试系列之混合app实战(十一)

    12.1 什么是混合App 12.1.1 混合app定义 什么是混合app,其实这个不言而喻,我们的app正常来说应该都是native的,但是实际工作中却不是,反正种种原因我们的app会有native ...

  7. Linux crontab定时器设置(定期执行java程序)(转)

    Crontab 语法 Crontab语法一个crontab文件用五个段来定义:天,日期和时间,和一个要定期执行的命令代码. *    *  *  *   *  command to be execut ...

  8. poj 2720 Last Digits

    Last Digits Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 2233   Accepted: 474 Descri ...

  9. Android 开发笔记___图像视图

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  10. 轻松驾驭Tomcat

    Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选.对于一个初学者来说,可以这样 ...