Spring Data Jpa双向OneToMany实体保存失败问题求助
问题描述
使用Spring Data JPA持久化JPA实体到数据库时遇到问题:存在子实体Schedule和父实体MessageConfig,发送如下JSON请求体的@PostMapping请求时:
{ "emailAdvertisement":{ "template":"some string", "topic": "some string", "image": "some string" }, "smsAdvertisement":{ "text":"smsText" }, "sendTypes":"Sms,Email", "schedule":[ { "timeToSend":"17-04-2023 17:35"}, { "timeToSend":"17-04-2023 17:45"} ] }
出现错误:Object references an unsaved transient instance - save the transient instance before flushing。MessageConfig已成功保存到数据库,但Schedule表中的config_id外键列值为null。推测是Schedule实体尝试关联尚未持久化的实体导致的。
已尝试以下方案但未解决:
- 在关联双方添加
@CascadeType.All - 在
ManyToOne端添加@Cascade(CascadeType.SAVE_UPDATE) - 在
ManyToOne端添加@CascadeType.Persist
子实体Schedule代码
@Entity @Data public class Schedule { @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MM-yyyy HH:mm") private LocalDateTime timeToSend; @ManyToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "config_id") private MessageConfig config; // 此处可能存在问题 private SendStatus sendStatus = SendStatus.WAITING; }
父实体MessageConfig代码
@Entity @Data @NoArgsConstructor public class MessageConfig { @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; @ManyToOne(cascade = CascadeType.ALL) private Customer customer; private String name; @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true) private EmailAdvertisement emailAdvertisement; @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true) private SmsAdvertisement smsAdvertisement; @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true) private MessengerAdvertisement messengerAdvertisement; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval = true) private List<Client> clients; @OneToMany(mappedBy = "config", cascade = CascadeType.ALL, orphanRemoval = true) // 此处可能存在问题 private List<Schedule> schedule; private String sendTypes; public MessageConfig(EmailAdvertisement emailAdvertisement, SmsAdvertisement smsAdvertisement, MessengerAdvertisement messengerAdvertisement, String sendTypes, List<Client> clients) { this.emailAdvertisement = emailAdvertisement; this.smsAdvertisement = smsAdvertisement; this.messengerAdvertisement = messengerAdvertisement; this.sendTypes = sendTypes; this.clients = clients; } }
保存配置的服务方法代码
public ResponseEntity<MessageConfig> postConfig(String configDto, MultipartFile file) { try { MessageConfig config = mapper.readValue(configDto, MessageConfig.class); Customer customer = new Customer("NikePR", "nikePR@gmail.com", "password"); config.setClients(csvParser.parseCsvToList(file)); config.setCustomer(customer); messageConfigRepository.save(config); return new ResponseEntity<>(config, HttpStatus.CREATED); } catch (JsonProcessingException e) { e.printStackTrace(); } return new ResponseEntity<>(HttpStatus.BAD_REQUEST); }
请问是否存在无需额外创建message_config_id/schedule_id映射表的解决方案?
解决方案
不需要额外创建映射表,问题核心是双向关联的关系维护缺失。Jackson反序列化JSON时,只会给MessageConfig的schedule列表赋值,但不会自动给每个Schedule对象设置对应的config属性,导致Schedule处于未关联持久化实体的 transient 状态,最终触发错误。
方案1:服务层手动维护关联关系
在调用save()之前,遍历MessageConfig的schedule列表,给每个Schedule设置config引用:
public ResponseEntity<MessageConfig> postConfig(String configDto, MultipartFile file) { try { MessageConfig config = mapper.readValue(configDto, MessageConfig.class); Customer customer = new Customer("NikePR", "nikePR@gmail.com", "password"); config.setClients(csvParser.parseCsvToList(file)); config.setCustomer(customer); // 手动维护双向关联 if (config.getSchedule() != null && !config.getSchedule().isEmpty()) { config.getSchedule().forEach(schedule -> schedule.setConfig(config)); } messageConfigRepository.save(config); return new ResponseEntity<>(config, HttpStatus.CREATED); } catch (JsonProcessingException e) { e.printStackTrace(); } return new ResponseEntity<>(HttpStatus.BAD_REQUEST); }
方案2:在实体中添加关联维护方法(推荐)
在MessageConfig类中添加自定义方法,自动维护双向关联,避免每次保存都手动处理:
@Entity @Data @NoArgsConstructor public class MessageConfig { // ... 原有代码 // 重写或补充关联维护方法,替代@Data生成的默认setSchedule public void addSchedule(Schedule schedule) { if (this.schedule == null) { this.schedule = new ArrayList<>(); } this.schedule.add(schedule); schedule.setConfig(this); } // 批量添加方法 public void addAllSchedules(List<Schedule> schedules) { if (schedules == null) return; schedules.forEach(this::addSchedule); } }
然后在服务层调用该方法:
// 替换直接赋值逻辑 config.addAllSchedules(config.getSchedule());
额外优化建议
- 移除
Schedule类中@ManyToOne上的cascade = CascadeType.PERSIST,因为MessageConfig的@OneToMany已经配置cascade = CascadeType.ALL,重复配置易导致逻辑混乱。 - 检查
@Data生成的equals/hashCode方法,避免因双向关联引发无限递归(必要时手动重写,排除关联属性)。
内容的提问来源于stack exchange,提问作者Ivan Dvurechenskiy
相关产品推荐
相关产品推荐

