spring boot redis 緩存注解使用教程

<meta charset="utf-8">

依賴(lài)

  • pom文件添加如下依賴(lài)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置

  • application.yml配置文件添加如下配置
spring.cache.type: REDIS

# REDIS (RedisProperties)
spring.redis.database: 0
spring.redis.host: 127.0.0.2
spring.redis.password:
spring.redis.port: 6379
spring.redis.pool.max-idle: 8
spring.redis.pool.min-idle: 0
spring.redis.pool.max-active: 100
spring.redis.pool.max-wait: -1

  • 在啟動(dòng)類(lèi)添加@EnableCaching注解開(kāi)啟注解驅(qū)動(dòng)的緩存管理秽五,如下
@Configuration
@EnableAutoConfiguration
@ComponentScan("org.hsweb.demo")
@MapperScan("org.hsweb.demo.dao")
@EnableCaching//開(kāi)啟注解驅(qū)動(dòng)的緩存管理
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

示例項(xiàng)目基本邏輯

  • 為了方便理解明场,示例項(xiàng)目演示了一個(gè)簡(jiǎn)單的對(duì)用戶(hù)數(shù)據(jù)的增刪改查巧娱,主要有兩方面:

    • redis緩存注解與mybatis集成宦搬,主要邏輯位于SimpleUserService
    • 僅使用redis緩存注解建炫,主要邏輯位于RedisOnlyServiceImpl
  • 示例java代碼結(jié)構(gòu)

.
├── Application.java    //啟動(dòng)類(lèi)
├── controller 
│   ├── RedisOnlyController.java
│   └── UserController.java
├── dao
│   └── UserDao.java    //mapper接口(mybatis)
├── po
│   └── User.java   // 用戶(hù)po類(lèi)
└── service
    ├── impl
    │   ├── RedisOnlyServiceImpl.java
    │   └── SimpleUserService.java
    ├── RedisOnlyService.java
    └── UserService.java

redis 注解使用入門(mén)

@Cacheable 注解簡(jiǎn)單使用教程——用于查詢(xún)操作接口

  • @Cacheable注解的作用:緩存被調(diào)用方法的結(jié)果(返回值)元媚,已經(jīng)緩存就不再調(diào)用注解修飾的方法稚伍,適用于查詢(xún)接口

  • controller層和service層的相關(guān)代碼如下

@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
    @Resource
    RedisOnlyService redisOnlyService;
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public User getById(@PathVariable("id") String id) {
        return redisOnlyService.selectById(id);
    }
}

@Service
public class RedisOnlyServiceImpl implements UserService {
    /**
     *  先用id生成key弯予,在用這個(gè)key查詢(xún)r(jià)edis中有無(wú)緩存到對(duì)應(yīng)的值
     *
     *  若無(wú)緩存,則執(zhí)行方法selectById个曙,并把方法返回的值緩存到redis
     *
     *  若有緩存锈嫩,則直接把redis緩存的值返回給用戶(hù),不執(zhí)行方法
     */
    @Cacheable(cacheNames="user", key="#id")
    @Override
    public User selectById(String id) {
        //直接new一個(gè)給定id的用戶(hù)對(duì)象垦搬,來(lái)返回給用戶(hù)
        return new User(id,"redisOnly","password");
    }
}

  • 可見(jiàn)這是一個(gè)簡(jiǎn)單查詢(xún)用戶(hù)接口呼寸。它與典型接口只多了@Cacheable注解

    • 為了方便演示,我直接返回一個(gè)用戶(hù)對(duì)象悼沿,而不是查詢(xún)數(shù)據(jù)
  • 啟動(dòng)程序等舔,訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876接口,查詢(xún)一個(gè)id為9876的用戶(hù)糟趾,結(jié)果如圖:

image
  • 可以看見(jiàn)返回用戶(hù)數(shù)據(jù)的同時(shí)慌植,spring還把用戶(hù)數(shù)據(jù)緩存到redis中。

  • 第二次訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876接口(查詢(xún)id為9876的用戶(hù))時(shí)就會(huì)直接從redis中返回redis緩存的用戶(hù)數(shù)據(jù)

    • 而不再執(zhí)行RedisOnlyServiceImplselectById()方法

深入理解@Cacheable注解

  • @Cacheable注解的作用:緩存被調(diào)用方法的結(jié)果(返回值)义郑,已經(jīng)緩存就不再調(diào)用注解修飾的方法蝶柿,適用于查詢(xún)接口

  • 以下面代碼的@Cacheable(cacheNames="user", key="#id")為例說(shuō)明

    • cacheNames="user"用于把用戶(hù)數(shù)據(jù)存在同一個(gè)用戶(hù)空間user中,如圖

      image
      • cacheNames="user"本質(zhì)是在redis緩存鍵值對(duì)中的key加個(gè)user:的前綴
    • key="#id"當(dāng)中的#id是按照spring表達(dá)式(詳細(xì)看官方教程)書(shū)寫(xiě)的,這里意思是使用方法參數(shù)中的id參數(shù)生成緩存鍵值對(duì)中的key

      • 若不滿(mǎn)足于上面的key生成規(guī)則非驮,可以通過(guò)實(shí)現(xiàn)KeyGenerator接口自定義交汤,詳細(xì)看
    • 實(shí)質(zhì)上cacheNameskey這連個(gè)屬性都是用于配置redis緩存鍵值對(duì)中的key

    • 這里僅解析個(gè)屬性的作用,下面在解析spring遇到這個(gè)注解后的邏輯

  • 為了方便理解劫笙,列出示例代碼的邏輯

