포스트

240223 기록(2)

BoardApplication

  • 포스트맨 사용하여 가동테스트
Controller
  • 파일명 BoardController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import com.example.board.dto.BoardRequestDto;
import com.example.board.dto.BoardResponseDto;
import com.example.board.dto.SuccessResponseDto;
import com.example.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

  private final BoardService boardService;
  
  // 전체조회
  @GetMapping
  public List<BoardResponseDto> getBoardList(){
    return boardService.getBoardList();
  }
  
  // 상세조회
  @GetMapping("/{id}")
  public BoardResponseDto getBoard(@PathVariable Long id){
    return boardService.getBoardDetail(id);
  }

  // 신규생성
  @PostMapping
  public BoardResponseDto createBoard(@RequestBody BoardRequestDto requestDto){
    log.info("{}", "requestDto :: " +  requestDto.getTitle() + " , " + requestDto.getContents()  + " , " + requestDto.getWriter() + " , " +  requestDto.getEmail());
    return boardService.createBoard(requestDto);
  }

  // 수정
  @PatchMapping("/{id}")
  public BoardResponseDto updateBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) throws Exception{
    return boardService.updateBoard(id, requestDto);
  }

  // 삭제
  @DeleteMapping("/{id}")
  public SuccessResponseDto deleteBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto){
    return boardService.deleteBoard(id, requestDto);
  }
}
dto
  • 기본적으로 entity는 데이터를 주고 받는 용으로 사용하는 것을 지양하기 때문에 데이터를 주고 받는 dto를 만들어 사용한다.

  • 파일명 BoardRequestDto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class BoardRequestDto {
  private String title;
  private String contents;
  private String writer;
  private String email;
}
  • 게시글 작성 시 입력되는 값들

  • 파일명 BoardResponseDto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.example.board.model.Board;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class BoardResponseDto {
  private Long id;
  private String title;
  private String contents;
  private String writer;
  private String email;
  private LocalDateTime createdAt;
  private LocalDateTime modifiedAt;

  public BoardResponseDto(Board entity) {
    this.id = entity.getId();
    this.title = entity.getTitle();
    this.contents = entity.getContents();
    this.writer = entity.getWriter();
    this.email = entity.getEmail();
    this.createdAt = entity.getCreatedAt();
    this.modifiedAt = entity.getModifiedAt();
  }
}
  • 게시글 조회 시 출력되는 값들

  • 파일명 SuccessResponseDto
1
2
3
4
5
6
7
8
9
10
11
import lombok.Getter;

@Getter
public class SuccessResponseDto {

  private boolean success;

  public SuccessResponseDto(boolean success){
    this.success = success;
  }
}
  • 게시글 삭제 시 성공 여부
model

-파일명 Board

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import com.example.board.dto.BoardRequestDto;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@NoArgsConstructor
// 허용 범위를 설정하여 private는 proxy객체를 만들지 못하고, public은 무분별한 객체 생성이 됨으로 protected로 설정
public class Board extends Timestamped {

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

  @Column(nullable = false, length = 500)
  private String title;

  @Column(nullable = false, length = 3000)
  private String contents;

  @Column(nullable = false)
  private String writer;

  @Column(nullable = false)
  private String email;

  public Board(BoardRequestDto requestDto) {
    this.title = requestDto.getTitle();
    this.contents = requestDto.getContents();
    this.writer = requestDto.getWriter();
    this.email = requestDto.getEmail();
  }

  public void update(BoardRequestDto requestDto) {
    this.title = requestDto.getTitle();
    this.contents = requestDto.getContents();
    this.writer = requestDto.getWriter();
    this.email = requestDto.getEmail();
  }
}
  • jpa를 사용하려고 했기 때문에 @Entity 사용하여 DB 테이블에 대한 정보를 만든다.
  • @Id는 테이블에서 PK라고 보면된다.
  • GeneratedValue(strategy = GenerationType.IDENTITY) 자동으로 값을 올려주는 어노테이션. auto Increase라고 보면된다.
  • 생성자의 경우는 글 작성하여 입력 시, update는 글 수정 시 사용하는 메소드

  • 파일명 Timestamped
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass // 공통으로 사용하는 맵핑 정보만 상속하는 부모 클래스에 선언
@EntityListeners(AuditingEntityListener.class) // JPA에서 제공하는 EventListener
public class Timestamped {

  @CreatedDate
  private LocalDateTime createdAt;

