一级缓存
MyBatis 会在表示会话的 SqlSession 对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。其作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。
二级缓存(第三方缓存)
二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 去操作数据库得到数据会存在二级缓存区域,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。其作用域是 mapper 的同一个 namespace,不同的 sqlSession 两次执行相同 namespace下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。
开启 MyBatis 二级缓存
在 Server-admin配置文件中开启 MyBatis 二级缓存,配置代码如下:
mybatis: configuration: cache-enabled: true
IDEA 提示生成序列号
默认情况下 Intellij IDEA 不会提示继承了 Serializable 接口的类生成 serialVersionUID 的警告。如果需要生成 serialVersionUID,需要手动配置。
File -> Settings -> Inspections -> Serialization issues -> Serialization class without 'serialVersionUID'
实体类实现序列化接口并声明序列号
实体类继承Serializable
public class Api implements Serializable { }
ALT+Enter生成序列号
private static final long serialVersionUID = 8289770415244673535L;
创建相关工具类
实现 Spring ApplicationContextAware 接口,用于手动注入 Bean
创建context文件夹
创建一个名为 ApplicationContextHolder 的工具类,代码如下:
package com.funtl.itoken.common.context; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean { private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class); private static ApplicationContext applicationContext; /** * 获取存储在静态变量中的 ApplicationContext * * @return */ public static ApplicationContext getApplicationContext() { assertContextInjected(); return applicationContext; } /** * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型 * * @param name * @param <T> * @return */ public static <T> T getBean(String name) { assertContextInjected(); return (T) applicationContext.getBean(name); } /** * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型 * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { assertContextInjected(); return applicationContext.getBean(clazz); } /** * 实现 DisposableBean 接口,在 Context 关闭时清理静态变量 * * @throws Exception */ public void destroy() throws Exception { logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext); applicationContext = null; } /** * 实现 ApplicationContextAware 接口,注入 Context 到静态变量中 * * @param applicationContext * @throws BeansException */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.applicationContext = applicationContext; } /** * 断言 Context 已经注入 */ private static void assertContextInjected() { Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 ApplicationContextHolder");
实现 MyBatis Cache 接口,用于自定义缓存为 Redis
创建一个名为 RedisCache 的工具类,代码如下:
package com.funtl.itoken.common.utils; import com.funtl.itoken.common.context.ApplicationContextHolder; import org.apache.ibatis.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Redis 缓存工具类 * <p>Title: RedisCache</p> * <p>Description: </p> * * @author Lusifer * @version 1.0.0 * @date 2018/8/13 6:03 */ public class RedisCache implements Cache { private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final String id; // cache instance id private RedisTemplate redisTemplate; private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间 public RedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; } @Override public String getId() { return id; } /** * Put query result to redis * * @param key * @param value */ @Override public void putObject(Object key, Object value) { try { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); logger.debug("Put query result to redis"); } catch (Throwable t) { logger.error("Redis put failed", t); } } /** * Get cached query result from redis * * @param key * @return */ @Override public Object getObject(Object key) { try { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); logger.debug("Get cached query result from redis"); // System.out.println("****" + opsForValue.get(key).toString()); return opsForValue.get(key); } catch (Throwable t) { logger.error("Redis get failed, fail over to db", t); return null; } } /** * Remove cached query result from redis * * @param key * @return */ @Override @SuppressWarnings("unchecked") public Object removeObject(Object key) { try { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.delete(key); logger.debug("Remove cached query result from redis"); } catch (Throwable t) { logger.error("Redis remove failed", t); } return null; } /** * Clears this cache instance */ @Override public void clear() { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.execute((RedisCallback) connection -> { connection.flushDb(); return null; }); logger.debug("Clear all the cached query result from redis"); } /** * This method is not used * * @return */ @Override public int getSize() { return 0; } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } private RedisTemplate getRedisTemplate() { if (redisTemplate == null) { redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTempla
Mapper 接口中增加注解
在 Mapper.xml中增加cache,声明需要使用二级缓存
<?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.xd.admin.mapper.UserMapper"> <cache type="com.xd.admin.utils.RedisCache"/> <select id="selectUserById" resultType="User" parameterType="String"> select * from user where id = 1 </select> </mapper>
在bootstrap.yml添加读取redis配置
spring: cloud: config: label: master #配置仓库的分支 uri: http://localhost:8888 name: Server-Admin,Server-Redis profile: dev