Springboot Redis在分页查询中,缓存和反序列化问题

首先整合Redis

1.导入依赖

<!--redis-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--hutool-->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.7.20</version>
</dependency>

2.RedisConfig配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

        //Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String序列号配置
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key和hash的key都采用String的序列化配置
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        //value和hash的value采用Json的序列化配置
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;
    }

}

3.在Service实现类中进行缓存

由于我们请求的都是json字符串,所以使用StringRedisTemplate

@Service
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
 	@Autowired
    private StringRedisTemplate stringRedisTemplate;
}

  由于我是初学Redis,于是使用一个分页查询的接口来练手。
  首先说明,这里的分页查询是一对多查询,自己写了page方法以及sql语句,在整合Redis前通过mybatis-plus的分页功能进行。由于3.4.0之后的Mybatis-Plus,不再使用旧版本的PaginationInterceptor ,而是使用MMybatisPlusInterceptor。
  可以使用以下配置类 :

@Configuration
@MapperScan("com.mu.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

  原始的查询方法如下:
  DeviceServiceImpl.java

   //多表查询所有设备信息
    @Override
    public Result pageAllData(Integer pageNum, Integer pageSize, String devicename, Integer start, Integer end) {
        IPage<DeviceDTO> page = new Page<>(pageNum,pageSize);
        Result result =  Result.success(deviceMapper.pageAllData(page,devicename,start,end));
        System.out.println(result);
        return result;
    }

  pageNum,pageSize : 分别为当前页数,以及每页显示多少数据。
  devicename : 是按条件查询的设备名称。
  start,end : 是按设备价格区间查询的参数。

初次整合Redis中时:
代码如下:

    @Override
    public Result pageAllData(Integer pageNum, Integer pageSize, String devicename, Integer start, Integer end) {
        IPage<DeviceDTO> page = new Page<>(pageNum,pageSize);
        //1.从缓存获取数据
        String json = stringRedisTemplate.opsForValue().get(FILES_KEY);
        IPage<DeviceDTO> deviceDTOIPage;
        if (StrUtil.isBlank(json)) { //2.缓存取出来为空,就从数据库里面查
            deviceDTOIPage = deviceMapper.pageAllData(page, devicename, start, end);
            //3.从数据库取出来之后,4.再次缓存到redis
            stringRedisTemplate.opsForValue().set(FILES_KEY, JSONUtil.toJsonStr((deviceDTOIPage)));
        } else {
            //5.如果缓存有
            //从缓存中获取数据
            deviceDTOIPage = JSONUtil.toBean(json, new TypeReference<IPage<DeviceDTO>>() {
            }, true);
        }

       return  Result.success(deviceDTOIPage);
    }

第一次启动时,将redis里面的缓存清空,进入设备信息的模块时,第一次从数据库中查询数据,并装入Redis中缓存,页面显示数据,一切顺利。
在这里插入图片描述
在这里插入图片描述

可是当再次刷新,原本已经有缓存的情况下,页面数据直接清空,当打断点调试的时候,发现原来是当获取redis缓存中数据进行反序列化时候出现问题。
在这里插入图片描述
正常获取到json数据,但是转化为deviceDOTIPage后就变为空。

cn.hutool.core.convert.ConvertException报错

后来卡了一会儿,尝试了通过字符串转化,JSON对象转化,fastjson工具的使用都不行,后才才意识到IPage是一个接口类型,而json反序列化终究还是要转为一个实现类对象,那于是可以自定义一个分类类,实现IPage接口,根据json中的数据,可以自定义下面这些属性。

MyPage.java


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.mu.controller.dto.DeviceDTO;

import java.util.List;

public class MyPage implements IPage<DeviceDTO> {

    private static final long serialVersionUID = 5760097915453738435L;
    public static final int DEFAULT_PAGE_SIZE = 10;
    /**
     * 每页显示个数
     */
    private int size;
    /**
     * 当前页数
     */
    private int current;
    /**
     * 总页数
     */
    private int total;
    /**
     * 总记录数
     */
    private int totalCount;
    /**
     * 结果列表
     */
    private List<DeviceDTO> records;

    private List<OrderItem> orders;

    @Override
    public long getSize() {
        return size;
    }

    @Override
    public IPage<DeviceDTO> setSize(long size) {
        return null;
    }

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public long getCurrent() {
        return current;
    }

    @Override
    public IPage<DeviceDTO> setCurrent(long current) {
        return null;
    }

    public void setCurrent(int current) {
        this.current = current;
    }

    @Override
    public long getTotal() {
        return total;
    }

    @Override
    public IPage<DeviceDTO> setTotal(long total) {
        return null;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    @Override
    public List<OrderItem> orders() {
        return null;
    }

    @Override
    public List<DeviceDTO> getRecords() {
        return records;
    }

    @Override
    public IPage<DeviceDTO> setRecords(List<DeviceDTO> records) {
        this.records = records;
        return null;
    }
}

修改分页查询方法

//多表查询所有设备信息
    @Override
    public Result pageAllData(Integer pageNum, Integer pageSize, String devicename, Integer start, Integer end) {
        IPage<DeviceDTO> page = new Page<>(pageNum,pageSize);
        String jsonn = stringRedisTemplate.opsForValue().get(FILES_KEY);
        JSONObject json  = JSON.parseObject(jsonn);
        IPage<DeviceDTO> deviceDTOIPage;
        if (StrUtil.isBlank(jsonn)) { //2.缓存取出来为空,就从数据库里面查
            deviceDTOIPage = deviceMapper.pageAllData(page, devicename, start, end);
            //3.从数据库取出来之后,4.再次缓存到redis
            stringRedisTemplate.opsForValue().set(FILES_KEY, JSONUtil.toJsonStr(deviceDTOIPage));
            return  Result.success(deviceDTOIPage);
        } else {
            //5.如果缓存有
            //从缓存中获取数据
            MyPage myPage = JSONUtil.toBean(jsonn, new TypeReference<MyPage>() {
            }, false);
            return  Result.success(myPage);
        }
    }

最终,刷新可以显示出Redis缓存中的数据!
但是,目前还存在很多BUG。

首先就是点击第二页,竟然没有数据。在debug调试中,发现只返回第一页的数据,但是total是正确的,而records记录不全。可能自己对于redis的掌握不全面,后来意识到,当前key的缓存只存入了第一页,并且即使切换到了第二页,也因为json有数据,而没有再从数据库查询。

因此,一个简单的方法就是将每一页缓存一个key,当切换页数的时候自然会去数据库查询当前页的数据然后存入。

修改后代码如下:

    //多表查询所有设备信息
//    @Cacheable(value = "emp",key = "'findAll'")
    @Override
    public Result pageAllData(Integer pageNum, Integer pageSize, String devicename, Integer start, Integer end) {
        IPage<DeviceDTO> page = new Page<>(pageNum,pageSize);
        //1.从缓存获取数据
        String key = "fenye" + pageNum;
        this.key = key;
        String jsonn = stringRedisTemplate.opsForValue().get(key);
        JSONObject json  = JSON.parseObject(jsonn);
        IPage<DeviceDTO> deviceDTOIPage;
        if (StrUtil.isBlank(jsonn)) { //2.缓存取出来为空,就从数据库里面查
            deviceDTOIPage = deviceMapper.pageAllData(page, devicename, start, end);
            //3.从数据库取出来之后,4.再次缓存到redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(deviceDTOIPage));
            return  Result.success(deviceDTOIPage);
        } else {
            //5.如果缓存有
            //从缓存中获取数据
            MyPage myPage = JSONUtil.toBean(jsonn, new TypeReference<MyPage>() {
            }, false);
            return  Result.success(myPage);
        }

    }

如果要对当前某一条记录进行修改,就需要在修改后,删除当前缓存,下一次加载页面时,就会自动重新从数据库查出最新的数据。

@Service
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
@Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String key;//得到当前页数的key


    @Override
    public Result saveOrUp(DeviceDTO deviceDTO) {
        System.out.println(deviceDTO);
        Device device = new Device();
        if(deviceDTO.getOwnerName()!=""){
            deviceDTO = owner(deviceDTO);//将设备拥有者与老师绑定
        }
        //通过deviceDto查询出对应的device
        BeanUtil.copyProperties(deviceDTO,device,true);
        System.out.println(device);
        Result result = Result.success(saveOrUpdate(device));
        photo(deviceDTO);
        //删除当前页缓存
        flushRedis(key);
        return result;

    }

    private void flushRedis(String key) {
        stringRedisTemplate.delete(key);
    }
}

问题解决。
由于是练手,所以并不是很规范,希望也能给大家提供一点帮助!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值