springboot+mybatis+redis 二级缓存问题实例详解

springboot+mybatis+redis 二级缓存问题实例详解,第1张

概述前言什么是mybatis二级缓存?二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。

前言

什么是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,但是在接下来的场景没有使用到。

@Configurationpublic class RedisConfig { @Value("${spring.redis.host}") private String host; // 篇幅受限,省略了 @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相关配置如下

#redisspring.redis.host=10.93.84.53spring.redis.port=6379spring.redis.password=bigdata123spring.redis.database=15spring.redis.timeout=0spring.redis.pool.maxTotal=8spring.redis.pool.maxWaitMillis=1000spring.redis.pool.maxIDle=8spring.redis.pool.minIDle=0

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

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

3、Mybatis Cache

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

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

我们实现实现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

@Componentpublic class RedisCacheTransfer { @autowired public voID setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {  MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory); }}

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

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

这样就把spring容器管理的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.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));

指定了mapper.xml的存放路径,在mybatis-mapper路径下,所有后缀是.xml的都会读入。

bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));

指定了mybatis-config.xml的存放路径,直接放在Resource目录下即可。

@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 >= #{startTime} </if> <if test="endTime != null">  and createTime <= #{endTime} </if> </select></mapper>

注意:

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

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

这里只实现了一个查询queryGeoFence,你可以在select标签中,开启或者关闭这个sql的缓存。使用属性值useCache=true/false。

7、Mapper和Model

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

public class GeoFence implements Serializable { // setter和getter省略 }public class GeoFenceParam implements Serializable { // setter和getter省略 }

mapper就还是以前的写法,使用mapper.xml的方式这里只需要定义出抽象函数即可。

@Mapperpublic 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发送请求,注意

1)-H - Content-type:application/Json方式

2)-d - 后面是Json格式的参数包体

curl -H "Content-Type:application/Json" -XPOST http://。。。/moonlight/fence/query -d '{ "name" : "test","group": "test","type": 1,"startTime":"2017-12-06 00:00:00","endTime":"2017-12-06 16:00:00","pageNum": 1,"pageSize": 8

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

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

在我们的测试环境中由于数据量比较小,缓存对查询速度的优化并不明显。这里就不过多说明了。

总结

以上是内存溢出为你收集整理的springboot+mybatis+redis 二级缓存问题实例详解全部内容,希望文章能够帮你解决springboot+mybatis+redis 二级缓存问题实例详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1227363.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-05
下一篇 2022-06-05

发表评论

登录后才能评论

评论列表(0条)

保存