十、“仿小红书”单体全栈项目开发实战(四)

作者:互联网

2026-04-16

⼤语⾔模型脚本

1.1 笔记模块编辑、删除功能概述

  • 笔记模块编辑功能:实现一个仿小红书的笔记修改界面,包含标题编辑、内容编辑、话题标签管理、分类管理等核心功能。
  • 笔记模块删除功能:实现笔记的删除。

核心功能与设计特点

  1. 编辑界面布局

    • 顶部导航栏包含返回和保存按钮
    • 清晰的展示标题、图片、内容、话题标签和分类
  2. 图片展示功能

    • 图片网格布局展示已上传图片
  3. 内容编辑

    • 标题输入框支持修改
    • 内容编辑区域支持修改
    • 输入验证确保内容完整性
  4. 话题标签管理

    • 支持添加多个话题标签(用空格分隔)
    • 标签删除功能
  5. 交互体验

    • 操作反馈提示
    • 删除确认提示
    • 表单验证和错误提示

1.2 使用Bootstrap、Font Awesome以及Thymeleaf轻松实现笔记编辑界面

下面我将为你实现一个仿小红书的笔记修改界面,包含标题编辑、内容编辑、话题标签管理、分类管理等核心功能。

界面设计与实现

可以基于note-publish.html进行修改,只需要删除”图片上传区域“相关的样式、组件即可,

以下是笔记修改界面note-edit.html的完整实现代码:

