이지은님의 블로그
250131 - Java Spring 일정관리 앱 구현과 트러블슈팅: API 명세서, JDBC 연결, DTO와 Entity, CRUD 구현, 동적쿼리 사용 본문
250131 - Java Spring 일정관리 앱 구현과 트러블슈팅: API 명세서, JDBC 연결, DTO와 Entity, CRUD 구현, 동적쿼리 사용
queenriwon3 2025. 1. 31. 20:51▷ 코드 문제풀이
[JAVA] 코드카타 - (56)~(60)
문제 (56) : 과일 장수과일 장수가 사과 상자를 포장하고 있습니다. 사과는 상태에 따라 1점부터 k점까지의 점수로 분류하며, k점이 최상품의 사과이고 1점이 최하품의 사과입니다. 사과 한 상자
queenriwon3.tistory.com
▷ 오늘 배운 것
Spring boot 일정관리 개인과제 프로젝트의 과제 수행과정을 작성해보려고 한다.
<<목차>>
0. Api 명세서 작성
1. todos 테이블 생성
2. DTO와 Entity작성
3. 일정 작성(POST) 구현
4. 조건 일정 조회(GET) 구현
5. 조건 일정 조회(GET) 구현 - 조건옵션
6. 단일 일정 조회(GET) 구현
7. 전체 일정 조회(GET) 구현 (트러블 슈팅)
8. 일정 수정(PATCH) 구현
9. 일정 삭제(DELETE) 구현 (트러블 슈팅)
10. 조건 일정 조회(GET) 구현 - 동적 쿼리 이용
0. Api 명세서 작성
프로젝트를 제대로 작성하기 전에 api를 어떻게 요청하고 응답할 것인지 설계해 보았다.
https://flaxen-swan-41e.notion.site/Lv-0-186b649ebbbd80f2a570ccd9ef43adb1?pvs=4
Lv. 0 기획단계 | Notion
Made with Notion, the all-in-one connected workspace with publishing capabilities.
flaxen-swan-41e.notion.site
(자세한 명세서 내용 - 필수 API 명세서)
과제 요구사항에 따라 등록, 조건조회, 일부조회, 수정, 삭제에 대한 api를 작성했으며, 예상 상태코드, 출력될 수 있는 에러코드를 표의 형태로 작성해보았다.
이후 swagger를 이용하여 제대로된 api명세서를 작성할 계획이지만, swagger의 사용은 과제를 모두 작성한 다음 이용하는데에 가까우므로, 프로젝트 작성전 설계용으로 간단하게 api 명세서를 작성하였다.
명세서를 작성하니 어떤 데이터베이스 테이블이 필요한지, 각 칼럼의 속성을 파악하기 쉬워 구조를 빠르게 파악할 수 있었던 것 같다.
1. todos 테이블 생성
jdbc 의존성 추가
// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
// JDBC Template
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
.properties
spring.datasource.url=jdbc:mysql://localhost:3306/schedules
spring.datasource.username= /*유저이름 작성*/
spring.datasource.password= /*비밀번호 작성*/
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
데이터 베이스 테이블을 만들기 전에, 각 테이블에 어떤 칼럼이 필요한지, 기타 제약 조건은 어떻게 되는지 표의 형태로 먼저 작성해보았다. 그리고 이를 sql코드로 바꾸어보면 다음과 같다.
USE schedules;
CREATE TABLE todos
(
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '일정 식별자',
name VARCHAR(50) NOT NULL COMMENT '이름',
todo VARCHAR(50) NOT NULL COMMENT '할일',
password VARCHAR(50) NOT NULL COMMENT '비밀번호',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '작성일',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일'
);
이때 특이한 제약 조건을 주었다.
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
이 코드는 기본값은 현재 시간으로 하고, 업데이트 될 때마다 업데이트되는 현재시간으로 업데이트한다는 뜻인데,
만약 아무 값을 넣지 않았다면 기본값 현재 날짜와 시간이 자동으로 들어간다.
그리고 업데이트에 대한 java코드를 작성해주지 않아도 jdbctemplet.update를 통해서 수정일을 업데이트 할 수 있다.
CURRENT_TIMESTAMP는 현재시간을 나타낸다.
![]() |
![]() |
위 sql코드를 통해 생성된 db와 URD 다이어그램이다.
2. DTO와 Entity작성
위 api명세서에는 생략했지만 각 api마다 요청과 응답에 대한 예시를 작성해보았다. 이를 통해 어떤 필드를 RequestDto 또는 ResponseDto에 두면 좋을지 한 눈에 파악하기 좋았다.
그리고 entity의 필드는 todos의 테이블에 작성되는 칼럼을 기준으로 선택했다.
entity/Todos.java
Todos 테이블에 작성될 칼럼은 id, name, todo, password, created_at, updated_at이므로 그에 맞추어 필드를 생성하고,
@AllArgsConstructor @Getter어노테이션을 사용해준다.
dto/todoRequestDto.java
todoRequestDto는 작성시 필요한 내용대로, name, todo, password, passwordCheck를 필드로 두었다.
여기에도 @AllArgsConstructor @Getter어노테이션을 사용해준다.
dto/todoResponseDto.java
todoResponseDto는 응답시 필요한 데이터대로, id, name, todo, createdAt, updatedAt을 필드로 두었다.
마찬가지로 @AllArgsConstructor @Getter어노테이션을 사용해주는데, 혹시 필요하게될 상황이 생길 수 있으므로 @Setter까지 작성해두었다.
3. 일정 작성(POST) 구현
강의에서 실습한 내용대로 코드를 작성했는데,
강의 내용은 id, title, contents에 대한 내용만 작성해준 반면, 이번 프로젝트에는 날짜까지 작성해야한다.
처음에는 날짜 시간 코드 작성에대해 System.currenTimeMillis()를 사용했다가 밀리초까지 나온다는 것을 확인하고, datetime에 대한 format이 필요하다고 생각했다.
Timestamp currentTimestamp = new Timestamp(System.currentTimeMillis)); // 밀리초까지 출력
그래서 밀리초까지가 아닌 초까지만 나올 수 있도록 다음블로그를 참고하여 확실한 formating을 해주는 코드로 변경해주었다.
https://dev-coco.tistory.com/31#google_vignette
[Java] 현재 날짜, 시간 구하기 (SimpleDateFormat, DateTimeFormatter)
자바에서 현재 날짜와 시간을 구하는 방법은 여러가지가 있다. Java 8 이전 Date 객체 사용 Calendar 클래스의 getInstance() 메소드 사용 System 클래스의 currentTimeMillis() 메소드 사용 원하는 포맷의 문자열
dev-coco.tistory.com
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
return sdf.format(now);
그럼 다음과 같이 출력이 잘된다는 것을 확인할 수 있다.
하지만 밀리초 방법이나 format방법 둘다 똑같은 형식으로 데이터 베이스에 입력된다.
그럼 그냥 데이터 베이스에 들어간 것을 바로 꺼내 쓰는게 낫지 않을까?
라는 생각으로 가져온 entity를 ResponseDto로 변환해주는 방법으로 조회하는게 좋다고 생각하게 된다.
아이디어를 따라 create를 한 뒤, 조회하는 코드를 따로 작성하여, 요청과 응답이 똑같아지지 않도록(응답은 entity에서 가져올 수 있도록) 작성해보았다.
// 일정 등록
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
// key를 이용해 생성된 일정 조회
Todos result = jdbcTemplate.query("select * from todos where id = ?", todosRowMapper(), key).get(0);
// 일정조회한 entity를 응답 Dto로 변경
return new TodoResponseDto(key.longValue(), result.getName(), result.getTodo(), result.getCreatedAt(), result.getUpdatedAt());
이 과정을 따라 Post 메서드 코드를 작성한 Repository이다.
@Repository
public class ScheduleRepositoryImpl implements ScheduleRepository{
private final JdbcTemplate jdbcTemplate;
public ScheduleRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public TodoResponseDto createTodo(TodoRequestDto dto) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("todos").usingGeneratedKeyColumns("id");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String nowTime = sdf.format(now);
Todos todos = new Todos(dto.getName(), dto.getTodo(), dto.getPassword());
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", todos.getName());
parameters.put("todo", todos.getTodo());
parameters.put("password", todos.getPassword());
parameters.put("created_at", nowTime);
parameters.put("updated_at", nowTime);
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
Todos result = jdbcTemplate.query("select * from todos where id = ?", todosRowMapper(), key).get(0);
return new TodoResponseDto(key.longValue(), result.getName(), result.getTodo(), result.getCreatedAt(), result.getUpdatedAt());
}
private RowMapper<Todos> todosRowMapper() {
return new RowMapper<Todos>() {
@Override
public Todos mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Todos(
rs.getLong("id"),
rs.getString("name"),
rs.getString("todo"),
rs.getString("password"),
rs.getString("created_at"),
rs.getString("updated_at")
);
}
};
}
}
그런데, 기본값을 제약조건에서 설정해 줬음에도 따로 현재시간을 구하여 일정을 등록해주었다.
기본값을 사용한 코드 삽입 방법도 생각해야할 과제인 것 같다.
(DEFAULT CURRENT_TIMESTAMP)
그러면 service레이어에는 어떤 코드를 작성해주는 것이 좋을까? 비밀번호 확인란을 만들었으니, 내가 입력한 비밀번호와 비밀번호 확인이 서로 동일한지 확인하는 비즈니스 로직을 service단에서 작성해주어야 한다.
만약 제대로 비밀번호가 다르면 사용자의 잘못된 요청임을 알려주는 HttsStatus.BAD_REQUEST를 이용한다.
@Override
public TodoResponseDto createTodo(TodoRequestDto dto) {
log.info("key1Value={}, key2Value={}", dto.getPassword(), dto.getPasswordCheck());
if (!(dto.getPassword().equals(dto.getPasswordCheck())))
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"Password != PasswordCheck");
return scheduleRepository.createTodo(dto);
}
4. 조건 일정 조회(GET) 구현
우선 검색하고 싶은 일정의 이름과, 수정일 처음과 끝의 기준을 입력받는다.
(이때는 요청 @RequestBody로 작업하였음 추후 @RequestParam으로 수정함- GET은 @RequestBody사용 지양)
그리고 jdbcTemplet.query()문을 이용해 조회 쿼리문을 작성 이후 결과값을 list형태로 출력한다.
@Override
public List<TodoResponseDto> findTodoByNameOrUpdatedAt(String name, String updatedAtFrom, String updatedAtTo) {
List<TodoResponseDto> result = jdbcTemplate.query("select * from todos where name = ? and ( date(?) <= date(updated_at) and date(updated_at) <= date(?))",
todoResponseDtoRowMapper(), name, updatedAtFrom, updatedAtTo);
result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "검색결과를 찾을 수 없음"));
return result;
}
모든 조건을 And를 이용하여 구현했기 때문에 3개의 값 모두를 입력받아야하며 그에 따른 결과가 출력되도록 한다.
그러나 과제 요구사항에는 이름만 입력하면 이름에 대한 조회조건에 따른 결과값이 출력되게 하거나, 수정일만 입력하면 수정일에 대한 조회조건에 따른 결과값이 출력, 둘다 입력된다면 둘다 검색되어 출력되도록 해야한다.
5. 조건 일정 조회(GET) 구현 - 조건옵션
각 입력에 따라 쿼리가 동적으로 작성되는 동적쿼리 방법도 있지만, 먼저 조건문을 사용하여 해당 쿼리문을 작성하여 조회하는 방법을 사용하였다.
service 레이어
@Override
public List<TodoResponseDto> findTodoByNameOrUpdatedAt(String name, String updatedAtFrom, String updatedAtTo) {
if (!updatedAtValidation(updatedAtFrom, updatedAtTo)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정일의 처음과 끝 둘 중 하나만 입력됨");
}
if (name != null && updatedAtFrom == null) {
return scheduleRepository.findTodoByName(name);
} else if (name == null && updatedAtFrom != null) {
return scheduleRepository.findTodoByUpdatedAt(updatedAtFrom, updatedAtTo);
} else if (name != null && updatedAtFrom != null) {
return scheduleRepository.findTodoByNameAndUpdatedAt(name, updatedAtFrom, updatedAtTo);
} else {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정일 이나 이름이 입력되지 않았을 떄");
}
}
먼저 수정일 시작과 수정일 끝이 둘다 입력되거나, 둘다 입력이 되지 않도록 확인을 한다. (둘 중 하나만 입력되면 BAD_REQUEST 응답이 출력되도록한다.)
이후 이름이나 수정일 입력에따라 repository에서 정의한 메서드를 호출한다.
repository 레이어
// 이름과 수정일이 작성되었을 때 두 조건에 대한 일정 조회
@Override
public List<TodoResponseDto> findTodoByNameAndUpdatedAt(String name, String updatedAtFrom, String updatedAtTo) {
List<TodoResponseDto> result = jdbcTemplate.query("select * from todos where name = ? and (date(?) <= date(updated_at) and date(updated_at) <= date(?))",
todoResponseDtoRowMapper(), name, updatedAtFrom, updatedAtTo);
result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "검색결과를 찾을 수 없음"));
return result;
}
// 이름만 작성되었을 때 이름 조건에 대한 일정 조회
@Override
public List<TodoResponseDto> findTodoByName(String name) {
List<TodoResponseDto> result = jdbcTemplate.query("select * from todos where name = ?",
todoResponseDtoRowMapper(), name);
result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "검색결과를 찾을 수 없음"));
return result;
}
// 수정일이 작성되었을 때 수정일에 대한 일정 조회
@Override
public List<TodoResponseDto> findTodoByUpdatedAt(String updatedAtFrom, String updatedAtTo) {
List<TodoResponseDto> result = jdbcTemplate.query("select * from todos where date(?) <= date(updated_at) and date(updated_at) <= date(?)",
todoResponseDtoRowMapper(), updatedAtFrom, updatedAtTo);
result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "검색결과를 찾을 수 없음"));
return result;
}
물론 이런 호출방식은 코드의 반복을 발생시킨다. 작성하는 개발자 입장에서도 각 경우를 따져보며 코드를 작성해야하니 그에 따른 피로도 생길 수 있다.
이를 막기 위해 이 이후에 동적쿼리를 사용할 예정이다.
6. 단일 일정 조회(GET) 구현
Id를 이용한 단일 일정조회는 어렵지 않다. jdbcTemplete.quert()의 쿼리 작성을 통해 바로 출력하면 된다.
@Override
public TodoResponseDto findTodoByIdElseThrow(Long id) {
List<TodoResponseDto> result = jdbcTemplate.query("select * from todos where id = ?", todoResponseDtoRowMapper(), id);
return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "찾을 수 없는 id = " + id));
}
이때, 만약 존재하지 않는 id값이라고 한다면, 이에따른 null 예외를 처리해준다.
(findAny()는 어느것이라도 있는지 확인하는 것이고, orElseThrow는 아무것도 없으면(else) 예외를 던진다는 뜻이다.)
7. 전체 일정 조회(GET) 구현(트러블 슈팅)
전체 일정조회 또한 강의에서 실습한 내용을 바탕으로 그대로 작성하였다. 그런데 다음과 같은 오류코드를 발견한다.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Ambiguous mapping. Cannot map 'scheduleController' method
com.example.scheduleproject.controller.ScheduleController#findTodoAll()
to {GET [/schedules]}: There is already 'scheduleController' bean method
com.example.scheduleproject.controller.ScheduleController#findTodoByNameOrUpdatedAt(TodoGetRequestDto) mapped.
이 에러코드의 핵심은 다음과 같다.
{GET [/schedules]}: There is already 'scheduleController' bean method
GET /schedules 이미 존재하는 메서드라는 뜻이다. 이 에러코드는 두 개 이상의 메서드나 컨트롤러에서 같은 HTTP 요청(같은 URL, 같은 HTTP 방식)에 대해 매핑을 하려고 할 때 발생한다.
그러니까 @GetMapping을 이미 작성했다면서 오류가 난 것이다.
앞서 조건에 따른 일정을 조회하는데 @GetMapping을 사용했으니 다른 Get메서드를 사용하고 싶다면 다른 경로를 지정해주어야한다.
첫번째랑 두번째가 같은 경로로 코드가 작성되어 충돌이 발생하게된다.
그래서 전체 일정을 조회하는 메서드에는 /read-all 이라는 경로를 따로 설정해주었다.
설정해준 경로에 맞추어 입력값을 설정하면 다음과 같이 전체 일정을 조회할 수 있다.
8. 일정 수정(PATCH) 구현
일정을 수정하려면 api명세서에 작성한 것 대로, 이름과 일정내용, 비밀번호를 입력받아야한다.
먼저 service레이어에서 해당 일정 id의 일정의 비밀번호와 사용자가 작성한 비밀번호가 일치하는지 확인을 해야한다.
비밀번호는 데이터베이스에서 id를 통해 불러와야하는데, 단일 값을 불러올 수 있는지는 구글링을 통해서 찾아보았다.
https://bibi6666667.tistory.com/300
[Spring] Spring JdbcTemplate 가이드 (공식문서, Baeldung)
Spring JdbcTemplate에 대한 공식문서 가이드 (K가 알려주셨다🙇♀️ 감사합니다!) 3.1 Choosing an Approach for JDBC Database Access에서 간단한 설명을 읽고 3.3.1. UsingJdbcTemplate 부분부터 보면 될 듯! https://docs.
bibi6666667.tistory.com
이 참고한 블로그에서는 다음과 같이 jdbcTemplete.queryForObject를 통해 row가 아닌 하나의 값을 구해 java Object로 변환할 수 있다.
이때 메서드를 입력하는 곳에는 어떤 형식으로 변환할 것인지를 작성해주어야한다.
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);
이 코드를 이용하여 비밀번호를 매칭시키는 코드를 작성해보았다.
repository 레이어
@Override
public int updateNameAndTodo(Long id, String name, String todo, String password) {
String storedPassword = jdbcTemplate.queryForObject("select password from todos where id = ?", String.class, id);
if (!storedPassword.equals(password))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호 확인");
return jdbcTemplate.update("update todos set name = ?, todo = ? where id = ?", name, todo, id);
}
쿼리문을 이용해 id에 따른 password값을 불러오고 이를 입력받은 비밀번호와 비교한다. 비밀번호가 일치하지 않으면 예외를 던지는 방법으로 인증불가에 대한 예외코드를 출력한다.
만약 일치 하면 성공적으로 jdbcTemplate.update()를 사용해 일정을 수정할 수 있다.
service 레이어
@Override
public TodoResponseDto updateNameAndTodo(Long id, String name, String todo, String password) {
if (name == null || todo == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "항상 필수값임");
}
int updatedRows = scheduleRepository.updateNameAndTodo(id, name, todo, password);
if (updatedRows == 0) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "찾을수 없는 id" + id);
return scheduleRepository.findTodoByIdElseThrow(id);
}
Service레이어에는 수정할 이름과 일정내용을 모두 입력받을 수 있도록 하기위해, 입력되지 않았을 경우 예외를 던지는 조건문을 작성한다.
그리고 업데이트된 row의 개수가 아무것도 없다면 찾을 수 없는 id라고 예외를 던지도록한다.
이후 업데이트한 일정을 조회하고 이를 출력하는 것으로 일정 수정을 구현할 수 있다.
입력된 비밀번호와 작성한 비밀번호가 일치할 때 —> 성공적으로 일정이 수정됨
입력된 비밀번호와 작성한 비밀번호가 일치하지 않을 때 -> Unauthorized
9. 일정 삭제(DELETE) 구현(트러블 슈팅)
일정 삭제는 일정 수정과 마찬가지로 jdbcTemplete.update()를 이용하면 된다.
그러나 존재하지 않는 id값의 일정을 삭제하려고 할때, 다음과 같은 오류가 발생한다.
수정 전 코드
에러코드의 대락적인 내용은 1개의 값을 찾아서 storedPassword에 넣어야하는데 아무 값도 조회되지 않아서 EmptyResultDataAccessException 예외가 발생한 것이다.
String storedPassword = jdbcTemplate.queryForObject("select password from todos where id = ?", String.class, id);
이 코드의 문제는 없는 값을 들고 오려고 하는데서 발생한다.
그러니 이때 발생하는 예외를 잡아서 try-catch문으로 예외를 처리하도록 했다.
이를 통해 존재하지 않는 id값을 삭제하려고 할 때의 예외처리 로직을 작성하여, NOT_FOUND상태를 출력하도록 했다.
10. 조건 일정 조회(GET) 구현 - 동적 쿼리 이용
5번에서 이미 작성되어 있는 쿼리 메서드를 조건문에 따라 실행시키는 방법을 사용하여 코드를 작성했다. 하지만 이럴경우 코드의 반복이 발생하고, 입력에 따른 유연한 처리가 힘들어진다.
그래서 동적으로 쿼리를 작성하고 파라미터 값을 넣어 줄 수 있도록 코드를 구성한다.
여기서 StringBuilder sb는 조건에 따른 쿼리를 동적으로 추가할 수 있도록 앞부분 쿼리를 통해 초기화 시켜준 부분이고,
list는 ?마다 들어갈 파라미터를 하나씩 추가해주는 ArrayList이다.(배열이 아닌 이유는 파라미터를 동적으로 추가해주기 위함이다)
StringBuilder sb = new StringBuilder("select * from todos where 1=1");
List<Object> list = new ArrayList<>();
그래서 이름이 입력될 경우 이름을 조건으로 row를 검색하는 쿼리문과 이름 파라미터를 추가 시킬 수 있으며, 수정일이 입력될 경우 수정일을 조건으로 row를 검색하는 쿼리문과 수정일 파라미터를 추가 시킬 수 있다.
@Override
public List<TodoResponseDto> findTodoByNameAndUpdatedAt(String name, String updatedAtFrom, String updatedAtTo) {
StringBuilder sb = new StringBuilder("select * from todos where 1=1");
List<Object> list = new ArrayList<>();
if (name != null) {
sb.append(" and name = ?");
list.add(name);
}
if (updatedAtFrom != null && updatedAtTo != null) {
sb.append(" and (date(?) <= date(updated_at) and date(updated_at) <= date(?))");
list.add(updatedAtFrom);
list.add(updatedAtTo);
} else if (updatedAtFrom != null) {
sb.append(" and (date(?) <= date(updated_at)");
list.add(updatedAtFrom);
} else if (updatedAtTo != null) {
sb.append(" and (date(updated_at) <= date(?))");
list.add(updatedAtTo);
}
List<TodoResponseDto> result = jdbcTemplate.query(sb.toString(),
todoResponseDtoRowMapper(), list.toArray());
result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "검색결과를 찾을 수 없음"));
return result;
}
이를 통해 이름, 수정일 각각의 조회도 가능할 뿐더러 이름, 수정일 모든 조건에 대한 검색도 유연하게 처리할 수 있다.
그리고 아무것도 입력되지 않을 경우 전체 조회가 가능하다.
이후의 코드는 리팩토링과 함께 다음 도전과제 내용 구현에서 계속 작성될 예정이다.