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

Time:06-29

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!

CodePudding user response:

The problem was that the entity was not mapping properly with dto. The solution is explicit mapping plus the answer of Gescof.

Here I found information about explicit mapping: ModelMapper mapping the wrong id

Changed code in the service class:

@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();
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
    }


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

        Board newBoard =  boardRepository.save(boardToSave);

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

Changed code in the entity classes:

@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
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.PERSIST,
                    CascadeType.MERGE
            },
            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(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "account_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Account boardOwnerId;

    @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<>();

CodePudding user response:

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/

  • Related