게시글을 작성할 때 이미지 파일도 업로드 가능하게 기능을 추가하였다.
참고로 파일 업로드를 구현할 때 폼에 enctype="multipart/form-data"를 추가해줘야한다.
해시태그는 구현중이다........
file:
  dir: /Users/이름/Desktop/file-upload/
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Board extends BaseTimeEntity {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String title;
  @Lob
  private String content;
  @ManyToOne(fetch = FetchType.LAZY) "user" 추가해주면 됨.
  @JoinColumn(name = "user_id")
  private User user;
  @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // 글이 삭제되면 댓글 모두 삭제
  @OrderBy("createdDate")
  private List<Reply> replies = new ArrayList<>();
  @OneToMany(mappedBy = "board",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  private List<ImgFile> imgFiles = new ArrayList<>(); 
  @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
  private List<Tag> tags = new ArrayList<>();
  public void setUser(User user){
    this.user = user;
  }
  public void setImgFile(List<ImgFile> imgFiles){
    this.imgFiles = imgFiles;
    for (ImgFile file : imgFiles) {
      file.setBoard(this);
    }
  }
  public void addTag(Tag tag){
    tags.add(tag);
    tag.setBoard(this);
  }
  
  public void update(String title, String content) {
    this.title = title;
    this.content = content;
  }
  @Builder
  public Board(String title, String content) {
    this.title = title;
    this.content = content;
  }
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED) // 이거 추가해주니까 되네... 이유가..
public class ImgFile extends BaseTimeEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String originFilename; // 업로드된 파일 이름
  private String storeFilename; // 저장한 파일명 ex) 132-5sdf-23451-1as.png -> UUID로 식별자 생성 + 확장자
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "board_id")
  private Board board;
  @Builder
  public ImgFile(String originFilename, String storeFilename) {
    this.originFilename = originFilename;
    this.storeFilename = storeFilename;
  }
  public void setBoard(Board board) {
    this.board = board;
  }
}
@Component
public class FileStore {
  @Value("${file.dir}") // application.yaml or application.properties에 지정한 파일 경로
  private String fileDir;
  
  // 파일 저장 경로 Full
  public String getFullPath(String filename){
    return fileDir + filename;
  }
  
  public ImgFile storeFile(MultipartFile multipartFile) throws IOException {
    if (multipartFile.isEmpty()){
      return null;
    }
    String originalFileName = multipartFile.getOriginalFilename();
    String ext = extractedExt(originalFileName);
    String storeFileName = createStoreFileName(ext);
    multipartFile.transferTo(new File(getFullPath(storeFileName)));
    return ImgFile.builder()
        .originFilename(originalFileName)
        .storeFilename(storeFileName)
        .build();
  }
  // 여러 개 업로드 하는 경우
  public List<ImgFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException{
    List<ImgFile> files = new ArrayList<>();
    for (MultipartFile multipartFile : multipartFiles) {
      if (multipartFile.isEmpty()) {
        return null;
      }
      files.add(storeFile(multipartFile));
    }
    return files;
  }
  
  // UUID를 활용해 저장용 파일명
  private String createStoreFileName(String ext) {
    String uuid = UUID.randomUUID().toString();
    String storedFileName = uuid + "." + ext;
    return storedFileName;
  }
  // 파일 확장자 추출
  private String extractedExt(String originalFileName) {
    int pos = originalFileName.lastIndexOf(".");
    String ext = originalFileName.substring(pos + 1);
    return ext;
  }
}
@Service
@RequiredArgsConstructor
public class FileService {
  private final FileStore fileStore;
  private final FileRepository fileRepository; 
  public List<ImgFile> transferImgFile(List<MultipartFile> imgFile) throws IOException {
    List<ImgFile> imgFiles = fileStore.storeFiles(imgFile);
    return imgFiles;
  }
  public void deleteBeforeFile(Board board){
    if (!board.getImgFiles().isEmpty()) {
      // 이전 첨부파일 삭제
      if (!board.getImgFiles().isEmpty()) {
        for (ImgFile imgFile : board.getImgFiles()) {
          // 파일 시스템에서 실제 이미지 파일 삭제
          File imgFileOnDisk = new File(fileStore.getFullPath(imgFile.getStoreFilename()));
          if (imgFileOnDisk.exists()) {
            imgFileOnDisk.delete();
          }
          // 데이터베이스에서 이미지 파일 레코드 삭제
          this.delete(imgFile);
        }
      }
    }
  }
  public void delete(ImgFile imgFile){
    fileRepository.delete(imgFile);
  }
  
}
public interface FileRepository extends JpaRepository<ImgFile, Long> {
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BoardSaveRequestDto {
  private String title;
  private String content;
  private List<MultipartFile> imgFiles;
  public Board toEntity() {
    return Board.builder()
        .title(title)
        .content(content)
        .imgFiles(new ArrayList<>())
        .build();
  }
}
...
// 글 등록
  @PostMapping("/board/form")
  public String boardSave(@ModelAttribute BoardSaveRequestDto boardSaveRequestDto,
                          @RequestBody TagSaveRequestDto tagSaveRequestDto, // 해시태그
                          HttpSession session) throws IOException {
    User loginUser = (User) session.getAttribute("loginUser");
    boardService.save(boardSaveRequestDto, loginUser);
    return  "redirect:/boards";
  }
...
// 게시판 등록
  private final BoardRepository boardRepository;
  private final FileService FileService;
  
  ...
  @Transactional
  public Long save(BoardSaveRequestDto request, User user) throws IOException {
    List<ImgFile> imgFiles = FileService.transferImgFile(request.getImgFiles());// List<multipartfile> -> List<ImgFile>
    Board board = request.toEntity(); // title, content
    board.setUser(user);
    if (imgFiles == null){
      board.setImgFile(new ArrayList<>()); // 첨부파일이 없더라도 게시글이 저장이 되도록하기 위해서 추가함. 
    } else {
      board.setImgFile(imgFiles); //
    }
    Board savedBoard = boardRepository.save(board);
    return savedBoard.getId();
  }
다음은 게시글을 수정할 때의 로직이다.
// 게시판 등록
 // 게시글 수정
  @Transactional
  public void update(Long id, BoardUpdateRequestDto request) throws IOException {
    Board board = boardRepository.findById(id)
        .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 포스트입니다."));
    List<ImgFile> imgFiles = FileService.transferImgFile(request.getImgFiles()); // 새로 업로드한 파일
    FileService.deleteBeforeFile(board); // 이전에 업로드 한 파일 삭제
    // 수정한 첨부파일로 연관관계 매핑
    board.setImgFile(imgFiles);
    board.update(request.getTitle(), request.getContent());



뭔가 부족함이 많은 코드인거같다. 계속 공부하면서 리팩토링 해야겠다.
피드백을 주시면 감사하겠습니다.........