POM依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.4.3version>
dependency>
配置文件
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=xiaobu1994
# 连接超时时间(毫秒)
spring.redis.timeout=10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
#缓存配置
# 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 先后顺序 JCache -> EhCache -> Redis -> Guava
spring.cache.type=redis
启动类
package com.xiaobu;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import tk.mybatis.spring.annotation.MapperScan;
/**
* @author xiaobu
* @EnableCaching 开启缓存
*/
@EnableCaching
@SpringBootApplication
@Slf4j
public class SsmApplication implements WebMvcConfigurer, CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SsmApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("服务启动成功。。。。");
}
}
直接使用
用FastJson实现序列化
package com.xiaobu.base.entity;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* @author xiaobu
* @version JDK1.8.0_171
* @date on 2019/9/4 17:07
* @description 序列化器
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
自定义序列化使用
package com.xiaobu.config;
import com.alibaba.fastjson.parser.ParserConfig;
import com.xiaobu.base.entity.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author xiaobu
* @version JDK1.8.0_171
* @date on 2019/9/4 19:25
* @description V1.0
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
@Primary//当有多个管理器的时候,必须使用该注解在一个管理器上注释:表示该管理器为默认的管理器
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//序列化方式1
//设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下(4行)注释代码为默认实现
// ClassLoader loader = this.getClass().getClassLoader();
// JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
// RedisSerializationContext.SerializationPair
// RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//序列化方式1---另一种实现方式
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();//该语句相当于序列化方式1
//序列化方式2
//Jackson2JsonRedisSerializer serializer=new Jackson2JsonRedisSerializer(Object.class);
//RedisSerializationContext.SerializationPair
//RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//序列化方式3 JSONObject
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//设置过期时间 30天
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofDays(30));
//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
//设置白名单---非常重要********
/*
使用fastjson的时候:序列化时将class信息写入,反解析的时候,
fastjson默认情况下会开启autoType的检查,相当于一个白名单检查,
如果序列化信息中的类路径不在autoType中,
反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
可参考 https://blog.csdn.net/u012240455/article/details/80538540
*/
ParserConfig.getGlobalInstance().addAccept("com.xiaobu.entity.");
return cacheManager;
}
@Bean(name = "redisTemplate")
@SuppressWarnings("unchecked")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
package com.xiaobu;
import com.xiaobu.entity.City;
import com.xiaobu.mapper.CountryMapper;
import com.xiaobu.service.CountryService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.TimeUnit;
/**
* @author xiaobu
* @version JDK1.8.0_171
* @date on 2019/9/4 10:40
* @description V1.0 手动使用redis
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class RedisTest {
@Autowired
private CountryMapper countryMapper;
@Autowired
CountryService countryService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void test(){
//字符串
stringRedisTemplate.opsForValue().set("name", "小布");
log.info(stringRedisTemplate.opsForValue().get("name"));
City city = new City();
city.setName("深圳");
city.setState("广东");
ValueOperations<String, City> ops = redisTemplate.opsForValue();
ops.set("city1", city);
City city1= (City) redisTemplate.opsForValue().get("city1");
log.info("city1 ==> [{}]", city1);
//设置过期
ops.set("tempCity",city,1, TimeUnit.HOURS);
City tempCity= (City) redisTemplate.opsForValue().get("tempCity");
log.info("tempCity ==> [{}]", tempCity);
}
}
查看redis,可以看出已经设置成功.
使用Spring Cache自动根据方法生成缓存
key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。例如:@Cacheable(value=”testcache”,key=”#id”)
value: 缓存的名称,必须指定至少一个。例如:@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:condition ="#id<2",只缓存id<2的;condition=”#userName.length()>2”只缓存名字长度大于2的)
@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
@CachePut注解的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。但需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同。
@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。
@CacheConfig 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig @CacheConfig是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager 和CacheResolver。
该操作会被覆盖。
控制层
@RequestParam 和 @PathVariable 注解是用于从request中接收请求的,两个都可以接收参数,关键点不同的是@RequestParam 是从request里面拿取值,而 @PathVariable 是从一个URI模板里面来填充
package com.xiaobu.controller;
import com.xiaobu.entity.City;
import com.xiaobu.service.CityService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* @author xiaobu
* @version JDK1.8.0_171
* @date on 2019/9/4 16:25
* @description
*/
@RestController
@RequestMapping("/cache")
public class CacheController {
@Resource
private CityService cityService;
/**
* 功能描述:只有当id<2是才会缓存 redis的key为 cacheCity::1 http://localhost:8899/cities/getCityByCache/1
* @author xiaobu
* @date 2019/7/29 15:12
* @param id id
* @return com.xiaobu.entity.City
* @version 1.0
*/
@Cacheable(value = "cacheCity",key ="#id", condition ="#id<2")
@GetMapping("getCityByCache/{id}")
public City getCityByCache(@PathVariable Integer id) {
return cityService.getCityByCache(id);
}
/**
* 功能描述:把更新后的放入缓存 key为 cacheCity::1 http://localhost:8899/cities/getCityByCachePut/1
* @author xiaobu
* @date 2019/9/4 13:41
* @param id id
* @return com.xiaobu.entity.City
* @version 1.0
*/
@CachePut(value = "cacheCity",key = "#id")
@GetMapping("getCityByCachePut/{id}")
public City getCityByCachePut(@PathVariable Integer id) {
return cityService.getCityByCachePut(id);
}
/**
* 功能描述:清除 key为 cacheCity::1的缓存数据 http://localhost:8899/cities/getCityByNoCache/1/xiaobu
* @author xiaobu
* @date 2019/7/29 15:11
* @param id, name]
* @return com.xiaobu.entity.City
* @version 1.0
*/
@CacheEvict(value = "cacheCity",key = "#id")
@GetMapping(value = "getCityByNoCache/{id}")
public City getCityByNoCache(@PathVariable Integer id, @RequestParam String name) {
System.out.println("id = " + id+",name="+name);
return cityService.getCityByNoCache(id);
}
}
服务层:
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 [email protected]
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.xiaobu.service;
import com.github.pagehelper.PageHelper;
import com.xiaobu.entity.City;
import com.xiaobu.mapper.CityMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author xiaobu
* @since 2019-09-04 11:09
*/
@Service
public class CityService {
@Autowired
private CityMapper cityMapper;
public List<City> getAll(City city) {
if (city.getPage() != null && city.getRows() != null) {
PageHelper.startPage(city.getPage(), city.getRows());
}
return cityMapper.selectAll();
}
public City getById(Integer id) {
return cityMapper.selectByPrimaryKey(id);
}
public void deleteById(Integer id) {
cityMapper.deleteByPrimaryKey(id);
}
public void save(City country) {
if (country.getId() != null) {
cityMapper.updateByPrimaryKey(country);
} else {
cityMapper.insert(country);
}
}
@Cacheable(value = "cacheCity",key ="#id", condition ="#id<2")
public City getCityByCache(Integer id){
System.out.println("getCityByCache 执行查询。。。。");
return cityMapper.selectByPrimaryKey(id);
}
@CachePut(value = "cacheCity",key = "#id")
public City getCityByCachePut(Integer id){
System.out.println("getCityByCachePut 执行查询。。。。");
City city= cityMapper.selectByPrimaryKey(id);
city.setState("广东");
cityMapper.updateByPrimaryKey(city);
return city;
}
@CacheEvict(value = "cacheCity",key = "#id")
public City getCityByNoCache(Integer id){
System.out.println("getCityByNoCache 执行查询。。。。");
return cityMapper.selectByPrimaryKey(id);
}
}
访问 http://localhost:8899/cache/getCityByCache/1 第一次会去数据库查,第二次则直接在缓存里面查找。
访问 http://localhost:8899/cache/getCityByCachePut/1 两次都会去数据库查
先访问 http://localhost:8899/cache/getCityByNoCache/1/xiaobu 会发现缓存被清除了,数据是从数据库重新查的.
参考:
史上超详细的SpringBoot整合Cache使用教程-Java知音
springboot2.x使用redis作为缓存