在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
相关产品推荐
相关产品推荐