graph TB
    A[查詢(xún)id為9876的用戶(hù)] -->|RedisOnlyController| B(調(diào)用RedisOnlyServiceImpl前被aop攔截)
    B --> C{id為9876的用戶(hù)數(shù)據(jù)是否緩存到redis?}
    C -->|是| D[從redis中獲取用戶(hù)數(shù)據(jù)并立刻返回]
    C -->|否| E[執(zhí)行service類(lèi)的selectById方法并緩存結(jié)果后返回]

  • 上面邏輯都是通過(guò)aop封裝好的芙扎,對(duì)我們來(lái)說(shuō)上面過(guò)程是透明的。詳細(xì)可以看源碼中CacheAspectSupport類(lèi)的第二個(gè)execute()方法(傳送門(mén)

@CacheEvict 注解簡(jiǎn)單使用教程——用于刪除操作接口

  • @CacheEvict注解的作用:刪除redis中對(duì)應(yīng)的緩存填大,適用于刪除接口

  • controller層和service層的相關(guān)代碼如下

@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public boolean delete(@PathVariable String id) {
        return redisOnlyService.delete(id);
    }
}

@Service
public class RedisOnlyServiceImpl implements UserService {
    @CacheEvict(cacheNames="user", key="#id")
    @Override
    public boolean delete(String id) {
        // 可以在這里添加刪除數(shù)據(jù)庫(kù)對(duì)應(yīng)用戶(hù)數(shù)據(jù)的操作
        return true;
    }
}

  • service中的delete()是一個(gè)空方法戒洼,僅多了個(gè)@CacheEvict

  • 啟動(dòng)程序,設(shè)置請(qǐng)求方法為DELETE允华,用postman訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876接口圈浇,即可刪除id為9876的用戶(hù)數(shù)據(jù)于redis的緩存

    • 注意:@RequestMapping設(shè)置的請(qǐng)求方法為RequestMethod.DELETE
  • @CacheEvict(cacheNames="user", key="#id")注解中的兩個(gè)屬性類(lèi)似上面說(shuō)的@Cacheable(cacheNames="user", key="#id")

  • 原理很簡(jiǎn)單寥掐,僅僅是把緩存數(shù)據(jù)刪除,無(wú)特別邏輯

  • 若想刪除redis緩存的所有用戶(hù)數(shù)據(jù)磷蜀,可以把注解改成@CacheEvict(cacheNames="user", allEntries=true)

    • 本質(zhì)是刪除redis數(shù)據(jù)庫(kù)的user命名空間下的所有鍵值對(duì)

@CachePut 注解簡(jiǎn)單使用教程——用于刪除操作接口

  • @CachePut注解的作用同樣是緩存被調(diào)用方法的結(jié)果(返回值)召耘,當(dāng)與@Cacheable不一樣的是:

    • @CachePut在值已經(jīng)被緩存的情況下仍然會(huì)執(zhí)行被@CachePut注解修飾的方法,而@Cacheable不會(huì)
    • @CachePut注解適用于更新操作插入操作
  • 我們從更新操作(update方法)了解@CachePut注解

  • controller層和service層的相關(guān)代碼如下

@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
    @Resource
    RedisOnlyService redisOnlyService;

    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public User update(@PathVariable String id, @RequestBody User user) {
        user.setId(id);
        redisOnlyService.update(user);
        return user;
    }
}

@Service
public class RedisOnlyServiceImpl implements UserService {
    // 記錄
    private AtomicInteger executeCout = new AtomicInteger(0);

    @CachePut(cacheNames="user", key="#user.id")
    @Override
    public User update(User user) {
        // 每次方法執(zhí)行executeCout
        user.setUsername("redisOnly" + executeCout.incrementAndGet());
        // 必須把更新后的用戶(hù)數(shù)據(jù)返回褐隆,這樣才能把它緩存到redis中
        return user;
    }
}

  • 注意: 由于update(User user)方法的參數(shù)不再是id而是對(duì)象user污它,key="#id"中的SpEL(spring表達(dá)式)被改為#user.id

  • 啟動(dòng)程序,設(shè)置請(qǐng)求方法為DELETE庶弃,用postman訪(fǎng)問(wèn)http://localhost:9999/redisOnly/user/9876接口5次轨蛤,如下圖

    • 可以看到每次訪(fǎng)問(wèn)接口時(shí)時(shí)update()方法都會(huì)執(zhí)行一次(即,executeCout的值為5)
    • 注意:@RequestMapping設(shè)置的請(qǐng)求方法為RequestMethod.DELETE
