springboot中整合redis有许多种方法,cache是springboot官方作为一个统一的定义标准。下面来实现spring cache整合redis
1.工程创建
创建springboot工程,创建时需要引入四个依赖:web,spring security,spring data redis,spring cache.
在pom.xml文件中可看到刚才添加的四个依赖已经被引入。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.设置cache之前的操作
配置redis的基础信息和cache的名称
spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.password=
spring.cache.cache-names=c1
打开redis
编写实体类user
package org.javaboy.redis;
public class User {
private Integer id;
private String username;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
service类中的方法作用是根据id返回user。
package org.javaboy.redis;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUserById(Integer id) {
System.out.println("getUserById>>>" + id);
User user = new User();
user.setId(id);
return user;
}
}
测试类:
测试类中首先注入service类,通过userservice调用方法getUserById,这里调用两次getUserById是为了查看redis是否成功启用。
package org.javaboy.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisApplicationTests {
@Autowired
private UserService userService;
@Test
public void contextLoads() {
User u1 = userService.getUserById(1);
User u2 = userService.getUserById(1);
System.out.println(u1);
System.out.println(u2);
}
}
启动方法之后:
可以看出这里cache还未配置所以redis还没启用。
getUserById>>>1
getUserById>>>1
User{id=1, username='null', address='null'}
User{id=1, username='null', address='null'}
3.cache的相关配置
在启动类上添加注解@EnableCaching用来启用缓存
package org.javaboy.redis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
在service方法上添加注解:
@Cacheable(cacheNames = "c1")
该注解用于开启缓存,该方法就具备缓存功能,名字就是先前在properties中配置的cache的名称。
现在参数就是缓存中的key,返回值就是缓存中的value。
package org.javaboy.redis;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(cacheNames = "c1")
public User getUserById(Integer id) {
System.out.println("getUserById>>>" + id);
User user = new User();
user.setId(id);
return user;
}
}
配置完之后现在来启动测试类:
可以看出报错了,因为我们的user实体类没有进行序列化。
将实体类user继承序列化接口,实现序列化
public class User implements Serializable
再次执行测试类:
方法执行成功了,而且和没有配置cache时的结果有所不同,缺少了getUserById>>>1语句,这是因为,我们测试的时候执行了两次service中的方法,而且两次的参数都是相同的。这就导致了第一次执行之后,结果存储到了缓存中,第二次再次执行时就不会执行该方法了,直接从缓存中将值取出。这就是redis最大的作用
getUserById>>>1
User{id=1, username='null', address='null'}
User{id=1, username='null', address='null'}
下面再来做个测试:
现在在serice方法中添加一个参数,响应的测试类中也传递一个值:
public User getUserById(Integer id,String name) {
User u1 = userService.getUserById(1,"aa");
User u2 = userService.getUserById(1,"bb");
再次执行测试方法:
因为传递的参数值不一样所以redis认为这两次查询不是相同的,所以第二次调用方法时就不会从缓存中去取值,而是再次执行service中的方法。在redis中将参数看出是key,无论参数有多少个。
getUserById>>>1
getUserById>>>1
User{id=1, username='null', address='null'}
User{id=1, username='null', address='null'}
如果现在非要以第一个参数为key呢?只需要在@Cacheable上添加key属性即可
key=“#id”表示这里将id当作是key,属性name就直接被忽略掉了。
@Cacheable(cacheNames = "c1",key = "#id")
现在我们再来执行test方法:
可以看到方法只执行了一次,原理前面已经讲过,redis这里默认id是key,而这里两次的id值相同所以第二次执行时,就会从缓存中获取第一次的结果。
getUserById>>>2
User{id=2, username='null', address='null'}
User{id=2, username='null', address='null'}
@Cacheable中的key默认是以全部参数作为key,当然你也可以特定的参数为key,方法名为key,甚至可以设置cache名为key。不过一般我们都是把参数当成key。
如果key太复杂你也可以自己定义一个方法来设置具体的key
让这个方法去实现KeyGenerator接口,然后实现改接口中的方法。改方法用来定义key,改方法第一个参数是对象名,第二个参数是方法名,第三个参数是参数列表。这里我们将方法名+所有参数当成key。
package org.javaboy.redis;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Component
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName() + ":" + Arrays.toString(objects);
}
}
定义好配置key的类之后我们需要在service方法中将该方法引入:
在注解中通过keyGenerator将配置的类引入,方法名称首字母需要改为小写。
@Cacheable(cacheNames = "c1",keyGenerator = "myKeyGenerator")
再次执行测试方法:
由于这里的参数值不一样,所以redis又失效了。
getUserById>>>2
getUserById>>>2
User{id=2, username='null', address='null'}
User{id=2, username='null', address='null'}
现在我们要试一下删除方法是否可以用缓存实现:
@CacheEvict注解主要用于清除缓存中的内容 ,注解中cacheNames为cache的名字,allEntries为false代表根据id删除缓存中的内容,为true则代表将缓存中的数据全部删除。默认为false,这里我们只需要让他根据id删除就可以了。
@CacheEvict(cacheNames = "c1")
public void deleteUserById(Integer id) {
System.out.println("deleteUserById>>>" + id);
}
编写测试类:
分析:我们先要根据id去查询user,然后执行删除方法,将缓存中的结果删除,然后我们再次调用查询方法,保证和第一次调用的id值要一样。预期结果是:两次查询都会执行方法,因为中间将第一次在缓存中的内容删除了。
package org.javaboy.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisApplicationTests {
@Autowired
private UserService userService;
@Test
public void contextLoads() {
User u1 = userService.getUserById(3,"aa");
userService.deleteUserById(3);
User u2 = userService.getUserById(3,"bb");
System.out.println(u1);
System.out.println(u2);
}
}
与预期相符,删除缓存生效
getUserById>>>3
deleteUserById>>>3
getUserById>>>3
User{id=3, username='null', address='null'}
User{id=3, username='null', address='null'}
查询,删除都说了,还剩下更新问题。加入我查询一条数据,然后去更新这条数据,再去查询时在缓存中得到的数据就是尚未更新时的数据值,这是不对的,下面来讲一下更新操作时cache的配置。
在service类中添加修改方法 @CachePut用于数据更新时即使更新缓存中的数据,key时user的id属性。
@CachePut(cacheNames = "c1",key = "#user.id")
public User updataUserById(User user) {
return user;
}
测试类:
预期这里打印出的结果应该不一样
package org.javaboy.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisApplicationTests {
@Autowired
private UserService userService;
@Test
public void contextLoads() {
User u1 = userService.getUserById(3,"aa");
// userService.deleteUserById(3);
User user = new User();
user.setId(3);
user.setUsername("javaboy");
user.setAddress("上海");
userService.updataUserById(user);
User u2 = userService.getUserById(3,"bb");
System.out.println(u1);
System.out.println(u2);
}
}
可以发现更新操作成功
User{id=3, username='null', address='null'}
User{id=3, username='javaboy', address='上海'}
这里还有一个问题就是每个方法的注解中都需要声明cache的名称,这样写非常麻烦,我们需要在service类最前面添加:cacheNames = (“c1”),这样service类中的方法就不需要在写cache的名称了。
总结:
以上这些cache的注解是规范并没有实现,实现是在redis中。