4 Springboot中使用redis存储集合数据,并模拟条件查询、分页读取

时间:2022-06-17
本文章向大家介绍4 Springboot中使用redis存储集合数据,并模拟条件查询、分页读取,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前面几篇讲了使用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里做相应的处理。

可以通过添加多条数据来测试一下,缓存是否生效。

上面这个只是实现了逻辑,还有一些异常处理数据同步的没有处理,只讲了思路。