[Springboot] 23 검색 기능 ( 자바스크립트 키업 이벤트, RestAPI, JSON, 디바운싱과 쓰로틀링 )
내용업데이트필요
Sep 13, 2024

<div class="d-flex justify-content-end mb-2">
<form class="d-flex col-md-3">
<input class="form-control me-2" type="text" placeholder="Search">
<button class="btn btn-primary" type="button">Search</button>
</form>
</div>
검색 디자인 추가
- 부트스트랩 d-flex 는 해당 태그 내부를 flex로 만들때 사용
<form class = “d-flex> 의 내부에 있는 태그에는 flex가 적용된다.
<form>을 div로 감싸고 d-flex를 적으면 form과 input, button 까지 다 flex가 적용된다. 그리고 justify-content-end 를 적으면 자식 요소들이
다 오른쪽 끝으로 정렬됨. mb-2는 margin bottom 이다.
아래 form 태그에 col-md-3은 “그리드”
→ 부트스트랩은 12개의 그리드로 화면을 나눈다.
→ 3은 12 그리드 중 3개를 차지한다는 의미
→ 전체 화면의 3/12를 차지하게 됨


(기존에 만들었는데 사용안하는 mFindAll매서드는 지우고)
검색 쿼리 생성 : )
// 검색 시 사용할 findAll 만들어주기 ( 검색 쿼리 )
@Query("select b from Board b where b.title like %:title% order by b.id desc")
List<Board> mFindAll(@Param("title") String title);
쿼리가 그리 복잡한게 아니니 Repository에 만든다!
복잡한 쿼리는 xxxQueryRepository 에 만든다.
++

네이티브 쿼리로 써봤을 때!

네이티브 쿼리로 정렬까지 해봄. → 이렇게 하면 위의 JPQL 로 쓴거랑 똑같은 결과가 나옴

BoardRepository에서 만든 매서드 테스트하기 : )
String title = “제”로 바꾸면 제목1~제목5까지 5개가 출력될 것이다!


Get 요청은 쿼리스트링 밖에 없는데
이렇게 들어온다.
? 해서 들어오는 데이터는 @requestParam을 앞에 써준다.
원래 해당 어노테이션은 생략할 수 있다. 근데 버그 때문에 써줘야함
localhost:8080/?title=1 로 쳐보니까

쿼리스트링 값(1)이 제대로 전달 된 것을 확인할 수 있다.

localhost:8080/?title= 이렇게 값 없이 전달해도
defaultValue 가 “”이기 때문에 “” 이 title의 값으로 들어가서 실행된다.

required = false 걸어주면 쿼리스트링이 반드시 필요한게 아니니 title을 전달하지 않아도 에러가 안난다. → 대신 쿼리스트링으로 파라미터가 전달되지 않았으니 null 값이 들어간다.

게시물목록보기 매서드에 파라미터를 매개변수로 넣어주고

서비스 레이어에서 위에서 만든 검색 쿼리 mFindAll 를 사용하도록 코드를 수정한다.
지금 list 화면은 “메인/목록보기 화면”인데
검색어를 입력했을 때 게시글 목록보기 부분이 리로드되면서 바뀌는 형태이다.
- 검색을 안했을 때, 모든 게시글이 보이는 경우 와
- 검색을 했을 때, 목록보기 부분이 바뀌는 경우
2가지를 고려하면 이 게시글목록보기 매서드는 2가지 경우에 따라 다른 쿼리를 사용해야한다.
→ 쿼리스트링으로 파라미터가 넘어오지 않은 경우 ( = 메인/목록보기 화면으로 이동하는 경우 )
에는 findAll 매서드를 사용 (정렬만 하는 매서드)
→ 쿼리스트링 파라미터가 넘어오는 경우 → mFindAll ( → 검색어로 제목을 찿는 매서드 )
그리고 각자 return 해주기
아 그리고 영속객체로 return 하면 안되니 DTO 만들어서 return 해야하는데 이건 뒤에서.
public List<Board> 게시글목록보기(String title) {
if(title == null){
//Pageable pg = PageRequest.of(0, 3, Sort.Direction.DESC, "id");
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<Board> boardList = boardRepository.findAll(sort);
return boardList;
}else{
List<Board> boardList = boardRepository.mFindAll(title);
return boardList;
}
}
위에 if else로 적은건 동적쿼리가 아니다.
동적쿼리(Dynamic Query)는 런타임 중에 조건에 따라 쿼리를 생성하고 실행하는것을 의미
→ 쿼리의 일부 또는 전체가 런타임 데이터나 사용자의 입력에 따라 변경됨
if(title == null) {
em. createQuery (””)
} else {
em.createQuery(””)
}
이게 동적 쿼리임. 띠ㅐ렸을 때 쿼리를 만들어서 실행함. 입력에 따라서 다른 쿼리가 실행됨