https://images.jiaoben.nethtml>
https://images.jiaoben.nethtml https://images.jiaoben.netlang=https://images.jiaoben.net"en" https://images.jiaoben.netxmlns:th=https://images.jiaoben.net"http://www.thymeleaf.org">
https://images.jiaoben.nethead>
    https://images.jiaoben.netmeta https://images.jiaoben.netcharset=https://images.jiaoben.net"UTF-8">
    https://images.jiaoben.netmeta https://images.jiaoben.netname=https://images.jiaoben.net"viewport" https://images.jiaoben.netcontent=https://images.jiaoben.net"width=device-width, initial-scale=1.0">
    https://images.jiaoben.nettitle>RN - 笔记编辑https://images.jiaoben.nettitle>
    https://images.jiaoben.net
    https://images.jiaoben.netlink https://images.jiaoben.nethref=https://images.jiaoben.net"https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.6/css/bootstrap.min.css"
          https://images.jiaoben.netth:href=https://images.jiaoben.net"@{/css/bootstrap.min.css}" https://images.jiaoben.netrel=https://images.jiaoben.net"stylesheet">

    https://images.jiaoben.net
    https://images.jiaoben.netlink https://images.jiaoben.nethref=https://images.jiaoben.net"https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
          https://images.jiaoben.netth:href=https://images.jiaoben.net"@{/css/font-awesome.min.css}" https://images.jiaoben.netrel=https://images.jiaoben.net"stylesheet">

    https://images.jiaoben.net
    https://images.jiaoben.netstyle>https://images.jiaoben.net
        https://images.jiaoben.net/* 基础样式 */
        https://images.jiaoben.netbody {
            https://images.jiaoben.netbackground-color: https://images.jiaoben.net#fef6f6;
            https://images.jiaoben.netfont-family: -apple-system, BlinkMacSystemFont, https://images.jiaoben.net"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }

        https://images.jiaoben.net.container {
            https://images.jiaoben.netmax-width: https://images.jiaoben.net768px;
            https://images.jiaoben.netmargin: https://images.jiaoben.net0 auto;
            https://images.jiaoben.netpadding: https://images.jiaoben.net0 https://images.jiaoben.net16px;
        }

        https://images.jiaoben.net/* 顶部导航栏 */
        https://images.jiaoben.net.header {
            https://images.jiaoben.netbackground-color: white;
            https://images.jiaoben.netborder-bottom: https://images.jiaoben.net1px solid https://images.jiaoben.net#eee;
            https://images.jiaoben.netpadding: https://images.jiaoben.net12px https://images.jiaoben.net0;
            https://images.jiaoben.netposition: sticky;
            https://images.jiaoben.nettop: https://images.jiaoben.net0;
            https://images.jiaoben.netz-index: https://images.jiaoben.net100;
        }

        https://images.jiaoben.net.header https://images.jiaoben.net.btn {
            https://images.jiaoben.netpadding: https://images.jiaoben.net6px https://images.jiaoben.net16px;
            https://images.jiaoben.netborder-radius: https://images.jiaoben.net20px;
            https://images.jiaoben.netfont-weight: https://images.jiaoben.net600;
        }

        https://images.jiaoben.net.btn-cancel {
            https://images.jiaoben.netcolor: https://images.jiaoben.net#333;
            https://images.jiaoben.netborder: https://images.jiaoben.net1px solid https://images.jiaoben.net#ddd;
        }

        https://images.jiaoben.net.btn-publish {
            https://images.jiaoben.netbackground-color: https://images.jiaoben.net#ff2442;
            https://images.jiaoben.netcolor: white;
            https://images.jiaoben.netborder: none;
        }

        https://images.jiaoben.net.btn-publishhttps://images.jiaoben.net:hover {
            https://images.jiaoben.netbackground-color: https://images.jiaoben.net#e61e3a;
        }

        https://images.jiaoben.net/* 内容区域 */
        https://images.jiaoben.net.content {
            https://images.jiaoben.netpadding: https://images.jiaoben.net16px https://images.jiaoben.net0;
        }

        https://images.jiaoben.net/* 标题输入框 */
        https://images.jiaoben.net.note-title {
            https://images.jiaoben.netborder: none;
            https://images.jiaoben.netwidth: https://images.jiaoben.net100%;
            https://images.jiaoben.netfont-size: https://images.jiaoben.net20px;
            https://images.jiaoben.netfont-weight: https://images.jiaoben.net600;
            https://images.jiaoben.netpadding: https://images.jiaoben.net12px https://images.jiaoben.net0;
            https://images.jiaoben.netoutline: none;
        }

        https://images.jiaoben.net.note-titlehttps://images.jiaoben.net::placeholder {
            https://images.jiaoben.netcolor: https://images.jiaoben.net#999;
        }

        https://images.jiaoben.net/* 已上传图片展示 */
        https://images.jiaoben.net.uploaded-images {
            https://images.jiaoben.netdisplay: flex;
            https://images.jiaoben.netflex-wrap: wrap;
            https://images.jiaoben.netgap: https://images.jiaoben.net8px;
            https://images.jiaoben.netmargin-top: https://images.jiaoben.net16px;
        }

        https://images.jiaoben.net.uploaded-image {
            https://images.jiaoben.netwidth: https://images.jiaoben.net80px;
            https://images.jiaoben.netheight: https://images.jiaoben.net80px;
            https://images.jiaoben.netborder-radius: https://images.jiaoben.net8px;
            https://images.jiaoben.netoverflow: hidden;
            https://images.jiaoben.netposition: relative;
        }

        https://images.jiaoben.net.uploaded-image https://images.jiaoben.netimg {
            https://images.jiaoben.netwidth: https://images.jiaoben.net100%;
            https://images.jiaoben.netheight: https://images.jiaoben.net100%;
            https://images.jiaoben.netobject-fit: cover;
        }

        https://images.jiaoben.net.uploaded-image https://images.jiaoben.net.delete-btn {
            https://images.jiaoben.netposition: absolute;
            https://images.jiaoben.nettop: https://images.jiaoben.net4px;
            https://images.jiaoben.netright: https://images.jiaoben.net4px;
            https://images.jiaoben.netwidth: https://images.jiaoben.net20px;
            https://images.jiaoben.netheight: https://images.jiaoben.net20px;
            https://images.jiaoben.netbackground-color: https://images.jiaoben.netrgba(https://images.jiaoben.net0, https://images.jiaoben.net0, https://images.jiaoben.net0, https://images.jiaoben.net0.6);
            https://images.jiaoben.netcolor: white;
            https://images.jiaoben.netborder-radius: https://images.jiaoben.net50%;
            https://images.jiaoben.netdisplay: flex;
            https://images.jiaoben.netalign-items: center;
            https://images.jiaoben.netjustify-content: center;
            https://images.jiaoben.netcursor: pointer;
            https://images.jiaoben.netfont-size: https://images.jiaoben.net12px;
        }

        https://images.jiaoben.net/* 笔记内容编辑器 */
        https://images.jiaoben.net.note-content {
            https://images.jiaoben.netwidth: https://images.jiaoben.net100%;
            https://images.jiaoben.netmin-height: https://images.jiaoben.net200px;
            https://images.jiaoben.netborder: none;
            https://images.jiaoben.netoutline: none;
            https://images.jiaoben.netfont-size: https://images.jiaoben.net16px;
            https://images.jiaoben.netline-height: https://images.jiaoben.net1.6;
            https://images.jiaoben.netpadding: https://images.jiaoben.net12px https://images.jiaoben.net0;
        }

        https://images.jiaoben.net.note-contenthttps://images.jiaoben.net::placeholder {
            https://images.jiaoben.netcolor: https://images.jiaoben.net#999;
        }

        https://images.jiaoben.net/* 话题选择 */
        https://images.jiaoben.net.topic-input {
            https://images.jiaoben.netposition: relative;
            https://images.jiaoben.netmargin-bottom: https://images.jiaoben.net20px;
        }

        https://images.jiaoben.net.topic-input https://images.jiaoben.netinput {
            https://images.jiaoben.netwidth: https://images.jiaoben.net100%;
            https://images.jiaoben.netpadding: https://images.jiaoben.net12px;
            https://images.jiaoben.netborder: https://images.jiaoben.net1px solid https://images.jiaoben.net#eee;
            https://images.jiaoben.netborder-radius: https://images.jiaoben.net8px;
            https://images.jiaoben.netoutline: none;
        }

        https://images.jiaoben.net/* 分类选择 */
        https://images.jiaoben.net.category-selector {
            https://images.jiaoben.netmargin-bottom: https://images.jiaoben.net20px;
        }

        https://images.jiaoben.net.category-input https://images.jiaoben.neti {
            https://images.jiaoben.netcolor: https://images.jiaoben.net#ff2442;
        }

        https://images.jiaoben.net/* 添加到 style 标签中 */
        https://images.jiaoben.net.category-selector select {
            https://images.jiaoben.netwidth: https://images.jiaoben.net100%;
            https://images.jiaoben.netpadding: https://images.jiaoben.net12px;
            https://images.jiaoben.netborder: https://images.jiaoben.net1px solid https://images.jiaoben.net#eee;
            https://images.jiaoben.netborder-radius: https://images.jiaoben.net8px;
            https://images.jiaoben.netbackground-color: white;
            appearance: none;
            -webkit-appearance: none;
            https://images.jiaoben.netbackground-image: https://images.jiaoben.neturl(https://images.jiaoben.net"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23666'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
            https://images.jiaoben.netbackground-repeat: no-repeat;
            https://images.jiaoben.netbackground-position: right https://images.jiaoben.net12px center;
            https://images.jiaoben.netbackground-size: https://images.jiaoben.net16px;
            https://images.jiaoben.netcursor: pointer;
        }

        https://images.jiaoben.net.category-selector selecthttps://images.jiaoben.net:focus {
            https://images.jiaoben.netoutline: none;
            https://images.jiaoben.netborder-color: https://images.jiaoben.net#ff2442;
            https://images.jiaoben.netbox-shadow: https://images.jiaoben.net0 https://images.jiaoben.net0 https://images.jiaoben.net0 https://images.jiaoben.net2px https://images.jiaoben.netrgba(https://images.jiaoben.net255, https://images.jiaoben.net36, https://images.jiaoben.net66, https://images.jiaoben.net0.1);
        }

        https://images.jiaoben.net.btn-view-note {
            https://images.jiaoben.netbackground-color: https://images.jiaoben.net#ff2442;
            https://images.jiaoben.netcolor: white;
        }

        https://images.jiaoben.net.error-message {
            https://images.jiaoben.netcolor: https://images.jiaoben.net#ff2442;
            https://images.jiaoben.netfont-size: https://images.jiaoben.net12px;
            https://images.jiaoben.netmargin-top: https://images.jiaoben.net4px;
        }
    https://images.jiaoben.netstyle>
