inblog logo
|
keepgoing
    JavaScriptSpringBoot

    [Springboot] 21 댓글 삭제 ( 제네릭, AJAX )

    김호정's avatar
    김호정
    Sep 13, 2024
    [Springboot] 21 댓글 삭제 ( 제네릭, AJAX )
     

    제네릭

     
    Object 타입은 .length 해서 길이를 알 수 없다.
     
    그래서 그렇게 만들어두면 나중에 호출자가 다운캐스팅 해서 사용해야한다.
     
    그래서 new의 제어권을 호출자에게 주자!
     
    하고 제네릭이 나왔다.
     
    → 제네릭은 반드시 new 하는 사람이 타입을 정해야 한다
     
    타입을 아직 결정하지 않았을 때는 T는 Object 타입이다.
    Box<String> 처럼 타입을 설정하면 T는 스트링 타입이다.
     
    요청을 받으면 톰캣이 request 객체를 만드는데 ( → request 객체를 NEW 한다.)
    이때 객체를 Object 타입으로 만든다.
    → request객체를 생성해서 전달해주는건 톰캣이 하는거라 Object 로 설정할
    수 밖에 없지만 만약 new를 개발자가 할 수 있으면, 제네릭으로 타입을
    설정하는게 낫다!
     
    session도 스프링이 만드는데 내가 설정하는게 아니니까 Object 타입으로
    세션을 만든다. 생성해서 전달해주는 측에서는 호출자가 무슨 타입을
    담을지 모르니까 Object 타입으로 만들 수 밖에 없다.
     
     
    즉, 제네릭은
     
    박스가 있을 때 내가 만들 수 있으면 박스 설계를 제네릭으로 해둔다.
    박스를 만들 때 사과를 담으면서 사과라고 박스에 적어두고.
    바나나를 담으면 바나나라고 타입을 박스에 적어둔다.
    이미 누가 다른 박스를 만들었을 때는 Object 타입으로만 박스를 만들어
    둘 수밖에 없다. ⇒ 사용자가 뭘 담을지 모르니까!
     
     
    제네릭은 메서드, 클래스에 사용하는데
     
    1. 매서드에 제네릭을 사용
    → 매개변수에 들어가는 건 stack 영역에 저장됨.
    그리고 클래스에 설정된 건 heap 에 저장됨.
    그래서 매서드에 <타입>을 적어줌
     
    매서드에 <타입>을 적어줌
    매서드에 <타입>을 적어줌
     
    1. 클래스에서 제네릭을 사용
    → 타입을 아직 모를때는 <B> 이렇게 적어주거나, <?> 와일드카드 사용해준다.
     
    클래스에서 제네릭을 사용. Body에 어떤 객체를 담을 지 모르니까 <T>라고 적어줌
    클래스에서 제네릭을 사용. Body에 어떤 객체를 담을 지 모르니까 <T>라고 적어줌
     
    ⭐⭐⭐⭐⭐
    ->Class의 <T>는 new할때 결정남
    매서드가 static 이니까 new를 안해도 매서드의 제네릭 타입을 먼저 알 수 있다.
    → public static <B> Resp<?> ok (B body) {
    } 는 static 이니까 메인이 실행되기 전에 static 영역에 미리 띄워져 있음.
    그래서 body에 들어오는게 User 타입이면 이 ok매서드는 User 타입이 되는거임
    → ok 매서드를 호출할때 <B>의 타입이 결정됨
     
     
    타입을 모르니까 T 를 설계 → 언제는 User, 언제는 Board가 들어오니까.
    → 리스트 타입의 컬렉션이나 언제는 제이슨 배열이 넘어갈 수 있음.
    → 뭐가 넘어오는지 상황에 따라서 프론트에서 파싱하는게 달라짐. (힘들어짐)
    → 그래서 항상 RESP같은 “공통응답DTO” 를 사용해서 넘겨주면
    프론트가 잘 받아서 처리할 수 있다.
     
    ++
    ‘삭제’처럼 딱히 프론트에 돌려줄 데이터가 없으면 body에 NULL 을 넣어서
    돌려줘도 된다.
     
    ++
    RESP 안에 생성자를 만들어 넣는것보단
    static으로 OK, FAIL 매서드를 만들어서 return 하는게 낫다.
     
     
    util → Resp 응답객체 생성
    util → Resp 응답객체 생성
    package org.example.springv3.core.util; import lombok.AllArgsConstructor; import lombok.Data; @AllArgsConstructor @Data public class Resp<T> { private Integer status; private String msg; private T body; public static <B> Resp<?> ok(B body){ return new Resp<>(200, "성공", body); } public static Resp<?> fail(Integer status, String msg){ return new Resp<>(status, msg, null); } }
     
     
    BoardController
    BoardController
    BoardController에서 Resp 응답 객체를 사용해서 return 하는거 연습해보자.
     
    v1
    → User 객체를 new해서 값을 담고 ok매서드의 body에 User를 담아주기
     
    notion image
     
    body에 User 객체가 잘 담겨서 넘어간 걸 볼 수 있다.
    → 나중에 AJAX요청에 응답할 때,
    Resp로 return 해서 body에 있는 데이터를 프론트가 꺼내쓰게 되는 것.
     
    v2
    notion image
     
    User 객체를 2개 return 하고 싶으면 ?
    → ArrayList에 담아서 Resp body에 싣어 보낸다.
     
    Arrays.asList
    Arrays.asList로 생성된 리스트의 크기는 고정됨!
    → 한번 생성하고 나서 리스트의 크기를 변경할 수 없음
    notion image
     
    notion image
    notion image
     
     
    배열을 body에 담아서 return
     
    /test/v2를 때려보면
     
    notion image
     
    JSON 컬렉션, JSON 어레이 형태로 body에 데이터가 잘 들어온다.
     
    body : [
    {
    “id” : 1,
    “username” : “ssar”,
    “password” : “1234”,
    “email” : “ssar@nate.com”,
    “createdAt” : null
    },
    {
     
    }
    ]
     
    배열안에 객체가 들어있는 모습이군. 뭔지 알겠다!
     
    v3
     
    예외처리할 때 throw 를 날려서 new Exception을 하면 ?
     
    notion image
    notion image
     
    호출하는순간 자바스크립트가 뜬다.
    이렇게 하면 안된다.
     
    AJAX로 데이터를 응답하기를 요청했다면
    이때 Exception은 데이터 그대로 응답해야하지 위처럼 view를 응답하면 안됨.
    (→ Exception404는 자바스크립트 코드를 발동시키고 추가로 화면을 이동시키거나 할때 사용하도록 해놓은 거니까 이럴때 쓰면 안됨)
     
    notion image
     
    Resp를 사용해서 return 하면
     
    notion image
     
    Resp ( → status , msg, body ) 구조로 넘어가서 프론트가 이걸 잘 받아서 처리할 수 있다.
     
    notion image
    근데 Status code 상태코드를 200으로 전달하는 문제가 있다.
    브라우저도 이게 에러라는거 알아야하는데
    우리가 Resp 만들어서 404를 JSON으로 전달하니까 못알아차리나 ?
     
    그래서 HttpServletResponse 를 사용해서 직접 status를 설정해줄 수 있다.
     
    v4
    notion image
    setStatus를 하면 브라우저에게 상태를 알려준다.
    Resp.fail의 status와는 별개로!
     
    notion image
    브라우저의 상태도 404로 잘 변경된 것을 확인할 수 있다.
     
    근데 HttpServletResponse 를 작성하는 이 v4는 귀찮은 방법이다.
     
    업그레이드된 방법이 v5
     
    v5
    notion image
     
    ResponseEntity를 이용해서 Resp객체를 응답함과 동시에 브라우저?에
    HttpStatus.Not_Found 를 알려준다.
     
    notion image
    404!
     
    이제 GlobalApiExceptionHandler 를 만들어보자.
     
    기존 핸들러(→ 일반적인 view를 요청했을 때 쓰는 핸들러 = GlobalExceptionHandler)는 자바스크립트를 발동시키기 때문에
    (→ 글자가 아니라 자바스크립트를 발동시키기 위해 우리가 만든 Script를
    return 하도록 해두었음)
    notion image
     
    그래서 AJAX 요청한 것을 처리할 때는 이 핸들러를 쓰면 안된다.
     
    새로운 핸들러를 만들어줘야 한다!
    ++
    상태코드 넘길때 그냥 String ok = “ok”; 이거 넘기면 안되나 ? 하는데
    String ok는 안됨 → 프론트에서 파싱을 못한다!
     
    GlobalApiExceptionHandler 생성
    GlobalApiExceptionHandler 생성
     
    안의 내용은 일단 기존 핸들러의 400 ~ 500까지 복사해서 붙여넣어준다.
     
    ! ) 서버에서 심각한 오류가 발생했을때 사용하는 ex 매서드는 만들어주지 않는다.
    ex 매서드는 API 핸들러에서는 필요없다. 이미 기존 핸들러에 있으니까 여기서도 쓰면 충돌남
     
    notion image
     
    API로 요청할때는 예외처리는 API 달려있는 Exception으로 return 할 거임.
    → 애는 Script를 발동시켜서 응답하는게 아니라 JSON형태로 응답하는걸로
    만든다!
     
    notion image
     
    Api핸들러안에 return 하는걸 Api상태코드로 다 바꿔준다.
     
    400 bad_request
    401 unauthorized
    403 forbidden
    404 not_found
    500 internal_server_error
     
    package org.example.springv3.core.error; import org.example.springv3.core.error.ex.*; import org.example.springv3.core.util.Resp; import org.example.springv3.core.util.Script; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalApiExceptionHandler { // 유효성 검사 실패 (잘못된 클라이언트의 요청) @ExceptionHandler(ExceptionApi400.class) public ResponseEntity<?> ex400(Exception e) { return new ResponseEntity<>(Resp.fail(400, e.getMessage()), HttpStatus.BAD_REQUEST); } // 인증 실패 (클라이언트가 인증없이 요청했거나, 인증을 하거나 실패했거나) @ExceptionHandler(ExceptionApi401.class) public ResponseEntity<?> ex401(Exception e) { return new ResponseEntity<>(Resp.fail(401, e.getMessage()), HttpStatus.UNAUTHORIZED); // 인증안됨 } // 권한 실패 (인증은 되어 있는데, 삭제하려는 게시글이 내가 적은게 아니다) @ExceptionHandler(ExceptionApi403.class) public ResponseEntity<?> ex403(Exception e) { return new ResponseEntity<>(Resp.fail(403, e.getMessage()), HttpStatus.FORBIDDEN); // 권한없음 } // 서버에서 리소스(자원) 찾을 수 없을때 @ExceptionHandler(ExceptionApi404.class) public ResponseEntity<?> ex404(Exception e) { return new ResponseEntity<>(Resp.fail(404, e.getMessage()), HttpStatus.NOT_FOUND); } // 서버에서 심각한 오류가 발생했을때 (알고 있을 때) @ExceptionHandler(ExceptionApi500.class) public ResponseEntity<?> ex500(Exception e) { return new ResponseEntity<>(Resp.fail(500, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } }
     
    ++
     
    왜 404가 있는데 HttpStatus.Not_Found 도 써줘야하는가 ?
     
    notion image
     
    → 프론트엔트가 보는거(JSON)랑 브라우저에 알려주는 거랑 2개 보내려고
    404, HttpStatus.Not_Found 각각 써줬다.
     
    ExceptionApi404를 return 하는걸로 매서드를 만들자
     
    v6
    notion image
    throw new ExceptionApi404("페이지를 찾을 수 없습니다.");
     
    v5 처럼 new ResponseEntity해서 상태코드 return 할 필요도 없고,
    예외 발생 부분은 throw 바로 하면 된다.
     
    /test/v6 때리면
    컨트롤러에 들어와서 throw 부분에서 GlobalApiExceptionHandler
    가 발동?한다.
     
     
    notion image
    GlobalApiExceptionHandler 에서 애를 때린다.
     
    결과는
     
    notion image
    Resp도 JSON으로 잘 넘어갔고 , httpStatus도 404로 잘 바뀌었다.
     
    → v6를 사용하면 컨트롤러에서는 v5처럼 Resp.fail을 직접 리턴할 일은 없음.
    무조건 정상리턴! 잘못(예외)되면 throw 하면 됨!
     
    notion image
     
    앞으로 이렇게 throw 하면 됨!
     

     
    왜 이제 form action 을 안쓰고 AJAX를 사용해야 하는지 ?
     
    form 태그로 포스트 요청해도 됨 → 이건 브라우저가 요청하는 것
    → 브라우저한테는 코드를 돌려줄 수 없고 무조건 html을 돌려줘야함
    → a 태그 혹은 form 태그 요청을 하면 응답을 받아서 무조건 새로고침을 한다.
    전체를 다시그리는 리랜더링을 하는데 이건 프로토콜(약속)이다.
     
    휴대폰은 브라우저가 아니니까 브라우저의 프로토콜이 적용되지 않는다.
    그래서 새로고침이 되지 않는다.
     
    → form태그 말고 자바스크립트 AJAX로 요청하면 데이터를 응답할 수 있음
     
    부분 리로딩을 하고 싶다! 댓글을 삭제하고 싶다!
    → 그럼 AJAX로 요청하고 응답받아서 해당 댓글 부분만 remove 하면
    그 부분만 사라진다.
     
    서버가 서버사이드 랜더링( → 서버에게 부하가 크다)을 안해줘도
    CSR 을 하면 브라우저가 연산을 해야해서 그럼 서버대신 브라우저의
    부하가 커진다. 브라우저(클라이언트)는 수만명인데, 서버는 1개니까
    서버에 부하가 많이 걸리는것 보다는 낫다.
    그러니 부라우저에게 부하를 전가?하는 CSR 을 하기 시작했다.
    그리고 응답하는 용량에 따라서 트래픽 비용이 달라지는데
    html대신 JSON으로 응답하면 트래픽 비용이 많이 준다.
    (html 속 그 코드를 다 만들어서 전달하려면 용량이 커지는데,
    JSON으로 응답하면 필요한 데이터만 담겨있으니까 용량이 수십배 줄어든다.
    당연이 JSON으로 전달하면 트래픽 비용도 수십배 줄어든다.)
     
    전체를 다 AJAX를 사용하면 개발자가 많이 필요하고 인건비도 늘어난다.
    그래서 나온 리액트로 개발하면 데이터 넣는게 쉬워서 개발 인건비도 줄어든다.
     
    AJAX
    1. 비동기 통신을 할 수 있다.
    1. 부분 리로딩을 할 수 있다 (핵심!)
     

     
    댓글 삭제 가즈아
     
    *
    원래 reply 컨트롤러 안 만들고 board 컨트롤러에서 다 처리하는데 일단 지금은
    reply 컨트롤러 만들어보자.
     
    ReplyController
    ReplyController
     
    댓글 삭제 버튼을 눌렀을 때, 작동하는 매서드를 만들어주자.
    notion image
    /api/reply/{id}
    인터셉터에 인증로직을 짜놨으니 url 앞에 /api 만 추가해주면 인터셉터에서 인증이 발동되는데
    일단 지금은 인증 안할거라서 /api 지우고 reply/{id}만!
     
    일단 서비스는 만들지 말고, AJAX 연습하게 바로 상세페이지에서 script 를 작성해보자.
     
    detail.mustache로 가서 삭제 버튼 쪽에 수정 :)
    notion image
     
    이제 form 태그로 요청 보내는건 안할 거니까 form 태그 지워줘!
     
    notion image
     
    type을 sumit으로 하면 전체 리로드 되니까 submit X
    → AJAX 요청할 때 버튼 타입은 button 으로 !
     
    notion image
     
    자바스크립트한테 문자열로 넘겨야 해서 ‘’ 추가.
    문자열로 안보내면 뻗음
     
    notion image
     
    put, post 말고는 body데이터가 없다. ( → body데이터 보낼때는 content type 꼭 넣어줘야 한다. )
    get, delete는 body없이 요청 보냄! 그래서 지금 delete에 body 데이터 안넣음
     
    notion image
     
    이 response 는 아직 객체가 아니다.
    → 이 안에는 header랑 body가 같이 들어가 있다. (console.log(response)해서 찍어보면 확인됨)
     
    그러니 response 의 body를 파싱하자 -!
     
    notion image
     
    JSON으로 파싱하는 거 앞에도 await를 걸어준다.
     
    파싱해서 콘솔에 responseBody를 찍어보면
     
    notion image
     
    삭제버튼을 클릭하면 찍힌다.
    response와 responseBody를 콘솔에서 확인할 수 있다.
    → response의 body를 파싱한게 responseBody
    → 프론트에서 필요한 데이터가 responseBody다 들어있다!
    → 상태코드 같은건 response에서 꺼내써도 되지만 프론트가 더 편리하게
    일할 수 있게 Resp에 담아서 보내준 거임
     
    notion image
    컨트롤러에서 throw 하면 어떻게 전달될까?
     
    throw해보면
     
    notion image
     
    response의 ok도 false로 바뀌고, status 도 403으로 바뀐 걸 확인할 수 있다.
    body의 데이터도 잘 넘어왔다.
     
    이제는 상세보기 페이지 CSR 하면된다. 삭제한 댓글 부분 DOM 없애기!
     
     
    notion image
     
    먼저 dom에 각자의 id 넣어주기
     
    id는 보통 pk를 사용해서 만든다.
    notion image
    id 잘 들어갔다 : )
     
    notion image
    아이디를 선택해서 삭제하는 코드르 써준다.
     
    notion image
    댓글 1삭제
     
     
    notion image
    remove 됨
     
     
     
    그럼 이제 서비스하고 다 만들어보자
     
    notion image
    인증을 위해 api 추가
     
    notion image
    컨트롤러에도 api 추가
     
    이제 인증은 완료됨
     
    서비스 만들자!
     
    notion image
     
    Repository 없네 먼저 만들자
     
    notion image
     
    ++
     
    데이터베이스 트랜잭션(transaction)을 아십니까? 그리고 트랜잭션의 매우 중요한 속성들인 ACID를 아십니까? 모르신다면 들렀다 가시지요
    #transaction #acid #sql #database #rdb #dbms #atomicity #consistency #isolation #durability #트랜잭션 #쉬운코드 #백발백중 이번 영상은 데이터베이스에 너무나도 중요한 개념인 transaction(트랜잭션)을 설명하는 영상입니다 그리고 transaction의 중요한 속성인 ACID도 깔끔하게 설명합니다! 기본기 제대로 다지고 가시죠! * 댓글에 보충 설명이 필요한 부분을 써두었습니다!! ** 처음 버전에서 화면에 노이즈처럼 자글자글거리는 현상이 있어서 개선해서 새로 올린 영상입니다 00:00 인트로 00:13 예제를 통해 배워보는 트랜잭션 개념 02:21 트랜잭션 개념 정리 03:01 SQL을 통해 트랜잭션 사용해 보기 03:23 START TRANSACTION 03:49 COMMIT 05:21 ROLLBACK 06:11 autocommit 10:12 트랜잭션 사용패턴 11:05 Java에서 트랜잭션 사용 예제 12:41 Spring에서 트랜잭션 사용 예제 13:26 ACID 설명 시작 13:43 Atomicity 16:01 Consistency 18:54 Isolation 22:43 Durability 24:24 참고사항 25:12 마무으리
    데이터베이스 트랜잭션(transaction)을 아십니까? 그리고 트랜잭션의 매우 중요한 속성들인 ACID를 아십니까? 모르신다면 들렀다 가시지요
    https://www.youtube.com/watch?v=sLJ8ypeHGlM&list=PLcXyemr8ZeoREWGhhZi5FZs6cvymjIBVe&index=14
    데이터베이스 트랜잭션(transaction)을 아십니까? 그리고 트랜잭션의 매우 중요한 속성들인 ACID를 아십니까? 모르신다면 들렀다 가시지요
     
    notion image
    고립성
     
    내가 건드릴려고 한거 못건드리게 하는거!
    내가 건드리고 있는데
     
    이거보면 readonly = true 를 이해할 수 있다.
    보고와서 쌤한테 readonly = true 설명해달라고 하기
     

     
    notion image
     
    하나의 비즈니스 로직이자 하나의 트랜젝션(일의 최소단위)!
     
    db를 update하는거니 @Transactional 붙여주기
    package org.example.springv3.reply; import lombok.RequiredArgsConstructor; import org.example.springv3.core.error.ex.ExceptionApi403; import org.example.springv3.core.error.ex.ExceptionApi404; import org.example.springv3.user.User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) // 트랜젝션 고립성때문에 생성해줌 @RequiredArgsConstructor @Service // Component 스캔을 위해 IoC에 띄우기 public class ReplyService { private final ReplyRepository replyRepository; @Transactional public void 댓글삭제(int id, User sessionUser){ // 삭제하기 전에 조회먼저. 없으면 throw Reply replyPS = replyRepository.findById(id).orElseThrow( () -> new ExceptionApi404("댓글을 찾을 수 없습니다.") ); // 권한체크 // boardId랑 UserId는 reply가 처음 select할때 가져왔으니까 여기서 또 select 안한다. // 만약 username이 필요한 경우에는 mFindById를 만들어서 조인해서 사용하면 select 한번만 치니까 속도에서 개선이 된다. if(replyPS.getUser().getId() != sessionUser.getId()){ throw new ExceptionApi403("댓글 삭제 권한이 없습니다."); } // replyRepository.deleteById(id); } }
    notion image
    컨트롤러 작성
     
     
    notion image
    notion image
    상태코드에따라 remove 할지말지 분기하는 조건문 넣어주기
     
    조건문에 상태코드를 쓸지, msg를 쓸지 프론트엔드끼리 정해야 한다 : )
    notion image
    response.ok로 하면 더 간단하다고 한다!
     
     
    ++
    AJAX를 사용하는 주 이유는 비동기통신이라서가 아니라 부분 리로딩이
    가능하기 때문이다.
    응답하는 데이터 . 트래픽마다 돈을 내니까 ajax 써서 부분 리로딩만 해서
    트래픽 비용을 줄일 수 있다.
     
    댓글 등록은
    댓글 리스트 앞에 Prepend로 넣으면 됨!
     
    notion image
    notion image
    Share article

    keepgoing

    RSS·Powered by Inblog