image
  • 為了更好地對(duì)比虫埂,我把@Cacheable注解修飾的查詢(xún)操作改成如下,同樣用postman訪(fǎng)問(wèn)5次
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
    return new User(id,"redisOnly" + executeCout.incrementAndGet(),"password");
}

  • 測(cè)試結(jié)果是圃验,selectById()方法只執(zhí)行了一次掉伏,后面4次請(qǐng)求都是從redis緩存里取出用戶(hù)數(shù)據(jù)返回到客戶(hù)端

redis注解與mybatis一起使用

  • redis注解與mybatis一起使用的方式很簡(jiǎn)單,就是在上面提到的各個(gè)方法內(nèi)添加相應(yīng)的dao操作就行了

  • 具體的service層代碼如下:

@Service("userService")
public class SimpleUserService implements UserService {
    @Resource
    private UserDao userDao;
    @Cacheable(cacheNames="user", key="#id")
    @Override
    public User selectById(String id) {
        return userDao.selectById(id);
    }
    @Cacheable(cacheNames="user", key="#username")
    @Override
    public User selectByUserName(String username) {
        return userDao.selectByUserName(username);
    }
    @Override
    public List<User> selectAll() {
        return userDao.selectAll();
    }
    @CachePut(cacheNames="user", key="#user.id")
    @Override
    public User insert(User user) {
        user.setId(UUID.randomUUID().toString());
        userDao.insert(user);
        return user;
    }
    @CachePut(cacheNames="user", key="#user.id")
    @Override
    public User update(User user) {
        userDao.update(user);
        return user;
    }
    @CacheEvict(cacheNames="user", key="#id")
    @Override
    public boolean delete(String id) {
        return userDao.deleteById(id) == 1;
    }
}

redis 注解的其他知識(shí)點(diǎn)

  • 除了cacheNameskey澳窑,其他注解屬性的作用

    • keyGenerator:定義鍵值對(duì)中key生成的類(lèi)斧散,和key屬性的不能同時(shí)存在
    • sync:如果設(shè)置sync=true:
      • a. 如果緩存中沒(méi)有數(shù)據(jù),多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)這個(gè)方法摊聋,則只有一個(gè)方法會(huì)執(zhí)行到方法鸡捐,其它方法需要等待;
      • b. 如果緩存中已經(jīng)有數(shù)據(jù),則多個(gè)線(xiàn)程可以同時(shí)從緩存中獲取數(shù)據(jù);

    注意:sync不能解決緩存穿透的問(wèn)題

    由于 redis 注解不支持超時(shí)時(shí)間麻裁,所有不存在緩存擊穿和雪崩的問(wèn)題

    • condition: 在執(zhí)行方法后箍镜,如果condition的值為true,則緩存數(shù)據(jù)煎源;如果不滿(mǎn)足條件色迂,僅執(zhí)行方法,不緩存結(jié)果
    • unless :在執(zhí)行方法后手销,判斷unless 歇僧,如果值為true,則不緩存數(shù)據(jù)
  • @CacheConfig: 類(lèi)級(jí)別的注解:

    • 如果我們?cè)诖俗⒔庵卸xcacheNames锋拖,則此類(lèi)中的所有方法上的cacheNames默認(rèn)都是此值诈悍。
    • 當(dāng)然@Cacheable也可以重定義cacheNames的值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市兽埃,隨后出現(xiàn)的幾起案子侥钳,更是在濱河造成了極大的恐慌,老刑警劉巖讲仰,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慕趴,死亡現(xiàn)場(chǎng)離奇詭異痪蝇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)冕房,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)躏啰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耙册,你說(shuō)我怎么就攤上這事给僵。” “怎么了详拙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵帝际,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我饶辙,道長(zhǎng)蹲诀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上碍侦,老公的妹妹穿的比我還像新娘。我一直安慰自己痕慢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布涌矢。 她就那樣靜靜地躺著掖举,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娜庇。 梳的紋絲不亂的頭發(fā)上塔次,一...
    開(kāi)封第一講書(shū)人閱讀 52,166評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音名秀,去河邊找鬼俺叭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泰偿,可吹牛的內(nèi)容都是我干的熄守。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼耗跛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼裕照!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起调塌,我...
    開(kāi)封第一講書(shū)人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晋南,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后羔砾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體负间,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偶妖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了政溃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趾访。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖董虱,靈堂內(nèi)的尸體忽然破棺而出扼鞋,到底是詐尸還是另有隱情,我是刑警寧澤愤诱,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布云头,位于F島的核電站,受9級(jí)特大地震影響淫半,放射性物質(zhì)發(fā)生泄漏溃槐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一科吭、第九天 我趴在偏房一處隱蔽的房頂上張望竿痰。 院中可真熱鬧,春花似錦砌溺、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至匣缘,卻和暖如春猖闪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肌厨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工培慌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柑爸。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓吵护,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親表鳍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馅而,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容