Redis 是一個由Salvatore Sanfilippo寫的key-value存儲系統(tǒng)蒋譬。Redis是一個開源的使用ANSI C語言編寫、遵守BSD協(xié)議鬼譬、支持網(wǎng)絡(luò)刃宵、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫韧献,并提供多種語言的API末患。它通常被稱為數(shù)據(jù)結(jié)構(gòu)服務(wù)器,因為值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型锤窑。
我們將redis作為mysql的緩存服務(wù)器璧针,每次請求數(shù)據(jù)都會去redis里查一遍,若沒有則請求mysql走真正的查詢再把數(shù)據(jù)存到redis渊啰。當(dāng)有新增探橱,修改,刪除操作的時候更新緩存中的數(shù)據(jù)以防止臟讀的發(fā)生绘证。
引入redis的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
配置文件
#redis
spring.redis.database=0
spring.redis.host=172.16.10.11
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
redis緩存配置類
@Configuration
@EnableCaching //開啟緩存
public class RedisCacheConfig {
/**
* 自定義key的生成規(guī)則,保證key的唯一性
* @return
*/
@Bean
public KeyGenerator wiselyKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//設(shè)置key的超時時間
cacheManager.setDefaultExpiration(60*30);
return cacheManager;
}
@Bean
public RedisTemplate<String, String> redisTemplate(
RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
封裝一個redis的操作類
@Component
public class RedisClient {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 獲取key對應(yīng)list的長度
* @param key
* @return
*/
public long getListSize(String key) {
Long size = stringRedisTemplate.opsForList().size(key);
return size;
}
/**
* 根據(jù)key獲取list
* @param key
* @param i
* @return
*/
public String getListData(String key,int i) {
String json = stringRedisTemplate.opsForList().index(key,i);
return json;
}
/**
* 逐條加入數(shù)據(jù)到key所在的list
* @param key
* @param json
*/
public void setListData(String key,String json) {
stringRedisTemplate.opsForList().rightPush(key, json);
}
/**
* 修改index的數(shù)據(jù)
* @param key
* @param index
* @param json
*/
public void updateListData(String key, int index, String json) {
stringRedisTemplate.opsForList().set(key, index, json);
}
/**
* 獲取key的值
* @param key
* @return
*/
public String getValue(String key){
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 設(shè)置key的值
* @param key
* @param json
*/
public void setValue(String key,String json){
stringRedisTemplate.opsForValue().set(key, json);
}
}
寫一個簡單的controller
@Controller
@RequestMapping("/user")
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserDao userDao;
@RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
@Cacheable(value="userCache",key= "'user' + #id" )
@ResponseBody
public User user(@PathVariable long id) {
User u = userDao.findOne(id);
log.info("走db查詢");
return u;
}
}
關(guān)于Spring緩存的三個注解:
1.@Cacheable
@Cacheable是用來聲明方法是可緩存的隧膏。將結(jié)果存儲到緩存中以便后續(xù)使用相同參數(shù)調(diào)用時不需執(zhí)行實際的方法。直接從緩存中取值嚷那。最簡單的格式需要制定緩存名稱胞枕。
默認key生成:
默認key的生成按照以下規(guī)則:
- 如果沒有參數(shù),則使用0作為key
- 如果只有一個參數(shù),使用該參數(shù)作為key
- 如果又多個參數(shù)魏宽,使用包含所有參數(shù)的hashCode作為key
也可以通過keyGenerator="wiselyKeyGenerator"自定義key的生成策略曲稼。
2.@CachePut
如果緩存需要更新,且不干擾方法的執(zhí)行,可以使用注解@CachePut湖员。@CachePut標(biāo)注的方法在執(zhí)行前不會去檢查緩存中是否存在之前執(zhí)行過的結(jié)果,而是每次都會執(zhí)行該方法瑞驱,并將執(zhí)行結(jié)果以鍵值對的形式存入指定的緩存中娘摔。
3.@CacheEvict
@CacheEvict要求指定一個或多個緩存,使之都受影響唤反。此外凳寺,還提供了一個額外的參數(shù)allEntries 。表示是否需要清除緩存中的所有元素彤侍。默認為false肠缨,當(dāng)指定了allEntries為true時,將會清除緩存中所有的元素盏阶。
首次訪問http://localhost:8080/user/1時會打日志走了db查詢晒奕,之后的每次訪問都沒有再打印db的日志。通過redis客戶端可以看到我們緩存的數(shù)據(jù)
更新緩存
controller里新建一個更新數(shù)據(jù)的方法,配合@CachePut更新緩存
@Controller
@RequestMapping("/user")
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserDao userDao;
@RequestMapping(value = {"/{id}"}, method = RequestMethod.GET)
@Cacheable(value="userCache",key= "'user' + #id" )
@ResponseBody
public User user(@PathVariable long id) {
User u = userDao.findOne(id);
log.info("走db查詢");
return u;
}
@RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
@CachePut(value= "userCache",key= "'user' + #id")
@ResponseBody
public User update(@PathVariable long id) {
User user = userDao.findOne(id);
user.setAge(10086);
userDao.save(user);
return user;
}
}
訪問http://localhost:8080/user/update/1 更新數(shù)據(jù)同時更新了緩存
上述是針對單一一條數(shù)據(jù)的緩存脑慧,實際中也會有緩存列表數(shù)據(jù)的場景魄眉,我們看下列表數(shù)據(jù)要如何緩存和更新
在原來的controller上新增查詢列表的接口
@RequestMapping(value = {""}, method = RequestMethod.GET)
@ResponseBody
public List<User> list() {
List<User> dataList = new ArrayList<>();
Long size = redisClient.getListSize("userList");
if(0==size){
dataList= userDao.findAll();
log.info("FindAll 走db查詢");
if (null != dataList) {
for (int i = 0; i < dataList.size(); i++) {
redisClient.setListData("userList", JSON.toJSONString(dataList.get(i)));
}
}
}else{
log.info("FindAll 從redis中獲取");
for (int i = 0; i < size; i++) {
String json = redisClient.getListData("userList",i);
User u = JSON.parseObject(json, User.class);
dataList.add(u);
}
}
return dataList;
}
@RequestMapping(value = {"/update/{id}"}, method = RequestMethod.GET)
@CachePut(value= "userCache",key= "'user' + #id")
@ResponseBody
public User update(@PathVariable long id) {
User user = userDao.findOne(id);
user.setAge(10086);
userDao.save(user);
Long size = redisClient.getListSize("userList");
for (int j = 0; j < size; j++) {
User u = JSON.parseObject(redisClient.getListData("userList", j), User.class);
if(id==u.getId()){
redisClient.updateListData("userList", j, JSON.toJSONString(user));
log.info("已更新緩存");
}
}
return user;
}
對于列表數(shù)據(jù)緩存的更新我還沒有想好一個更好的方式,先實現(xiàn)一種闷袒。
緩存的刪除
@RequestMapping(value = {"/delete/{id}"})
@CacheEvict(value = "userCache", allEntries = true)
@ResponseBody
public String delete(@PathVariable long id) {
userDao.delete(id);
return "success";
}
這樣實現(xiàn)了一個簡單的讀寫分離坑律,實際場景中還會更加嚴(yán)謹(jǐn),本文僅提供一個想法囊骤。
代碼gti地址:http://git.oschina.net/gpy1994/redisdemo
福利