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

Spring Boot员工管理CRUD应用提交后400 Bad Request问题排查

员工管理系统提交表单出现400 Bad Request错误排查

我正在开发一个员工管理系统的简易CRUD应用,点击添加按钮可正常跳转到localhost:9090/employees/new,但点击提交按钮时,未能重定向回localhost:9090/employees,反而出现400 Bad Request错误。已将服务器端口设为9090并硬编码重定向地址,但问题仍未解决,请问可能的原因是什么?

Employee Controller

package com.example.crudempdep.controller;

import com.example.crudempdep.model.Employee;
import com.example.crudempdep.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;


@Controller
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;
    @GetMapping("/employees")
    public String getAllEmployees(Model model) {
        List<Employee> employees = employeeService.getAllEmployees();
        model.addAttribute("employees", employees);
        return "employees";
    }
    @GetMapping("/employees/new")
    public String createEmployee(Model model) {
        model.addAttribute("employee", new Employee());
        return "create_employee";
    }

    @PostMapping("/employees")
    public String addEmployee(@ModelAttribute("employee") Employee employee) {
        employeeService.saveEmployee(employee);
        return "redirect:http://localhost:9090/employees";
    }


    @PutMapping("/employees/{id}")
    public void updateEmployeeById(@PathVariable Long id, @RequestBody Employee employee) {
        Employee e = employeeService.getEmployeeById(id);
        if (e != null) {
            e.setFname(employee.getFname());
            e.setLname(employee.getLname());
            e.setDepartment(employee.getDepartment());
            employeeService.saveEmployee(e);
        }
    }
    @DeleteMapping("/employees/{id}")
    public void deleteEmployeeById(@PathVariable Long id) {
        employeeService.deleteEmployeeById(id);
    }

}

employees.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="ISO-8859-1">
  <title>Employee Management System</title>
  <link rel="stylesheet"
        href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
        crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <!-- Brand -->
  <a class="navbar-brand" href="#">Employee Management System</a>

  <!-- Toggler/collapsibe Button -->
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>

  <!-- Navbar links -->
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" th:href="@{/employees}">Employee Management</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" th:href="@{/departments}">Department Management</a>
      </li>
    </ul>
  </div>
</nav>

<div class ="container">
  <div class = "row">
    <h1> List Students </h1>
  </div>

  <div class = "row">
    <div class = "col-lg-3">
      <a th:href = "@{/employees/new}" class = "btn btn-primary btn-sm mb-3"> Add Student</a>
    </div>
  </div>
  <table class = "table table-striped table-bordered">
    <thead class = "table-dark">
    <tr>
      <th> Employee First Name</th>
      <th> Employee Last Name</th>
      <th> Employee Department</th>
      <th> Actions </th>
    </tr>
    </thead>

    <tbody>
    <tr th:each = "employee: ${employees}">
      <td th:text = "${employee.fname}"></td>
      <td th:text = "${employee.lname}"></td>
      <td th:text = "${employee.department}"></td>
      <td>
        <a th:href = "@{/employees.html/edit/{id}(id=${employee.id})}"
           class = "btn btn-primary">Update</a>

        <a th:href = "@{/employees.html/delete/{id}(id=${employee.id})}"
           class = "btn btn-danger">Delete</a>

      </td>
    </tr>
    </tbody>

  </table>

</div>
</body>
</html>

create_employee.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
  <meta charset="ISO-8859-1">
  <title>Employee Management System</title>
  <link rel="stylesheet"
        href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
        crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <!-- Brand -->
  <a class="navbar-brand" href="#">Employee Management System</a>

  <!-- Toggler/collapsibe Button -->
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>

  <!-- Navbar links -->
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" th:href="@{/employees}">Employee Management</a>
      </li>
    </ul>
  </div>
