现在做的大部分项目都用到了SpringBoot和Redis,不过实际开发中,Redis只存了Token之类的数据,大部分的增删改查还是直接走的MySQL数据库。这2天逛论坛发现其实很多场景其实更需要Redis这样的存在,于是参考网上的代码简单做了个文章浏览量记录的Demo。
首先放出maven依赖:
junit
junit
3.8.1
test
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-jdbc
org.springframework
spring-jdbc
5.1.5.RELEASE
org.springframework
spring-beans
com.baomidou
mybatis-plus-boot-starter
3.4.1
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
commons-lang
commons-lang
2.6
com.alibaba
fastjson
1.2.3
org.projectlombok
lombok
1.16.20
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
io.springfox
springfox-boot-starter
3.0.0
com.github.pagehelper
pagehelper-spring-boot-starter
1.3.0
redis配置:
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 100
max-wait: 1
max-idle: 10
min-idle: 0
timeout: 1000
Redis配置类:
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.StringRedisSerializer;
/**
* Redis配置类
* @author wl
*
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
/**
* 配置自定义redisTemplate
* @return
*/
@Bean
RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 设置值(value)的序列化采用Jackson2JsonRedisSerializer。
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
Redis工具类:
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
private StringRedisTemplate stringRedisTemplate;
public RedisUtil(RedisTemplate redisTemplate, StringRedisTemplate stringRedisTemplate) {
this.redisTemplate = redisTemplate;
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key,Object value,long time){
try {
if(time>0){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}else{
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map
文章类,数据库表不赘述:
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* @author wl
* @date 2022/2/22
*/
@Data
@TableName("article")
public class Article {
@TableId
private Long id;
@ApiModelProperty("作者ID")
private Long authorId;
@ApiModelProperty("类别ID")
private Long categoryId;
@ApiModelProperty("标题")
private String title;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("浏览量")
private Integer viewNum;
@ApiModelProperty("评论数")
private Integer commentNum;
@ApiModelProperty("点赞数")
private Integer likeNum;
@ApiModelProperty("创建时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
Dao层接口:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wl.standard.entity.Article;
/**
* @author wl
* @date 2022/2/22
*/
public interface ArticleMapper extends BaseMapper {
int updateNumById(Article article);
}
Dao层XML:
update article
view_num = #{viewNum},
comment_num = #{commentNum},
like_num = #{likeNum}
id = #{id}
Service层接口:
import com.baomidou.mybatisplus.extension.service.IService;
import com.wl.standard.entity.Article;
/**
* @author wl
* @date 2022/2/22
*/
public interface ArticleService extends IService {
Boolean updateNumById(Article article);
}
Service层实现类:
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wl.standard.entity.Article;
import com.wl.standard.mapper.ArticleMapper;
import com.wl.standard.service.ArticleService;
import org.springframework.stereotype.Service;
/**
* @author wl
* @date 2022/2/22
*/
@Service
public class ArticleServiceImpl extends ServiceImpl implements ArticleService {
@Override
public Boolean updateNumById(Article article) {
return baseMapper.updateNumById(article) > 0;
}
}
万事具备,接下来写一个监听类来实现Redis和MySQL数据库的数据交换。项目启动时,将MySQL数据库中的文章浏览量查询出来写入到Redis中。Servlet销毁时或者每隔多少秒就将Redis中的文章浏览量写入到MySQL数据库当中,这样就保证了不会因为服务关闭而导致数据丢失和数据不一致的情况。
import com.wl.standard.entity.Article;
import com.wl.standard.service.ArticleService;
import com.wl.standard.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.omg.CORBA.PRIVATE_MEMBER;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
* @author wl
* @date 2022/2/22
*/
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ListenHandler {
private final ArticleService articleService;
private final RedisUtil redisUtil;
private static final String VIEW_KEY = "viewNum";
private static final String COMMENT_KEY = "commentNum";
private static final String LIKE_KEY = "likeNum";
@Autowired
public ListenHandler(ArticleService articleService, RedisUtil redisUtil) {
this.articleService = articleService;
this.redisUtil = redisUtil;
}
@PostConstruct
public void init() throws Exception {
log.info("数据初始化开始...");
//将数据库中的数据写入redis
List articleLst = articleService.list();
articleLst.forEach(article -> {
//将浏览量、点赞数和评论数写入redis
redisUtil.zAdd(VIEW_KEY, article.getId().toString(), article.getViewNum());
redisUtil.zAdd(COMMENT_KEY, article.getId().toString(), article.getCommentNum());
redisUtil.zAdd(LIKE_KEY, article.getId().toString(), article.getLikeNum());
});
log.info("数据已写入redis...");
}
/**
* 关闭时 *** 作
*/
@PreDestroy
public void afterDestroy() {
log.info("开始关闭...");
//将redis中的数据写入数据库
Set> viewNum = redisUtil.zReverseRangeWithScores("viewNum", 0, 10);
Set> commentNum = redisUtil.zReverseRangeWithScores("commentNum", 0, 10);
Set> likeNum = redisUtil.zReverseRangeWithScores("likeNum", 0, 10);
writeNum(viewNum, VIEW_KEY);
writeNum(commentNum, COMMENT_KEY);
writeNum(likeNum, LIKE_KEY);
log.info("redis写入数据库完毕");
}
@Scheduled(cron = "*/15 * * * * ?")
public void updateNum() {
log.info("周期任务开始执行...");
Set> viewNum = redisUtil.zReverseRangeWithScores("viewNum", 0, 10);
writeNum(viewNum, VIEW_KEY);
log.info("周期任务执行完毕,redis写入数据库完毕");
}
private void writeNum(Set> set, String fieldName) {
set.forEach(item -> {
Long id = Long.valueOf(item.getValue());
Integer num = item.getScore().intValue();
Article article = articleService.getById(id);
switch (fieldName) {
case VIEW_KEY:
article.setViewNum(num);
break;
case COMMENT_KEY:
article.setCommentNum(num);
break;
case LIKE_KEY:
article.setLikeNum(num);
break;
default:
return;
}
//更新数据库
articleService.updateNumById(article);
log.info("{} 更新完毕", fieldName);
});
}
}
监听类写好后,接下来最后一步就是实现每次有用户浏览文章,浏览量就加一的这个功能。首先放出controller层代码:
import com.wl.standard.common.result.HttpResult;
import com.wl.standard.entity.Article;
import com.wl.standard.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wl
* @date 2022/2/22
*/
@RestController
@RequestMapping("/article")
public class ArticleController {
private final ArticleService articleService;
@Autowired
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
@GetMapping("/getById/{id}")
public HttpResult getById(@PathVariable("id") Long id) throws Exception {
Article article = articleService.getById(id);
if (article == null) {
return HttpResult.fail("此ID不存在");
}
return HttpResult.success(article);
}
}
HttpResult类可查看之前的文章获取详情
最后利用AOP在用户请求上面这个接口后实现查看的文章浏览量加一:
import com.wl.standard.service.ArticleService;
import com.wl.standard.util.RedisUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author wl
* @date 2022/2/22
*/
//指定为切面类
@Aspect
@Component
public class MyAspect {
private final ArticleService articleService;
private final RedisUtil redisUtil;
public MyAspect(ArticleService articleService, RedisUtil redisUtil) {
this.articleService = articleService;
this.redisUtil = redisUtil;
}
//定义一个名为"myPointCut()"的切面,getById()这个方法中
@Pointcut("execution(public * com.wl.standard.controller.ArticleController.getById(..))")
public void myPointCut(){}
//在这个方法执行后
@After("myPointCut()")
public void doAfter(JoinPoint joinPoint) throws Throwable {
Object[] objs=joinPoint.getArgs();
Long id=(Long) objs[0];
//根据id更新浏览量
redisUtil.zIncrementScore("viewNum",id.toString(),1);
}
}
启动前查看数据库当前数据:
项目启动:
请求接口:
日志输出:
数据库当前数据:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)