Java/Spring Boot 게시판

spring boot 게시판 - 14 <게시물 이미지 기능>

요술공주밍키 2023. 6. 19. 08:09

2023.06.16 - [Java/spring 게시판] - spring boot 게시판 - 13 <프로필 사진 구현>

 

spring boot 게시판 - 13 <프로필 사진 구현>

2023.06.01 - [Java/spring 게시판] - spring boot 게시판 - 12 spring boot 게시판 - 12 2023.02.23 - [Java/spring 게시판] - spring boot 게시판 - 11 spring boot 게시판 - 11 2023.02.21 - [Java/spring 게시판] - spring boot 게시판 - 10 spri

magicmk.tistory.com

게시판 만들기 마지막 코스 게시물에 이미지 추가하는 기능을 만들었다.

기본적인 게시판 만들기는 3일 이내로 만드는 게 좋다는데 게으르고 멍청해서 반년이 걸렸다..

그래도 마무리한게 다행인가...


Table 작성

지난번 프로필 사진을 구현했을 때처럼 간단하게 Entity를 만들어준다.

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "boardImage")
public class BoardImage {

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

    @Column(nullable = false)
    private String url;

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

위와 같이 Entity를 만들었으면 기존 Board Entity에서도 지금 만든 것을 확인할 수 있도록 연관관계를

설정해 준다.

 

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "board")
public class Board extends BaseEntity{

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

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    @OrderBy("id asc")
    private List<Comment> comments;

	// 이부분이 추가
    @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    @OrderBy("id asc")
    private List<BoardImage> boardImages;

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

보이는 것처럼 기존 코드에서 주석처리 한 곳을 추가하였다.


 비즈니스 로직

그리고 게시물에 들어가는 이미지는 다중 이미지로 전달할 것이기 때문에

다중 이미지를 전달받을 DTO를 먼저 만들었다.

🟧 DTO

⏹️ BoardImageUploadDTO.java

@Data
public class BoardImageUploadDTO {

    private List<MultipartFile> files;
}

그저 MultipartFile을 List로 받는 간단한 DTO이다.

 

그리고 이후에 나올 테지만 board detail 페이지에서 board의 이미지 url을 넘겨줘야 하기 때문에

기존 BoardResponseDTO의 코드를 수정하였다.

@Getter
@NoArgsConstructor
public class BoardResponseDTO {

    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdAt;
    private String username;
    private String email;
    // 이부분 추가
    private List<String> imageUrls;

    @Builder
    public BoardResponseDTO(Board board) {
        this.id = board.getId();
        this.title = board.getTitle();
        this.content = board.getContent();
        this.createdAt = board.getCreatedAt();
        this.username = board.getMember().getUsername();
        this.email = board.getMember().getEmail();
        // 이부분 추가
        this.imageUrls = board.getBoardImages().stream()
                .map(BoardImage::getUrl)
                .collect(Collectors.toList());
    }
}

위와 같이 뷰에서 url을 통해 이미지를 보여줄 수 있게끔 이미지 url을 추가해 줬다.

 

 

이제 DTO도 만들었다면 기존에 게시물을 작성하는 컨트롤러를 변경해 준다.

 

🟧 Controller

⏹️ BoardController.java

@PostMapping("/write")
public String write(BoardWriteRequestDTO boardWriteRequestDTO,
                    @ModelAttribute BoardImageUploadDTO boardImageUploadDTO,
                    Authentication authentication) {

    logger.info("boardImageDTO is {}", boardImageUploadDTO);
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    boardService.saveBoard(boardWriteRequestDTO, boardImageUploadDTO, userDetails.getUsername());

    return "redirect:/";
}

기존의 컨트롤러에서 새로 만든 BoardImageUploadDTO를 추가한 뒤 service로 같이 넘겨준다.

 

🟧 Service

⏹️ ImageServiceImpl.java

@Override
@Transactional
public Long saveBoard(BoardWriteRequestDTO boardWriteRequestDTO,
                      BoardImageUploadDTO boardImageUploadDTO,
                      String email) {
    Member member = memberRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("이메일이 존재하지 않습니다."));

    Board result = Board.builder()
            .title(boardWriteRequestDTO.getTitle())
            .content(boardWriteRequestDTO.getContent())
            .member(member)
            .build();

    boardRepository.save(result);

	// 이 부분 추가
    if (boardImageUploadDTO.getFiles() != null && !boardImageUploadDTO.getFiles().isEmpty()) {
        for (MultipartFile file : boardImageUploadDTO.getFiles()) {
            UUID uuid = UUID.randomUUID();
            String imageFileName = uuid + "_" + file.getOriginalFilename();

            File destinationFile = new File(uploadFolder + imageFileName);

            try {
                file.transferTo(destinationFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            BoardImage image = BoardImage.builder()
                    .url("/boardImages/" + imageFileName)
                    .board(result)
                    .build();

            boardImageRepository.save(image);
        }
    }

    return result.getId();
}

기존 board를 업로드하던 Service에 파일을 전달받아 저장하고 DB에 저장하는 로직을 추가한 뒤

실패 시 롤백을 위해 @Transactional 어노테이션을 추가하였다.

 

🟧 Repository

⏹️ boardImageRepository.java

public interface BoardImageRepository extends JpaRepository<BoardImage, Long> {
}

레포지토리는 별거 없이 extends만 해줬다.


 View

위와 같이 이미지를 등록하고 게시물 상세페이지로 들어가면 등록했던 이미지가 나오도록

구현하였다.


 

길고 길었던 게시판의 여정이 끝이 났다. 사실 실제로 개발했던 시간만 생각하면 얼마 안 되지만 뭐가 그리

귀찮고 하기 싫었는지 뒹굴뒹굴 거리다가 이제야 마무리를 지었다.

낭비했던 시간만 잘 활용했으면 프로젝트 2개쯤은 더 나왔을 텐데 하는 아쉬움도 있다.

항상 시간 낭비하지 말고 유튜브나 만화에 시간 쏟지 말고 활동적인 것을 하자고 다짐하지만

유혹 앞에 나는 연약하고 늘 무너지는 것 같다. 그래도 이렇게 꾸준히 해서 완성하는 게 어디인가 싶다 ^^

 

https://github.com/Kimmingki/board

 

GitHub - Kimmingki/board: 강의만 듣다 때려치우지 말고 조금씩이라도 개발해보자...!!

강의만 듣다 때려치우지 말고 조금씩이라도 개발해보자...!! Contribute to Kimmingki/board development by creating an account on GitHub.

github.com