1
我一些麻煩嘗試存儲一些實體JPA,情況如下:Spring rest + JPA + H2 @ManyToOne雙向關係。無法存儲子實體
WebMessageEntity.java
@EqualsAndHashCode @Data @Entity(name = "web_message") @NoArgsConstructor @AllArgsConstructor @Builder public class WebMessageEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Getter @Column(name = "WEB_MESSAGE_ID") private Long id; @Getter @Setter @NotEmpty private String hotelTicker; @Getter @Setter @NotNull @Enumerated(EnumType.STRING) private WebMessageColor color; @Getter @Setter @NotNull @Enumerated(EnumType.STRING) @Column(name = "message_type") private WebMessageType type; @Getter @Setter @NotNull @Enumerated(EnumType.STRING) private ReservationStep step; @Getter @Setter @NotNull @Enumerated(EnumType.STRING) @Column(name = "message_trigger") private WebMessageTrigger trigger; private int duration; @Enumerated(EnumType.STRING) private WebMessagePosition position; @Getter @Setter @NotNull private boolean isActive; @Getter @Setter @NotNull @Convert(converter = LocalDateAttributeConverter.class) private LocalDate startDate; @Getter @Setter @NotNull @Convert(converter = LocalDateAttributeConverter.class) private LocalDate endDate; @Setter @OneToMany(mappedBy = "webMessage", cascade = CascadeType.ALL) @NotNull private List<WebMessageTranslationEntity> translations; @Getter @Setter @NotEmpty private String userName; @Getter @Setter @NotNull @Convert(converter = LocalDateTimeAttributeConverter.class) private LocalDateTime creationDate; @Getter @Setter private String modifiedBy; @Getter @Setter @Convert(converter = LocalDateTimeAttributeConverter.class) private LocalDateTime modificationDate; //constructors //------------------------------------------------------------------------------------------------------ private WebMessageEntity(String hotelTicker, WebMessageColor color, WebMessageType type, ReservationStep step, WebMessageTrigger trigger, boolean isActive, LocalDate startDate, LocalDate endDate, List<WebMessageTranslationEntity> translations, String userName, LocalDateTime creationDate, String modifiedBy, LocalDateTime modificationDate) { this.hotelTicker = hotelTicker; this.color = color; this.type = type; this.step = step; this.trigger = trigger; this.isActive = isActive; this.startDate = startDate; this.endDate = endDate; this.translations = translations; this.userName = userName; this.creationDate = creationDate; this.modifiedBy = modifiedBy; this.modificationDate = modificationDate; } private WebMessageEntity(String hotelTicker, WebMessageColor color, WebMessageType type, ReservationStep step, WebMessageTrigger trigger, int duration, WebMessagePosition position, boolean isActive, LocalDate startDate, LocalDate endDate, List<WebMessageTranslationEntity> translations, String userName, LocalDateTime creationDate, String modifiedBy, LocalDateTime modificationDate) { this.hotelTicker = hotelTicker; this.color = color; this.type = type; this.step = step; this.trigger = trigger; this.setDuration(duration); this.setPosition(position); this.isActive = isActive; this.startDate = startDate; this.endDate = endDate; this.translations = translations; this.userName = userName; this.creationDate = creationDate; this.modifiedBy = modifiedBy; this.modificationDate = modificationDate; } //GETTERS, SETTERS and some private field verification methods
WebMessageTranslationEntity
@Data @EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Builder @Entity(name = "web_message_translation") @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"locale", "message_id"})) public class WebMessageTranslationEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Getter @Column(name = "MESSAGE_TRANSLATION_ID") private Long id; @NotEmpty private String content; @NotEmpty private String locale; @ManyToOne @JoinColumn(name = "MESSAGE_ID") private WebMessageEntity webMessage; }
@控制器
@RestController @RequestMapping("/api") public class WebMessageResource { private final WebMessageService messageService; @Autowired public WebMessageResource(WebMessageService messageService) { this.messageService = messageService; } @PostMapping("{hotelTicker}/messages") public ResponseEntity<?> createMessage(@RequestBody @Valid WebMessageDTO dto, @PathVariable @NotNull String hotelTicker) { if (verifyHotelTicker(dto, hotelTicker)) { WebMessageEntity newEntity = messageService.store(fromWebMessageDTOToEntity(dto)); HttpHeaders headers = new HttpHeaders(); //TODO rebuild URI with exact path to access resource headers.setLocation(ControllerLinkBuilder.linkTo(FilterEntity.class).slash(newEntity.getHotelTicker()).slash(newEntity.getId()).toUri()); return new ResponseEntity<>(fromWebMessageEntityToDTO(newEntity), headers, HttpStatus.CREATED); } return new ResponseEntity<>("Hotel ticker specified in URI doesn't match with DTO's hotel ticker", HttpStatus.BAD_REQUEST); } private boolean verifyHotelTicker(WebMessageDTO dto, String hotelTicker) { return hotelTicker.equals(dto.getHotelTicker()); } private List<WebMessageTranslationEntity> fromTranslationDTOsToEntities(List<WebMessageTranslationDTO> translationDTOs) { return translationDTOs .stream() .map(translation -> WebMessageTranslationEntity .builder() .content(translation.getContent()) .locale(translation.getLocale()) .build()) .collect(toList()); } private WebMessageEntity fromWebMessageDTOToEntity(WebMessageDTO webMessageDTOs) { return WebMessageEntity .builder() .hotelTicker(webMessageDTOs.getHotelTicker()) .color(webMessageDTOs.getColor()) .type(webMessageDTOs.getType()) .step(webMessageDTOs.getStep()) .trigger(webMessageDTOs.getTrigger()) .duration(webMessageDTOs.getDuration()) .position(webMessageDTOs.getPosition()) .isActive(webMessageDTOs.getIsActive()) .startDate(webMessageDTOs.getStartDate()) .endDate(webMessageDTOs.getEndDate()) .userName(webMessageDTOs.getUserName()) .creationDate(webMessageDTOs.getCreationDate()) .modifiedBy(webMessageDTOs.getModifiedBy()) .modificationDate(webMessageDTOs.getModificationDate()) .translations(this.fromTranslationDTOsToEntities(webMessageDTOs.getTransla tions())) .build(); } private WebMessageDTO fromWebMessageEntityToDTO(WebMessageEntity webMessageEntity) { return WebMessageDTO .builder() .id(webMessageEntity.getId()) .hotelTicker(webMessageEntity.getHotelTicker()) .color(webMessageEntity.getColor()) .type(webMessageEntity.getType()) .step(webMessageEntity.getStep()) .trigger(webMessageEntity.getTrigger()) .duration(webMessageEntity.getDuration()) .position(webMessageEntity.getPosition()) .isActive(webMessageEntity.isActive()) .startDate(webMessageEntity.getStartDate()) .endDate(webMessageEntity.getEndDate()) .userName(webMessageEntity.getUserName()) .creationDate(webMessageEntity.getCreationDate()) .modifiedBy(webMessageEntity.getModifiedBy()) .modificationDate(webMessageEntity.getModificationDate()) .translations(this.fromTranslationEntitiesToDTO(webMessageEntity.getTranslations())) .build(); } private List<WebMessageTranslationDTO> fromTranslationEntitiesToDTO(List<WebMessageTranslationEntity> translationEntities) { return translationEntities .stream() .map(translation -> WebMessageTranslationDTO .builder() //.id(translation.getId()) .content(translation.getContent()) .locale(translation.getLocale()) .build()) .collect(toList()); } }
堆棧跟蹤
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "MESSAGE_ID"; SQL statement: insert into web_message_translation (message_translation_id, content,locale, message_id) values (null, ?, ?, ?) [23502-194] at org.h2.message.DbException.getJdbcSQLException(DbException.java:345) at org.h2.message.DbException.get(DbException.java:179) at org.h2.message.DbException.get(DbException.java:155) at org.h2.table.Column.validateConvertUpdateSequence(Column.java:311) at org.h2.table.Table.validateConvertUpdateSequence(Table.java:793) at org.h2.command.dml.Insert.insertRows(Insert.java:151) at org.h2.command.dml.Insert.update(Insert.java:114) at org.h2.command.CommandContainer.update(CommandContainer.java:101) at org.h2.command.Command.executeUpdate(Command.java:258) ...
表創建YAML文件
- changeSet: id: '008-1' author: arnau comment: 'create table web_message' preConditions: - onFail: MARK_RAN - onFailMessage: 'Table already exists, must be production environment...' - not: - tableExists: tableName: web_message changes: - createTable: tableName: web_message columns: - column: name: WEB_MESSAGE_ID type: NUMBER autoIncrement: true constraints: primaryKey: true nullable: false - column: name: hotel_ticker type: VARCHAR(155) constraints: nullable: false - column: name: color type: VARCHAR(255) constraints: nullable: false - column: name: message_type type: VARCHAR(255) constraints: nullable: false - column: name: step type: VARCHAR(255) constraints: nullable: false - column: name: message_trigger type: VARCHAR(255) constraints: nullable: false - column: name: duration type: NUMBER constraints: nullable: true - column: name: position type: VARCHAR(255) constraints: nullable: true - column: name: is_active type: BOOLEAN(1) constraints: nullable: false - column: name: start_date type: DATE constraints: nullable: false - column: name: end_date type: DATE constraints: nullable: false - column: name: user_name type: VARCHAR(255) constraints: nullable: false - column: name: creation_date type: TIMESTAMP constraints: nullable: false - column: name: modified_by type: VARCHAR(255) constraints: nullable: true - column: name: modification_date type: TIMESTAMP constraints: nullable: true - changeSet: id: '008-2' author: arnau comment: 'create table web_message_translation' preConditions: - onFail: MARK_RAN - onFailMessage: 'Table already exists, must be production environment...' - not: - tableExists: tableName: web_message_translation changes: - createTable: tableName: web_message_translation columns: - column: name: MESSAGE_TRANSLATION_ID type: NUMBER autoIncrement: true constraints: primaryKey: true nullable: false - column: name: locale type: VARCHAR(2) constraints: unique: true nullable: false - column: name: content type: VARCHAR(255) constraints: nullable: false - column: name: message_id type: CHAR(22) constraints: nullable: false references: web_message(WEB_MESSAGE_ID) foreignKeyName: fk_web_message_translation__web_message - changeSet: id: '008-3' author: arnau comment: 'Add unique constraint by locale and web_message_id' changes: - addUniqueconstraint: tableName: web_message_translation columnNames: locale, message_id constraintName: uc_locale__message_id
的問題是,當我嘗試存儲這些實體,JPA嘗試將實體webMessageTranslationEntity
存儲與NULL值在列message_id
,顯然,這被數據庫拒絕,因爲該字段被設置爲NOT NULL。
如何將這些條件存儲在這些條件下?
更改約束不是一種選擇,所以我試圖在WebMessageTranslation entit的WebMessageEntity屬性添加一個CascadeType.ALL,像這樣: '@ManyToOne(級聯= CascadeType.ALL) @JoinColumn(name = 「MESSAGE_ID」) 私人WebMessageEntity webMessage;' 但問題仍然存在,所以最後我不得不堅持實體單獨,先堅持WebMessageEntity,然後堅持WebMessageTranslations。但我認爲這應該是一個更乾淨的方式來做到這一點。我會把它解決,但如果我找到更好的方法,我會在這裏發佈。非常感謝@AmerQarabsa – Tricoman
級聯定義了操作在子實體上的級聯,所以如果你有A有一個對B的引用,並且你想在持久化A時堅持引用的B,你可以在A中定義級聯關於B參考的關係,所以在你的手杖中它應該在OneToMany而不是ManyToOne中 –