前言java
什麼是mybatis二級緩存?redis
二級緩存是多個sqlsession共享的,其做用域是mapper的同一個namespace。spring
即,在不一樣的sqlsession中,相同的namespace下,相同的sql語句,而且sql模板中參數也相同的,會命中緩存。sql
第一次執行完畢會將數據庫中查詢的數據寫到緩存,第二次會從緩存中獲取數據將再也不從數據庫查詢,從而提升查詢效率。數據庫
Mybatis默認沒有開啓二級緩存,須要在全局配置(mybatis-config.xml)中開啓二級緩存。apache
本文講述的是使用Redis做爲緩存,與springboot、mybatis進行集成的方法。json
一、pom依賴緩存
使用springboot redis集成包,方便redis的訪問。redis客戶端選用Jedis。springboot
另外讀寫kv緩存會進行序列化,因此引入了一個序列化包。session
<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客戶端。
二、Redis訪問使用的Bean
增長Configuration,配置jedisConnectionFactory bean,留待後面使用。
通常來說,也會生成了redisTemplate bean,可是在接下來的場景沒有使用到。
@Configuration public 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相關配置以下
#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的緩存。
三、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來實現的。
四、靜態注入
RedisCacheTransfer.java
@Component public 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實例序列化。
五、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(); }
六、配置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。
七、Mapper和Model
讀寫緩存Model須要序列化:只須要類聲明的時候實現Seriaziable接口就行了。
public class GeoFence implements Serializable { // setter和getter省略 } public class GeoFenceParam implements Serializable { // setter和getter省略 }
mapper就仍是之前的寫法,使用mapper.xml的方式這裏只須要定義出抽象函數便可。
@Mapper public interface MoonlightMapper { List<GeoFence> queryGeoFence(GeoFenceQueryParam geoFenceQueryParam); }
到這裏,全部的代碼和配置都完成了,下面測試一下。
八、測試一下
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模板查詢,後面都是命中了緩存。
在咱們的測試環境中因爲數據量比較小,緩存對查詢速度的優化並不明顯。這裏就不過多說明了。
最後上一篇打臉文。給你參考http://blog.csdn.net/isea533/article/details/44566257
完畢。