Thymeleaf 编辑表单详解:从字段回填到模拟 PUT 请求

Thymeleaf 编辑表单详解:从字段回填到模拟 PUT 请求

原始代码

html 复制代码
<form th:action="@{/user}" th:object="${user}" method="post">
    <input type="hidden" name=_method value="put"/>
    <input type="hidden" name="id" th:value="*{id}"/>
    <label>用户名:</label>
    <input type="text" name="username" th:value="*{username}" required>
    <label>性别:</label>
    <select name="gender" th:field="*{gender}" required>
        <option value="">-- 请选择 --</option>
        <option value="1" selected>男</option>
        <option value="0">女</option>
    </select>

    <label>邮箱:</label>
    <input type="email" name="email" th:value="*{email}" required>

    <button type="submit">修改</button>
</form>

一、这段代码的作用

这是一个"修改用户信息"的表单。它完成四件事:

  1. 打开页面时,从后端传入的 user 对象中读取数据,回填到输入框和下拉框。
  2. 提交时携带用户主键 id,让后端知道要修改哪一条记录。
  3. 通过隐藏字段 _method=put,把浏览器原生的 POST 请求模拟成 PUT 请求。
  4. 用户修改后提交,后端按 PUT 方法接收并更新数据。

整体数据流:

sql 复制代码
GET 打开编辑页
  → Controller 查询 User,放入 Model
  → Thymeleaf 用 th:object 绑定 user,用 *{} / th:field 回填
  → 用户修改并提交
POST /user
  → 携带 _method=put、id、username、gender、email
  → HiddenHttpMethodFilter 把 POST 改写为 PUT
  → Spring MVC 匹配 @PutMapping("/user")
  → 参数绑定到 User 对象,执行更新

二、逐行代码解释

1. form 标签

html 复制代码
<form th:action="@{/user}" th:object="${user}" method="post">
属性 作用
th:action="@{/user}" 表单提交地址。@{} 是 URL 表达式,会自动加上项目上下文路径,例如项目部署在 /usermgt 下,最终生成 /usermgt/user。
th:object="${user}" 把当前表单绑定到 Model 中的 user 对象。绑定后,表单内部可以用 *{属性名} 直接访问该对象的属性。
method="post" 浏览器表单原生只支持 GET 和 POST,因此这里写 POST,真正的 PUT 由后面的 _method 隐藏字段模拟。

th:object 是整段代码的上下文开关。没有它,下面的 *{id}、*{username} 等表达式找不到目标对象,会直接报错。

2. 模拟 PUT 的隐藏字段

html 复制代码
<input type="hidden" name=_method value="put"/>

浏览器表单不能直接发送 PUT 或 DELETE 请求。Spring MVC 提供的标准做法是:

  1. 表单以 POST 方式提交。
  2. 增加隐藏字段 _method,值为目标 HTTP 方法名。
  3. 服务端配置 HiddenHttpMethodFilter,它会读取 _method 参数,把请求方法从 POST 改写成 PUT 或 DELETE。

因此这一行的作用是:让后端把这次 POST 识别为 PUT 请求 ,这样 @PutMapping("/user") 才能匹配到。

当前写法 name=_method 没有加引号。HTML5 的宽松解析通常能运行,但规范写法应加引号:

html 复制代码
<input type="hidden" name="_method" value="put"/>

3. 主键隐藏字段

html 复制代码
<input type="hidden" name="id" th:value="*{id}"/>

修改操作必须提交主键,否则后端不知道要更新哪一条记录。

属性 作用
name="id" 提交时的参数名,后端用 id 接收。
th:value="*{id}" 从 th:object 绑定的 user 对象中读取 id,渲染到 value 属性中。

这里的写法可以工作,但更简洁的方式是使用 th:field:

html 复制代码
<input type="hidden" th:field="*{id}"/>

th:field 会自动生成 name="id"、id="id",并根据模型值生成 value,三件事一次完成。

4. 用户名输入框

html 复制代码
<label>用户名:</label>
<input type="text" name="username" th:value="*{username}" required>
属性 作用
name="username" 提交参数名,Spring MVC 会把它绑定到 User.username。
th:value="*{username}" 从 user 对象中读取用户名,回填到输入框。
required HTML5 原生校验,为空时浏览器阻止提交,不进入后端。

同样可以简化为:

html 复制代码
<input type="text" th:field="*{username}" required>

th:field 会自动处理 name、id 和 value。

5. 性别下拉框(存在冲突)

html 复制代码
<select name="gender" th:field="*{gender}" required>
    <option value="">-- 请选择 --</option>
    <option value="1" selected>男</option>
    <option value="0">女</option>
</select>

这一段存在一个典型问题:name、th:field 和手写 selected 混用,导致回填逻辑冲突。

写法 实际作用
name="gender" 手动指定提交参数名。
th:field="*{gender}" 自动生成 name="gender" 和 id="gender",并根据 user.gender 的值自动给对应 option 加上 selected。
<option value="1" selected> 手写死"男"为选中状态。

问题在于:th:field 会自动处理选中逻辑,手写的 selected 会覆盖它的判断。结果是:不管 user.gender 是 0 还是 1,页面永远选中"男",编辑回填失效。

正确写法二选一。

方案一:只用 th:field,去掉手写 selected 和手动 name

html 复制代码
<select th:field="*{gender}" required>
    <option value="">-- 请选择 --</option>
    <option value="1">男</option>
    <option value="0">女</option>
</select>

方案二:不用 th:field,手动判断选中

html 复制代码
<select name="gender" required>
    <option value="">-- 请选择 --</option>
    <option value="1" th:selected="${user.gender == 1}">男</option>
    <option value="0" th:selected="${user.gender == 0}">女</option>
</select>

两种方案都能工作,但不能混用。混用就是当前这段代码的问题。

