使用 spring-data-redis 访问Redis
“spring-data-redis” 是 Spring 框架为 Redis 提供的简化抽象。底层可以支持Jedis、Lettuce 等客户端API(Spring Boot 2.x 后Lettuce为默认客户端API),并提供RedisTemplatehe、Repository和整合Spring缓存等多种简便的使用方式。
1 使用 RedisTemplate
(1)创建SpringBoot项目,添加redis支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)配置 Redis 连接
spring:
#redis配置连接
redis:
database: 0
host: localhost
port: 6379
password: 1234
timeout: 120000
# 配置MySQL数据源和JPA(以下配置与redis无关)
datasource:
url: jdbc:mysql://localhost:3306/MyCinema?serverTimezone=GMT%2B8
username: root
password: 1234
jpa:
show-sql: true
hibernate:
naming:
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
(3)使用默认的 StringRedisTemplate
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTemplateTest {
@Autowired
private RedisTemplate<String, String> strRedisTemplate;
@Test
public void testStringRedisTemplate() {
strRedisTemplate.opsForValue().set("timeStr", new Date().toLocaleString(), 1, TimeUnit.MINUTES);
String time = strRedisTemplate.opsForValue().get("timeStr");
System.err.println(time);
}
}
“spring-boot-starter-data-redis” 默认提供了 StringRedisTemplate 实现,可以直接实现String型KV数据的保存。使用RedisTemplate读写数据,需要选择一个Operations操作,针对不同的数据类型(如string、hash、set、zset等),RedisTemplate提供了不同的操作方法,返回不同的Operations操作对象。
redisTemplate.opsForValue(); //操作字符串,返回ValueOperations对象
redisTemplate.opsForHash(); //操作hash,返回HashOperations对象
redisTemplate.opsForList(); //操作list,返回ListOperations对象
redisTemplate.opsForSet(); //操作set,返回SetOperations对象
redisTemplate.opsForZSet(); //操作有序set,返回ZSetValueOperations对象
如下面的单元测试所示:我们向 Redis 放入了一个key为timeStr的当前时间字符串,并设置了过期时间为1分钟。
(4)定义自己的对象型RedisTemplate
“spring-boot-starter-data-redis” 没有提供保存value为对象的RedisTemplate,但可以简单的自定义一个。
@SpringBootApplication
public class BootRedisCacheDemoApplication {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String, Object> tmpl = new RedisTemplate<String, Object>(); //创建RedisTemplate
tmpl.setConnectionFactory(connectionFactory); //设置连接工厂
tmpl.setKeySerializer(RedisSerializer.string()); //把key的序列化器设置为String序列化器
tmpl.setValueSerializer(RedisSerializer.java()); //把key的序列化器设置为JDK序列化器
tmpl.setHashKeySerializer(RedisSerializer.string());
tmpl.setHashValueSerializer(RedisSerializer.java());
return tmpl;
}
...省略其他代码...
}
定义RedisTemplate对象时,应注意设置Redis序列化器。Redis实际上只能存放字符串型数据,如果要把Java对象保存到Redis中就需要把对象序列化成string再保存。spring-data-redis为我们提供了三种序列化器,他们都派生自RedisSerializer基类。
序列化器 |
工厂 |
描述 |
StringRedisSerializer |
RedisSerializer.string() |
字符串序列化器 |
JdkSerializationRedisSerializer |
RedisSerializer.java() |
JDK序列化器 |
GenericJackson2JsonRedisSerializer |
RedisSerializer.json() |
JSON序列化器 |
修改 Spring Boot 启动类,添加一个RedisTemplate<String,Object>的bean的声明。
经过上述定义,我们就可以使用 RedisTemplate 保存对象型数据了。下面单元测试向Redis放入一个Date对象,过期时间1分钟。
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTemplateTest {
@Autowired
private RedisTemplate<String, Object> objRedisTemplate;
@Test
public void testObjectRedisTemplate() {
objRedisTemplate.opsForValue().set("timeObj", new Date(), 1, TimeUnit.MINUTES);
Object time = objRedisTemplate.opsForValue().get("timeObj");
System.err.println(time+"\t"+time.getClass());
}
}
2 使用 RedisTemplate 缓存数据对象,减少SQL查询
假设应用中有如下Movie实体类:
@Data
@Entity
public class Movie implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String movieCode;
private String director;
private Date dateReleased;
@ManyToOne
@JoinColumn(name = "categoryId")
private Category category;
}
我们可以使用之前定义强类型的 RedisTemplate<String,Movie>(也可以用之前自定义的弱类型RedisTemplate<String,Object>):
@Bean
public RedisTemplate<String, Movie> movieRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Movie> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.java());
template.setHashValueSerializer(RedisSerializer.java());
return template;
}
在业务对象中,我们可以先从Redis缓存中获取数据对象,如果缓存没有,我们才使用SQL从关系型数据库获取。
下面代码先从Redis的hash缓存中查找key为id(字符串)的对象,缓存中有就直接返回数据,缓存中没有就从数据库查找,查询后先把数据保存在Redis缓存中再返回。
@Service
public class MovieBizImpl implements MovieBiz {
private final static String CACHE = "mycinema-movie"; //定义hash缓存的主key
@Autowired
private MovieRepository movieDb;
@Autowired
private RedisTemplate<String,Object> rd;
@Override
public Movie findOneFromCache(int id) {
HashOperations<String, String, Object> ops = rd.opsForHash();
//先检查缓存中是否有所要的数据(hash中每行数据使用id(字符串)作为key),优先从缓存取
if(rd.hasKey(CACHE) && ops.hasKey(CACHE, id+"")) {
return (Movie)ops.get(CACHE, id+"");
}else {
Movie m = movieDb.findById(id).orElse(null);
if(m!=null) {
ops.put(CACHE, id+"", m); //hash中每行数据使用id(字符串)作为key
rd.expire(CACHE, 30, TimeUnit.SECONDS); //设置缓存过期时间
}
return m;
}
}
}
注意:为了数据安全性,使用缓存时,必须设置缓存的时间!
3 使用 Redis Repository
Repository 是Spring Data的一种编程模式,在Repository模式下,只要编写一个接口继承自Repository或CrudRepository接口,无需编程就能实现数据和数据源之间的持久化,之前学习过的SpringDataJPA主要使用的就是Repository模式。Repository模式不仅可以用在JPA上,也可以用在Redis上。
在这种模式下,我们把Redis作为数据库看待而不是仅仅作为缓存看待,下面演示如何使用。
(1)创建实体类并使用 “Redis注解” 标记实体类缓存的规则
@RedisHash(value="mycinema-category", timeToLive=60)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CategoryCache {
@Id //标记主键,可以用id作redis的key
private int id;
@Indexed //标记索引,可以用name作为redis的key
private String name;
}
(2)创建 Repository 用于 Redis缓存 存取数据
//这里只能继承CrudRepository
public interface CategoryRedisRepository extends CrudRepository<CategoryCache, Integer> {
Optional<CategoryCache> findOneByName(String name);
}
(3)在SpringBoot启动类中开启 “@EnableRedisRepositories”
@SpringBootApplication
@EnableRedisRepositories
public class SpringRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJedisApplication.class, args);
}
}
(4)测试 Redis Repository 的效果
@SpringBootTest
class CategoryRedisRepositoryTest {
@Autowired
private CategoryRedisRepository target;
@Test
void testSave() {
CategoryCache c = CategoryCache.builder().id(10).name("悬疑").build();
target.save(c); //把对象存放到Redis
}
@Test
void testFindOneByName() {
CategoryCache c = target.findOneByName("悬疑").orElse(null);
System.err.println("Test findOneByName: "+c); //根据名称索引获取对象
}
@Test
void testFindById() {
CategoryCache c = target.findById(10).orElse(null);
System.err.println("Test findById: "+c); //根据Id获取对象
}
}
4 使用 Spring 缓存抽象整合 Redis
使用RedisTemplate来缓存数据虽然可行但会产生许多管道代码。如果对缓存的操作不要求很精细,可以使用Spring提供的Cache抽象API来实现对业务查询的缓存。Spring Cache可以整合Redis 并提供声明式的缓存功能,让我们无需编码就可以透明的实现缓存功能。
Spring Cache提供的缓存注解:
注解 |
描述 |
@Cacheable |
配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找 |
@CacheEvict |
用来清除用在本方法或者类上的缓存数据 |
@CachePut |
类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果 |
@Caching |
注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict |
@CacheConfig |
配置在类上,cacheNames即定义了本类中所有用到缓存的地方,都去找这个库。只要使用了这个注解,在方法上@Cacheable @CachePut @CacheEvict就可以不用写value去找具体库名了 |
Spring Cache整合Redis的用法如下所示。
(1)修改 application.yml 添加 Spring 缓存配置(整合Redis)
spring:
#spring缓存配置
cache:
type: redis
redis:
time-to-live: 60000 #缓存超时时间ms
cache-null-values: false #是否缓存空值
(3)在SpringBoot启动类中开启 “@EnableCaching”
@SpringBootApplication
@EnableCaching
public class SpringJedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJedisApplication.class, args);
}
}
(4)使用注解为业务添加缓存
首先在业务类中添加 “@CacheConfig” 指定缓存的公共信息,然后用“@Cacheable”等注解指定每一个方法的具体缓存规则。
@Service
@CacheConfig(cacheNames = "mycinema-user") // 以 mycinema-user 作为hash本身的*key
public class UserBizImpl implements UserBiz {
@Autowired
private UserRepository userDb;
@Cacheable(key="'all_users'") // 以 all_users 作为hash内的二级key
public List<User> findAll() {
return userDb.findAll();
}
@Cacheable(key="'user_'+#id") // 以 user_id(参数) 作为hash内的二级key
public User findById(int id) {
return userDb.findById(id).orElse(null);
}
@CacheEvict(allEntries = true) // 删除元素时清除当前hash的所有值
public void delete(int id) {
userDb.delete(userDb.findById(id).orElse(null));
}
@CachePut(key="'user_'+#result.id") // 把返回值重新保存到user_id指定的key中
@CacheEvict(key="'all_users'") // 清除all_users缓存
public User save(User user) {
return userDb.saveAndFlush(user);
}
@CacheEvict(allEntries = true)
public void clearCache() {}
}
创建单元测试测试缓存中的数据:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserBizTest {
@Autowired
private UserBiz target;
@Test
public void testFindAll() {
System.err.println(target.findAll());
}
@Test
public void testFindById() {
System.err.println(target.findById(1));
}
@Test
public void testDelete() {
target.delete(4);
}
@Test
public void testSave() {
User user = new User(0, "username1", "password1", "name1", "address1", "phone1", "email1", "role1");
target.save(user); // 测试添加数据
user = target.findById(3);
user.setAddress("address1");
target.save(user); // 测试修改数据
}
}