list에 추가 ( → Get 요청할 거니까 form에 action 및 input에 name 추가 )
쿼리스트링은 날아가서 쿼리의 where절에 걸린다! (중요!!!)
( → 그래서 id같은 pk는 input type hidden 써서 body에 담아서 보내거나 그러지 말고
쿼리스트링으로 전달하기 )
왜 AJAX로 요청하지 않고 Form 태그를 사용하는지 궁금할 것이다.
AJAX를 무조건 써야하는 경우가 있고(ex : 아이디 중복체크. 아이디 부분만 리로드해서 패스워드랑 이메일에 입력한 값은 유지되도록 지킴)
전체 리로드해도 괜찮은 경우(→ 지금 같은 경우. AJAX 쓰는거보다 트래픽은 더 걸리지만)가 있다.
→ 무조건 다 AJAX로 처리한다고 좋은게 아니다
→ 검색은 검색하는 경우 전체 리로드를 해서 결과를 보여줄 수 있으니 지금 FORM 태그를 사용

form 태그안에 있는거니까 submit으로 바꿔준다.
→ 폼태그안에 button은 type=submit 이라고 해줘도 되지만 없어도 자동으로 submit됨
→ type=”submit”은 지워줘도 된다.
키업 이벤트 샘플링 하기
키보드 이벤트
-> 내가 할 수 없는 기술을 프로젝트에 바로 적용하지 말고 샘플링 해봐야 한다.
통신만 안하면
-> vscode켜서 샘플링 하기
자바스크립트 이벤트가 아니라 통신에서 터질수도 있으니
만약 이걸 바로 프로젝트에 바로 작성하면
디버깅이 어려워질 수 있따.
실제 필드에 나갈 필요는 없다.
통신없이 연결안하고 기술을 한번 적용해봐야한다.
뭐가 잘못됐는지 알기 위해서는 샘플링을 해야한다.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body>
<input type="text" id="keyword">
<script>
</script>
</body>
</html>

공식문서를 봐도 되지만 최근의 블로그 포스팅 보는게 지금 상태에서는 더 쉽다.

keyup이벤트 적어주고 테스트삼아 console.log에 아무거나 찍어보면

잘 찍힌다.

이벤트를 보면



target안에

value 가 값을 가지고 잇음



이 형태로 하면 된다.
실험해봤으니 스프링에 해보자

일단 지금 검색 키업 부분은 추가된 기능이기 때문에 따로 브랜치를 만들어서 작업하기!
→ git checkout -b ajax/topic 브랜치 만들고 작업하고
다하고 나서 add. commit 하고 해당 브랜치로 push 하면 된다 !
→ 지금 만든 ajax/topic 브랜치는 master에 merge만 안하면 됨. 실제 프로젝트에 사용할 게 아니니까 브랜치 만들어서 기능 실험만 해보고 나중에 완성되면 깃허브에서는 해당 브랜치 삭제하거나 함.

AJAX로 요청할 거니 검색부분 위처럼 수정
→ name → id

