表单提交失败,页面上用户名那栏空着,用户得从头再填一遍。打开模板一看,input 上写的是 th:value,值明明取到了,回显却不走这条路。换成 th:field 之后问题没了,顺带把校验错误也显示了出来。

th:field 一次干三件事

它按表达式里的字段名生成 name 和 id,再把对应的值填进 value。外面套着 th:object 时,*{userName} 会自动补全成 user.userName。所以 form 标签上要先写 th:object,只给 input 加 th:field 会抛 Neither BindingResult nor plain target object 一类异常。

<form th:object=${user} method=post><input th:field=*{userName}><span th:errors=*{userName}></span></form>

select 和 checkbox 差别不小

绑定下拉框时,th:field 会自动给匹配的 option 加 selected,不用手写三元判断。checkbox 更特殊,它会额外生成一个隐藏域,值为 false,保证不勾选时后端也能收到这个字段。手写 th:checked 经常漏掉这一层,布尔字段因此一直是 null,查半天以为是前端没提交。

什么时候用 th:value

只读展示、或者同一个字段要出现在多个 input 上,用 th:value 更直接,想填什么填什么。需要回显、需要错误提示、需要和后端对象双向对应,就用 th:field。两者混在一个表单里没有冲突,别同一个字段一半 th:value 一半 th:field,提交后值对不上很难查。

循环里的绑定容易断

th:field 的表达式只能引用 form 上 th:object 里的属性。想在列表里绑定集合元素,得把 th:object 写在循环那一层,比如 th:each 配合 th:object=${item},否则字段名找不到宿主对象,渲染直接报错。

时间字段还要注意格式。后端是 LocalDate,页面却提交 2024/5/1,绑定会失败并提示类型转换错误。要么加 DateTimeFormat 注解,要么用 th:field 配合统一的日期格式,别指望 Spring 自己猜。判断标准就一条:这个字段要不要跟着对象来回走,要就走 th:field。