MySQL 从hashmap 批量update Redis缓存+定时任务实现

记录一次MySQL数据库批量更新的麻烦事

论坛发帖,需要记录浏览次数,为了减轻数据库压力,采用Redis+延时任务的模式,批量更新到数据库,下图中views字段为浏览次数

Pasted image 20240128215827.png

大致思路:

1. 访问帖子详情时 把访问次数存放到Redis

Pasted image 20240128220126.png

 private long getViewsCount(int pid) {  
        String key = Const.FORUM_POST_VIEW_COUNT;  
        if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {  
            long views = this.getById(pid).getViews();  
            redisTemplate.opsForHash().put(key,String.valueOf(pid),String.valueOf(views));  
//            redisTemplate.opsForValue().set(key, String.valueOf(views));  
        }  
        Long increment = redisTemplate.opsForHash().increment(key, String.valueOf(pid), 1);  
        this.saveViewsSchedule();  
        return increment;  
    }

2. 执行定时任务saveViewsSchedule

private final Map<String, Boolean> state = new HashMap<>();  
ScheduledExecutorService viewService = Executors.newScheduledThreadPool(1);
//为了测试 把延迟时间缩短为10s
private void saveViewsSchedule() {  
    if (!state.getOrDefault("views", false)) {  
        state.put("views", true);  
        log.info("保存Views定时任务执行!");  
        viewService.schedule(() -> {  
            try {  
                this.saveViews();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            state.put("views", false);  
        }, 10, TimeUnit.SECONDS);  
    }  
}

3. 一定时间后执行saveViews

private void saveViews() {  
    // map中 key:id , value 更新的值  
    Map<Integer,Long> map = new HashMap<>();  
    redisTemplate.opsForHash().entries(Const.FORUM_POST_VIEW_COUNT).forEach((k,v)->{  
        map.put(Integer.valueOf(k.toString()), Long.valueOf(v.toString()) );  
        redisTemplate.opsForHash().delete(Const.FORUM_POST_VIEW_COUNT,k);  
    });  
    Long updated = postMapper.updateViews(map);  
    log.info("saveViews 更新了 {} 条记录",updated);  
}

4. 执行postMapper.updateViews(map) 关键的来了!

经过搜索,一般批量更新有两种方式:

1. 一句一句执行 使用Mybatis foreach 循环执行 比如:
   UPDATE db_post_copy1 set views = 3 where id = 14
   UPDATE db_post_copy1 set views = 3 where id = 14
   ......
   
2. 使用下面这种批量更新语法
UPDATE db_post_copy1
SET views = CASE
  WHEN id = 14 THEN 3
  WHEN id = 15 THEN 5
  WHEN id = 16 THEN 6
  ELSE views
END
WHERE id IN (14,15,16)

理论上是第二种方式快的多
于是就开始研究 Mybatis 动态拼接 (最终失败了,总是报错map为空,错误代码就不贴了)
厉害的大佬可以去看 https://www.cnblogs.com/eternityz/p/12284760.html

换个思路:
Mybatis中拼接SQL很费力气,不如直接在Java中拼接字符串!
(代码AI生成 自己拼接得到什么时候)

package com.example.backend.utils;  
  
import java.util.Map;  
  
public class SqlProvider {  
    public String updateViews(Map<Integer, Long> map) {  
        StringBuilder sql = new StringBuilder("UPDATE db_post SET views = CASE id ");  
        for (Map.Entry<Integer, Long> entry : map.entrySet()) {  
            sql.append("WHEN ").append(entry.getKey()).append(" THEN ").append(entry.getValue()).append(" ");  
        }  
        sql.append("ELSE views END WHERE id IN (");  
        for (Integer id : map.keySet()) {  
            sql.append(id).append(",");  
        }  
        // 删除最后一个逗号  
        sql.deleteCharAt(sql.length() - 1);  
        sql.append(")");  
        return sql.toString();  
    }  
}

postmapper:

(第一次知道还能这么写。。。)
@UpdateProvider(type = SqlProvider.class, method = "updateViews")  
Long updateViews(Map<Integer, Long> map);

测试成功。
GIF.gif

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值