6. 邮箱输入框

html 复制代码
<label>邮箱:</label>
<input type="email" name="email" th:value="*{email}" required>
属性 作用
type="email" HTML5 输入类型,浏览器会校验邮箱格式。
name="email" 提交参数名。
th:value="*{email}" 回填已有邮箱。
required 必填校验。

可简化为:

html 复制代码
<input type="email" th:field="*{email}" required>

7. 提交按钮

html 复制代码
<button type="submit">修改</button>

type="submit" 触发表单提交。如果不写 type,<button> 默认也是 submit,但显式写出更清晰。

三、三种 Thymeleaf 表达式

表达式 含义 典型用途
${...} 从 Model、request、session 等作用域取全局值 th:object="${user}"
*{...} 从 th:object 绑定的当前表单对象中取属性 th:value="*{username}"
@{...} 生成 URL,自动处理上下文路径 th:action="@{/user}"

记忆口诀:

bash 复制代码
${} 找全局,
*{} 找当前表单对象,
@{} 生成 URL。

四、th:field 为什么能一个顶三个

th:field 会自动完成三件事:

  1. 生成 name 属性,提交时后端能收到参数。
  2. 生成 id 属性,方便 <label for> 关联。
  3. 读取模型值,自动回填输入框,或自动选中下拉框对应项。

因此使用 th:field 时,不要再手写 name 和 selected。混用会导致冲突,尤其是下拉框的选中状态。

记忆口诀:

bash 复制代码
th:field 一个顶三个:
生成 name、生成 id、自动回填。
用了 field,就别再手写 name 和 selected。

五、修正后的完整表单

html 复制代码
<form th:action="@{/user}" th:object="${user}" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="hidden" th:field="*{id}"/>

    <label>用户名:</label>
    <input type="text" th:field="*{username}" required>

    <label>性别:</label>
    <select th:field="*{gender}" required>
        <option value="">-- 请选择 --</option>
        <option value="1">男</option>
        <option value="0">女</option>
    </select>

    <label>邮箱:</label>
    <input type="email" th:field="*{email}" required>

    <button type="submit">修改</button>
</form>

改动点:

  1. name=_method 改为 name="_method",补上引号。
  2. 所有输入框统一使用 th:field,去掉手动的 name 和 th:value。
  3. 下拉框去掉手写的 selected,让 th:field 统一处理选中逻辑。

六、编辑表单的关键点

1. 必须携带主键

html 复制代码
<input type="hidden" th:field="*{id}"/>

2. 模拟 PUT/DELETE 三步

arduino 复制代码
第一步:form 的 method 写 post
第二步:增加隐藏字段 _method,值为 put 或 delete
第三步:服务端配置 HiddenHttpMethodFilter

Spring Boot 中可以通过配置开启:

yaml 复制代码
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

3. 前端校验只是辅助

required、type="email" 是浏览器层面的校验,用户可以绕过。后端仍然需要校验。

七、记忆方法

1. 表单四件套

vbnet 复制代码
th:action 定地址,
th:object 定对象,
method 定提交方式,
th:field 绑字段。

2. 编辑表单必带主键

bash 复制代码
修改先带 id,
隐藏字段传主键。

3. 模拟 PUT 三步

复制代码
POST 提交,
_method 声明,
过滤器改写。

4. th:field 一个顶三个

bash 复制代码
生成 name,
生成 id,
自动回填。
用了 field,不写 name,不写 selected。

5. 三种表达式分工

bash 复制代码
${} 找全局,
*{} 找对象,
@{} 生成 URL。

6. 当前代码最大的坑

csharp 复制代码
select 上同时出现 name、th:field、selected,
选中逻辑会打架,回填会失效。

八、总结

代码片段 作用 注意事项
th:action="@{/user}" 提交地址,自动带上下文路径 避免硬编码路径
th:object="${user}" 绑定表单对象 没有它 *{} 无法工作
method="post" 浏览器原生提交方式 PUT 靠 _method 模拟
_method=put 让过滤器把 POST 改写为 PUT 需要 HiddenHttpMethodFilter
th:field="*{id}" 提交主键并回填 编辑表单必须有
th:field="*{username}" 生成 name/id 并回填 不再手写 name
th:field="*{gender}" 自动选中对应 option 不再手写 selected
required 浏览器必填校验 后端仍需校验

这段代码的核心是 th:object、*{}、th:field 三者的配合。记住"对象绑定用 object,字段取值用星号,控件回填用 field",再记住"编辑表单必带 id,模拟 PUT 加 _method",这类表单就能独立写出。最容易出错的地方是 th:field 与手写 name、selected 混用,导致回填失效,修改时应统一交给 th:field 处理。

相关推荐
Song_RsHt2 小时前
Redis 与微服务,什么场景下无可替代
java·redis·后端·学习·微服务
学长毕设3 小时前
基于SpringBoot的建筑材料管理系统-附源码
java·spring boot·后端
程序员cxuan3 小时前
掀桌子了!Kimi K3.1 Pro 这效果也太离谱了。
人工智能·后端·程序员
摸爬滚打的小李3 小时前
关于 Arthas
java·后端
武子康3 小时前
黑图、彩色噪声、NaN、偏色怎么排查
人工智能·后端·agent
苏三说技术3 小时前
企业智能知识库 Python 后端实战:FastAPI + RAG + KAG 企业级 AI 项目
后端
Mk598784 小时前
从跑通到上生产:AgentScope-Java 六篇实战收官盘点
后端
彭亚川Allen4 小时前
从零手搓 RAG 之 Embedding 技术
前端·后端·面试
星光开发者4 小时前
基于springboot智慧草场牛羊管理系统-计算机毕设【课程设计】50360
vue.js·spring boot·后端·mysql·php·课程设计·express