原文出處: oKong
前言
上幾節(jié)講了利用
Mybatis-Plus
這個(gè)第三方的ORM框架進(jìn)行數(shù)據(jù)庫訪問周叮,在實(shí)際工作中欺矫,在存儲(chǔ)一些非結(jié)構(gòu)化或者緩存一些臨時(shí)數(shù)據(jù)及熱點(diǎn)數(shù)據(jù)時(shí),一般上都會(huì)用上mongodb
和redis
進(jìn)行這方面的需求。所以這一章節(jié)準(zhǔn)備講下緩存數(shù)據(jù)庫Redis
的集成霹肝,同時(shí)會(huì)介紹下基于Redis
和注解驅(qū)動(dòng)的Spring Cache
的簡(jiǎn)單使用。
Redis 介紹
大家應(yīng)該對(duì)
Redis
應(yīng)該比較熟悉了塑煎。這幾年也是大行其道的緩存數(shù)據(jù)庫袖牙,目前的memcached
由于使用場(chǎng)景及其存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)的單一(不知道現(xiàn)在是否有改善赂韵,現(xiàn)在基本沒有接觸了),在工作中也使用的少了。引用官網(wǎng)的簡(jiǎn)介,Redis是一個(gè)開源的使用ANSI C語言編寫繁扎、遵守BSD協(xié)議、支持網(wǎng)絡(luò)豁跑、可基于內(nèi)存亦可持久化的日志型力细、Key-Value數(shù)據(jù)庫,并提供多種語言的API雀哨。
推薦redis中國(guó)社區(qū):http://www.redis.cn/
SpringBoot的Redis集成
0.本章節(jié)以上一章節(jié)的示例基礎(chǔ)上進(jìn)行集成磕谅。所以大家可下載第十章節(jié)示例或者在章節(jié)末尾直接下載本章節(jié)示例私爷。
1.pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
直接引入,相關(guān)依賴會(huì)自動(dòng)加載的膊夹,這就是springboot
讓人愉悅之處呀衬浑。
2.application.properties
配置加入redis相關(guān)配置
配置自動(dòng)加載類為:
org.springframework.boot.autoconfigure.data.redis.RedisProperties
,可在屬性文件中點(diǎn)擊某屬性快捷跳轉(zhuǎn)放刨。注意到其啟動(dòng)類為org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
工秩。這里就不介紹了,后面會(huì)寫一篇關(guān)于Springboot
自動(dòng)加載配置的文章宏榕。
# REDIS (RedisProperties)
# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.pool.min-idle=0
# 連接超時(shí)時(shí)間(毫秒)
spring.redis.timeout=0
3.一般上通過以上兩步就可使用了拓诸,但工作中一般上是通過StringRedisTemplate
(默認(rèn)采用string
的序列化,保存key和值時(shí)都是通過此序列化策略)接口進(jìn)行操作麻昼,所以這里直接配置了StringRedisTemplate
bean類奠支。
RedisConfig.java
/**
*
* @author oKong
*
*/
@Configuration
public class RedisConfig {
/**
* 定義 StringRedisTemplate ,指定序列化和反序列化的處理類
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer<Object> 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);
//序列化 值時(shí)使用此序列化方法
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.編寫控制類抚芦,測(cè)試集成是否生效倍谜。
RedisController.java
@RestController
@RequestMapping("/redis")
@Api(tags = "redis 測(cè)試API")
public class RedisController {
@Autowired
StringRedisTemplate redisTemplate;
@GetMapping("set/{key}/{value}")
@ApiOperation(value="設(shè)置緩存")
public String set(@PathVariable("key")String key,@PathVariable("value") String value) {
//注意這里的 key不能為null spring 內(nèi)部有檢驗(yàn)
redisTemplate.opsForValue().set(key, value);
return key + "," + value;
}
@GetMapping("get/{key}")
@ApiOperation(value="根據(jù)key獲取緩存")
public String get(@PathVariable("key") String key) {
return "key=" + key + ",value=" + redisTemplate.opsForValue().get(key);
}
}
5.訪問:http://127.0.0.1:8080/swagger-ui.html。 也可直接瀏覽器輸入:
set值 get值瀏覽器訪問
查看redis記錄:
redis記錄至此叉抡,redis
就集成好了尔崔。實(shí)際中可根據(jù)業(yè)務(wù)需要進(jìn)行相關(guān)操作,比如緩存session記錄褥民,緩存菜單列表等季春。
Spring Cache 和 redis 使用。
Spring Cache
是Spring
框架提供的對(duì)緩存使用的抽象類消返,支持多種緩存载弄,比如Redis
、EHCache
等撵颊,集成很方便宇攻。同時(shí)提供了多種注解來簡(jiǎn)化緩存的使用,可對(duì)方法進(jìn)行緩存倡勇。
0.修改RedisConfig
配置類逞刷,加入注解@EnableCaching
,同時(shí)設(shè)置CacheManager
緩存管理類妻熊,這里使用RedisCacheManager
夸浅,其他的管理類還有:SimpleCacheManager
、ConcurrentMapCacheManager
等固耘,默認(rèn)提供的在類org.springframework.cache.support
下题篷,可自行查閱。
/**
*
* @author oKong
*
*/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 定義 StringRedisTemplate 厅目,指定序列號(hào)和反序列化的處理類
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer<Object> 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);
//序列化 值時(shí)使用此序列化方法
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisTemplate<String,String> redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
//使用前綴
rcm.setUsePrefix(true);
//緩存分割符 默認(rèn)為 ":"
// rcm.setCachePrefix(new DefaultRedisCachePrefix(":"));
//設(shè)置緩存過期時(shí)間
//rcm.setDefaultExpiration(60);//秒
return rcm;
}
}
1.改造UserController
控制層番枚,引入@Cacheable
等注解法严。
/**
* 用戶控制層 簡(jiǎn)單演示增刪改查及分頁
* 新增了swagger文檔內(nèi)容 2018-07-21
* 新增了@caching使用 2018-07-23
* @author oKong
*
*/
@RestController
@RequestMapping("/user")
@Api(tags="用戶API")
public class UserController {
@Autowired
IUserService userService;
@PostMapping("add")
@ApiOperation(value="用戶新增")
//正常業(yè)務(wù)時(shí), 需要在user類里面進(jìn)行事務(wù)控制葫笼,控制層一般不進(jìn)行業(yè)務(wù)控制的深啤。
//@Transactional(rollbackFor = Exception.class)
public Map<String,String> addUser(@Valid @RequestBody UserReq userReq){
User user = new User();
user.setCode(userReq.getCode());
user.setName(userReq.getName());
//由于設(shè)置了主鍵策略 id可不用賦值 會(huì)自動(dòng)生成
//user.setId(0L);
userService.insert(user);
Map<String,String> result = new HashMap<String,String>();
result.put("respCode", "01");
result.put("respMsg", "新增成功");
//事務(wù)測(cè)試
//System.out.println(1/0);
return result;
}
@PostMapping("update")
@ApiOperation(value="用戶修改")
//更新時(shí) 直接刪除緩存 以保證下次獲取時(shí)先從數(shù)據(jù)庫中獲取最新數(shù)據(jù)
@CacheEvict(value="OKONG", key="#userReq.id")
public Map<String,String> updateUser(@Valid @RequestBody UserReq userReq){
if(userReq.getId() == null || "".equals(userReq.getId())) {
throw new CommonException("0000", "更新時(shí)ID不能為空");
}
User user = new User();
user.setCode(userReq.getCode());
user.setName(userReq.getName());
user.setId(Long.parseLong(userReq.getId()));
userService.updateById(user);
Map<String,String> result = new HashMap<String,String>();
result.put("respCode", "01");
result.put("respMsg", "更新成功");
return result;
}
@GetMapping("/get/{id}")
@ApiOperation(value="用戶查詢(ID)")
@ApiImplicitParam(name="id",value="查詢ID",required=true)
@Cacheable(value="OKONG",key="#id")
public Map<String,Object> getUser(@PathVariable("id") String id){
//查詢
User user = userService.selectById(id);
if(user == null) {
throw new CommonException("0001", "用戶ID:" + id + ",未找到");
}
UserResp resp = UserResp.builder()
.id(user.getId().toString())
.code(user.getCode())
.name(user.getName())
.status(user.getStatus())
.build();
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", "01");
result.put("respMsg", "成功");
result.put("data", resp);
return result;
}
@GetMapping("/page")
@ApiOperation(value="用戶查詢(分頁)")
public Map<String,Object> pageUser(int current, int size){
//分頁
Page<User> page = new Page<>(current, size);
Map<String,Object> result = new HashMap<String,Object>();
result.put("respCode", "01");
result.put("respMsg", "成功");
result.put("data", userService.selectPage(page));
return result;
}
}
2.利用Swagger
控制頁面路星,新增一個(gè)用戶溯街,然后獲取用戶,會(huì)發(fā)現(xiàn)緩存里已經(jīng)有此id的用戶數(shù)據(jù)了洋丐。
再次獲取呈昔,會(huì)發(fā)現(xiàn)這次沒有直接訪問數(shù)據(jù)庫了,而是直接從緩存讀取友绝。大家可在觀察下控制臺(tái)的輸出情況(可先清空控制臺(tái)堤尾,然后在請(qǐng)求)。
控制臺(tái)此時(shí)控制臺(tái)無任何輸出迁客,但前端已經(jīng)獲取到值了郭宝。
關(guān)于SpringCache 注解的簡(jiǎn)單介紹
- @Cacheable:標(biāo)記在一個(gè)方法上,也可以標(biāo)記在一個(gè)類上掷漱。主要是緩存標(biāo)注對(duì)象的返回結(jié)果粘室,標(biāo)注在方法上緩存該方法的返回值,標(biāo)注在類上卜范,緩存該類所有的方法返回值衔统。
參數(shù): value緩存名、 key緩存鍵值海雪、 condition滿足緩存條件缰冤、unless否決緩存條件
- @CacheEvict:從緩存中移除相應(yīng)數(shù)據(jù)。
- @CachePut:方法支持緩存功能喳魏。與@Cacheable不同的是使用@CachePut標(biāo)注的方法在執(zhí)行前不會(huì)去檢查緩存中是否存在之前執(zhí)行過的結(jié)果,而是每次都會(huì)執(zhí)行該方法怀薛,并將執(zhí)行結(jié)果以鍵值對(duì)的形式存入指定的緩存中刺彩。
- @Caching:多個(gè)Cache注解使用,比如新增用戶時(shí),刪除用戶屬性等需要?jiǎng)h除或者更新多個(gè)緩存時(shí)枝恋,集合以上三個(gè)注解创倔。
常用的就以上幾個(gè),對(duì)于@CacheConfig
沒使用過焚碌,這里就不說明了畦攘。
對(duì)于對(duì)幾個(gè)注解類的簡(jiǎn)單使用就結(jié)束了,相關(guān)的詳細(xì)用法十电,比如自定義條件緩存知押,自定義注解等叹螟,這里就不闡述了,請(qǐng)讀者自行
SpEL上下文數(shù)據(jù)
Spring Cache提供了一些供我們使用的SpEL上下文數(shù)據(jù)台盯,下表直接摘自互聯(lián)網(wǎng):
名稱 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root對(duì)象 | 當(dāng)前被調(diào)用的方法名 | root.methodName |
method | root對(duì)象 | 當(dāng)前被調(diào)用的方法 | root.method.name |
target | root對(duì)象 | 當(dāng)前被調(diào)用的目標(biāo)對(duì)象 | root.target |
targetClass | root對(duì)象 | 當(dāng)前被調(diào)用的目標(biāo)對(duì)象類 | root.targetClass |
args | root對(duì)象 | 當(dāng)前被調(diào)用的方法的參數(shù)列表 | root.args[0] |
caches | root對(duì)象 | 當(dāng)前方法調(diào)用使用的緩存列表(如@Cacheable(value={“cache1”, “cache2”}))罢绽,則有兩個(gè)cache | root.caches[0].name |
argument name | 執(zhí)行上下文 | 當(dāng)前被調(diào)用的方法的參數(shù),如findById(Long id)静盅,我們可以通過#id拿到參數(shù) | user.id |
result | 執(zhí)行上下文 | 方法執(zhí)行后的返回值(僅當(dāng)方法執(zhí)行之后的判斷有效良价,如‘unless’,’cache evict’的beforeInvocation=false) | result |
@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true) public void conditionUpdate(User user)
總結(jié)
本章節(jié)主要是對(duì)
redis
結(jié)合Spring Cache
的集成和簡(jiǎn)單使用進(jìn)行了說明蒿叠,詳細(xì)的用法明垢,可自行搜索相關(guān)資料下,這里就不闡述了市咽。因?yàn)閷?duì)于百分之八十之上的緩存要求基本能滿足了痊银。使用緩存時(shí),一定要注意緩存生命周期的控制魂务,不然容易出現(xiàn)數(shù)據(jù)不一致的情況曼验,謹(jǐn)記!
最后
目前互聯(lián)網(wǎng)上很多大佬都有
SpringBoot
系列教程粘姜,如有雷同鬓照,請(qǐng)多多包涵了。本文是作者在電腦前一字一句敲的孤紧,每一步都是實(shí)踐的豺裆。若文中有所錯(cuò)誤之處,還望提出号显,謝謝臭猜。