Spring Boot Controller实体关联映射异常(TITLE_ID为空)求助
问题场景
项目结构:
project-root |-- main | |-- java | | |-- com.kodilla.library | | |-- controller | | |-- domain | | |-- dto | | |-- error | | |-- mapper | | |-- repository | | |-- service |-- test | |-- java | | |-- com.kodilla.library | | |-- controller | | |-- repository
测试com.kodilla.library.controller下的控制器时,BookCopy与BookTitle的关联映射出现异常:通过控制器创建BookCopy对象时,数据库中BookCopy的TITLE_ID字段始终为null。
测试代码示例(BookCopyControllerTestSuite.java):
@Test void testCreateBookCopy() throws Exception { // 创建BookTitle mockMvc.perform(post("/api/booktitles") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookTitleDto))) .andExpect(status().isOk()); // 创建BookCopy mockMvc.perform(post("/api/bookcopies") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookCopyDto))) .andExpect(status().isOk()); // 问题:控制器中bookCopyDto.getTitleId()为null,本应与bookTitleDto建立关联 }
注:服务层、仓库层的关联映射测试均正常,仅控制器层无法正确关联。
排查与解决步骤
1. 检查BookCopyDTO的字段绑定
确保BookCopyDto类中存在titleId字段,且有对应的Getter/Setter方法,字段名称需与请求JSON中的键名匹配(Spring默认支持驼峰与下划线的自动转换,若JSON使用title_id,DTO可通过@JsonProperty指定映射)。
示例DTO结构:
public class BookCopyDto { private Long id; private Long titleId; // 必须包含该字段 // 其他业务字段 // Getter & Setter public Long getTitleId() { return titleId; } public void setTitleId(Long titleId) { this.titleId = titleId; } }
2. 确保请求中传递有效的titleId
测试时需为bookCopyDto设置正确的titleId值,对应之前创建的BookTitle的ID。若创建BookTitle后未获取其ID,需修改测试代码:
// 创建BookTitle并获取返回的ID MvcResult result = mockMvc.perform(post("/api/booktitles") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookTitleDto))) .andExpect(status().isOk()) .andReturn(); // 解析返回的BookTitleDto,提取ID BookTitleDto createdTitle = objectMapper.readValue(result.getResponse().getContentAsString(), BookTitleDto.class); Long titleId = createdTitle.getId(); // 将ID设置到BookCopyDto中 bookCopyDto.setTitleId(titleId); // 执行BookCopy创建请求 mockMvc.perform(post("/api/bookcopies") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(bookCopyDto))) .andExpect(status().isOk());
3. 验证控制器的参数绑定逻辑
确保控制器的创建方法正确使用@RequestBody接收BookCopyDto,无参数绑定异常:
@PostMapping("/api/bookcopies") public ResponseEntity<Void> createBookCopy(@RequestBody BookCopyDto bookCopyDto) { bookCopyService.saveBookCopy(bookCopyDto); return ResponseEntity.ok().build(); }
若JSON键名与DTO字段名不一致,可通过@JsonProperty明确映射:
@JsonProperty("title_id") // 适配JSON中的下划线格式 private Long titleId;
4. 检查Mapper层的转换逻辑
确认BookCopyMapper正确将BookCopyDto的titleId转换为BookCopy实体与BookTitle的关联关系:
@Component public class BookCopyMapper { public BookCopy mapToBookCopy(BookCopyDto dto) { BookCopy bookCopy = new BookCopy(); bookCopy.setId(dto.getId()); // 关键:通过titleId关联BookTitle实体 BookTitle bookTitle = new BookTitle(); bookTitle.setId(dto.getTitleId()); bookCopy.setBookTitle(bookTitle); // 其他字段映射 return bookCopy; } }
服务层需通过bookTitleRepository.findById(dto.getTitleId())获取持久化的BookTitle实体,再设置到BookCopy中,避免JPA关联失效。
5. 确认JPA实体的关联配置
检查BookCopy实体的关联注解是否正确,确保@JoinColumn的name与数据库TITLE_ID字段一致:
@Entity @Table(name = "BOOK_COPIES") public class BookCopy { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TITLE_ID") // 对应数据库字段名 private BookTitle bookTitle; // Getter & Setter public BookTitle getBookTitle() { return bookTitle; } public void setBookTitle(BookTitle bookTitle) { this.bookTitle = bookTitle; } }
内容的提问来源于stack exchange,提问作者reizorwins