스크립트 작성하고 여기까지 해서 되는지 먼저 돌려서 확인!
키보드 이벤트 처리 기능을 작성하고 되는지 확인 : )
개발할 때는 한꺼번에 작성해서 다 되는지 마지막에 돌려보고 확인하는게 아니라
중간중간 기능1을 다 작성했으면 그때 되는지 확인해야한다.
앞에 기능1이 되면 그 다음 기능2로 넘어가서 작업한다!
→ 아니면 나중에 어디서 안되는지 찾을때 디버깅이 어려워짐
( 자바스크립트에서 안되는지, 데이터가 안넘어오는건지, 통신에서 문제인지 모르기 때문에 디버깅 시간이 길어짐)

keyup해서 데이터 가져와지는걸 확인했다.
이제 통신으로 날리자.
컨트롤러로 이동

아래 list 복사해서 데이터만 날릴 boardList 매서드를 만들어준다.

2가지 방법 중 하나를 사용하면 된다.
- 게시글목록보기는 Board가 아닌 DTO를 return 하도록 BoardResponse안에 DTO를 만들어준다.

기존에 있는 DTO는 content도 주니까 이거 쓰지말고 새로 만들어주기
근데 귀찮으니까 이번에는 이거 쓰자. content 만 안쓰면 되니까

서비스 수정

컨트롤러 수정

잘 나온다
이렇게 하면 엔티티가 아니니까 No session 이 뜰 수가 없다 : )
이제 FETCH 때리자


상세보기에 repplyItem 만든거처럼 여기도 해당 부분 item으로 빼서 만들어 주기!
그전에 <div>로 감싸야 append할 수 있으니, div id=”board-box” 하나 만들어준다.

boardItem 작성 : ) return 부분에 models 안의 div 부분 복사해서 붙여넣어준다.
책임(기능)에 따라 3가지로 나누었다.
- 데이터 렌더링 → boardItem

→ 지금까지 AJAX 활용해서 부분리로딩 하는건 SRR 하기로 한 master 브랜치의 기능과 분리되는
것이니 ajax/topic 브랜치에 push 해준다!
그리고 master 에 merge 하면 안되고 브랜치로 두고 코드 확인하면 된다!
(→ master의 코드에 추가할 것이 아니기 때문에 머지하면 안됨 )

RestApi (/board) 로 요청해보면 검색 쿼리가 잘 동작해 필요한 데이터만 전달해 주는것을 확인할 수 있다.

파라미터로 아무것도 안들어가면 “”공백이 들어가니 전체 가 나온다!

타이틀에 1이 들어간 것도 잘 조회된다 : )
화면에서 해보면

“제”가 들어간 것도 잘 검색된다. ( 검색창 아래부분이 부분 리로드 되는 것도 확인하자 ? )

제목5 라고 검색해도 잘 나온다.
바운싱
1초로 키보드 속도를 “바운싱”을 잡으면 사용자가 키보드로 적기 시작하고 1초 지나면 요청이 간다.
그럼 통신이 1번만 일어난다.
바운싱을 “키보드에서 손을 떼는 순간 + 0.5.초”를 줬으면
0.5초에 1번 , 키보드 손떼는 순간 1번 - > 2번 일어난다.
검색버튼을 안눌러도 입력만하면 검색되니까 UX는 좋아지는데
요청을 엄청 많이해서
디바운싱과 쓰로틀링

스크롤 내릴때 수많은 이벤트가 발생하니까 그걸 다 보려면 부하가 걸린다.
→ 아무리 이벤트가 들어와도 “일정시간마다 이벤트를 처리”하도록 해서 과도한 이벤트 처리가 발생하지 않도록 할 수 있다.
디바운싱과 쓰로틀링 이해하기
즉,
- 디바운스 : 특정시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술
→ 10번의 패치가 일어나면 앞에 9개는 무시하고 마지막꺼면 때림
- 쓰로틀 : 특정 시간이 지났을 때 일정한 주기마다 실행되도록 하는 기술
→ 해주는데 마지막에는 강제로 1번 실행되도록 해야함
제일 처음에 연습해볼때 Throttle 로 해보고 Debounce 연습해보기
연습해보고
패치가 너무 많이 일어나서 줄이기 위해서 인터넷 찾아보니까
쓰로틀링과 디바운싱이 있어서 이걸 적용해봤다.
고 대답하기
Share article