https://images.jiaoben.nethead>
https://images.jiaoben.netbody>
https://images.jiaoben.net
https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"header">
    https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"container">
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"d-flex justify-content-between align-items-center">
            https://images.jiaoben.netbutton https://images.jiaoben.netclass=https://images.jiaoben.net"btn btn-cancel" https://images.jiaoben.netid=https://images.jiaoben.net"cancelPublishBtn">
                取消
            https://images.jiaoben.netbutton>
            https://images.jiaoben.netbutton https://images.jiaoben.netclass=https://images.jiaoben.net"btn btn-publish" https://images.jiaoben.netid=https://images.jiaoben.net"publishNoteBtn">
                保存
            https://images.jiaoben.netbutton>
        https://images.jiaoben.netdiv>
    https://images.jiaoben.netdiv>
https://images.jiaoben.netdiv>

https://images.jiaoben.net
https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"container content">
    https://images.jiaoben.netform https://images.jiaoben.netid=https://images.jiaoben.net"noteForm" https://images.jiaoben.netmethod=https://images.jiaoben.net"post" https://images.jiaoben.netth:object=https://images.jiaoben.net"${note}"
          https://images.jiaoben.netth:action=https://images.jiaoben.net"@{/note/{noteId}(noteId=${note.noteId})}">
        https://images.jiaoben.net
        https://images.jiaoben.netinput https://images.jiaoben.nettype=https://images.jiaoben.net"text" https://images.jiaoben.netclass=https://images.jiaoben.net"note-title" https://images.jiaoben.netid=https://images.jiaoben.net"title" https://images.jiaoben.netname=https://images.jiaoben.net"title"
               https://images.jiaoben.netth:field=https://images.jiaoben.net"*{title}" https://images.jiaoben.netplaceholder=https://images.jiaoben.net"分享你的生活点滴...">
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"error-message" https://images.jiaoben.netth:if=https://images.jiaoben.net"${#fields.hasErrors('title')}" https://images.jiaoben.netth:errors=https://images.jiaoben.net"*{title}">
        https://images.jiaoben.netdiv>

        https://images.jiaoben.net
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"uploaded-images" https://images.jiaoben.netid=https://images.jiaoben.net"uploadedImages">
            https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"uploaded-image" https://images.jiaoben.netth:each=https://images.jiaoben.net"image : ${note.images}">
                https://images.jiaoben.netimg https://images.jiaoben.netth:src=https://images.jiaoben.net"${image}" https://images.jiaoben.netclass=https://images.jiaoben.net"preview-img">
            https://images.jiaoben.netdiv>
        https://images.jiaoben.netdiv>
        https://images.jiaoben.net
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"error-message" https://images.jiaoben.netth:if=https://images.jiaoben.net"${#fields.hasErrors('images')}" https://images.jiaoben.netth:errors=https://images.jiaoben.net"*{images}">
        https://images.jiaoben.netdiv>

        https://images.jiaoben.net
        https://images.jiaoben.nettextarea https://images.jiaoben.netclass=https://images.jiaoben.net"note-content" https://images.jiaoben.netid=https://images.jiaoben.net"content" https://images.jiaoben.netname=https://images.jiaoben.net"content"
                  https://images.jiaoben.netth:field=https://images.jiaoben.net"*{content}" https://images.jiaoben.netplaceholder=https://images.jiaoben.net"详细描述你的分享内容...">https://images.jiaoben.nettextarea>
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"error-message" https://images.jiaoben.netth:if=https://images.jiaoben.net"${#fields.hasErrors('content')}" https://images.jiaoben.netth:errors=https://images.jiaoben.net"*{content}">
        https://images.jiaoben.netdiv>

        https://images.jiaoben.net
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"topic-input">
            https://images.jiaoben.netinput https://images.jiaoben.nettype=https://images.jiaoben.net"text" https://images.jiaoben.netclass=https://images.jiaoben.net"form-control" https://images.jiaoben.netid=https://images.jiaoben.net"topicInput" https://images.jiaoben.netname=https://images.jiaoben.net"topics"
                   https://images.jiaoben.netth:field=https://images.jiaoben.net"*{topics}" https://images.jiaoben.netplaceholder=https://images.jiaoben.net"添加话题,多个话题用空格隔开">
        https://images.jiaoben.netdiv>

        https://images.jiaoben.net
        https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"category-selector">
            https://images.jiaoben.netlabel https://images.jiaoben.netfor=https://images.jiaoben.net"categorySelect" https://images.jiaoben.netclass=https://images.jiaoben.net"form-label">请选择一个分类:https://images.jiaoben.netlabel>
            https://images.jiaoben.netselect https://images.jiaoben.netclass=https://images.jiaoben.net"form-control" https://images.jiaoben.netid=https://images.jiaoben.net"categorySelect" https://images.jiaoben.netname=https://images.jiaoben.net"category"
                    https://images.jiaoben.netth:field=https://images.jiaoben.net"*{category}">
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"穿搭">穿搭https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"美食">美食https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"彩妆">彩妆https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"影视">影视https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"职场">职场https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"情感">情感https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"家居">家居https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"游戏">游戏https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"旅行">旅行https://images.jiaoben.netoption>
                https://images.jiaoben.netoption https://images.jiaoben.netvalue=https://images.jiaoben.net"健身">健身https://images.jiaoben.netoption>
            https://images.jiaoben.netselect>
            https://images.jiaoben.netdiv https://images.jiaoben.netclass=https://images.jiaoben.net"error-message" https://images.jiaoben.netth:if=https://images.jiaoben.net"${#fields.hasErrors('category')}" https://images.jiaoben.netth:errors=https://images.jiaoben.net"*{category}">
            https://images.jiaoben.netdiv>
        https://images.jiaoben.netdiv>
    https://images.jiaoben.netform>

    https://images.jiaoben.net
    https://images.jiaoben.netdiv https://images.jiaoben.netth:if=https://images.jiaoben.net"${success}" https://images.jiaoben.netclass=https://images.jiaoben.net"alert alert-success mt-4">
        https://images.jiaoben.neti https://images.jiaoben.netclass=https://images.jiaoben.net"fa fa-check-circle">https://images.jiaoben.neti>
        [[${success}]]
    https://images.jiaoben.netdiv>
    https://images.jiaoben.netdiv https://images.jiaoben.netth:if=https://images.jiaoben.net"${error}" https://images.jiaoben.netclass=https://images.jiaoben.net"alert alert-danger mt-4">
        https://images.jiaoben.neti https://images.jiaoben.netclass=https://images.jiaoben.net"fa fa-exclamation-circle">https://images.jiaoben.neti>
        [[${error}]]
    https://images.jiaoben.netdiv>
