You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Spring MVC的@GetMapping方法中执行更新操作是否合规?

关于Spring MVC中GET接口执行更新操作的合理性与最佳实践

结论先行:这种做法不推荐

你当前在@GetMapping方法内执行更新操作的方式,违反了HTTP方法的核心语义规范:

  • GET请求被定义为只读、幂等的操作,仅用于获取资源,不应该修改服务器端的状态。
  • 浏览器、代理服务器可能会缓存GET请求的结果,或者因用户刷新、网络重试等原因重复发送请求,这会导致更新操作被意外执行多次,引发数据不一致(虽当前场景影响不大,但养成习惯后在其他业务场景易出问题)。

最佳实践:拆分读写逻辑,遵循HTTP语义

正确的做法是把「标记消息为已读」这个状态变更操作,用POST/PUT方法处理,GET方法仅负责展示消息详情。具体实现步骤如下:

1. 新增POST接口处理状态更新

专门写一个接口来处理标记已读的逻辑,处理完成后重定向到详情页(避免表单重复提交):

@PostMapping(value = "msgMarkAsRead")
public String markMessageAsRead(@RequestParam("msg_no") Long msgNo) {
    Message receivedMessage = msgService.getReceivedMessageById(msgNo);
    if (receivedMessage.getMsg_readdate() == null) {
        msgService.updateReadDate(msgNo);
    }
    // 重定向到详情页,使用GET请求展示内容
    return "redirect:/msgReadReceived?msg_no=" + msgNo;
}

2. 修改原GET接口,仅负责查询展示

去掉原GET接口里的更新逻辑,让它只做资源查询和页面渲染:

@GetMapping(value = "msgReadReceived")
public String readReceivedMessage(@RequestParam("msg_no") Long msgNo, Model model) {
    Message receivedMessage = msgService.getReceivedMessageById(msgNo);
    model.addAttribute("receivedMessage", receivedMessage);
    return "kdw/msgReadReceived";
}

3. 前端调整触发方式

把原来点击查看消息的GET链接,改成POST请求的触发方式,比如用表单提交或AJAX:

<!-- 方案1:使用表单提交 -->
<form action="/msgMarkAsRead" method="post">
    <input type="hidden" name="msg_no" value="${msg.msg_no}">
    <button type="submit" style="border:none;background:none;color:blue;cursor:pointer;">查看消息</button>
</form>

<!-- 方案2:使用AJAX先标记已读,再跳转详情页 -->
<a href="javascript:void(0)" onclick="viewMessage(${msg.msg_no})">查看消息</a>
<script>
function viewMessage(msgNo) {
    fetch('/msgMarkAsRead', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `msg_no=${msgNo}`
    }).then(() => {
        window.location.href = `/msgReadReceived?msg_no=${msgNo}`;
    });
}
</script>

过渡妥协方案:保证更新操作的幂等性

如果暂时无法调整前端逻辑,至少要保证更新操作是幂等的,避免重复执行带来副作用。可以修改Service层的updateReadDate方法,在SQL层面加条件限制:

// Service层的updateReadDate方法对应的SQL逻辑
public void updateReadDate(Long msgNo) {
    String sql = "UPDATE message SET msg_readdate = NOW() WHERE msg_no = ? AND msg_readdate IS NULL";
    // 执行SQL的具体逻辑
}

这样即使GET请求被重复触发,SQL也只会在读取日期为null时执行更新,不会产生重复修改的问题。但这只是临时方案,依然建议尽早调整为符合HTTP语义的写法。


代码优化:封装Service层逻辑

不管采用哪种方式,都可以把「检查并标记已读」的逻辑封装到Service层,减少Controller的代码耦合:

// Service层新增方法
public Message getReceivedMessageAndMarkAsReadIfNeeded(Long msgNo) {
    Message message = getReceivedMessageById(msgNo);
    if (message.getMsg_readdate() == null) {
        updateReadDate(msgNo);
        // 直接更新对象属性,避免再次查询数据库
        message.setMsg_readdate(new Date());
    }
    return message;
}

之后Controller可以简化调用:

@GetMapping(value = "msgReadReceived")
public String readReceivedMessage(@RequestParam("msg_no") Long msgNo, Model model) {
    Message receivedMessage = msgService.getReceivedMessageAndMarkAsReadIfNeeded(msgNo);
    model.addAttribute("receivedMessage", receivedMessage);
    return "kdw/msgReadReceived";
}

内容的提问来源于stack exchange,提问作者eueuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 01:42:17