前言

什么是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 581. Shortest Unsorted Continuous Subarray (最短无序连续子数组)

    Given an integer array, you need to find one continuous subarray that if you only sort this subarray ...

  2. LeetCode 104. Maximum Depth of Binary Tree (二叉树的最大深度)

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...

  3. Hibternate框架笔记

    Hibernate框架 配置 配置文件: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE h ...

  4. 笔记-java泛型详解

    首先,先说明一下,java泛型文章的出处:http://www.cnblogs.com/lzq198754/p/5780426.html 作为学习笔记保存. 1.为什么需要泛型 泛型在Java中有很重 ...

  5. bug:记最近出现的非功能bug

    1.android 4.1.2 的兼容bug 一直以为Android 测试 4 5 6就可以了,结果发现Android4.1.2 和Android4.3之间还是有差距的. 处理办法:验证版本兼容的时候 ...

  6. HQL语法

    HQL:Hibernate Query Language HQL是完全面向对象的查询语言,因此可以支持继承和多态等特征. $下面介绍HQL语句的语法 1.from子句 from Person 表明从P ...

  7. iOS 极光推送的集成以及一些集成后的狗血

    1.首先进入极光文档下载激光推送的SDk---传送门http://docs.jiguang.cn/jpush/client/iOS/ios_sdk/   将解压后的lib子文件夹(包含JPUSHSer ...

  8. 有关数据传输GET和POST的方法的区别

    有关前后端数据交互,主要是通过走http协议通过post或get的方法,拿angularJS来说:通过JS来发送http请求调用相关接口: $scope.apostDate=function(){ $ ...

  9. 在ASP.NET Core Web API中为RESTful服务增加对HAL的支持

    HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务 ...

  10. mybatis从数据库中取到的date格式不是yyyy-MM-dd HH:mm:ss

    问题:sqlserver中的存储时间格式为date,pojo的时间属性也是date,直接mybatis取出的时间格式是带英语的那种,不满足客户要求. 解决:将pojo的时间属性改为string类型,在 ...