https://images.jiaoben.netdiv>

https://images.jiaoben.net
https://images.jiaoben.netscript https://images.jiaoben.netsrc=https://images.jiaoben.net"https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.6/js/bootstrap.bundle.min.js"
        https://images.jiaoben.netth:src=https://images.jiaoben.net"@{/js/bootstrap.bundle.min.js}">https://images.jiaoben.netscript>

https://images.jiaoben.netscript>https://images.jiaoben.net
    https://images.jiaoben.net// 笔记发布表单的校验
    https://images.jiaoben.net// 在发布按钮上设置点击事件
    https://images.jiaoben.netdocument.https://images.jiaoben.netgetElementById(https://images.jiaoben.net"publishNoteBtn").https://images.jiaoben.netaddEventListener(https://images.jiaoben.net"click", https://images.jiaoben.netfunction (https://images.jiaoben.netevent) {
        https://images.jiaoben.net// 获取笔记标题
        https://images.jiaoben.netconst title = https://images.jiaoben.netdocument.https://images.jiaoben.netgetElementById(https://images.jiaoben.net"title").https://images.jiaoben.netvalue;
        https://images.jiaoben.netif (title.https://images.jiaoben.nettrim() === https://images.jiaoben.net"") {
            https://images.jiaoben.netalert(https://images.jiaoben.net"请输入笔记标题");
            https://images.jiaoben.netreturn;
        }

        https://images.jiaoben.net// 获取笔记内容
        https://images.jiaoben.netconst content = https://images.jiaoben.netdocument.https://images.jiaoben.netgetElementById(https://images.jiaoben.net"content").https://images.jiaoben.netvalue;
        https://images.jiaoben.netif (content.https://images.jiaoben.nettrim() === https://images.jiaoben.net"") {
            https://images.jiaoben.netalert(https://images.jiaoben.net"请输入笔记内容");
            https://images.jiaoben.netreturn;
        }

        https://images.jiaoben.net// 提交表单
        https://images.jiaoben.netdocument.https://images.jiaoben.netgetElementById(https://images.jiaoben.net"noteForm").https://images.jiaoben.netsubmit();
    })

    https://images.jiaoben.net// 取消发布的事件处理
    https://images.jiaoben.netdocument.https://images.jiaoben.netgetElementById(https://images.jiaoben.net"cancelPublishBtn").https://images.jiaoben.netaddEventListener(https://images.jiaoben.net"click", https://images.jiaoben.netfunction (https://images.jiaoben.netevent) {
        https://images.jiaoben.net// 用户确认是否取消发布
        https://images.jiaoben.netif (https://images.jiaoben.netconfirm(https://images.jiaoben.net"确定要取消发布吗?所有内容将不会被保存")) {
            https://images.jiaoben.netwindow.https://images.jiaoben.nethistory.https://images.jiaoben.netback();
        }
    })
https://images.jiaoben.netscript>
https://images.jiaoben.netbody>
https://images.jiaoben.nethtml>

1.3 NoteController控制器来处理笔记编辑请求

在原有的NoteController基础上,增加方法以实现相关功能。

创建笔记编辑DTO

https://images.jiaoben.netpackage com.waylau.rednote.dto;

https://images.jiaoben.netimport jakarta.validation.constraints.NotEmpty;
https://images.jiaoben.netimport jakarta.validation.constraints.NotNull;
https://images.jiaoben.netimport jakarta.validation.constraints.Size;
https://images.jiaoben.netimport lombok.Getter;
https://images.jiaoben.netimport lombok.Setter;

