Search code examples
javadatabasemany-to-manyone-to-many

Cannot create entity where two of the properties are another entity's Id and ArrayList of Ids of another entity


I'm new at Spring Boot's JPA concept so need your help in deciding how to import the ID of another entity and ArrayList of Ids of another entity. I want to create a board, providing an account's Id and ArrayList of Ids of accounts. Following are my Account and Board entities:

@Entity(name = "Account")
@Table(name = "account", uniqueConstraints = {@UniqueConstraint(name = "account_email_unique", columnNames = "email")})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "account_id")
    private Integer accountId;

    @JsonIgnore
    @OneToMany(targetEntity = Board.class, mappedBy = "boardOwnerId")
    private Set<Board> boardSet = new HashSet<>();

    @JsonIgnore
    @ManyToMany(mappedBy = "boardMembers")
    private Set<Board> boards = new HashSet<>();


@Entity(name = "Board")
@Table(name = "board", uniqueConstraints = {@UniqueConstraint(name = "board_name_unique", columnNames = "name")})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "board_id")
    private Integer boardId;

    @ManyToOne(targetEntity = Account.class, cascade = CascadeType.ALL)
    @JoinColumn(name = "account_id", referencedColumnName = "account_id")
    private Account boardOwnerId;

    @ManyToMany
    @JoinTable(name = "board_member", joinColumns = @JoinColumn(name = "board_id"), inverseJoinColumns =
    @JoinColumn(name = "account_id"))
    private Set<Account> boardMembers = new HashSet<>();



@Repository
public interface BoardRepository extends JpaRepository<Board, Integer> {
}


@RestController
@RequestMapping("/boards")
public class BoardController {
    private final BoardService boardService;

    @Autowired
    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    @PostMapping("/create-board")
    ResponseEntity<BoardDtoResponse> createBoard(@Valid @RequestBody BoardDto boardDto) {
        return new ResponseEntity<>(boardService.createBoard(boardDto), HttpStatus.CREATED);
    }
}


@Service
public class BoardServiceImpl implements BoardService {
    private final BoardRepository boardRepository;
    private final ModelMapper modelMapper;

    @Autowired
    public BoardServiceImpl(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
        modelMapper = new ModelMapper();
    }

    @Override
    public BoardDtoResponse createBoard(BoardDto boardDto) {
        Board boardToSave = modelMapper.map(boardDto, Board.class);

        Board newBoard =  boardRepository.save(boardToSave);

        return modelMapper.map(newBoard, BoardDtoResponse.class);
    }
}

I can successfully create an account, but when I want to create a board and pass boardOwnerId and membersIds, it creates a board, but boardOwnerId and membersIds are set to null.

Here is the request via Postman:

Thanks in advance for your time!


Solution

  • As far as I have seen, you should change the mapping between the two entities for both mappings. Let me explain:

    • For the mapping of the board owner (@OneToMany) try to maintain only that one annotation and remove the property with @ManyToOne from Board entity. In addition, change the properties values of the @OneToMany annotation and add a @JoinColumn with next values:
    @Entity(name = "Account")
    @Table(name = "account", uniqueConstraints = {@UniqueConstraint(name = "account_email_unique", columnNames = "email")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @Column(name = "account_id")
        private Integer accountId;
    
        @JsonIgnore
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
        @JoinColumn(name = "boardOwnerId")
        private Set<Board> boardSet = new HashSet<>();
        ...
    
    
    @Entity(name = "Board")
    @Table(name = "board", uniqueConstraints = {@UniqueConstraint(name = "board_name_unique", columnNames = "name")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Board {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @Column(name = "board_id")
        private Integer boardId;
        ...
    

    This is known as a One To Many unidirectional mapping (https://www.bezkoder.com/jpa-one-to-many-unidirectional/).

    • On the other hand you could try to maintain only the @ManyToOne annotation on Board entity, but remove the property with @OneToMany annotation from Account entity with next properties values:
    @Entity(name = "Account")
    @Table(name = "account", uniqueConstraints = {@UniqueConstraint(name = "account_email_unique", columnNames = "email")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @Column(name = "account_id")
        private Integer accountId;
        ...
    
    
    @Entity(name = "Board")
    @Table(name = "board", uniqueConstraints = {@UniqueConstraint(name = "board_name_unique", columnNames = "name")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Board {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @Column(name = "board_id")
        private Integer boardId;
    
        @JsonIgnore
        @ManyToOne(fetch = FetchType.LAZY, optional = false) 
        @JoinColumn(name = "account_id", nullable = false)
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Account boardOwnerId;
        ...
    

    This is known as the default One To Many mapping (https://www.bezkoder.com/jpa-one-to-many/).

    In any case, you see you only have to implement one of the two types of annotations for a One To Many mapping.

    • And last, for the @ManyToMany mappings, try the next implementation (adding fetch and cascade properties values):
    @Entity(name = "Account")
    @Table(name = "account", uniqueConstraints = {@UniqueConstraint(name = "account_email_unique", columnNames = "email")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account {
        ...
    
        @ManyToMany(fetch = FetchType.LAZY,
          cascade = {
              CascadeType.PERSIST,
              CascadeType.MERGE
          },
          mappedBy = "boardMembers")
        @JsonIgnore
        private Set<Board> boards = new HashSet<>();
    
    
    @Entity(name = "Board")
    @Table(name = "board", uniqueConstraints = {@UniqueConstraint(name = "board_name_unique", columnNames = "name")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Board {
        ...
    
        @ManyToMany(fetch = FetchType.LAZY,
          cascade = {
              CascadeType.PERSIST,
              CascadeType.MERGE
          })
        @JoinTable(name = "board_member", joinColumns = @JoinColumn(name = "board_id"), inverseJoinColumns =
        @JoinColumn(name = "account_id"))
        private Set<Account> boardMembers = new HashSet<>();
    

    You can find this implementation design here: https://www.bezkoder.com/jpa-many-to-many/