1.컨트롤러에 있는 예외처리를 한 클래스에서 정리하기

**@ControllerAdvice
-->이 클래스가 Controller단에서 발생하는 모든 예외를 모니터링 하고, Controller에서 예외가 발생하면
인터셉트해온다고 선언하는 어노테이션
(Controller어노테이션을 붙인 모든 컨트롤러를 모니터링 하는 것)**

public class CommonExceptionHandler {
    
    
    **@ExceptionHandler(EntityNotFoundException.class)
 -->메서드에 ExceptionHandler어노테이션을 붙이고()안에 처리할 예외 클래스를 지정하면
 해당 예외가 발생했을 때, 이 메서드를 실행하겠다는 뜻**   
    public ResponseEntity<?>  thisMethod(EntityNotFoundException e) {
        return new ResponseEntity<>(new CommonErrorDto(HttpStatus.NOT_FOUND.value(), e.getMessage()), HttpStatus.NOT_FOUND);
    }
**-->컨트롤러 단에서 EntityNotFound가 발생하면, thisMethod가 실행되고 ResponseEntity가 리턴된다.
ResponseEntity<>(A,B)는 A가 HTTP프로토콜 바디부에 담기고, B가 헤더부분에 담긴다.
이 객체를 리턴하는 이유가 헤더뿐만 아니라 바디부분에도 상태코드와 상태메세지를 넘겨 프론트에서
작업하기 편하게 하기 위함.
따라서, int타입과, String타입의 속성을 가진 CommonErrorDto를 정하고 int타입 속성에 HttpStatus.NOT_FOUND.value(),
Stirng 타입의 속성에 에러메시지를 넣으면 결과적으로 우리는 HTTP프로토콜 본문에도 상태코드와 상태메세지를
전달할 수 있는 것이다!**

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<?> illegalArgument(IllegalArgumentException e) {
        return new ResponseEntity<>(new CommonErrorDto(HttpStatus.BAD_REQUEST.value(), e.getMessage()), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> illegal(MethodArgumentNotValidException e) {
        return new ResponseEntity<>(new CommonErrorDto(HttpStatus.BAD_REQUEST.value(), e.getMessage()), HttpStatus.BAD_REQUEST);
    }
}

Post Api만들어보기

아래 요구사항에 맞게 만들어보아야한다.

@Entity
@NoArgsConstructor
@Getter
@AllArgsConstructor
@Builder
**-->생성자를 유연하게 만들게 해주는 어노테이션. 이 어노테이션이 붙으면 생성자의 매개변수 개수,
순서의 제한 없이 새로운 객체를 만들 수 있다.(그렇다고 없는 속성을 매개변수로 넣으면 안됨.)**
**-->Builder어노테이션을 선언하기 전에 반드시 AllArgsConstructor어노테이션이 선언되어야한다.**
**-->이제 Post 객체를 post.builder.build를 통해 유연하게 만들어낼 수 있다.**

public class Post extends BaseTimeEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) **-->오토인크리먼트 설정**
    private Long id;
    @Column(nullable = false) 
    private String title;
    **-->title필드에 null값을 허용하지 않는다. 그러나! 에러는 스프링에서 터지는게 아니라 db에서 터지는 것
    title이 없는 채로 값을 받아도 스프링에서는 그대로 db에 전달하고 db에서 null값을 허용하지
    않기 때문에 에러가 터진다. 하지만 스프링은 이에대해 500번, 서버오류로 에러코드를 보냄에 주의
    그리고 db에서 에러가 터지는 것은 위험하기 때문에 스프링에서 에러가 터지도록 조치를 취해주어야한다.**
    private String contents;

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "member_id") 
    private Member member;
**-->Post는 속성값으로 다른 엔티티인 Member를 가지는데 
한 엔티티가 다른 엔티티를 속성값을 가진다는 것은 db상에서 외래키가 연결된 것이다.
따라서 스프링이 이를 인식할 수 있게 몇대몇 관계(@ManyToOne)로 연결(@JoinColumn)되었는지
두 어노테이션으로 알려줘야한다.

-->ManyToOne은 Post입장에서 Member와의 관계가 다대일 관계이기 때문에 붙는 어노테이션.
만약 Member가 속성값으로 Post를 가진다면 그땐 Member클래스에 OneToMany어노테이션을 붙여야한다.**

**-->JoinColumn(name=" ") 외래키 연결 관계를 설정하고 테이블에 해당 외래키 칼럼에 대해 이름을 붙이는 어노테이션
스프링에서 Member는 객체지만, DB는 객체개념이 없다. 따라서 DB에 Member그자체를 저장할 순없는데
스프링JPA는 자동으로 객체에 대해 객체의 id값을 db에 저장시켜준다. 
따라서 post의 Member속성값에 대해서는 db에 member_id라는 컬럼으로 Member객체의 id를 저장한다.**

    public PostListRes toListDto(){
//        return new PostListRes(this.id,this.title);
        return PostListRes.builder().id(this.id).title(this.title).build();
    }

