인프런 커뮤니티 질문&답변

파이팅파이팅님의 프로필 이미지
파이팅파이팅

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

단방향 연관관계

@OneToMany , @ManyToOne 질문 있습니다.

작성

·

189

0

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]
안녕하세요 JPA 강의를 수강 후 게시판에 이미지 첨부를 구현하기 위해 공부하고 있습니다.

1.Board와 Image는 1:N 관계

2.연관관계 주인은 Board

3.cascade = CascadeType.ALL로 옵션을 주어
부모 엔티티(board)가 영속화될 때, 자식 엔티티(Image)도 함께 영속화되도록 했습니다.


위와 같이 설계를 한뒤 코드를 작성했습니다.

근데, Image와 Board 테이블에 모든 데이터가 다 들어가는데

Image 테이블의 board_id가 계속 null값이 들어갑니다.

이 부분이 잘 이해가 안되는데 원인이 무엇일까요?

 

코드는 아래와 같습니다 !

@Entity
@Getter @Setter
@NoArgsConstructor
public class Board {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_id")
    private Long id;

    @Column(name="title")
    private String title;
    @Column(name="content")
    private String content;
    @Column(name="writer")
    private String writer;
    @Column(name="date")
    private String date;

   /* @OneToMany(mappedBy = "board")
    @Nullable
    private List<Image> images = new ArrayList<>();
*/

    @OneToMany(mappedBy = "board",cascade = CascadeType.ALL)
    private List<Image> images = new ArrayList<>();

    @Builder
    public Board(String writer,String title,String content,String date,List<Image> images){
        this.writer=writer;
        this.title=title;
        this.content=content;
        this.date=date;
        this.images=images;
    }
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Image {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fileName;

    @ManyToOne
    @JoinColumn(name = "board_id")
    private Board board;

    private String url;


    @Builder
    public Image(String fileName,String url){
        this.fileName=fileName;
        this.url =url;
    }

}
@Service
@Transactional
@RequiredArgsConstructor
public class BoardService {
    private final BoardRepository boardRepository;
    LocalDateTime currentTime = LocalDateTime.now(); // 현재 시간을 가져옴

    /**
     * Create
     */
    @Transactional
    public Long
    saveBoard(BoardSaveRequest request,List<Image> images){
        Board board= Board.builder()
                .writer(request.writer())
                .content(request.content())
                .title(request.title())
                .date(String.valueOf(currentTime))
                .images(images)
                .build();

        board.setImages(images);
        //cascade = CascadeType.PERSIST : 보드 생성 -> image add
        boardRepository.save(board);

        return board.getId();

    }

@RestController
@RequestMapping("/board")
@RequiredArgsConstructor
@Slf4j
public class BoardController {

    private final BoardService boardService;
    private final ImageService imageService;

    /**
     *  게시글 생성
     * @param writer
     * @param title
     * @param content
     * @param images
     * @return 생성된 게시글 id
     */
   
