스터디를 통해 스프링부트와 AWS로 혼자 구현하는 웹 서비스(저자 이동욱) 서적을 공부하는 중입니다.
공부/실습한 내용을 정리한 포스팅입니다.
책에 모르는 부분이 있으면 구글링하거나 챗gpt에 물어봐서 보충하였습니다.
(아직 초보라 모르는 부분이 많아 이것저것 다 적었습니다.)
참고한 사이트 출처는 포스팅 맨 하단에 적었습니다.
web.dto
패키지에 클래스로 생성
PostsSaveRequestDto
- 등록요청package com.webservice.springboot.springboot_board.web.dto;
import com.webservice.springboot.springboot_board.domain.posts.Posts;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
private String title;
private String content;
private String author;
@Builder
public PostsSaveRequestDto(String title, String content, String author){
this.title = title;
this.content = content;
this.author = author;
}
public Posts toEntity() {
return Posts.builder()
.title(title)
.content(content)
.author(author)
.build();
}
}
PostsUpdateRequestDto
- 수정요청package com.webservice.springboot.springboot_board.web.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
private String title;
private String content;
@Builder
public PostsUpdateRequestDto(String title, String content){
this.title=title;
this.content=content;
}
}
PostResponseDto
- 조회응답package com.webservice.springboot.springboot_board.web.dto;
import com.webservice.springboot.springboot_board.domain.posts.Posts;
import lombok.Getter;
@Getter
public class PostResponseDto {
private Long id;
private String title;
private String content;
private String author;
public PostResponseDto(Posts entity){
this.id=entity.getId();
this.title=entity.getTitle();
this.content=entity.getContent();
this.author=entity.getAuthor();
}
}
@NoArgsConstructor
사용하지 않는 이유@RequestBody
와 같은 annotation을 통해 요청이 들어올 시, Controller 메서드 매개변수 인자로 들어가는 것이 아니기 때문.@RequestBody
를 통해 가져올 때, 바인딩을 ObjectMapper
가 수행. 이때 ObjectMapper가 매핑 시, DTO를 DTO의 기본 생성자를 이용하여 생성. 기본 생성자 없을 시, 맵핑 오류 발생.Jackson
라이브러리의 ObjectMapper
를 사용하여 JSON 매핑web
패키지에 클래스로 생성
PostApiController
package com.webservice.springboot.springboot_board.web;
import com.webservice.springboot.springboot_board.service.posts.PostsService;
import com.webservice.springboot.springboot_board.web.dto.PostResponseDto;
import com.webservice.springboot.springboot_board.web.dto.PostsSaveRequestDto;
import com.webservice.springboot.springboot_board.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RequiredArgsConstructor
@RestController
public class PostApiController {
private final PostsService postsService;
@PostMapping("/api/v1/posts")
public Long save(@RequestBody PostsSaveRequestDto requestDto){
return postsService.save(requestDto);
}
@PutMapping("/api/v1/posts/{id}")
public Long update(@PathVariable Long id,@RequestBody PostsUpdateRequestDto requestDto){
return postsService.update(id, requestDto);
}
@GetMapping("/api/v1/posts/{id}")
public PostResponseDto findById(@PathVariable Long id){
return postsService.findById(id);
}
}
service.posts
패키지에 클래스로 생성
PostsService
package com.webservice.springboot.springboot_board.service.posts;
import com.webservice.springboot.springboot_board.domain.posts.Posts;
import com.webservice.springboot.springboot_board.domain.posts.PostsRepository;
import com.webservice.springboot.springboot_board.web.dto.PostResponseDto;
import com.webservice.springboot.springboot_board.web.dto.PostsSaveRequestDto;
import com.webservice.springboot.springboot_board.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
@Transactional
public Long save(PostsSaveRequestDto requestDto) {
return postsRepository.save(requestDto.toEntity()).getId();
}
@Transactional
public Long update(Long id, PostsUpdateRequestDto requestDto){
Posts posts = postsRepository.findById(id)
.orElseThrow(()->new IllegalArgumentException("해당 게시글이 없습니다. id="+id));
posts.update(requestDto.getTitle(),requestDto.getContent());
return id;
}
public PostResponseDto findById(Long id){
Posts entity = postsRepository.findById(id)
.orElseThrow(()-> new IllegalArgumentException("해당 게시글이 없습니다. id="+id));
return new PostResponseDto(entity);
}
}
@Autowired
를 사용한 필드 주입 방식 : 권장하지 않음.public class PostsApiController {
@Autowired
private final PostsService postService;
}
@Autowired
를 사용한setter
주입 방식 : 권장하지 않음.public class PostsApiController {
private PostsService postsService;
@Autowired
public void setPostsService(PostsService postService){
this.postService = postService;
}
}
생성자
: 가장 권장하는 방식. @RequiredArgsConstructor
을 사용함으로써 롬복의 @RequiredArgsConstructor
가 대신 생성. 순환참조를 방지할 수 있는 장점이 있음.@Autowired
가 붙은 필드의 Bean을 생성(setter 주입 방식의 경우, 생성자 인자에 사용하는 Bean 생성) 그리고 실제로 사용하는 시점에 주입함. 실제로 사용하는 시점이란, 순환참조를 일으킬 수 있는 메서드를 호출하는 시점이며 이때 순환참조 문제가 발생.update
기능 중 DB에 쿼리를 날리는 부분이 없는 이유: JPA의 영속성 컨텍스트 때문. 영속성 컨텍스트란 Entity를 영구 저장하는 환경. 일종의 논리적 개념이라 보면 되며, JPA의 핵심 내용은 Entity가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈림. JPA의 Entity Manager가 활성화된 상태로(Spring Data Jpa는 기본 옵션) 트랜잭션 안에서 DB에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태. 이 상태에서 값을 변경 시, 트랜잭션이 끝나는 시점에 해당 테이블에 변경분 반영. 즉, Entity 객체 값 변경 시 별도로 update 쿼리를 날릴 필요가 없음. dirty checking
이라 불림.
※ 이해가 안되어서 더 찾아본 영속성 컨텍스트 내용
: 어플리케이션과 DB사이에서 객체를 보관하는 가상의 DB같은 역할.
서비스별로 하나의 EntityManager Factory가 존재하며 Entity Manager Factory에서 DB에 접근하는 트랜잭션이 생길 때 마다 쓰레드 별로 Entity Manager를 생성하여 영속성 컨텍스트에 접근.
EntityManager에 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리.
영속성 컨텍스트는 EntityManager를 생성할 때 만들어지며 EntityManager를 통해 영속성 컨텍스트에 접근하고 관리.
※ dirty checking
: dirty란 상태의 변화가 생김을 의미. dirty checking이란 상태 변경 검사를 의미. JPA에서 트랜잭션이 끝나는 시점에 변화가 있는 모든 Entity 객체를 DB에 자동으로 반영. 기준은 최초 조회 상태.
public void update(String title,String content){
this.title = title;
this.content = content;
}
update
메서드 추가생성자 주입을 @Autowired를 사용하는 필드 주입보다 권장하는 하는 이유
스프링 순환 참조(spring circular reference)
[Spring Boot] 순환참조문제
DTO에 기본 생성자가 필요한 이유
[Spring JPA] 영속성 컨텍스트(Persistence Context)
더티 체킹 (Dirty Checking)이란?
많은 도움이 되었습니다, 감사합니다.