2017-04-13 127 views
1

我一些麻煩嘗試存儲一些實體JPA,情況如下:Spring rest + JPA + H2 @ManyToOne雙向關係。無法存儲子實體

  1. 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 
    
  2. 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; 
    
    } 
    
  3. @控制器

    @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()); 
    } 
    
    
    } 
    
  4. 堆棧跟蹤

    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) 
        ... 
    
  5. 表創建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。

如何將這些條件存儲在這些條件下?

回答

0
@NotNull 
private List<WebMessageTranslationEntity> translations; 

這將設置約束的外鍵不能爲空

  1. 保持約束和注入持久WebMessageEntity
  2. 保持約束和注入沒有堅持WebMessageEntity但你需要設置級聯在WebMessageEntity側通過除去堅持(或ALL)
  3. 變化約束@NotNull
+0

更改約束不是一種選擇,所以我試圖在WebMessageTranslation entit的WebMessageEntity屬性添加一個CascadeType.ALL,像這樣: '@ManyToOne(級聯= CascadeType.ALL) @JoinColumn(name = 「MESSAGE_ID」) 私人WebMessageEntity webMessage;' 但問題仍然存在,所以最後我不得不堅持實體單獨,先堅持WebMessageEntity,然後堅持WebMessageTranslations。但我認爲這應該是一個更乾淨的方式來做到這一點。我會把它解決,但如果我找到更好的方法,我會在這裏發佈。非常感謝@AmerQarabsa – Tricoman

+0

級聯定義了操作在子實體上的級聯,所以如果你有A有一個對B的引用,並且你想在持久化A時堅持引用的B,你可以在A中定義級聯關於B參考的關係,所以在你的手杖中它應該在OneToMany而不是ManyToOne中 –