    @PostMapping()
    public ResponseEntity<ApiUtil.ApiSuccessResult<Long>> createBoard(
            @RequestPart(value="writer") String writer,
            @RequestPart(value="title") String title,
            @RequestPart(value="content") String content,
            @RequestPart(value = "images", required = false) List<MultipartFile> images) throws IOException {

        BoardSaveRequest boardSaveRequest= BoardSaveRequest.builder()
                .writer(writer)
                .content(content)
                .title(title).build();

        List<Image> listImage = imageService.saveImage(images);
        Long saveId = boardService.saveBoard(boardSaveRequest,listImage);

        return ResponseEntity.ok().body(ApiUtil.success(HttpStatus.CREATED,saveId));
    }

 

@Service
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class ImageService {

    public List<Image> saveImage(List<MultipartFile> multipartFiles) throws IOException {
        List<Image> images = new ArrayList<>();

        for(MultipartFile m : multipartFiles){
            m.transferTo(new File("/Users/parkjihyeon/Desktop/imageFolder/"+m.getOriginalFilename()));

            Image image= Image.builder()
                    .fileName(m.getOriginalFilename())
                    .url("/Users/parkjihyeon/Desktop/imageFolder/"+m.getOriginalFilename())
                    .build();

            images.add(image);
        }

        return images;
    }
}

답변 2

0

안녕하세요. 파이팅파이팅님, 공식 서포터즈 코즈위버입니다.

우선, 사용자가 Board 엔터티의 List<Image>를 setter 로 덮어씌우면 안됩니다. 이 경우 JPA가 제대로 동작하지 않을 수 있습니다.

아래와 같은 메서드를 Board 에 작성하여 이미지를 하나씩 추가해보시기 바랍니다.

public void addBoardImage(Image image) {
    image.setBoard(this);
    this.imageList.add(image);
}

감사합니다.

0

안녕하세요, 인프런 AI 인턴입니다.

파이팅파이팅님의 질문을 세심하게 읽었고, JPA에서 @OneToMany@ManyToOne 관계에서 board_idnull로 설정되는 문제에 대해 해결 방안을 제시하는 내용을 찾았습니다. 문제의 핵심은 Entity 간의 연관관계 설정 시, 부모 객체에 자식 객체를 추가하더라도 자식 객체에서 부모 객체를 참조하는 관계를 명확하게 설정해주지 않으면 외래 키(board_id)가 null로 설정될 수 있다는 점입니다.

아래의 정보는 연관 관계 매핑을 할 때 상대 엔티티를 제대로 참조하지 않아 발생하는 문제점과 해결 방안에 대한 설명입니다.

1. 김영한님의 답변(직접적으로 문제를 다루진 않지만 연관 관계 설정에 관한 유용한 팁 제공)

  • cascade = CascadeType.ALL질문에서, 김영한님이 cascade 옵션과 연관관계 설정에 대해 상세하게 설명하고 있습니다. 이는 Entity의 상태 변화를 전파하는 방법에 대해 이해할 수 있는 기초 지식을 제공합니다.

2. 해결 방안

  • 문제 해결의 핵심Image 엔티티 객체를 생성할 때, 그 Image가 속해야 할 Board 엔티티의 참조(board)도 함께 설정해야 한다는 것입니다. 즉, Image 객체를 생성하고 images.add(image)를 통해 Board에 추가할 때, image.setBoard(this) 같은 방식으로 Image 객체에도 Board를 참조하도록 해야 합니다. 이미 구현하신 Board 클래스에서 setUser 메소드가 이런 역할을 수행합니다만, Image 객체에 대한 Board 참조 설정이 누락된 것 같습니다.
public void addImage(Image image) {
    images.add(image);
    image.setBoard(this);
}

이와 같은 메소드를 Board 클래스에 추가하고, 이미지를 추가할 때 이 메소드를 사용하면 Imageboard 필드가 null로 남지 않고 올바르게 참조가 설정됩니다.

3. 구현 방법

BoardServicesaveBoard 메소드 내에서 Board 객체를 생성한 후 Image 객체들을 추가할 때는 단순히 board.setImages(images)를 호출하는 것이 아니라, 각 Image 객체에 대해 위에서 생성한 addImage 메소드를 호출하여 Board 객체에 추가하십시오. 이렇게 하면 각 Image 객체가 자신이 속한 Board를 제대로 참조하게 됩니다.

Board board = Board.builder()
            .writer(request.writer())
            .content(request.content())
            .title(request.title())
            .date(String.valueOf(currentTime))
            .build();

for (Image image : images) {
    board.addImage(image);
}

boardRepository.save(board);

이렇게 사용할 경우, Image 엔티티에 @ManyToOne으로 설정된 Board와의 관계가 올바르게 설정되어 board_id가 올바르게 저장될 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

파이팅파이팅님의 프로필 이미지
파이팅파이팅

작성한 질문수

질문하기