Spring MVC+JPA+Thymeleaf新增子实体时意外插入同名父实体问题
问题根因
你的故障是表单参数绑定规则和JPA级联逻辑叠加导致的,具体触发路径如下:
- 表单的
th:field绑定方式错误,输入框最终生成的字段名为name,SpringMVC参数绑定时会把该值赋值给所有@ModelAttribute接收对象的name属性,导致你输出的Geschaeftsfeld name和Marke name完全一致。 - 下拉框提交的父实体ID没有正确绑定到
Marke对象内部的关联父实体属性,你单独接收的Geschaeftsfeld对象只有自动赋值的name、没有正确的ID,属于无主键的游离对象。 - 你把这个游离的父对象设置到子实体后,JPA保存时会判定该父对象为新数据,自动触发新增操作,就出现了你看到的多余插入语句。
修复方案
1. 修正Thymeleaf表单绑定
表单已经通过th:object="${marke}"绑定了子实体对象,内部字段直接用相对绑定语法*{}绑定到子实体的对应属性即可:
<form action="#" th:action="@{/neuemarke}" th:object="${marke}" method="POST"> <select class="form-control" th:field="*{geschaeftsfeld.id}" id="geschaeftsfeld"> <option value="0">select Geschaeftsfeld</option> <option th:each="gf : ${listAllGf}" th:value="${gf.id}" th:text="${gf.name}"></option> </select> <input type="text" th:field="*{name}" placeholder="Name" class="form-control mb-4 col-4"> <button type="submit" class="btn btn-info col-2"> Save </button> </form>
2. 修正Controller提交逻辑
不需要单独接收父实体参数,直接通过子实体绑定的父实体ID查询已存在的父实体,再关联保存:
@PostMapping("/neuemarke") public String neueMarke(@ModelAttribute("marke") Marke marke) { // 查询已存在的父实体,避免JPA触发新增 Geschaeftsfeld existGf = gfService.getById(marke.getGeschaeftsfeld().getId()); marke.setGeschaeftsfeld(existGf); mkService.save(marke); return "redirect:/einstellungenmarken"; }
注意需要在Geschaeftsfeld对应的Service类中补充按ID查询的方法,直接调用JPA Repository的findById或getReferenceById即可。
3. 可选:检查实体级联配置
确认Marke实体类中多对一注解没有配置多余的级联新增规则,错误配置示例如下:
// 错误写法,不要加CascadeType.PERSIST或CascadeType.ALL @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "geschaeftsfeld_id") private Geschaeftsfeld geschaeftsfeld;
正确配置只需保留基础的多对一注解即可:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "geschaeftsfeld_id") private Geschaeftsfeld geschaeftsfeld;
内容的提问来源于stack exchange,提问作者Nortap
相关产品推荐
相关产品推荐