https://images.jiaoben.netimport java.util.ArrayList;
https://images.jiaoben.netimport java.util.List;

https://images.jiaoben.net/**
 * NoteEditDto 笔记编辑DTO
 *
 * https://images.jiaoben.net@author Way Lau
 * https://images.jiaoben.net@version 2025/08/19
 **/
https://images.jiaoben.net@Getter
https://images.jiaoben.net@Setter
https://images.jiaoben.netpublic https://images.jiaoben.netclass https://images.jiaoben.netNoteEditDto {
    https://images.jiaoben.net@NotNull
    https://images.jiaoben.netprivate Long noteId;

    https://images.jiaoben.net@NotEmpty(message = "标题不能为空")
    https://images.jiaoben.net@Size(max = 60, message = "标题长度不能超过60个字符")
    https://images.jiaoben.netprivate String title;

    https://images.jiaoben.net@NotEmpty(message = "内容不能为空")
    https://images.jiaoben.net@Size(max = 900, message = "内容长度不能超过900个字符")
    https://images.jiaoben.netprivate String content;

    https://images.jiaoben.netprivate String topics;

    https://images.jiaoben.net@NotEmpty(message = "分类不能为空")
    https://images.jiaoben.netprivate String category;

    https://images.jiaoben.netprivate List images = https://images.jiaoben.netnew https://images.jiaoben.netArrayList<>();
}

处理用户访问笔记编辑界面展示

新增方法如下。

https://images.jiaoben.net/**
 * 显示笔记编辑页面
 */
https://images.jiaoben.net@GetMapping("/{noteId}/edit")
https://images.jiaoben.netpublic String https://images.jiaoben.netshowEditFormhttps://images.jiaoben.net(https://images.jiaoben.net@PathVariable Long noteId, Model model) {
    https://images.jiaoben.net// 查询指定noteId的笔记
    Optional optionalNote = noteService.findNoteById(noteId);

    https://images.jiaoben.net// 判定笔记是否存在,不存在则抛出异常
    https://images.jiaoben.netif (!optionalNote.isPresent()) {
        https://images.jiaoben.netthrow https://images.jiaoben.netnew https://images.jiaoben.netNoteNotFoundException(https://images.jiaoben.net"");
    }

    https://images.jiaoben.net// 获取当前用户信息
    https://images.jiaoben.netUser https://images.jiaoben.netuser https://images.jiaoben.net= userService.getCurrentUser();

    https://images.jiaoben.netNote https://images.jiaoben.netnote https://images.jiaoben.net= optionalNote.get();

    https://images.jiaoben.net// 判定笔记是否属于当前用户,不属于则抛出异常
    https://images.jiaoben.netif (!note.getAuthor().getUserId().equals(user.getUserId())) {
        https://images.jiaoben.netthrow https://images.jiaoben.netnew https://images.jiaoben.netNoteNotFoundException(https://images.jiaoben.net"");
    }

    https://images.jiaoben.net// 将Note对象转为NoteEditDto对象
    https://images.jiaoben.netNoteEditDto https://images.jiaoben.netnoteEditDto https://images.jiaoben.net= https://images.jiaoben.netnew https://images.jiaoben.netNoteEditDto();
    noteEditDto.setNoteId(note.getNoteId());
    noteEditDto.setTitle(note.getTitle());
    noteEditDto.setContent(note.getContent());
    noteEditDto.setCategory(note.getCategory());
    noteEditDto.setImages(note.getImages());

    https://images.jiaoben.net// 话题的List要转为String
    noteEditDto.setTopics(StringUtil.joinToString(note.getTopics(), https://images.jiaoben.net" "));

    model.addAttribute(https://images.jiaoben.net"note", noteEditDto);

    https://images.jiaoben.netreturn https://images.jiaoben.net"note-edit";
}

当用户使用GET请求访问/note/{noteId}/edit时,则会返回note-edit.html模板页面。

需要注意是的,返回前端的NoteEditDto的topics是字符串类型,因此从Note获取到值之后,需要通过StringUtil.joinToString()工具做转换。

https://images.jiaoben.netpublic https://images.jiaoben.netclass https://images.jiaoben.netStringUtil {
    https://images.jiaoben.net// ...为节约篇幅,此处省略非核心内容

    https://images.jiaoben.net// List转字符串
    https://images.jiaoben.netpublic https://images.jiaoben.netstatic String https://images.jiaoben.netjoinToStringhttps://images.jiaoben.net(List source, String regex) {
        https://images.jiaoben.netreturn String.join(regex, source);
    }
}

控制器处理用户笔记编辑请求

新增方法如下。

https://images.jiaoben.net/**
 * 处理笔记编辑请求
 */
