
초기 구현 단계에서는 GET/api/memos 요청을 통해 전체 메모 목록을 조회하도록 구현되어 있었다.
초기 구현 단계에서 Memo 엔터티는 메모 테이터 구조를 정의하고 @Entity 어노테이션을 통해 DB테이블과 매핑되어 있었고 timestamped의 상속을 받아 생성/수정시간 등이 기록되어 있었다.
본 프로젝트는 메모 앱에서 간단 검색 기능을 추가하는 것이고 정렬이나 로직은 엔터티의 역할이 아니므로 Service Repository에서 처리해야한다.
MemoRequstDto는 클라이언트로부터 전달받는 데이터만을 담는 객체로 사용되었고 사용자의 입력값(usernmae,contents)를 엔티티로 전달하기 전 중간 매게체 역할을 하고 있었고 MemoResponseDto는 서버가 클라이언트에게 반환하는 데이터를 담는 객체로 메모 조회시 클라이언트에게 전달할 데이터를 정희하고 엔티티의 필요한 정보만 선택적으로 노출하는 역할을 하고 있었다.
이를 통해 엔티티를 외부에 직접 노출하지 않고, 요청과 응답에 필요한 데이터만을 전달하는 구조를 구성하여 유지보수성과 확장성을 확보하는 기반이 되었다.
메모 조회 동작 방식
그럼 지금부터 검색기능을 구현해보자
package com.meta.memo.controller;
import com.meta.memo.dto.MemoRequestDto;
import com.meta.memo.dto.MemoResponseDto;
import com.meta.memo.service.MemoService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("api/memos")
public class MemoController {
// 멤버 변수 선언
private final MemoService memoService;
//생성자 주입(DI)
public MemoController(MemoService memoService){
this.memoService = memoService;
}
@PostMapping()
public MemoResponseDto createMemo(@RequestBody MemoRequestDto memoRequestDto) {
return memoService.createMemo(memoRequestDto);
}
@GetMapping()
public List<MemoResponseDto> getMemos(){
return memoService.getMemos();
}
@PutMapping("{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto memoRequestDto) {
return memoService.updateMemo(id, memoRequestDto);
}
@DeleteMapping("{id}")
public Long deleteMemo(@PathVariable Long id){
return memoService.deleteMemo(id);
}
}
MemoController는 현재 메모 생성, 조회, 수정, 삭제에 대한 Rest API를 제공하고 있고 클라이언트의 요청을 받아 DTO 형태로 전달받아 서비스 계층에 처리결과를 JSON 형태로 반환한다. 여기서 특정 키워드가 포함된 메모를 조회하는 API를 구현해야한다.
API 구현 조건
http://localhost:8080/api/memos/contents?keyword=
- 메서드명은 getMemosByKeyword
@GetMapping()
public List<MemoResponseDto> getMemosByKeyword(@RequestParam String keyword){
return memoService.getMemosByKeyword(keyword);
}
해당 메서드는 메모 내용(/contents)에 특정 키워드가 포함된 메모 목록을 조회하는 기능을 수행한다. 클라이언트는 Query String 방식으로 검색할 키워드를 전달하고 JSON형태의 메모 목록으로 반환된다.
@RequestParam
-URL에 포함된 keyword 값을 파라미터로 바인딩하고 별도의 RequestBody 없이 조회 목적의 요청을 한다.
return memoService로 검색 조건 keyword만 서비스 계층으로 전달된다 참고로 memo.java에 @Getter가 있기 때문에 get~가 사용이 가능함
컨트롤러 계층에서는 위와 같은 키워드 기반 조회 API를 추가하였지만 해당 API를 호출했을 때 서비스 계층에 실제 실행될 메서드가 존재하지 않고 구현이 되어있지 않아 정상적으로 응답을 반환할 수 없다.
그래서 서비스 계층에 getMemosByKeyword를 구현하였다.
public List<MemoResponseDto> getMemosByKeyword(String keyword){
List<MemoResponseDto> memoResponseDtoList = memoRepository.findAll().stream()
.map(MemoResponseDto::new)
.toList();
return memoResponseDtoList;
}
해당 메서드의 역할
서비스 계층에서는 위와 같은 방식으로 findAll()로 임시 구현을 하였는데 이 방식은 문제가 생긴다. 일단 키워드 검색 조건이 데이터베이스에 반영되지 않고 메서드 이름은 검색이지만 실제로 작동은 되지 않는다.
그래서 검색과 정렬을 데이터베이스에서 처리하도록 MemoRepository에 쿼리 메서드를 추가하였다.
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findAllByContentsContainingOrderByModifiedAtDesc(String keyword);
}
메서드 구성과 의미
findAllBy : 조건에 맞는 모든 데이터를 조회한다.
contents : Memo 엔터티의 contents 내용
Containing : Keyword가 포함된 데이터
OrderByModifedAtDesc : 수정 시간 기준 내림차순 정렬
새로운 쿼리 메서드를 정의 했기 때문에 서비스 계층에 있는 List도 다시 작성해야한다.
public List<MemoResponseDto> getMemosByKeyword(String keyword){
List<MemoResponseDto> memoResponseDtoList = memoRepository.<span style = "color : red">findAllByContentsContainingOrderByModifiedAtDesc(keyword)</span>
.stream()
.map(MemoResponseDto::new)
.toList();
return memoResponseDtoList;
}
findAll()을 바꿔주어야한다. findAllByContentsContainingOrderByModifiedAtDesc(keyword)
하지만 실제로 코드를 반영해보면 웹에서는 내림차순 정렬이 되지 않는다. 검색 기능(getMemosByKeyword)은 수정 시간 기준 내림차순 정렬이 포함된 Query Method를 적용했으나, 실제 웹 화면에서는 전체 메모 목록을 조회하는 /api/memos API만 호출하고 있었기 때문에 검색 API의 정렬 결과가 화면에 반영되지 않았다. 이후 전체 조회(getMemos)에도 수정 시간 기준 내림차순 Query Method를 별도로 적용하면서 웹 화면에 최신순 정렬이 정상적으로 반영되었다.
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findAllByContentsContainingOrderByModifiedAtDesc(String keyword);
List<Memo> findAllByOrderByModifiedAtDesc();
}
MemoRepository에 새로운 메서드를 추가했으니 서비스 계층에서 getMemos 메서드에도 쿼리 메서드를 적용해야한다.
public List<MemoResponseDto> getMemos() {
List<MemoResponseDto> memoResponseDtoList = memoRepository.findAllByOrderByModifiedAtDesc().stream()
.map(MemoResponseDto::new).toList();
return memoResponseDtoList;
}