</nav>
<br>
<br>
<div class = "container">
  <div class = "row">
    <div class ="col-lg-6 col-md-6 col-sm-6 container justify-content-center card">
      <h1 class = "text-center"> Create New Employee </h1>
      <div class = "card-body">
        <form th:action="@{/employees}" th:object="${employee}" method="POST">
          <div class ="form-group">
            <label> Employee First Name </label>
              <label>
                  <input
                          type = "text"
                          name = "fname"
                          th:field = "*{fname}"
                          class = "form-control"
                          placeholder="Enter Employee First Name"
                  />
              </label>
          </div>

          <div class ="form-group">
            <label> Employee Last Name </label>
              <label>
                  <input
                          type = "text"
                          name = "lname"
                          th:field = "*{lname}"
                          class = "form-control"
                          placeholder="Enter Employee Last Name"
                  />
              </label>
          </div>

          <div class ="form-group">
            <label> Employee Department </label>
              <label>
                  <input
                          type = "text"
                          name = "department"
                          th:field = "*{department}"
                          class = "form-control"
                          placeholder="Enter Employee Department"
                  />
              </label>
          </div>

          <div class = "box-footer">
            <button type="submit" class = "btn btn-primary">
              Submit
            </button>
          </div>
        </form>

      </div>
    </div>
  </div>
</div>
</body>
</html>

可能的原因及解决方案

1. Employee实体类缺少无参构造函数或setter方法

Spring MVC通过@ModelAttribute绑定表单数据时,必须依赖实体类的无参构造函数来实例化对象,同时需要所有表单绑定字段的setter方法完成属性注入。如果你的Employee类没有显式定义无参构造函数,或者字段的setter缺失,会直接导致绑定失败触发400错误。

解决办法:
给Employee类添加无参构造函数,并补全所有字段的getter/setter:

public class Employee {
    private Long id;
    private String fname;
    private String lname;
    private String department;

    // 必须添加无参构造函数
    public Employee() {}

    // 全参构造函数(可选)
    public Employee(Long id, String fname, String lname, String department) {
        this.id = id;
        this.fname = fname;
        this.lname = lname;
        this.department = department;
    }

    // 所有字段的getter和setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFname() { return fname; }
    public void setFname(String fname) { this.fname = fname; }
    public String getLname() { return lname; }
    public void setLname(String lname) { this.lname = lname; }
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
}

2. 表单嵌套label标签干扰参数提交

在create_employee.html中,你将<input>标签嵌套在了<label>内部:

<label>
    <input type="text" name="fname" th:field="*{fname}" ... />
</label>

这种写法可能导致浏览器提交表单时,参数值混入label的文本内容,干扰Spring MVC的参数绑定逻辑。

解决办法:
修改表单结构,用for属性关联label和input,避免嵌套:

<div class ="form-group">
    <label for="fname">Employee First Name</label>
    <input
            type="text"
            id="fname"
            th:field="*{fname}"
            class="form-control"
            placeholder="Enter Employee First Name"
    />
</div>

(Thymeleaf的th:field会自动生成匹配的name和id,无需手动重复设置)

3. 字段名拼写不匹配

检查Employee类的字段名是否和表单中th:field的表达式完全一致,比如实体类字段是firstName但表单写了fname,大小写敏感的拼写错误会导致绑定失败。

检查方式:
打开浏览器开发者工具(F12),切换到Network标签,提交表单后查看请求的Form Data,确认参数名和实体类字段完全对应。

4. 启用日志排查具体错误

400错误的核心原因可以通过Spring Boot日志定位,开启DEBUG级别日志后,控制台会输出绑定失败的具体字段和原因。

操作步骤:
在application.properties中添加日志配置:

logging.level.org.springframework.web=DEBUG

重启应用后提交表单,查看控制台日志中BindingResult相关的错误信息,直接定位问题点。

5. 重定向地址优化(非400直接原因,但建议修改)

你当前硬编码了重定向地址,建议改为相对路径写法,更灵活且适配不同部署环境:

return "redirect:/employees";

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:34:58