前面几篇讲了使用redis存储单个对象,自动缓存、更新、删除的做法,在实际项目中,更常用的是分页查询集合数据,条件查询(譬如按照添加时间倒序排列)。
redis本身是不提供条件查询的,因为是一个非关系型数据库,那么其实通过一些手段,也是能完成条件查询的,尤其是有顺序的条件查询。因为redis里有个zset,这个结构里面存储的数据是有顺序的。
下面就来看看怎么做,接着前几篇的例子讲,以Post表为例。
之前Post的增删改查都是通过我们配置的CachePut,CacheEvict等,自动由框架完成的缓存,这些都是单个Post对象,那我们需要增加一个redis的zset来存储集合,思路就是在新增Post时,通过aop,在zset里也添加一条数据,保存Post的Id和将来要拿来排序用的某个字段做为zset的score。这样就能完成分页排序了。修改和删除时,同理,也在zset里完成相应的修改。
关于aop,我在另外一篇里讲的有。
下面直接来实现。
在controller里加个分页查询的方法:
@RequestMapping("/queryPage")
public Object query(int pageNum, int count) {
return postService.queryPage(pageNum, count);
}
在repository里加上分页查询的接口
@CacheConfig(cacheNames = "post")
public interface PostRepository extends PagingAndSortingRepository<Post, Integer> {
@Cacheable(key = "'PostId' + #p0")
Post findById(int id);
/**
* 新增或修改时
*/
@CachePut(key = "'PostId' + #p0.id")
@Override
Post save(Post post);
@Transactional
@Modifying
@CacheEvict(key = "'PostId' + #p0")
int deleteById(int id);
/**
* 正序分页
*/
List<Post> findAllOrderByWeight(Pageable pageable);
/**
* 倒序分页,注意:findAll后面要加个By,否则会报错启动不了
*/
List<Post> findAllByOrderByWeightDesc(Pageable pageable);
}
pom里加上aop
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在service层加上aop切面:
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
@Autowired
private RedisTemplate redisTemplate;
public Post findById(int id) {
return postRepository.findById(id);
}
public Post save(Post post) {
return postRepository.save(post);
}
public int delete(int id) {
return postRepository.deleteById(id);
}
public List<Post> queryPage(int pageNum, int count) {
//根据weight倒序分页查询
// Pageable pageable = new PageRequest(pageNum, count, Sort.Direction.DESC, "weight");
Pageable pageable = new PageRequest(pageNum, count);
return postRepository.findAllByOrderByWeightDesc(pageable);
}
}
定义一个aspect切面
package com.tianyalei.aspect;
import com.tianyalei.domain.Post;
import com.tianyalei.repository.PostRepository;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Created by wuwf on 17/4/28.
* aop切面处理
*/
@Component
@Aspect
public class PostAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private PostRepository postRepository;
private final String POST_SET_KEY = "post_set";
@Pointcut(value = "within(com.tianyalei.service.PostService)")
public void postAccess() {
}
@Around("postAccess()")
public Object around(ProceedingJoinPoint pjp) {
//方法名
String methodName = pjp.getSignature().getName();
//参数
Object[] objects = pjp.getArgs();
//分页查询
if ("queryPage".equals(methodName)) {
int pageNum = (int) objects[0];
int count = (int) objects[1];
try {
//倒序查询分页的ids
Set<Integer> ids = redisTemplate.opsForZSet().reverseRange(POST_SET_KEY, pageNum * count, pageNum * count + count - 1);
List<Post> posts = new ArrayList<>(ids.size());
for (int id : ids) {
posts.add(postRepository.findById(id));
}
return posts;
} catch (Exception e) {
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
} else if("save".equals(methodName)) {
Post post = null;
try {
post = (Post) pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
redisTemplate.opsForZSet().add(POST_SET_KEY, post.getId(), post.getWeight());
return post;
} else if("delete".equals(methodName)) {
int id = (int) objects[0];
redisTemplate.opsForZSet().remove(POST_SET_KEY, id);
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
这里通过方法名来区分是add、delete还是查询,然后在redis里做相应的处理。
可以通过添加多条数据来测试一下,缓存是否生效。
上面这个只是实现了逻辑,还有一些异常处理数据同步的没有处理,只讲了思路。