  @LastModifiedDate
  private LocalDateTime modifiedAt;
}
  • Timestamped의 경우는 DB에 테이블로 저장되는 entity가 아니다
  • 글 작성 시 혹은 수정 시 현재 시간을 넣는 등 특정 작업이 여기 저기서 반복되는 경우 사용하기 위해
  • 추상화한 클래스라고 보면 된다.
  • 현재 BoardApplication에서는 딱히 여기저기서 호출하여 사용되지는 않고 있다.
repository
  • 파일명 BoardRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.example.board.model.Board;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {
                                            // <엔티티, 엔티티의 ID값>

  List<Board> findAllByOrderByModifiedAtDesc();

  @Override
  <S extends Board> S saveAndFlush(S entity);
}
  • JpaRepository에서 만들어진 메소드들도 있지만 없는 경우는 형식에 맞게 메소드를 만들어야 한다.
  • saveAndFlush 는 Service에서 update 작성 시 @Transactional, save(), saveAndFlush()를 이것저것 테스트할 때 작성된 것 같다
service
  • 파일명 saveAndFlush
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.example.board.dto.BoardRequestDto;
import com.example.board.dto.BoardResponseDto;
import com.example.board.dto.SuccessResponseDto;

import java.util.List;

public interface BoardService {

  List<BoardResponseDto> getBoardList();

  BoardResponseDto createBoard(BoardRequestDto requestDto);

  BoardResponseDto getBoardDetail(Long id);

  BoardResponseDto updateBoard(Long id, BoardRequestDto requestDto) throws Exception;

  SuccessResponseDto deleteBoard(Long id, BoardRequestDto requestDto);
}
  • 파일명 BoardServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import com.example.board.dto.BoardRequestDto;
import com.example.board.dto.BoardResponseDto;
import com.example.board.dto.SuccessResponseDto;
import com.example.board.model.Board;
import com.example.board.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{

  private final BoardRepository boardRepository;

  @Override
  public List<BoardResponseDto> getBoardList() {
    return boardRepository.findAllByOrderByModifiedAtDesc().stream().map(BoardResponseDto::new).toList();
    // 수정일시 기준 내림차순
    // findAll은 JPA가 기본적으로 제공해 주지만, 수정일시 내림차순은 BoardRepository에서 따로 선언 필요
    // BoardResponseDto에서 Board 엔티티를 넣으면 매개변수 생성자가 실행
    // map(BoardResponseDto::new)를 통해 간편하게 dto로 바꿔줄 수 있다.
  }

  @Override
  public BoardResponseDto createBoard(BoardRequestDto requestDto) {
    Board board = new Board(requestDto);
    boardRepository.save(board);
    return new BoardResponseDto(board);
  }

  @Override
  public BoardResponseDto getBoardDetail(Long id) {
    return boardRepository.findById(id).map(BoardResponseDto::new).orElseThrow(
            () -> new IllegalArgumentException("아이디가 존재하지 않습니다")
    );
    // id를 가진 데이터를 boardRepository에서 찾아서 BoardResponseDto 객체로 만들어서 반환
    // 만약 boardRepository에 해당 id의 데이터가 없다면, 예외처리한다.
  }

  @Override
  public BoardResponseDto updateBoard(Long id, BoardRequestDto requestDto) throws Exception{
    Board board = boardRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("아이디가 존재하지 않습니다")
    );

    if(!requestDto.getEmail().equals(board.getEmail())){
        throw new Exception("게시글 작성자가 아닙니다.");
    }
    board.update(requestDto);
    board = boardRepository.save(board);
    return new BoardResponseDto(board);
  }

  @Override
  public SuccessResponseDto deleteBoard(Long id, BoardRequestDto requestDto) {
    Board board = boardRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("아이디가 존재하지 않습니다")
    );

    boardRepository.deleteById(id);

    return new SuccessResponseDto(true);
  }
}
  • 수정의 경우 @Transactional을 사용하는 방법과 save(), saveAndFlush()를 사용하는 경우가 있다
  • @Transactional의 경우 메서드가 종료될 때 쿼리문을 날린다. 그리고 트랜잭션 처리 한다.
  • save(), saveAndFlush() 의 경우는 트랜잭션이라기 보다는 덮어쓴다는 개념 merge의 개념이다.
  • 한 메소드에서 객체를 만듬(insert) 3번의 수정(update)를 수행했다면
  • save()의 경우는 insert와 마지막 update의 쿼리만을 날린다
  • saveAndFlush()의 경우는 insert와 모든 update의 쿼리를 날린다.
깃 주소

GitHub