@Cacheable、@CacheEvict、@CachePut


1.1.介紹三個注解@Cacheable、@CacheEvict、@CachePut

1.1.1@Cacheable

概述:

1)標記在類上:表示該類所有的方法都是支持緩存的。

2)標記在方法上:如果一個方法上添加了@Cacheable標記,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。

用法:

@Cacheable可以指定三個屬性value、key和condition

value必須指定,表示當前方法的返回值會被緩存在哪個Cache上,對應Cache的名稱。可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。

適用場景:查詢接口

【示例】

	@Cacheable("cache1")//Cache是發生在cache1上的
    public User find(Integer id) {
        return null;
    }

   @Cacheable(cacheNames="user", key="#id") // 使用方法參數中的id參數生成緩存鍵值對中的key
    public User query(Integer id) {
        return null;
    }

key 自定義緩存的鍵,用來指定Spring緩存時對應的key的

   @Cacheable(value="users", key="#user.id")
   public User find(User user) {
      return null;
   }

思路:

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

1.1.2@CacheEvict

概述:

本質是刪除redis數據庫的user命名空間下的所有鍵值對.原理很簡單,僅僅是把緩存數據刪除,無特別邏輯.

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

用法:

緩存被調用方法的結果(返回值),刪除redis中對應的緩存

@CacheEvict(cacheNames="user", key="#id")
    @Override
    public boolean delete(String id) {
        // 可以在這里添加刪除數據庫對應用戶數據的操作
        return true;
    }

適用場景:刪除接口

1.1.3@CachePut

概述:

@CachePut也可以聲明一個方法支持緩存功能。不同的是使用@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。

用法:

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

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

適用場景:更新操作,插入操作

2.如何搭建Redis緩存

整體結構布局

.
├── 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

pom引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置application.yml

注意:xxx.xx.xx.xx表示你需要的端口號

server:
  port: 8080
spring:
  datasource: #數據源配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxx.xx.xx.xx:3310/0812erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    username: root
    password: root
    druid:
      max-active: 10
      min-idle: 5
      max-wait: 5000
      initial-size: 5
      validation-query: select 1
      stat-view-servlet:
        enabled: true
        login-username: admin
        login-password: admin
        allow:
        deny:
        url-pattern: "/druid/*"
  thymeleaf:
    cache: false
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  #==================redis 配置開始============================
  #注意,redis是上面spring下面的,別寫錯了,層級關系
  redis:
    host: xxx.xx.xx.xx
    password: xxxxxx
    port: 6390
    jedis:
      pool:
        max-active: 20
        max-idle: 8
        min-idle: 0
        max-wait: 2000
  #==================redis 配置結束============================

#shiro的配置
shiro:
  hash-algorithm-name: md5
  hash-iterations: 2
  login-url: /index.html
  unauthorized-url: /unauthorized.html
  anon-urls:
    - /**
    - /index.html*
    - /login/login*
    - /resources/**
  logout-url: /login/logout*
  authc-urls:
  #  - /**
#mybatisplus的配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*/*Mapper.xml

修改Application

@SpringBootApplication
@MapperScan(basePackages = {"com.sxt.system.mapper"}) // 掃描mapper
@EnableCaching  // 開啟緩存
public class Application {

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

}

創建RedisOnlyController

@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);
    }
}

創建RedisOnlyServiceImpl

查詢:

@Service
public class RedisOnlyServiceImpl implements UserService {
    /**
     *  先用id生成key,在用這個key查詢redis中有無緩存到對應的值
     *
     *  若無緩存,則執行方法selectById,並把方法返回的值緩存到redis
     *
     *  若有緩存,則直接把redis緩存的值返回給用戶,不執行方法
     */
    @Cacheable(cacheNames="user", key="#id")
    @Override
    public User selectById(String id) {
        //直接new一個給定id的用戶對象,來返回給用戶
        return new User(id,"redisOnly","password");
    }

測試結果:

第一次訪問http://localhost:9999/redisOnly/user/1接口(查詢id為1的用戶)時就會從redis中查找redis緩存的用戶數據,沒有,放入數據庫和緩存中

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

而不再執行RedisOnlyServiceImplselectById()方法

修改RedisOnlyController

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

修改RedisOnlyServiceImpl

刪除:

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

測試:

啟動程序,設置請求方法為DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口,即可刪除id為9876的用戶數據於redis的緩存和數據庫對應的數據

修改RedisOnlyController

@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;
    }
}

修改RedisOnlyServiceImpl

修改:

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

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

測試:

啟動程序,設置請求方法為DELETE,用postman訪問http://localhost:9999/redisOnly/user/9876接口5次

我們發現每次訪問接口時時update()方法都會執行一次(即,executeCout的值為5)

注意:mybatis搭建,springboot搭建都是一樣的原理,不懂怎么搭建可看后續文檔。

3.redis注解的其他知識點(多線程訪問)

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

keyGenerator:定義鍵值對中key生成的類,和key屬性的不能同時存在

sync:如果設置sync=true:
    - a. 如果緩存中沒有數據,多個線程同時訪問這個方法,則只有一個方法會執行到方法,其它方法需要等待;
    - b. 如果緩存中已經有數據,則多個線程可以同時從緩存中獲取數據;

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM