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

Spring MVC中DTO接收表单参数时类型转换失败问题求助

问题描述

我有一对多关联的Tabel与Person实体类,同时定义了对应的TabelDto和PersonDto数据传输对象。在Spring MVC控制器中使用@ModelAttribute注解接收TabelDto类型的表单参数时,触发类型转换错误:无法将String类型的Person ID值转换为PersonDto类型。直接使用Tabel实体类接收参数功能正常,但我希望通过DTO处理请求而非直接操作实体,请问该如何解决此问题?

实体类代码

@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "tabel")
public class Tabel {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;
    private String comments;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "person_id")
    private Person person;

}

@Entity
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String lastname;
    private String surname;

}

DTO代码

@Data
@Builder
@EqualsAndHashCode
@Jacksonized
public class TabelDto implements Serializable {
    private Long id;
    private String comments;
    private PersonDto person;
}

@Value
@Builder
public class PersonDto implements Serializable {
    Long id;
    String name;
    String lastname;
    String surname;
}

控制器代码

@PostMapping("/addTabel")
public String addTabel(@ModelAttribute TabelDto tabelDto) {
    tabelService.save(tabelDto);
    return "redirect:/addTabel";
}

@GetMapping("/addTabel")
public String addTabel(Model model) {
    TabelDto tabelDto = TabelDto.builder().build();
    model.addAttribute("persons", personService.findAllPerson());
    model.addAttribute("tabelDTO", tabelDto);
    return "addTabel";
}

Thymeleaf表单代码

<form action="#" th:action="@{/tabel/addTabel}" method="post" th:object="${tabelDTO}">
    <label>person:</label>
    <label>
        <select th:field="*{person}" class="form-control">
            <option th:each="person: ${persons}" th:value="${person.id}" th:text="${person.name}"></option>
        </select>
    </label>
    <button type="submit">Register</button>
</form>

错误信息

[tabel] [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String by.gus.tabel.controller.TabelController.addTabel(by.gus.tabel.dto.TabelDto): [Field error in object 'tabelDto' on field 'person': rejected value [1]; codes [typeMismatch.tabelDto.person,typeMismatch.person,typeMismatch.by.gus.tabel.dto.PersonDto,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [tabelDto.person,person]; arguments []; default message [person]]; default message [Failed to convert value of type 'java.lang.String' to required type 'by.gus.tabel.dto.PersonDto'; Cannot convert value of type 'java.lang.String' to required type 'by.gus.tabel.dto.PersonDto' for property 'person': no matching editors or conversion strategy found]] ]

解决方案

Spring MVC默认无法自动将String类型的ID转换为自定义DTO对象,以下是三种可行的解决方式:

方式一:自定义PropertyEditor

为PersonDto类型注册自定义属性编辑器,告诉Spring如何将String类型的ID转换为PersonDto。

  1. 实现PropertyEditor类
import by.gus.tabel.dto.PersonDto;
import by.gus.tabel.entity.Person;
import by.gus.tabel.service.PersonService;
import java.beans.PropertyEditorSupport;
import org.springframework.beans.factory.annotation.Autowired;

public class PersonDtoEditor extends PropertyEditorSupport {

    private final PersonService personService;

    @Autowired
    public PersonDtoEditor(PersonService personService) {
        this.personService = personService;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        Long personId = Long.parseLong(text);
        Person person = personService.findPersonById(personId);
        PersonDto personDto = PersonDto.builder()
                .id(person.getId())
                .name(person.getName())
                .lastname(person.getLastname())
                .surname(person.getSurname())
                .build();
        setValue(personDto);
    }
}
  1. 在控制器中注册编辑器
    添加@InitBinder注解的方法,将自定义编辑器绑定到PersonDto类型:
@Controller
@RequestMapping("/tabel")
public class TabelController {

    private final PersonService personService;

    @Autowired
    public TabelController(PersonService personService) {
        this.personService = personService;
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(PersonDto.class, new PersonDtoEditor(personService));
    }

    // 原有GetMapping和PostMapping方法保持不变
}

方式二:实现Converter接口

实现Spring的Converter<String, PersonDto>接口,这种方式是全局生效的,适用于多个控制器需要转换的场景。

  1. 编写Converter类
import by.gus.tabel.dto.PersonDto;
import by.gus.tabel.entity.Person;
import by.gus.tabel.service.PersonService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
public class StringToPersonDtoConverter implements Converter<String, PersonDto> {

    private final PersonService personService;

    public StringToPersonDtoConverter(PersonService personService) {
        this.personService = personService;
    }

    @Override
    public PersonDto convert(String source) {
        Long personId = Long.parseLong(source);
        Person person = personService.findPersonById(personId);
        return PersonDto.builder()
                .id(person.getId())
                .name(person.getName())
                .lastname(person.getLastname())
                .surname(person.getSurname())
                .build();
    }
}
  1. 注册Converter(Spring Boot自动扫描@Component,无需额外配置)
    如果是传统Spring MVC,需在Web配置类中手动注册:
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final StringToPersonDtoConverter converter;

    public WebConfig(StringToPersonDtoConverter converter) {
        this.converter = converter;
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(converter);
    }
}

方式三:修改DTO结构与表单绑定

简化DTO结构,直接接收Person的ID,在服务层完成ID到DTO/实体的转换。

  1. 修改TabelDto
    将PersonDto person替换为Long personId:
@Data
@Builder
@EqualsAndHashCode
@Jacksonized
public class TabelDto implements Serializable {
    private Long id;
    private String comments;
    private Long personId; // 替换原有的PersonDto字段
}
  1. 调整Thymeleaf表单的字段绑定
<select th:field="*{personId}" class="form-control">
    <option th:each="person: ${persons}" th:value="${person.id}" th:text="${person.name}"></option>
</select>
  1. 在服务层处理转换
@Service
public class TabelService {

    private final TabelRepository tabelRepository;
    private final PersonService personService;

    @Autowired
    public TabelService(TabelRepository tabelRepository, PersonService personService) {
        this.tabelRepository = tabelRepository;
        this.personService = personService;
    }

    public void save(TabelDto tabelDto) {
        Person person = personService.findPersonById(tabelDto.getPersonId());
        Tabel tabel = Tabel.builder()
                .comments(tabelDto.getComments())
                .person(person)
                .build();
        tabelRepository.save(tabel);
    }
}

方案对比

  • 方式一、二适合多场景复用PersonDto接收参数的情况,方式一仅作用于当前控制器,方式二全局生效。
  • 方式三实现简单,无需额外转换类,适合仅少数场景使用的情况。

内容的提问来源于stack exchange,提问作者Сяргей Пяшэвіч

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:45:59