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/