코드를 실행하면 코드가 내림차순으로 정렬되는 것을 볼 수 있다. Postman에서도 실행해보자.


Postman과 구글 크롭 url에 조건에 있는 해당 url을 넣었을 때 Json 텍스트를 반환하고 200 OK가 뜨는 것을 볼 수 있다.
html은 시간 부족으로 기존에서 크게 건들지는 못했다.
1. Header 영역 구성

Memo 페이지의 메인 타이틀 header부분에 변화를 주었다. 먼저 좌우에 이미지를 배치하여 무슨 내용을 메모에 기록하면 되는지 주제를 주었고 이미지는 position : abolute를 기반으로 간단히 배치하였다.
2. 검색 영역 구성

사용자가 특정 키워드를 입력해 메모를 검색할 수 있도록 검색 영역을 추가하였다. 검색 입력창과 버튼을 분리하여 기능별 역할이 수행하도록 구생하였다. 검색 버튼은 입력한 키워드를 기반으로 서버에 검색 요청을 보내고, 전체보기는 기존 전체 메모 목록을 다시 불러오는 역할을 한다.
// 메모 검색
function searchMemos() {
const keyword = $('#keyword').val().trim();
if (keyword === '') {
alert('검색어를 입력해주세요');
return;
}
// 1) 기존 목록 비우기 (검색 결과로 덮어쓰기)
$('#cards-box').empty();
// 2) 검색 API 호출
$.ajax({
type: 'GET',
url: `/api/memos/contents?keyword=${encodeURIComponent(keyword)}`,
success: function (response) {
// 3) 검색 결과 렌더링 (기존 addHTML 재사용)
for (let i = 0; i < response.length; i++) {
const message = response[i];
addHTML(message['id'], message['username'], message['contents'], message['modifiedAt']);
}
// 검색 결과 없을 때(선택)
if (response.length === 0) {
$('#cards-box').append(`<div style="padding:15px; color:#666;">검색 결과가 없습니다.</div>`);
}
},
error: function () {
alert('검색 요청 실패: 서버 실행/URL을 확인하세요');
}
});
}
검색 버튼을 클릭할 경우 seachMemos()함수가 실행되고 해당 함수는 Ajax를 통해 GET /api/memos/contents?keyword=? 형태로 API에 요청을 보내고 서버는 contents에 키워드가 포함된 메모를 조회하여 JSON 형태로 응답한다. 응답으로 받은 결과는 기존 메모 목록을 비우고 검색결과를 화면에 랜더링한다.
새로고침은 getMessages() 함수를 재사용하여 검색결과에서 전체 메모 목록으로 돌아갈 수 있도록하게 한다.
3. 올라가는 버튼 구현

마지막으로 간단하게 전에 썼던 누르면 위로 올라가는 버튼 구현
MemoController에서 처음에 getMemosByKeyword 함수 기능을 넣으라고 했을 때 getMemos 전체 조회를 없애고 기능을 넣었다가 웹사이트가 안열려서 코드를 복구하고 새로 특정 키워드가 포함된 APi 메모함수를 만들었음
이후 서비스 계층과 MemoRepository까지 잘 작성하였으나 쿼리 메서드 이름을 찾아보고 입력했지만 실행에 오류가 생겨서 gpt에게 쿼리 메서드 이름을 어떻게 작성하면 되는지 물어보고 작성함.
이후 postman에 실행할 때 json 텍스트를 불러오지 못함 -> String keyword를 작성안했었음.
쿼리 메서드를 잘 작성했지만 실제 웹사이트에서는 반영이 되지 않음 -> Gpt에게 물어보니 getMemosByKeyword가 수정 시간 기준 내림차순 정렬은 반영이 되었으나 실제 웹사이트에서는 전체 메모 목록을 조회하는 api/memos를 호출하고 있었기 때문에 getMemos로 반영이 되어 내림차순 정렬이 되지 않고 있었다. 그래서 새로운 쿼리 메서드를 추가하였다.
html을 구성하는데는 시간이 좀 부족해서 디자인적으로 많이 아쉬웠다. 기존 html에 사진밖에 넣지 못했다.
검색기능을 구현하면서 기존목록을 비우는 과정을 생각하지는 못했고 키워드를 불러와야겠다는 생각만했었다. 그리고 js는 아직 잘 사용을 못한다고 생각했다.
오오오오오옹오오오옹노오오오오옹