@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