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의 쿼리를 날린다.