spring boot redis 緩存注解使用教程

<meta charset="utf-8">

依賴

  • pom文件添加如下依賴
<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

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

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

示例項目基本邏輯

  • 為了方便理解,示例項目演示了一個簡單的對用戶數(shù)據(jù)的增刪改查,主要有兩方面:

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

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

redis 注解使用入門

@Cacheable 注解簡單使用教程——用于查詢操作接口

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

  • 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,在用這個key查詢redis中有無緩存到對應(yīng)的值
     *
     *  若無緩存,則執(zhí)行方法selectById,并把方法返回的值緩存到redis
     *
     *  若有緩存,則直接把redis緩存的值返回給用戶,不執(zhí)行方法
     */
    @Cacheable(cacheNames="user", key="#id")
    @Override
    public User selectById(String id) {
        //直接new一個給定id的用戶對象,來返回給用戶
        return new User(id,"redisOnly","password");
    }
}

  • 可見這是一個簡單查詢用戶接口。它與典型接口只多了@Cacheable注解

    • 為了方便演示,我直接返回一個用戶對象,而不是查詢數(shù)據(jù)
  • 啟動程序,訪問http://localhost:9999/redisOnly/user/9876接口,查詢一個id為9876的用戶,結(jié)果如圖:

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

  • 第二次訪問http://localhost:9999/redisOnly/user/9876接口(查詢id為9876的用戶)時就會直接從redis中返回redis緩存的用戶數(shù)據(jù)

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

深入理解@Cacheable注解

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

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

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

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

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

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

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

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

  • 上面邏輯都是通過aop封裝好的,對我們來說上面過程是透明的。詳細(xì)可以看源碼中CacheAspectSupport類的第二個execute()方法(傳送門

@CacheEvict 注解簡單使用教程——用于刪除操作接口

  • @CacheEvict注解的作用:刪除redis中對應(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ù)庫對應(yīng)用戶數(shù)據(jù)的操作
        return true;
    }
}

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

  • 啟動程序,設(shè)置請求方法為DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口,即可刪除id為9876的用戶數(shù)據(jù)于redis的緩存

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

  • 原理很簡單,僅僅是把緩存數(shù)據(jù)刪除,無特別邏輯

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

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

@CachePut 注解簡單使用教程——用于刪除操作接口

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

    • @CachePut在值已經(jīng)被緩存的情況下仍然會執(zhí)行被@CachePut注解修飾的方法,而@Cacheable不會
    • @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());
        // 必須把更新后的用戶數(shù)據(jù)返回,這樣才能把它緩存到redis中
        return user;
    }
}

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

  • 啟動程序,設(shè)置請求方法為DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口5次,如下圖

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

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

redis注解與mybatis一起使用

  • redis注解與mybatis一起使用的方式很簡單,就是在上面提到的各個方法內(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 注解的其他知識點

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

    • keyGenerator:定義鍵值對中key生成的類,和key屬性的不能同時存在
    • sync:如果設(shè)置sync=true:
      • a. 如果緩存中沒有數(shù)據(jù),多個線程同時訪問這個方法,則只有一個方法會執(zhí)行到方法,其它方法需要等待;
      • b. 如果緩存中已經(jīng)有數(shù)據(jù),則多個線程可以同時從緩存中獲取數(shù)據(jù);

    注意:sync不能解決緩存穿透的問題

    由于 redis 注解不支持超時時間,所有不存在緩存擊穿和雪崩的問題

    • condition: 在執(zhí)行方法后,如果condition的值為true,則緩存數(shù)據(jù);如果不滿足條件,僅執(zhí)行方法,不緩存結(jié)果
    • unless :在執(zhí)行方法后,判斷unless ,如果值為true,則不緩存數(shù)據(jù)
  • @CacheConfig: 類級別的注解:

    • 如果我們在此注解中定義cacheNames,則此類中的所有方法上的cacheNames默認(rèn)都是此值。
    • 當(dāng)然@Cacheable也可以重定義cacheNames的值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容