使用redis做mybatis的二级缓存
在xiaolyuh123博主的基础上完善几个问题;
application.properties
在application.properties文件中配置Redis,Mybatis,开启Mybatis二级缓存等
server.port=8084
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=yong1014
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
#mybatis
mybatis.type-aliases-package=com.haoxy.example.model
mybatis.mapper-locations=classpath*:/mappers/*Mapper.xml
#开启mybatis的二级缓存
mybatis.configuration.cache-enabled=true
#pagehelper
#如果不配置,pagehelper会自动获取连接,检测数据库
pagehelper.helperDialect=mysql
#配置pageNum参数合理化,比如第0页,和超过最后一页,则返会第一页和最后一页。而不是意想不到的数据。
pagehelper.reasonable=true
#支持通过 Mapper 接口参数来传递分页参数
pagehelper.supportMethodsArguments=true
#为了支持PageHelper.startPage(Object params)方法,默认值为pageNum=pageNum;pageSize=pageSize;count=countSql
pagehelper.params=count=countSql
#redis
spring.redis.host=127.0.0.1
#server password
spring.redis.password=adminadmin
#connection port
spring.redis.port=6379
spring.redis.timeout=10000
spring.redis.database=0
spring.redis.pool.max-idle=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
pom.xml
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.6.RELEASEversion>
parent>
<groupId>com.haoxy.examplegroupId>
<artifactId>springboot-mybatis-redis-cacheartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.0version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.31version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.0.0version>
dependency>
dependencies>
Redis配置类替换序列化实现方式
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 设置值(value)的序列化采用Jackson2JsonRedisSerializer。
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
Spring容器获取Bean工具类
通过Spring Aware(容器感知)来获取到ApplicationContext,然后根据ApplicationContext获取容器中的Bean。
@Component
public class SpringContextHolder implements ApplicationContextAware {
//以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
/**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
}
}
}
自定的Mybatis缓存
自定义缓存需要实现Mybatis的Cache接口,我这里将使用Redis来作为缓存的容器。
public class RedisCache implements Cache {
private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplate redisTemplate;
private String id;
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
logger.info("Redis Cache id " + id);
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
if (value != null) {
//向redis中加入数据,有效期是2天
redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);
}
}
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
try {
if (key != null) {
Object object = redisTemplate.opsForValue().get(key.toString());
return object;
}
} catch (Exception e) {
logger.error("redis exception.....");
e.printStackTrace();
}
return null;
}
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
try {
if (key != null) {
redisTemplate.delete(key.toString());
}
} catch (Exception e) {
}
return null;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
logger.debug("清空缓存");
try {
Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
} catch (Exception e) {
logger.error("clear exception....");
}
}
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.dbSize();
}
});
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = SpringContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
在使用 springboot-mybatis-ehcache (使用EhcacheCache做二级缓存,使用pageHelper做分页插件)
同样有一个名叫EhcacheCache的类实现了Cache接口;并实现了其中的方法;只是这个mybatis帮我们做了,我们只需要引入相应的依赖即可,我们要自定义的话,所以上面我们使用redis一样要实现Cache接口;
Mapper文件
<mapper namespace="com.haoxy.example.mapper.PersonMapper">
<cache type="com.haoxy.example.utils.RedisCache">
<property name="eviction" value="LRU" />
<property name="flushInterval" value="6000000" />
<property name="size" value="1024" />
<property name="readOnly" value="false" />
cache>
<resultMap id="BaseResultMap" type="com.haoxy.example.model.Person">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="age" property="age" jdbcType="INTEGER"/>
<result column="address" property="address" jdbcType="VARCHAR"/>
resultMap>
<select id="findAll" resultMap="BaseResultMap" useCache="true">
select
<include refid="Base_Column_List"/>
from person
select>
<select id="findAllPerson" resultMap="BaseResultMap" useCache="false">
select
<include refid="Base_Column_List"/>
from person
select>
这里只列出重要的部分; 具体见项目源码;
PersonController
@RequestMapping("/findAll")
public String findAll() {
long begin = System.currentTimeMillis();
List<Person> persons = personService.findAll();
long ing = System.currentTimeMillis();
System.out.println(("请求时间:" + (ing - begin) + "ms"));
return JSON.toJSONString(persons);
}
@RequestMapping("/findAllPerson")
public String findAllPerson() {
long begin = System.currentTimeMillis();
List<Person> persons = personService.findAllPerson();
long ing = System.currentTimeMillis();
System.out.println(("请求时间:" + (ing - begin) + "ms"));
return JSON.toJSONString(persons);
}
测试
1,测试findAll
方法,测试结构如下:
2,测试findAllPerson
方法,
可以很明显的看到findAllPerson
方法比findAll
方法快很多,在mapper.xml文件中findAllPerson
方法的查询语句useCache="false"
这个语句的查询结果是没有放到二级缓存的,竟然比findAll
方法(在mapper.xml文件中findAll
方法的查询语句useCache="true"
放到了二级缓存中)快很多;
这是因为我们使用的是Redis做的二级缓存;这也是为什么mybatis帮我们实现了用EhcacheCache做二级缓存的原因;
下一篇文章我们对两者进行对比;
本案例地址;
对应的sql在项目的根目录下;