https://images.jiaoben.net@PostMapping("/{noteId}")
https://images.jiaoben.netpublic String https://images.jiaoben.netupdateNotehttps://images.jiaoben.net(https://images.jiaoben.net@PathVariable Long noteId,
                            https://images.jiaoben.net@Valid https://images.jiaoben.net@ModelAttribute("note") NoteEditDto noteEditDto,
                            BindingResult result,
                            Model model,
                            RedirectAttributes redirectAttributes) {
    https://images.jiaoben.net// 验证表单
    https://images.jiaoben.netif (result.hasErrors()) {
        model.addAttribute(https://images.jiaoben.net"note", noteEditDto);
        https://images.jiaoben.netreturn https://images.jiaoben.net"note-edit";
    }

    https://images.jiaoben.net// 检查笔记是否存在
    Optional optionalNote = noteService.findNoteById(noteId);
    https://images.jiaoben.netif (!optionalNote.isPresent()) {
        https://images.jiaoben.netthrow https://images.jiaoben.netnew https://images.jiaoben.netNoteNotFoundException(https://images.jiaoben.net"");
    }

    https://images.jiaoben.netNote https://images.jiaoben.netnote https://images.jiaoben.net= optionalNote.get();

    https://images.jiaoben.nettry {
        noteService.updateNote(note, noteEditDto);
        redirectAttributes.addFlashAttribute(https://images.jiaoben.net"success", https://images.jiaoben.net"笔记更新成功");
        https://images.jiaoben.netreturn https://images.jiaoben.net"redirect:/note/" + noteId;
    } https://images.jiaoben.netcatch (Exception e) {
        log.error(https://images.jiaoben.net"笔记更新失败:{}", e.getMessage(), e);

        model.addAttribute(https://images.jiaoben.net"error", https://images.jiaoben.net"笔记更新失败:" + e.getMessage());
        model.addAttribute(https://images.jiaoben.net"note", noteEditDto);
        https://images.jiaoben.netreturn https://images.jiaoben.net"note-edit";
    }
} 

当用户使用POST请求访问/note/{noteId}时,将修改后的笔记数据保存入库。

1.4 实现笔记编辑数据的保存方法

修改NoteService,增加如下接口:

https://images.jiaoben.netpublic https://images.jiaoben.netinterface https://images.jiaoben.netNoteService {
 

    https://images.jiaoben.net/**
     * 更新笔记
     *
     * https://images.jiaoben.net@param note
     * https://images.jiaoben.net@param noteEditDto
     */
    https://images.jiaoben.netvoid https://images.jiaoben.netupdateNotehttps://images.jiaoben.net(Note note, NoteEditDto noteEditDto);
}
``



修改 NoteServiceImpl,实现笔记编辑数据的保存方法:


