inblog logo
|
keepgoing
    SpringBoot

    [Springboot] 16 리팩토링 마무리, 더티체킹, AOP

    김호정's avatar
    김호정
    Aug 26, 2024
    [Springboot] 16 리팩토링 마무리, 더티체킹, AOP
    Contents
    앞으로 계획더티체킹AOP 1. 라이브러리 등록2. 어노테이션 만들기3. AOP 만들기4. 발동시키기
     

    앞으로 계획

     
    1. v1 단순히 nativeQuery CRUD 패턴구조
    1. V2 마무리
        • ORM
        • JPQL (Java Persistence Query Language) - select 랑 조인 해봄!!
        • 인증 (session) - 나중에 변경예정
        • Exception 컨트롤러
        • 서비스 레이어
        • OpenInView(Lazy Loading) - Eager을 쓰면 알수 없는 에러가 날 수있어서 Lazy를 쓰는게 좋다. 레이지는 서비스단에서 마무리하기
        • Static DTO
        • 유효성 검사
     
    1. 프로젝트 & Git 협업방법
    1. 주제정하기&화면설계서. 기획자가 피그마로 다 만들어줌
    1. HTML 디자인 수업
    1. V3 시작 (동시)
        • 양방향 매핑
        • 복잡한 쿼리(NativeQuery → QLRM 라이브러리)
        • 복잡한 쿼리(JQPL 썼을때 동일하게 안나오면 → 매핑하는 법 배울 예정)
        • 동적 쿼리(매개변수에 의해서 쿼리가 달라지는 것)
        • OAUTH2.0
        ⇒ 여기까지 배우면 프로젝트를 할 수 있다.
    1. 테이블 설계 (동시) - 화면나오면 검사받고 테이블 만드는거 검사받기
    1. 프로젝트 진행(9.24일)
    1. 수업
        • RestAPI 수업
        • JWT 인증 방식
        • Stream API
        • 통합테스트
        • API 문서
        • flutter 수업
        • 두번째 프로젝트 (이력서, 면접 준비) (9.25 시작예정!)
      8/29 ~ 9/24 까지 프로젝트
       

       

      BoardService

      notion image
      게시글수정화면가기 매서드 생성
       
       

      BoardController

      notion image
      컨트롤러에서 updateForm 매서드 service.게시글수정화면가기 사용하도록 수정
       
       
      ++
       
      만약에 컨트롤러에서 인증 검사를 안해서
      boardService.게시글수정화면가기(id, sessionUser)에서
      sessionUser 값이 null 이 들어가게 되면,,,
       
      service에 null 값이 넘어가서
      notion image
      저기 sessionUser.getId()할때 null pointer exception 이 터지게 된다.
      (→ 인증검사를 안한 컨트롤러의 잘못이다)
       
      → 인증검사를 안해서 null pointer exception이 발생하면 우리가 미처 처리하지 못한 예외이니
      예외의 ex매서드가 실행된다.
       
      위까지 하고
       
      수정하기 버튼을 클릭하면
      notion image
       
       
      notion image
      잘 이동한다.
       
       
      ++
      notion image
      notion image
      게시글 목록보기&상세보기는 인증체크가 필요없어서 sessionUser == null 조건을 안적었따.
      ( → 이 두페이지는 누구나 들어갈 수 있음.)
       
      상세보기 이동 코드에 sessionUser 를 넘겨준 이유는 BoardReponse.DetailDTO 에서 권한체크할 때 필요하기 때문 ( 버튼 생성용 권한체크 )
       
       

      BoardController

      notion image
      게시글쓰기 이동하는 코드에 인증 추가해주기
      (이 코드의 핵심로직은 return “board/save-form”; )
       
      ++ 추가
      notion image
      여기서 화살표 처리한 것(인증 코드)
       
      if(sessionUser == null){
      throw new Exception401(”인증되지 않았습니다.”);
      }
       
      이 인증코드는 이 코드의 핵심로직이 아니다. → 부가로직
      이 코드의 핵심로직은 네모친 부분 → 핵심로직
       
      유효성 검사 같은것도 부가로직이다.
       
      인증, 유효성 검사 → 부가로직
       
      ⇒ 부가로직은 처음부터 쓸 필요없다. 핵심로직부터 먼저 짜기.
       
      입사하면 인증, 권한체크 같은 부가로직말고 핵심로직만 짜라고 할 것이다.
       

      BoardRequest

      notion image
       
      BoardRequest에 UpdateDTO 생성 ⇒ 요청(Request) DTO 는 동일하게 생겨도 중복해서 만들기!
       
      SaveDTO와 UpdateDTO 를 보면 둘다 매개변수로 title과 content를 담기 때문
      왜 SaveDTO 가 있는데 UpdateDTO도 만들어야 하나 ? 생각할 수 있따.
       
      따로 만들어야 하는 이유는
      나중에 수정할 경우가 생길 수 있는데(만약에 사장님이 UPDATE할 때는 제목수정 못하게 해! 라고 하면 DTO의 title필드를 주석처리하거나 없애야 하는데 save할때는 필요하니까 없앨 수가 없다…)
      이런 경우에 저장(save)이랑 수정(update)이랑 매서드를 공유하면 dto 수정이 불가능해짐
       

      BoardController

      notion image
      생성한 UpdateDTO를 사용해서 리팩토링 하기
       
      → RequestParam으로 title,content 받지 말고, BoardRequest.UpdateDTO로 받아라!
       
      ++
       

      더티체킹

       
      더티체킹이란 ? 상태 변경 검사
       
      JPA에서는 트랜잭션이 끝나는 시점에 
      변화가 있는 모든 엔티티 객체를 데이터베이스에 자동으로 반영해준다.
      출처 : 아래 블로그
      출처 : 아래 블로그
       
      더티 체킹 (Dirty Checking)이란?
      Spring Data Jpa와 같은 ORM 구현체를 사용하다보면 더티 체킹이란 단어를 종종 듣게 됩니다. 더티 체킹이란 단어를 처음 듣는분들을 몇번 만나게 되어 이번 시간엔 더티 체킹이 무엇인지 알아보겠습니다. 모든 코드는 Github에 있습니다. 예를 들어 다음과 같은 코드가 있습니다. (Spring Data Jpa가 익숙하시겠지만, 네이티브한 코드 먼저 보고 가겠습니다.) @Slf4j @RequiredArgsConstructor @Service public class PayService { public void updateNative(Long id, String tradeNo) { EntityManager em = entityManagerFactory.createEntityManager(); Enti..
      더티 체킹 (Dirty Checking)이란?
      https://jojoldu.tistory.com/415
      더티 체킹 (Dirty Checking)이란?
       

      BoardService

      notion image
      boardRepository.findById(id); 가 실행되고 나면
      영속성 컨텍스트 (Persistence Context)에 객체로 응답받은 board(테이블)이 담긴다.
       
      board → 영속객체. (영속성 컨텍스트에 저장되어 있음)
       
      이 영속화되어있는 애를 ‘수정’하면 트랜젝션이 종료시에 update 쿼리가 자동으로 날아간다.
      위에서 setTItle, setContent 로 영속객체의 기존 값을 수정하고 있다.
      그럼 트랜젝션이 종료될때, update 쿼리가 실행된다.
       
      notion image
      boardRepositoryTest에서 테스트하면 update 쿼리가실행되는것을 볼 수 있다. (더티체킹)
       
      notion image
      위처럼 비영속객체(board)를 만들어서 set을하면(수정하면) 아무일도 일어나지 않는다.
      * flush 가 아닌 persist 를 사용하면 insert 됨
      *flush 는 DB에다가 현재 변경된 상태에 대한 결과를쏘라고 하는것. 모아두었다가.
       
       
      ++
      boardRepository.findById(id); 동일한 아이디를 2번 넣어서 실행시켰는데 캐싱이 안되는 문제 발생.(→ select 가 2번 실행되어버림)
      → 원인 : 우리가 직접 쿼리를 쓰면 캐싱이 안됨!
      notion image
      지금 findById는 우리가 작성한 쿼리를 실행시키고 있다. (네이트브 쿼리든, jpql이든 뭐든)
      영속성 컨텍스트에서 캐싱 안하고.
       
      ++
      원래 실무에서는 엔티티 매니저를 이용해서 영속성 컨테스트(PC)를
      조회하고 캐싱하든가 쿼리쓰든가 함
      → em.find(id); 코드 작성 → 영속성 컨텍스트에 캐싱할 수 있는게 있는지 조회(find)
      → 없으면 쿼리실행시킴(update)
      → 요약 : em.find 하면 먼저 pc에서 찾고 없으면 db에서 조회한다!
      notion image
       
       
       
       
      ++
      OKKY - JPA 쿼리 캐싱문제
      안녕하세요. 현재 Spring boot 와 Spring Data Jpa, QueryDsl 를 이용해 간단한 프로젝트를 만들고 있습니다. jpa의 1차 캐시 관련해서 질문드립니다.Spring Data Jpa 의 JpaRepository에서 기본으로 제공하는 findById 등을 사용하면 한 트랜잭션에서 같은 id로 몇번을 호출해도 쿼리가 한번만 날아가는데 j
      OKKY - JPA 쿼리 캐싱문제
      https://okky.kr/questions/934020
      OKKY - JPA 쿼리 캐싱문제
       
      ++
      notion image
      트렌젝션이 종료되면 변경을 감지해서 한번에 flush 를 함
       
      ++
      회사의 erp 시스템은 myBatis 를 사용한다..,
      JPA 를 쓰는 회사는 복잡한 쿼리 쓰려면 queryDSL? 을 사용함
       
      notion image
      boardService 게시글 수정 완성
       
      notion image
      boardController 완성
       
      notion image
      수정하면 잘 된다.
       
      글수정하기완료를 클릭학고 콘솔을 보면
       
      notion image
      트랜젝션이 끝나고 flush 될때 update 쿼리가 실행되는것을 확인할 수 있다.
       
       

      유효성 검사

       
       
      // 그림
      DS 와 같은 라인에 GlobalExceptionHandler 와 viewResolver 가 있음.
       
      예를 들어, 컨트롤러에서 return “/board/list”하면
      fileReader 로 해당 파일을 다 읽고, 순수한 html 파일이 나온다. → 이걸 view resolver 가 함.
       
      AOP는 관점 지향 프로그래밍 → 핵심로직이 아닌 부가로직을 프록시로 처리!
      → aop는 리플렉션으로 작동한다.
      → 부가로직(유효성 검사 등)을 앞단에서 처리한다.
       
      리플렉션으로 매개변수의 값을 분석해서 앞단(프록시)로 끌어서 처리하는게 aop이다!
      핵심로직을 제외한 모든 부가로직을 따로 처리하는게 aop이다!
      (→ aop는 프록시패턴과 리플렉션으로 작동한다!)
       
       
       

      build.gradle

      notion image
      라이브러리 추가
       

      UserRequest

       
      NotEmpty 어노테이션 추가
       
       

      인터셉터도 AOP도 프록시 역할을 할 수 있음
      → 이 2개를 스프링에서 제공하는 이유는 DS는 스프링이 만든 거니까 건드리지 말라는 것 ㅎ
       
      인터셉터와 AOP
       
      앞에서 실행할지 뒤에서 실행할지 정해줘야함!
       
      AOP(@before 손씻기, @after 양치하기) → 메서드 분석(리플렉션)
      Interceptor(prehandle 손씻기, afterhandle양치하기) → prehandle과 afterhandle은 어노테이션은
      아님.
       
      → 둘다 써도 됨
      → 차이는 aop는 매개변수를 분석할 수 있고, 인터셉터는 이전, 이후에 실행하는것만 지정할 수 있음
      → AOP는 매서드를 분석할 수 있다. 매개변수, 어노테이션 등을 분석할 수 있음.
      → 젓가락이라는 매개변수를 분석할 수 있음
      → 젓가락을 검사. 젓가락이 더러우면 한번 씻어야함.
       
      → 예를들어. 인증 검사는
      request 객체 안의 주소를 분석해서 처리할 수 있기 때문에 인터셉터로 처리할 수 있따.
      → 유효성 검사는
      뭐가 들어올지 모름. → 매개변수를 분석해서 처리해야하니 AOP로 처리한다.
       
      1. 아침먹기(젓가락)
      if(젓가락 == 더러움) throw
      ⇒ aop는 매개변수를 동적으로 분석할 수 있으니 이 코드를 자기 안에서 처리할 수 있다.
      손씻기 - 부가로직
      샌드위치먹기 - 핵심로직
      양치하기 - 부가로직
       
      1. 점심먹기(젓가락)
      손씻기 - 부가로직
      삼겹살먹기 - 핵심로직
      양치하기 - 부가로직
       
      1. 저녁먹기(젓가락)
      손씻기 - 부가로직
      김치찌개먹기 - 핵심로직
      양치하기 - 부가로직
      notion image
      코어 패키지안에 2가지 패키지를 만들어줌
       
      interceptor → 뭘 실행할지
      config → 언제 실행될지
       
      설정함
       
      notion image
       
       
       
      notion image
      default로 구현된 이유가 선택해서 구현하라고!
       
      preHandle 매서드 실행전
      postHandle view가 리턴되고 나서
      afterConpletion → ds가 뷰 리졸버한테 만들어달라고 하고 나서
      notion image
      작성해줌
       
       
      notion image
      notion image
      notion image
       
       
      notion image
      /board 쳐보니 프리핸들 동작함
      ⇒ 인터셉터가 동작한 것
       
      인증을 다 여기로 빼자.
      매개변수 필요없으니 aop 대신 인터셉터로.
       
       
      필터의 위치는 디스패쳐 앞.
       
      필터에서 throw걸면 절대 글로벌익셉션핸들러한테 안감
       
      인터셉터에서 throw 하면 나를 호출한 ds한테 넘기고
      ds는 geh한테 처리하라고 줌. 스프링 내부에서 처리하게 됨
       
      필터는 왜 그게 안될까.
       
      필터는 톰캣꺼여서 앞단에 있음.
       
      filter - dispatcher servlet - interceptor - aop - controller 순서
      (global Exception Handelr 와 view resolver 는 dispatcher servlet 과 같은 라인에 있다)
       
      ++
       
      print 는 \n 해야지 파싱할 때 알아먹을 수 있따.
      printwriter는 기본적으로 자기가 flush를 가지고 있다.
       
      소켓통신, flush 개념..
       
      notion image
      인터셉터에 만들어주고
       
      notion image
      board컨트롤러의 인증 조건문 다 제거
       
       
      지금은 /user, /board 를 인터셉터에 적었지만 인증이 필요한 주소는 앞에 /api 를 붙일 예정
       
      notion image
      api로 수정
       
      notion image
      수정하기에 인증이 필요하니 api 붙여ㅜㄴ다.
       
      그리고 다르,ㄴ것도
       
      notion image
      메인페이지 그냥 / 로 수정해줌
       
      /board 로 리턴되게 한 다른 코드도 / 로 수정하기
       
       
      뷰 파일가서 링크 수정하기
       
      notion image
      header 파일
       
       
      notion image
      detail.mustache
       
       
       
      notion image
      list. 게시글 상세보기는 인증이 필요없으니 수정 X
       
       
      notion image
      save-form.mustache
       
      notion image
      update-form.mustache
       
       
      notion image
      로그인 로그아웃도 메인으로 이동하도록 해둔거 수정해두기
       
      notion image
      위와같이 수정해두면 된다!
       
       
      notion image
      로그인 안하고 상세보기로 들어가면 null 이 터지니까
       
      notion image
       
      boardReponse 의 detailDTO 매서드의 이 권한체크 부분 코드 위와같이 수정해주기
       
       
      그럼 이제 저 alert 창 안뜨고 상세페이지로 갈 수 있다.
       

      AOP

       
      notion image
      라이브러리 추가
       
      💡
      기술보다는 기본기, 기본개념을 알아야한다. ㅎ 인터셉터를 아는것보다 기본개념..!!~~!~!~!~!
       
      notion image
      에러 패키지에 핸들러추가
       
      notion image
      • Aspect 붙이기 → AOP로 등록
       
       
      before, after, around 3개 가 있음
       
      notion image
       
      이렇게 작성하고
      notion image
      로그인폼을 때리면
       
      notion image
      잘 실행되는걸 확인할 수 있다.
       
       
      리플렉션은 어노테이션으로 발동시키는게 제일 좋다!
      정규표현식?을 이용해서 before 에 *.
       
       
      notion image
       
      core 패키지에 Hello 어노테이션 생성 ( → 위치가 core)
       
      notion image
      어노테이션 만들고
       
       
      notion image
      hello 어노테이션 붙은 매서드 실행되기 직전에 이게 실행됨
       
       
      notion image
      login-form 에 우리가 만든 어노테이션 붙여주고
       
      ++
      리플렉션 어려우니까 스프링이 만들어둔 기능이 aop임. AOP 써. 쉽쟈나~ 이러면서,
       
      매개변수를 분석할 수 있는 장점이 있다.
       
      어떤 매개변수를 가지면 어떻게 처리할지도 다 설정할 수 있다.
       
      notion image
      notion image
      로그인폼 때리면 호출됨
       
      조인폼처럼 안붙인거 실행시키면 안나옴
       
       
       
      notion image
      어라운드로 바꾸면
       
      • 이전 이후를 다 관리할 수 있다.
       
       
      notion image
       
      조인포인트를 통해서 매개변수에 접근할 수 있다.
      throw 날리기
       
      notion image
       
      완성
       
      어라운드는 헬로어노테이션이 붙은 매서드의 전후를 관리.
      jp.proceed() 는 헬로 어노테이션이 붙은 함수. 임
      그게 실행되어서 그 리턴값이 들어옴
      → 위에선 loginFORM 을 때려서 user/login-form 이 리턴됨
       
      마지막에 리턴 proceed 하면 디스패처 서블릿이 가져가서 뷰 리졸버한테 넘겨서 뷰 만듦.
       
       
       
       
      notion image
      다 완성시켜서
       
      로그인폼을 url 에서 때리면
       
      notion image
       
      잘나옴
       
      notion image
      로그인폼에 sout 추가해서 로그인폼때려서 확인해보면
       
      notion image
      notion image
      이렇게 나옴
       
       
      ++
      리플렉션을 적용해서 어노테이션을 활용한다는게 이런거임!!!
       
      @transactional 도 리플렉션이 발동해서 수행되는 것.
      → 매서드 시작과 끝을 관리.
       
      @transactional 들어가보면 type, method 에 걸 수 있는데,
      만약 클래스에 걸면, 해당 클래스의 모든 매서드가 실행될때 트렌젝션이 걸림.
      → select 에는 트렌젝션을 걸 필요가없으니 이렇게는 사용하지 말기
       
       

      AOP 정리
      💡
      aop 개념 적기
       

      1. 라이브러리 등록

      implementation 'org.springframework.boot:spring-boot-starter-aop'
       

      2. 어노테이션 만들기

      package shop.mtcoding.blog.core; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Hello { }
       
       

      3. AOP 만들기

      package shop.mtcoding.blog.core.error; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect // AOP 등록 public class GlobalValidationHandler { @Around("@annotation(shop.mtcoding.blog.core.Hello)") //@Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") public Object hello1(ProceedingJoinPoint jp) throws Throwable { System.out.println("aop hello1 before 호출됨"); Object proceed = jp.proceed(); // @Hello 어노테이션이 붙은 함수 호출 "user/login-form"; System.out.println("aop hello1 after 호출됨"); System.out.println(proceed); return proceed; } }
       
       

      4. 발동시키기

      @Hello @GetMapping("/login-form") public String loginForm() { System.out.println("loginForm 호출됨"); return "user/login-form"; }
       
       
      notion image
       
      출처 : AOP 정리 (notion.site)
       

       
      유효성 검사하는 매서드 만들어주기
       
      notion image
      before, after 는 Proceeding 아니고 그냥 JoinPoint 써줌
       
       
      notion image
      @NotEmpty 붙여주기 (→ 공백도 안되고 null도 안됨)
       
      아까 validation 라이브러리 넣은 이유가 이 어노테이션 쓸라고 넣은거임
       
       
      notion image
      컨트롤러에서 DTO 쓰는 부분에 @Valid (→ jakarta 꺼) 붙여주기
       
       
      notion image
       
      dto 앞에 @valid 붙여주면
       
      dto 객체가 만들어질때 안에 어노테이션을 분석해서 (→@notEmpty 같은거)
       
      null이거나 공백이면 Errors 에 넣어줌
       
       
      로그인 실행해보면
      notion image
       
       
      notion image
       
      이렇게 확인할 수 있다.,
       
      notion image
      수정하기를 하면
       
      notion image
       
      이렇게 매서드가 실행되기 전에 위의 validation 이 실행되어 콘솔에서 확인할 수 있다.
       
      글쓰기에 들어가서
       
      notion image
       
      @NotEmpty 인 title과 content를 “” 으로 둔 채 글쓰기완료를 클릭하면
       
      notion image
      에러가 2개 발생한 것을 확인할 수 있다.
       
       
      notion image
      제목만 작성하고 글쓰기완료를 누르면
       
      notion image
      내용에 “”이 들어가서 에러가 1개 발생한 것을 확인할 수 있다.
       
       
       
      ++
      정규표현식 모를 땐 (→ 챗지피티가 알려준다.)
       
       
       
       
       
       
      notion image
      notion image
      notion image
      notion image
      notion image
       
       
      지금은 정규식 공부하지 말고 이렇게 챗지피티한테 물어보기.
       
       
       
      ** 쌤이 준 정규표현식 DTO에 활용한 코드
       
       
      package com.example.kakao.user; import lombok.Getter; import lombok.Setter; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.util.Collections; public class UserRequest { @Getter @Setter public static class JoinDTO { @NotEmpty @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "이메일 형식으로 작성해주세요") private String email; @NotEmpty @Size(min = 8, max = 20, message = "8에서 20자 이내여야 합니다.") @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@#$%^&+=!~`<>,./?;:'\"\\[\\]{}\\\\()|_-])\\S*$", message = "영문, 숫자, 특수문자가 포함되어야하고 공백이 포함될 수 없습니다.") private String password; @NotEmpty private String username; public User toEntity() { return User.builder() .email(email) .password(password) .username(username) .roles(Collections.singletonList("ROLE_USER")) .build(); } } @Getter @Setter public static class LoginDTO { @NotEmpty @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "이메일 형식으로 작성해주세요") private String email; @NotEmpty @Size(min = 8, max = 20, message = "8에서 20자 이내여야 합니다.") @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@#$%^&+=!~`<>,./?;:'\"\\[\\]{}\\\\()|_-])\\S*$", message = "영문, 숫자, 특수문자가 포함되어야하고 공백이 포함될 수 없습니다.") private String password; } @Getter @Setter public static class EmailCheckDTO { @NotEmpty @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "이메일 형식으로 작성해주세요") private String email; } }
       
       
      위처럼 사용한다.
       
      정규식을 적용하기 전에 Test 하는 클래스를 만들어보자.
       
       
      notion image
       
      테스트에 temp 패키지랑 RegexTest 클래스 만들어주기 ( → 정규식 테스트 )
       
      package shop.mtcoding.blog.temp; import org.junit.jupiter.api.Test; import java.util.regex.Pattern; // java.util.regex.Pattern public class RegexTest { @Test public void 한글만된다_test() throws Exception { String value = "ㄱ"; boolean result = Pattern.matches("^[가-힣]+$", value); System.out.println("테스트 : " + result); } @Test public void 한글은안된다_test() throws Exception { String value = "abc"; boolean result = Pattern.matches("^[^ㄱ-ㅎ가-힣]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어만된다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어는안된다_test() throws Exception { String value = "가22"; boolean result = Pattern.matches("^[^a-zA-Z]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어와숫자만된다_test() throws Exception { String value = "ab12"; boolean result = Pattern.matches("^[a-zA-Z0-9]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어만되고_길이는최소2최대4이다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]{2,4}$", value); System.out.println("테스트 : " + result); } // username, email, fullname @Test public void user_username_test() throws Exception { String username = "ssar"; boolean result = Pattern.matches("^[a-zA-Z0-9]{2,20}$", username); System.out.println("테스트 : " + result); } @Test public void user_fullname_test() throws Exception { String fullname = "메타코딩"; boolean result = Pattern.matches("^[a-zA-Z가-힣]{1,20}$", fullname); System.out.println("테스트 : " + result); } @Test public void user_email_test() throws Exception { String fullname = "ssaraa@nate.com"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[a-zA-Z0-9]{2,10}@[a-zA-Z0-9]{2,6}\\.[a-zA-Z]{2,3}$", fullname); System.out.println("테스트 : " + result); } @Test public void account_gubun_test1() throws Exception { String gubun = "DEPOSIT"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_gubun_test2() throws Exception { String gubun = "TRANSFER"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT|TRANSFER)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_tel_test1() throws Exception { String tel = "010-3333-7777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{3}-[0-9]{4}-[0-9]{4}", tel); System.out.println("테스트 : " + result); } @Test public void account_tel_test2() throws Exception { String tel = "01033337777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{11}", tel); System.out.println("테스트 : " + result); } }
       
      선생님이 준 파일에서 복붙
       
       
       
       
       
      notion image
       
      테스트해보면 “한글”이라고 넣으면 TRUE
       
      notion image
       
      “ㄱ”을 넣으면 FALSE 가 나오는 것을 확인할 수 있다.
       
      이렇게 정규식을 가져다 쓰면 되는데 구글링해서 일일이 찾기는 어려우니
       
      챗지피티한테 물어보자.
       
      위의 경우 한글말고 ㄱ같은 자음도 TRUE로 나오는 정규표현식 알려줘! 하면서 코드 붙여넣어서
       
      물어보면 다 알려준다!
       
       
       
       
      그리고 DTO

      UserRequest

      notion image
       
      어노테이션 notBlank 말고 notEmpty 걸기 (공백 null 둘다 허용안함)
       
      ? ) NotBlank와 NotEmpty의 차이가 뭐지 ?
       
      @notEmpty 와 @notBlank 의 차이가 뭐야 ?

      ChatGPT의 말:

      ChatGPT
      @NotEmpty와 @NotBlank는 둘 다 자바에서 주로 사용되는 유효성 검사 어노테이션이지만, 서로 다른 상황에서 사용됩니다. 주로 빈 문자열과 공백 문자열을 처리하는 방식에서 차이가 있습니다.

      1. @NotEmpty

      • 대상: String, Collection, Map, 또는 Array와 같은 빈 상태를 가질 수 있는 객체.
      • 검사 조건: null이 아니고, 비어 있지 않음을 검사합니다.
      • 적용 예시: 이 어노테이션은 문자열이 비어 있지 않거나, 컬렉션이 최소한 하나의 요소를 가지고 있는지 확인하는 데 사용됩니다.
        • 빈 문자열 ""은 실패 (유효성 검사에 통과하지 않음).
        • null 값은 실패.
        • 공백 문자열 " "는 통과 (공백도 비어있지 않으므로 통과).
      예시:
      java코드 복사 @NotEmpty private String name;

      2. @NotBlank

      • 대상: String 데이터.
      • 검사 조건: null이 아니고, 비어 있지 않으며, 공백으로만 이루어지지 않음을 검사합니다.
      • 적용 예시: 이 어노테이션은 문자열이 비어 있지 않고, 단순히 공백 문자로만 이루어져 있지 않은지 확인하는 데 사용됩니다.
        • 빈 문자열 ""은 실패.
        • null 값은 실패.
        • 공백 문자열 " "도 실패 (공백만 있는 문자열은 비어있는 것으로 간주).
      예시:
      java코드 복사 @NotBlank private String username;

      주요 차이점 요약:

      • @NotEmpty: null이나 빈 문자열/컬렉션은 허용하지 않지만, 공백만 있는 문자열은 허용됩니다.
      • @NotBlank: null이나 빈 문자열뿐만 아니라 공백만 있는 문자열도 허용하지 않습니다.

      예시 비교:

      java코드 복사 @NotEmpty private String description; // null, ""은 허용되지 않지만, " "는 허용됩니다. @NotBlank private String username; // null, "", " " 모두 허용되지 않습니다.
      따라서 입력 데이터가 단순히 빈 값("")인지 확인하려면 @NotEmpty를 사용하고, 공백(" ")도 허용하지 않으려면 @NotBlank를 사용하는 것이 적합합니다.
       
       
       
      notion image
      넣어주면
       
      로그인할때 1개라도 에러(공백)걸리면 errors 에 간다.
       
      (→ loginDTO로 username 혹은 password 공백으로 전달되면 유효성 검사에서(@Valid) 적발되고 (?)
      걸리면 바로 뒤에 붙어있는 Errors 로 보냄 )
       
      • @Valid DTO 와 Errors 쓸때는 붙여서 써야한다. 중간에 파라미터 오면 Errors로 전달안됨!!
       
      실행해보면
       
      notion image
       
      username 크기를 4글자 이상으로 적었을 때
       
      notion image
       
      제대로 알려줌
       
      notion image
       
      패스워드를 공백으로 두고 시도하면
       
      notion image
       
      제대로 알려준다.
       
      • 요청(Request) dto (→ BoardRequest, UserRequset ) 에 다 NotEmpty 적어주기
      • NotEmpty에 message를 줘서 내가 원하는 문구로 알릴 수 있는데 기본 문구가 잘 되어있으니
      @NotEmpty(message = "비워놓지마. (근데 이거 안써줘도 됨)")
      이렇게 message 쓰지말고 NotEmpty 만 쓰자.

      BoardRequest

      package shop.mtcoding.blog.board; import jakarta.validation.constraints.NotEmpty; import lombok.Data; import shop.mtcoding.blog.user.User; public class BoardRequest { // 요청DTO는 동일하게 생겨도 중복해서 만들기 @Data public static class UpdateDTO { // title, content 2개만 담으면 된다. @NotEmpty private String title; @NotEmpty private String content; // insert 가 아니라 UPDATE 이니까 투엔티티 없어도 댐 } @Data public static class SaveDTO { // title, content 2개만 담으면 된다. //@Pattern(regexp = ) 정규표현식 패턴 @NotEmpty private String title; @NotEmpty(message = "비워놓지마. (근데 이거 안써줘도 됨)") private String content; // insert 할 때는 toEntity 를 만든다. public Board toEntity(User sessionUser) { // 날짜는 엔티티에 @CreationTimeStamp 붙여주면 자동으로 들어간다. return Board.builder() .title(title) .content(content) .user(sessionUser) .build(); // shift + enter } } }
       
       

      UserRequest

      package shop.mtcoding.blog.user; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Data; // 내부클래스로 관리!! // 이렇게 하면 위험하지 않음 public class UserRequest { @Data // getter, setter, tostring 들고 있음 public static class joinDTO { // static 이니까 new 하기전에 이 클래스는 static 에 뜬다. @NotEmpty private String username; @NotEmpty private String password; @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "이메일 형식으로 작성해주세요") @NotEmpty private String email; // 매서드 만들기 // 이 매서드는 DTO -> UserObject (유저 엔티티) 로 바꾸는 역할을 함!! public User toEntity() { // insert 하는건 toEntity로 만들어서 넣으면 완전 편리하고 코드가 심플해짐!! // 아래에서 유저객체 만들어서 return 함! return User.builder().username(username).password(password).email(email).build(); } } // 요청 바디 데이터 받기 위해 dto 생성함!!! (dto 만드는 목적) // 이렇게 만들어두면 관리하기 편하다. // 나중에 사용하기도 편함 @Data // getter, setter, tostring 들고 있음 public static class loginDTO { // static 이니까 new 하기전에 이 클래스는 static 에 뜬다. @Size(min = 2, max = 4) // 최소 2자, 최대 4자 @NotEmpty private String username; @NotEmpty private String password; } }
       
       
      DTO에 NotEmpty 어노테이션 다 붙여주고
       
      notion image
      BoardController에 가서
      DTO앞에 @Valid 없는거 다 붙여주고, Errors도 매개변수로 적어주기
       
      💡
      Errors 대신 BindingResult 같은거 적어놓은 코드도 종종 보일텐데
      BindingResult 같은 애들의 부모가 다 Errors 여서 (다형성) 다 Errors 구나 이해하면 된다.
      💡
      @Valid BoardRequest.UpdateDTO updateDTO, @PathVariable(”id”) int id, Errors errors
      → 이렇게 쓰면 안됨. 에러가 안나서 디버깅도 안됨 ㅎ
      → DTO 바로 뒤에 Errors 가 있어야 함
      → 아니면 UpdateDTO 뒤에 id 에 값을 넣어버림
      → @Valid BoardReuqest.UpdateDTO updateDTO, Errors errors, @PathVariable(”id”) int id
      이렇게 써주거라
       
       
       
      notion image
       
      인터셉터 적용하고 유효성 검사까지 완료한 파일
       
      blog.7z
      280.1KB
       
       
      Share article
      Contents
      앞으로 계획더티체킹AOP 1. 라이브러리 등록2. 어노테이션 만들기3. AOP 만들기4. 발동시키기

      keepgoing

      RSS·Powered by Inblog