POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.3</version>
</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("服务启动成功。。。。");
}
}
- opsForValue: 对应 String(字符串)
- opsForZSet: 对应 ZSet(有序集合)
- opsForHash: 对应 Hash(哈希)
- opsForList: 对应 List(列表)
- opsForSet: 对应 Set(集合)
- opsForGeo: 对应 GEO(地理位置)
直接使用
用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<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
// RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//序列化方式1---另一种实现方式
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();//该语句相当于序列化方式1
//序列化方式2
//Jackson2JsonRedisSerializer serializer=new Jackson2JsonRedisSerializer(Object.class);
//RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(serializer);
//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 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。
- allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。
- beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。
控制层
@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);
}
}
服务层:
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.stereotype.Service;
import java.util.List;
/**
* @author xiaobu
* @since 2019-09-04 11:09
*/
@Service
public class CityService {
@Autowired
private CityMapper cityMapper;
public City getCityByCache(Integer id){
System.out.println("getCityByCache 执行查询。。。。");
return cityMapper.selectByPrimaryKey(id);
}
public City getCityByCachePut(Integer id){
System.out.println("getCityByCachePut 执行查询。。。。");
City city= cityMapper.selectByPrimaryKey(id);
city.setState("广东");
cityMapper.updateByPrimaryKey(city);
return city;
}
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 会发现缓存被清除了,数据是从数据库重新查的.
参考: