在学习redis中的基本数据类型时,只知道增删改查,并体会不到数据类型的优点和缺点。
今天我们通过点赞和点赞排序,体验set和SortedSet的优点。
list:底层是链表,查找时需要根据链表遍历查找,所以o(n)
set:底层是哈希,所以查找速度非常快。
根据博客的id,用户为该博客进行点赞操作。
需求:
实现方法1 :直接使用数据库,不加redis缓存
在数据库中建立一张表格,记录Blog的id,以及用户的id,那么点赞以后,在该表中进行记录一次。
当再次点赞时,去数据库中判断是否存在。
缺点:数据库性能不是很好,点赞判断比较多,对数据库中的压力比较大。
实现方法2:用redis做缓存
其实判断用户没有点赞过,其实是记录当前博客被哪些用户点赞过。
key是 博客id,value是点赞过的用户id,value记录哪些用户为该博客id点过赞。
也就是需要一个集合,所有点过赞的id,全部放进去。
当需要再次点赞时,判断该用户id,在集合中是否存在。
一个用户只能点赞一次。那么就是说该集合中用户id不能重复。
对redis的数据类型要求:set集合能满足以下要求:
1.必须是一个集合
2.必须满足元素唯一性。
当用户点击 点赞按钮业务流程:
/**
* 是否点赞过了
* @TableField(exist = false) :表示数据库中并没有该字段
*/
@TableField(exist = false)
private Boolean isLike;
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 为该文章id,文章点赞功能
* @param id :文章id
* @return
*/
@Override
public Result likeBlog(Long id) {
// 1. 获取用户的信息
UserDTO user = UserHolder.getUser();
Long userId = user.getId();
// 2. 判断该用户在redis中是否存在。是否已经点赞了
String key = "bolg:liked:" + id;
Boolean member = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
if (BooleanUtil.isTrue(member)){
// 3. 存在说明,已经点赞了
// 3.1 从数据库中的like字段中-1
boolean isSuccess = update().setSql("liked = liked-1").eq("id", id).update();
// 3.2 从redis中删除用户信息
if (isSuccess) {
stringRedisTemplate.opsForSet().remove(key,userId.toString());
}
}else{
// 4. 不存在,说明还没有点赞
// 4.1 在数据库中的Like字段中+1
boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
// 4.2 在redis中增加用户信息
if (isSuccess) {
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
}
return Result.ok();
}
/**
* 根据id,获取博客
* @param id
* @return
*/
@Override
public Result queryBlogById(int id) {
// 1. 根据博客id,获取博客
Blog blog = getById(id);
if (blog == null){
return Result.fail("博客不存在");
}
// 2. 查询blog有关的用户信息
queryBlogUser(blog);
// 3. 查看当前用户是否点赞
isBlogLiked(blog);
return Result.ok(blog);
}
private void isBlogLiked(Blog blog) {
// 1. 获取当前用户信息
UserDTO user = UserHolder.getUser();
Long userid = user.getId();
// 根据当前用户去redis查询是否已经点赞了
String key = "bolg:liked:" + blog.getId();
Boolean member = stringRedisTemplate.opsForSet().isMember(key, userid.toString());
blog.setIsLike(BooleanUtil.isTrue(member));
}
/**
* 根据id,获取用户信息
* @param blog
*/
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
/**
* 分页查询热门文章
* @param current
* @return
*/
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.queryBlogUser(blog);
this.isBlogLiked(blog);
});
// records.forEach(this::queryBlogUser);
return Result.ok(records);
}
之前是单独完成点赞业务时,用的是redis中的set集合,因为set可以满足两点:
第一是value元素不唯一性,因为一个用户只允许为一个博客点赞一次。
第二是是一个集合,这样存多个元素。
而如今要实现的是点赞排行榜,那么set不支持排序,那么继续使用set作为点赞就不适合了,那么可以从集合中进行挑选:
SortedSet:
添加元素是:ZADD
判断是否存在:Zscore:根据指定元素,获取对应的分数。
查询范围:zrange:从索引开始0到n
127.0.0.1:6379[3]> ZADD key01 888 huyelin 999 zhangsan 123 lisi
(integer) 3
127.0.0.1:6379[3]> ZSCORE key01 huyelin
"888"
127.0.0.1:6379[3]> ZSCORE key01 zhangsan
"999" //查到的话返回的是元素的值
127.0.0.1:6379[3]> ZSCORE key01 jfklsajdf;lkj
(nil) // 返回的是空
127.0.0.1:6379[3]> ZRANGE key01 0 2
lisi
huyelin
zhangsan
/**
* 获取点赞列表
* @param id :文章id
* @return
*/
@Override
public Result queryLikes(int id) {
// 1. 查询都有哪些用户点赞了。查询出前五条数据:
String key = "bolg:liked:" +id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
//2.解析出其中的用户id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
// 3. 根据用户的id,查询所有的用户信息,放入准备好的实体类【UserDTO】返回前端
String idstr = StrUtil.join(",",ids); // 将ids字符串,进行分割
System.out.println("查看字符串进行分割-------》"+idstr);
List<User> userList = userService.query().in("id", ids).last("ORDER BY FIELD(id," + idstr + ")").list();
List<UserDTO> userDTOS = userList.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOS);
}
其中准备好的实体类,返回前端的数据是:
@Data
public class UserDTO {
// 用户id
private Long id;
// 名字
private String nickName;
//头像
private String icon;
}
package com.hmdp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IUserService;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* <p>
* 服务实现类
* </p>
*
*/
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
@Resource
private IUserService userService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 根据前端传过来的文字id,该用户为该文章点赞和取消赞的功能
* @param id :文章id
* @return
*/
@Override
public Result likeBlog(Long id) {
// 1. 获取用户的信息
UserDTO user = UserHolder.getUser();
Long userId = user.getId();
// 2. 判断该用户在redis中是否存在。是否已经点赞了
String key = "bolg:liked:" + id;
// Boolean member = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
// 根据key和value,得到的是分数
Double member = stringRedisTemplate.opsForZSet().score(key,userId.toString());
if (member != null ){
// 3. 如果说分数不为空,说明有分数。说明 已经点赞了
// 3.1 从数据库中的like字段中-1
boolean isSuccess = update().setSql("liked = liked-1").eq("id", id).update();
// 3.2 从redis中删除用户信息
if (isSuccess) {
// stringRedisTemplate.opsForSet().remove(key,userId.toString());
// 删除该用户信息
stringRedisTemplate.opsForZSet().remove(key,userId.toString());
}
}else{
// 4. 不存在,说明还没有点赞
// 4.1 在数据库中的Like字段中+1
boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
// 4.2 在redis中增加用户信息
if (isSuccess) {
stringRedisTemplate.opsForZSet().add(key,userId.toString(),System.currentTimeMillis());
}
}
return Result.ok();
}
/**
* 分页查询热门文章
* @param current
* @return
*/
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
//根据id,获取用户信息
this.queryBlogUser(blog);
// 查看当前用户是否点赞
this.isBlogLiked(blog);
});
// records.forEach(this::queryBlogUser);
return Result.ok(records);
}
/**
* 根据博客id,获取博客
* @param id
* @return
*/
@Override
public Result queryBlogById(int id) {
// 1. 根据博客id,获取博客
Blog blog = getById(id);
if (blog == null){
return Result.fail("博客不存在");
}
// 2. 查询blog有关的用户信息
queryBlogUser(blog);
// 3. 查看当前用户是否点赞
isBlogLiked(blog);
return Result.ok(blog);
}
/**
* 根据当前用户查询是否已经点赞
* @param blog
*/
private void isBlogLiked(Blog blog) {
// 1. 获取当前用户信息
UserDTO user = UserHolder.getUser();
// 判断,如果当前用户没有登录,不需要再去查是否点赞,直接返回空即可
if (user ==null){
return;
}
Long userid = user.getId();
// 根据当前用户去redis查询是否已经点赞了
String key = "bolg:liked:" + blog.getId();
// Boolean member = stringRedisTemplate.opsForSet().isMember(key, userid.toString());
Double member = stringRedisTemplate.opsForZSet().score(key,userid.toString());
if (member != null){
blog.setIsLike(true);
}else {
blog.setIsLike(false);
}
}
/**
* 根据id,获取用户信息
* @param blog
*/
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
/**
* 获取点赞列表
* @param id :文章id
* @return
*/
@Override
public Result queryLikes(int id) {
// 1. 查询都有哪些用户点赞了。查询出前五条数据:
String key = "bolg:liked:" +id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
//2.解析出其中的用户id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
// 3. 根据用户的id,查询所有的用户信息,放入准备好的实体类【UserDTO】返回前端
String idstr = StrUtil.join(",",ids); // 将ids字符串,进行分割
System.out.println("查看字符串进行分割-------》"+idstr);
List<User> userList = userService.query().in("id", ids).last("ORDER BY FIELD(id," + idstr + ")").list();
List<UserDTO> userDTOS = userList.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOS);
}
}