```java
https://images.jiaoben.netimport com.waylau.rednote.dto.NoteEditDto;

https://images.jiaoben.net// ...为节约篇幅,此处省略非核心内容

https://images.jiaoben.net@Service
https://images.jiaoben.netpublic https://images.jiaoben.netclass https://images.jiaoben.netNoteServiceImpl https://images.jiaoben.netimplements https://images.jiaoben.netNoteService {

    https://images.jiaoben.net// ...为节约篇幅,此处省略非核心内容

    https://images.jiaoben.net@Override
    https://images.jiaoben.netpublic https://images.jiaoben.netvoid https://images.jiaoben.netupdateNotehttps://images.jiaoben.net(Note note, NoteEditDto noteEditDto) {
        https://images.jiaoben.net// 更新基本信息
        note.setTitle(noteEditDto.getTitle());
        note.setContent(noteEditDto.getContent());
        note.setCategory(noteEditDto.getCategory());

        https://images.jiaoben.net// 字符串转为List
        note.setTopics(StringUtil.splitToList(noteEditDto.getTopics(),https://images.jiaoben.net" "));

        https://images.jiaoben.net// 保存更新
        noteRepository.save(note);
    }
}  

需要注意是的,前端传入的NoteEditDto的topics是字符串类型,在赋值到Note时,需要通过StringUtil.splitToList()工具做转换。

1.5 修改不可变集合导致UnsupportedOperationException错误分析

运行应用,试图保存笔记修改后的数据时,报错如下图10-1所示。

问题背景

执行 noteRepository.save(note) 时候报 java.lang.UnsupportedOperationException:

https://images.jiaoben.netjavahttps://images.jiaoben.net.langhttps://images.jiaoben.net.UnsupportedOperationException: https://images.jiaoben.netnull
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.utilhttps://images.jiaoben.net.AbstractListhttps://images.jiaoben.net.remove(AbstractList.https://images.jiaoben.netjava:https://images.jiaoben.net169) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.utilhttps://images.jiaoben.net.AbstractList$https://images.jiaoben.netItrhttps://images.jiaoben.net.remove(AbstractList.https://images.jiaoben.netjava:https://images.jiaoben.net389) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.utilhttps://images.jiaoben.net.AbstractListhttps://images.jiaoben.net.removeRange(AbstractList.https://images.jiaoben.netjava:https://images.jiaoben.net600) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.utilhttps://images.jiaoben.net.AbstractListhttps://images.jiaoben.net.clear(AbstractList.https://images.jiaoben.netjava:https://images.jiaoben.net245) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.typehttps://images.jiaoben.net.CollectionTypehttps://images.jiaoben.net.replaceElements(CollectionType.https://images.jiaoben.netjava:https://images.jiaoben.net506) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.typehttps://images.jiaoben.net.CollectionTypehttps://images.jiaoben.net.replace(CollectionType.https://images.jiaoben.netjava:https://images.jiaoben.net719) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.typehttps://images.jiaoben.net.TypeHelperhttps://images.jiaoben.net.replace(TypeHelper.https://images.jiaoben.netjava:https://images.jiaoben.net117) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.copyValues(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net596) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.entityIsPersistent(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net286) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.merge(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net220) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.doMerge(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net152) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.onMerge(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net136) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.DefaultMergeEventListenerhttps://images.jiaoben.net.onMerge(DefaultMergeEventListener.https://images.jiaoben.netjava:https://images.jiaoben.net89) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.eventhttps://images.jiaoben.net.servicehttps://images.jiaoben.net.internalhttps://images.jiaoben.net.EventListenerGroupImplhttps://images.jiaoben.net.fireEventOnEachListener(EventListenerGroupImpl.https://images.jiaoben.netjava:https://images.jiaoben.net127) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.internalhttps://images.jiaoben.net.SessionImplhttps://images.jiaoben.net.fireMerge(SessionImpl.https://images.jiaoben.netjava:https://images.jiaoben.net854) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.hibernatehttps://images.jiaoben.net.internalhttps://images.jiaoben.net.SessionImplhttps://images.jiaoben.net.merge(SessionImpl.https://images.jiaoben.netjava:https://images.jiaoben.net840) ~https://images.jiaoben.net[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjdkhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.DirectMethodHandleAccessorhttps://images.jiaoben.net.invoke(DirectMethodHandleAccessor.https://images.jiaoben.netjava:https://images.jiaoben.net104) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.langhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.Methodhttps://images.jiaoben.net.invoke(Method.https://images.jiaoben.netjava:https://images.jiaoben.net565) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.ormhttps://images.jiaoben.net.jpahttps://images.jiaoben.net.ExtendedEntityManagerCreator$https://images.jiaoben.netExtendedEntityManagerInvocationHandlerhttps://images.jiaoben.net.invoke(ExtendedEntityManagerCreator.https://images.jiaoben.netjava:https://images.jiaoben.net364) ~https://images.jiaoben.net[spring-orm-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2/https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2.$https://images.jiaoben.netProxy120https://images.jiaoben.net.merge(Unknown Source) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjdkhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.DirectMethodHandleAccessorhttps://images.jiaoben.net.invoke(DirectMethodHandleAccessor.https://images.jiaoben.netjava:https://images.jiaoben.net104) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.langhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.Methodhttps://images.jiaoben.net.invoke(Method.https://images.jiaoben.netjava:https://images.jiaoben.net565) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.ormhttps://images.jiaoben.net.jpahttps://images.jiaoben.net.SharedEntityManagerCreator$https://images.jiaoben.netSharedEntityManagerInvocationHandlerhttps://images.jiaoben.net.invoke(SharedEntityManagerCreator.https://images.jiaoben.netjava:https://images.jiaoben.net320) ~https://images.jiaoben.net[spring-orm-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2/https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2.$https://images.jiaoben.netProxy120https://images.jiaoben.net.merge(Unknown Source) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.jpahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.supporthttps://images.jiaoben.net.SimpleJpaRepositoryhttps://images.jiaoben.net.save(SimpleJpaRepository.https://images.jiaoben.netjava:https://images.jiaoben.net654) ~https://images.jiaoben.net[spring-data-jpa-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjdkhttps://images.jiaoben.net.internalhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.DirectMethodHandleAccessorhttps://images.jiaoben.net.invoke(DirectMethodHandleAccessor.https://images.jiaoben.netjava:https://images.jiaoben.net104) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netjavahttps://images.jiaoben.net.base/https://images.jiaoben.netjavahttps://images.jiaoben.net.langhttps://images.jiaoben.net.reflecthttps://images.jiaoben.net.Methodhttps://images.jiaoben.net.invoke(Method.https://images.jiaoben.netjava:https://images.jiaoben.net565) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.supporthttps://images.jiaoben.net.AopUtilshttps://images.jiaoben.net.invokeJoinpointUsingReflection(AopUtils.https://images.jiaoben.netjava:https://images.jiaoben.net359) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryMethodInvoker$https://images.jiaoben.netRepositoryFragmentMethodInvokerhttps://images.jiaoben.net.lambda$https://images.jiaoben.netnew$https://images.jiaoben.net0(RepositoryMethodInvoker.https://images.jiaoben.netjava:https://images.jiaoben.net277) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryMethodInvokerhttps://images.jiaoben.net.doInvoke(RepositoryMethodInvoker.https://images.jiaoben.netjava:https://images.jiaoben.net170) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryMethodInvokerhttps://images.jiaoben.net.invoke(RepositoryMethodInvoker.https://images.jiaoben.netjava:https://images.jiaoben.net158) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryComposition$https://images.jiaoben.netRepositoryFragmentshttps://images.jiaoben.net.invoke(RepositoryComposition.https://images.jiaoben.netjava:https://images.jiaoben.net515) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryCompositionhttps://images.jiaoben.net.invoke(RepositoryComposition.https://images.jiaoben.netjava:https://images.jiaoben.net284) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.RepositoryFactorySupport$https://images.jiaoben.netImplementationMethodExecutionInterceptorhttps://images.jiaoben.net.invoke(RepositoryFactorySupport.https://images.jiaoben.netjava:https://images.jiaoben.net734) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.ReflectiveMethodInvocationhttps://images.jiaoben.net.proceed(ReflectiveMethodInvocation.https://images.jiaoben.netjava:https://images.jiaoben.net184) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.QueryExecutorMethodInterceptorhttps://images.jiaoben.net.doInvoke(QueryExecutorMethodInterceptor.https://images.jiaoben.netjava:https://images.jiaoben.net174) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.corehttps://images.jiaoben.net.supporthttps://images.jiaoben.net.QueryExecutorMethodInterceptorhttps://images.jiaoben.net.invoke(QueryExecutorMethodInterceptor.https://images.jiaoben.netjava:https://images.jiaoben.net149) ~https://images.jiaoben.net[spring-data-commons-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.ReflectiveMethodInvocationhttps://images.jiaoben.net.proceed(ReflectiveMethodInvocation.https://images.jiaoben.netjava:https://images.jiaoben.net184) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.transactionhttps://images.jiaoben.net.interceptorhttps://images.jiaoben.net.TransactionAspectSupporthttps://images.jiaoben.net.invokeWithinTransaction(TransactionAspectSupport.https://images.jiaoben.netjava:https://images.jiaoben.net380) ~https://images.jiaoben.net[spring-tx-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.transactionhttps://images.jiaoben.net.interceptorhttps://images.jiaoben.net.TransactionInterceptorhttps://images.jiaoben.net.invoke(TransactionInterceptor.https://images.jiaoben.netjava:https://images.jiaoben.net119) ~https://images.jiaoben.net[spring-tx-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.ReflectiveMethodInvocationhttps://images.jiaoben.net.proceed(ReflectiveMethodInvocation.https://images.jiaoben.netjava:https://images.jiaoben.net184) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.daohttps://images.jiaoben.net.supporthttps://images.jiaoben.net.PersistenceExceptionTranslationInterceptorhttps://images.jiaoben.net.invoke(PersistenceExceptionTranslationInterceptor.https://images.jiaoben.netjava:https://images.jiaoben.net138) ~https://images.jiaoben.net[spring-tx-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.ReflectiveMethodInvocationhttps://images.jiaoben.net.proceed(ReflectiveMethodInvocation.https://images.jiaoben.netjava:https://images.jiaoben.net184) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.datahttps://images.jiaoben.net.jpahttps://images.jiaoben.net.repositoryhttps://images.jiaoben.net.supporthttps://images.jiaoben.net.CrudMethodMetadataPostProcessor$https://images.jiaoben.netCrudMethodMetadataPopulatingMethodInterceptorhttps://images.jiaoben.net.invoke(CrudMethodMetadataPostProcessor.https://images.jiaoben.netjava:https://images.jiaoben.net165) ~https://images.jiaoben.net[spring-data-jpa-3.5.0.jar:3.5.0]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.ReflectiveMethodInvocationhttps://images.jiaoben.net.proceed(ReflectiveMethodInvocation.https://images.jiaoben.netjava:https://images.jiaoben.net184) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netorghttps://images.jiaoben.net.springframeworkhttps://images.jiaoben.net.aophttps://images.jiaoben.net.frameworkhttps://images.jiaoben.net.JdkDynamicAopProxyhttps://images.jiaoben.net.invoke(JdkDynamicAopProxy.https://images.jiaoben.netjava:https://images.jiaoben.net223) ~https://images.jiaoben.net[spring-aop-6.2.7.jar:6.2.7]
	https://images.jiaoben.netat https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2/https://images.jiaoben.netjdkhttps://images.jiaoben.net.proxy2.$https://images.jiaoben.netProxy132https://images.jiaoben.net.save(Unknown Source) ~https://images.jiaoben.net[na:na]
	https://images.jiaoben.netat https://images.jiaoben.netcomhttps://images.jiaoben.net.waylauhttps://images.jiaoben.net.rednotehttps://images.jiaoben.net.servicehttps://images.jiaoben.net.implhttps://images.jiaoben.net.NoteServiceImplhttps://images.jiaoben.net.updateNote(NoteServiceImpl.https://images.jiaoben.netjava:https://images.jiaoben.net86) ~https://images.jiaoben.net[classes/:na]

分析

核心代码位置:

https://images.jiaoben.net@Override
https://images.jiaoben.netpublic https://images.jiaoben.netvoid https://images.jiaoben.netupdateNotehttps://images.jiaoben.net(Note note, NoteEditDto noteEditDto) {

	https://images.jiaoben.net// 更新基本信息
	note.setTitle(noteEditDto.getTitle());
	note.setContent(noteEditDto.getContent());
	note.setCategory(noteEditDto.getCategory());

	https://images.jiaoben.net// 字符串转为List
	note.setTopics(StringUtil.splitToList(noteEditDto.getTopics(),https://images.jiaoben.net" "));

	https://images.jiaoben.net// 保存更新
	noteRepository.save(note);
}

其中,实体Note的topics是由StringUtil.splitToList()生成的。splitToList实现如下:

https://images.jiaoben.netpublic https://images.jiaoben.netstatic List https://images.jiaoben.netsplitToListhttps://images.jiaoben.net(String source, String regex) {
	https://images.jiaoben.netif (source.isEmpty()) {
		https://images.jiaoben.netreturn Collections.emptyList();
	}

	https://images.jiaoben.netreturn  Arrays.asList(source.split(regex));
}

Arrays.asList() 返回的集合是不可变集合,而 Hibernate 在执行持久化操作时需要修改这些集合。

整改方案

在保存前临时替换集合:

https://images.jiaoben.net@Override
https://images.jiaoben.netpublic https://images.jiaoben.netvoid https://images.jiaoben.netupdateNotehttps://images.jiaoben.net(Note note, NoteEditDto noteEditDto) {
	https://images.jiaoben.net// 更新基本信息
	note.setTitle(noteEditDto.getTitle());
	note.setContent(noteEditDto.getContent());
	note.setCategory(noteEditDto.getCategory());

	https://images.jiaoben.net// 字符串转为List
	https://images.jiaoben.net// 确保体使用可变集合实现
	https://images.jiaoben.net// note.setTopics(StringUtil.splitToList(noteEditDto.getTopics()," "));
	note.setTopics(https://images.jiaoben.netnew https://images.jiaoben.netArrayList<>(StringUtil.splitToList(noteEditDto.getTopics(),https://images.jiaoben.net" ")));
	https://images.jiaoben.net// 保存更新
	noteRepository.save(note);
}

运行调测

下图10-2所示的是笔记编辑页面。

下图10-3所示的是笔记编辑成功后的页面。

总结

UnsupportedOperationException 通常表示你正在尝试修改一个不可变集合。确保你的实体使用可变集合实现(如 ArrayList),并在DTO到实体转换过程中创建新的可变集合实例。

1.6 从笔记详情页面触发编辑、删除笔记的请求

在笔记详情页面操作栏上已经预留了编辑、删除笔记的按钮。如下图10-4所示。

接下来实现从编辑、删除笔记的按钮执行触发编辑、删除笔记的请求。

修改编辑笔记按钮事件

修改编辑的按钮事件,在