**-->Member객체가 조회용dto객체인 PostListRes클래스로 만드는 과정을 메서드화 한 것
이때 PostListRes에도 Builder어노테이션을 붙였기 때문에 위와 같이
 PostListRes.builder().넣을 매개변수들.build() 를 통해 객체를 만들어낼 수 있다.**

    public PostDetailDto toDetailDto(String email){
        return PostDetailDto.builder().id(this.id).title(this.title).contents(this.contents).memberEmail(email).build();
    }

}

컨트롤러 계층

@RequestMapping("/post")
@RestController
**-->RestController어노테이션에는 @ResponseBody어노테이션이 내장되어 있어 이 클래스에 
있는 모든메서드에 자동으로 리스폰스바디 어노테이션이 적용된다.**
public class PostRestController {

    private final PostService postService;
    @AutoWired(생략가능)
    public PostRestController(PostService postService){
        this.postService = postService;
    **-->컨트롤러 계층은 서비스 계층으로부터 데이터를 전달하고 받아야하기 때문에 
    서비스계층의 의존성을 주입한다.
    -->AutoWired어노테이션은 매개변수인 postService에 적용되고 생성자가 한개일 시 생략가능하다**
    }

    @PostMapping("/create")
**//   Valid와 NotEmpty등 검증 어노테이션이 한 쌍(Dto에서 NotEmpty,여기서 valid)**
    public ResponseEntity<?> save(@Valid @RequestBody PostCreateDto postCreateDto){
        postService.save(postCreateDto);
        return new ResponseEntity<>(new CommonDto(HttpStatus.OK.value(),"success","sucess"),HttpStatus.OK);
    }
    
   **-->위 Post클래스에서 우리는 title속성에 @Column(nullable = false) 를 설정해주었으나
   에러는 db상에서 터져 프로그램에서 에러가 터지도록 설계해야한다고 했다.
   이를 가능하게 하는 것이 @NotEmpty,@Valid 어노테이션이다.
@NotEmpty어노테이션은 값이 비어있지 않은 지 '특정필드'에 대해 검증하는 어노테이션 null과 "" 둘 다 모두 유효하지 않다고 판단한다.**
**@Valid는 객체의 필드에 정의된 유효성을보고 '객체'에 대해 검증하는 어노테이션  
따라서, 게시글 작성 데이터를 받는 PostCreateDto클래스를 정의 할 때 title'필드'에 대해
@NotEmpty 어노테이션을 쓰고, 컨트롤러에서 매서드의 매개변수로 들어가는 postCreateDto'객체'
에 대해선 @Valid어노테이션을 사용한다.
이를 통해 title에 빈값이 들어오면 db에서 에러가 터지기 전에 
MethodArgumentNotValidException에러가 발생한다.**

    ---------------------PostCreateDto클래스--------------------------------
    
    public class PostCreateDto {
    **@NotEmpty (필드에 선언)**
   private String title;
   private String contents;
   private Long memberId;
    ---------------------------------------------------------------------

    @GetMapping("/list")
    public ResponseEntity<?> list(){
        List<PostListRes> postList = postService.findAll();
        return new ResponseEntity<>(new CommonDto(HttpStatus.OK.value(),"sucess",postList),HttpStatus.OK);
    }

    @GetMapping("/detail/{id}")
    public ResponseEntity<?> detail(@PathVariable Long id){
         PostDetailDto postDetailDto=postService.findById(id);
         return new ResponseEntity<>(new CommonDto(HttpStatus.OK.value(),"sucess",postDetailDto),HttpStatus.OK);
    }

서비스 계층

@Service
@Transactional
public class PostService {
    private final PostRepository postRepository;
    private final MemberRepository memberRepository;

    public PostService(PostRepository postRepository, MemberRepository memberRepository) {
        this.postRepository = postRepository;
        this.memberRepository = memberRepository;
    }
**-->의존성 주입. 그런데 여기서도 매개변수에 적용되는 @AutoWired어노테이션이 생략되었다.
당연히 생략 될 수 있다. 생성자가 한 개 일시 생략이 가능하니까. 위에는 매개변수가 2개인거지
생성자가 2개인 것이 아니다. 착각x**

    //    게시글 등록
    public void save(PostCreateDto postCreateDto){
        Member member = memberRepository.findById(postCreateDto.getMemberId()).orElseThrow(()->new EntityNotFoundException("없는 멤버id입니다"));
        Post post = postCreateDto.toEntityFromDto(member);
        postRepository.save(post);
    }

//    목록조회
    public List<PostListRes> findAll(){
       return postRepository.findAll().stream().map(p->p.toListDto()).collect(Collectors.toList());
    }
//    상세조회
    public PostDetailDto findById(Long id){
              Post post = postRepository.findById(id).orElseThrow(()->new EntityNotFoundException("없는 게시글입니다"));
        return post.toDetailDto(post.getMember().getEmail());
    }

레포지토리 계층

@Repository
public interface PostRepository extends JpaRepository<Post,Long> {

}
-->스프링부트jpa레포지토리는 인터페이스이며 Jpa레포지토리를 상속해야한다.