使用 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); // 测试修改数据 } }