问题背景
在实现点赞排行榜功能当中,需要展示出最先点赞的五个人的基础信息,形成排行榜。
需求分析
我们需要选择合适的数据类型来存放点赞人的ID,需要保证:
点赞人的ID是唯一的,并且能够根据时间戳来排序,在List,Set,Zset能够满足这两种需求的只有Zset。
在存放到Zset中时,以笔记ID作为key,点赞人的ID作为value。
三种数据类型比较:
存在的Bug
通过测试得到,Redis中存放的点赞人ID是没问题的,但是到数据库查询时返回的结果无法按照zset给的ID顺序返回,原来的SQL语句:
select * from tb_user where id in (5,2,3)
原始代码
@Override
public Result queryBogLikes(Long blogId) {
String key = BLOG_LIKE_PREFIX + blogId;
Set<String> userIds = stringRedisTemplate.opsForZSet().range(key, 0, 5);
//增加空指针判断
if (userIds == null || userIds.isEmpty()) return Result.ok();
//将ids到数据库查询
List<UserDTO> userDTOList = userService.query()
.in("id", userIds)
.list()
.stream()
.map(user -> BeanUtil.copyProperties(user,UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOList);
}
问题分析
:实际上,DBMS会按照其内部的优化策略或索引来决定如何检索和返回数据,这通常会导致结果按照与IN
子句中指定的顺序不同的顺序返回。在某些情况下,DBMS甚至可能为了性能考虑而重新排序结果集,以确保最有效的数据检索和传输。
所以在查询的时候需要进行order by。
解决方案
无论因为何种问题,导致我们无法按照有规则的字段进行排序返回结果,只能通过in中顺序来返回的结果,我们只能使用自定义排序,MySQL中ORDER BY的field()函数,可以用来对SQL中查询结果集进行指定顺序排序。其一般语法为:field(字段,value,value,…)
1, 使用工具类将待查询ids拼接成以,间隔的字符串
2, 拼接SQL,按照field指定的ID顺序来展示结果集
public Result queryBogLikes(Long blogId) {
String key = BLOG_LIKE_PREFIX + blogId;
Set<String> userIds = stringRedisTemplate.opsForZSet().range(key, 0, 5);
//增加空指针判断
if (userIds == null || userIds.isEmpty()) return Result.ok();
String idStr = StrUtil.join(",", userIds);
//将ids到数据库查询
List<UserDTO> userDTOList = userService.query()
.in("id", userIds)
.last("order by field(id," + idStr + ")")
.list()
.stream()
.map(user -> BeanUtil.copyProperties(user,UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOList);
}