개발환경

Springboot 2.7.0

SpringDataJPA

Lombok

MySQL

 

 

문제사항

JPA 공부하고 기존 진행했던 간단한 게시판 프로젝트에 JPA를 적용 중 이미지 파일 처리에서 문제가 발생.

//imageBoard Entity(사진 게시판)

@Entity
@Data
@Builder
public class ImageBoard {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long imageNo;
    
    @NonNull
    private String imageTitle;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userId")
    @ToString.Exclude
    private Member member;
    
    private Date imageDate;
    
    private String imageContent;
    
    @OneToMany(mappedBy = "imageBoard", fetch = FetchType.LAZY)
    @ToString.Exclude
    private final Set<comment> comments = new HashSet<>();
    
    @OneToMany(mappedBy = "imageBoard", fetch = FetchType.LAZY)
    @ToString.Exclude
    private final Set<ImageData> imageDataSet = new HashSet<>();
}
// ImageData Entity(이미지 파일)

@Entity
@Data
@Builder
public class ImageData {
    
    @Id
    private String imageName;
    
    @ManyToOne
    @JoinColumn(name = "imageNo")
    @ToString.Exclude
    private ImageBoard imageBoard;
    
    @NonNull
    private String oldName;
    
    private int imageStep;
}

이렇게 두가지 엔티티가 이미지 게시판을 구성하게 된다.

그럼 insert를 한다고 했을 때 ImageBoard 엔티티를 먼저 저장해주고 ImageData 엔티티를 저장해줘야 한다.

ImageBoard 테이블에 imageNo가 존재해야 ImageData에 해당 게시글의 파일 정보를 저장할 수 있으니까.

 

최근에 'Entity는 Setter를 최대한 사용하지 않는것이 좋다. 꼭 Entity에 set을 해줘야 한다면 builder를 사용하는것이 좋다.' 라는 포스팅을 봤다.

봤으면 적용해야되니 builder를 통해 처리하고자 했다.

 

//처리코드

// imageBoardInsert 메소드
....
ImageBoard imageBoard = ImageBoard.builder()
                            .member(principalService.checkPrincipal(principal)
                            .imageTitle(request.getParameter("imageTitle"))
                            .imageContent(request.getParameter("imageContent"))
                            .imageDate(Date.valueOf(LocalDate.now()))
                            .build();
imageBoardRepository.save(imageBoard);
...

//파일 처리 메소드(imageBoardInsert 메소드에서 호출)
ImageBoard imageBoard = new ImageBoard();
imageBoard.setImageNo(imageNo);

ImageData imageData = ImageData.builder()
                            .imageboard(imageBoard)
                            .imageName(saveName)
                            .oldName(originalName)
                            .imageStep(step)
                            .build();
                            
imageDataRepository.save(imageData);
....

이렇게 작성했더니 바로 오류가 발생했다.

오류는 imageTitle은 NonNull이기 때문에 null이 들어가면 안된다는것.

처음한 생각으로는 연관관계 매핑을 해놨고 imageNo만 있으면 되겠지? 해서 setImageNo를 했고

안되길래 builder로 imageNo만 넣어서 빌드를 한 다음에 넣어봤는데 그것도 안되고...

@NonNull 어노테이션만 지우면 바로 해결되는 문제이긴 했지만 만약 이걸 사용해야 하는 환경에서 이런 문제에 또 부딪히게 되면 그때가서 또 시간 써야 될것 같아서 방법을 찾아봄.

 

간단하게 설명하자면 연관관계 매핑이 되어있으면 One 에 해당하는 Entity에 Many 에 해당하는 Entity를 담아 한번에 save가 가능하다.

 

처리는 아래처럼 하면 된다.

// ImageBoard Entity

@Entity
@Data
@Builder
public class ImageBoard {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long imageNo;
    
    @NonNull
    private String imageTitle;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userId")
    @ToString.Exclude
    private Member member;
    
    private Date imageDate;
    
    private String imageContent;
    
    @OneToMany(mappedBy = "imageBoard", fetch = FetchType.LAZY)
    @ToString.Exclude
    private final Set<Comment> comments = new HashSet<>();
    
    // cascade = CascadeType.ALL 추가
    @OneToMany(mappedBy = "imageBoard", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @ToString.Exclude
    private final Set<ImageData> imageDataSet = new HashSet<>();
    
    //코드 추가
    public void addImageData(ImageData imageData) {
        
        //imageDataSet에 객체 추가
        imageDataSet.add(imageData);
        //imageData에도 imageBoard 객체를 추가
        imageData.setImageBoard(this);
    }
}
// 처리 코드

void imageInsertProc(.......){
    ....
    
    ImageBoard imageBoard = ImageBoard.builder()
                                .member(principalService.checkPrincipal(principal))
                                .imageTitle(request.getParameter("imageTitle"))
                                .imageContent(request.getParameter("imageContent"))
                                .imageDate(Date.valueOf(LocalDate.now())
                                .build();
                                
    imageInsert(...... , imageBoard);
    
    imageBoardRepository.save(imageBoard);
    ....
}
//파일 처리 메소드 호출

void imageInsert(..... , ImageBoard imageBoard) {
    ...
    ImageData imageData = ImageData.builder()
                            .imageName(saveName)
                            .oldName(originalName)
                            .imageStep(step)
                            .build();
                            
    imageBoard.addImageData(imageData);
    ....
}

이렇게 처리했다.

imageBoard를 먼저 빌드한 뒤 파일 처리 메소드를 호출할 때 imageBoard를 같이 넘겨준다.

그럼 파일 처리과정에서 imageData를 빌드할때는 ImageBoard를 제외한 나머지 값들로 빌드해준다.

그리고 Imageboard 엔티티에서 만들어놓은 addImageData 메소드를 통해 두 엔티티에 객체들을 저장할 수 있도록 해준다.

 

그리고나서 파일처리가 끝나면 Repository를 통해 save를 해주게 되면 연관관계에 있는 두 엔티티의 데이터가 한번에 저장되게 된다.

그럼 파일 처리 도중에 ImageBoard 엔티티 데이터에 대해 굳이 신경 쓸 필요도 없고 저장도 간단하게 해줄 수 있다.

 

Reference

https://data-make.tistory.com/730

 

[JPA] 양방향 관계 Entity 저장하기

JPA 양방향 관계 Entity 저장하기 인간은 습관의 동물이다. 습관에는 대부분 좋은 습관이 많지만 그중에 나쁜 습관도 있다. 그것은 바로.. 원리를 모르고 개발하는 습관이다. 😯 . '요로케할 때 이

data-make.tistory.com

 

+ Recent posts