[문제]
처음에는 전부 다 빨간 줄이 쳐져서 얼마나 심장이 벌렁벌렁 뛰었는지 모른다. 분명 강의를 들으며 코드를 따라 쳤는데, 왜 내가 쓴 코드에만 문제가 생기는지 영 감을 못 잡았다. 챗GPT에도 물었으나, 분명한 답을 얻지는 못했다.
[원인]
package com.spring.weekthree.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDate;
import java.time.LocalDateTime;
/*
entity에 해당하는 Plan 클래스
[오답] NoArgsConstructor
[정답] AllArgsConstructor
*/
@NoArgsConstructor
@Getter
public class Plan {
// 속성
private Long id;
private String name;
private String password;
private LocalDate plannedDate;
private String title;
private String task;
private LocalDateTime createdDateTime;
private LocalDateTime updatedDateTime;
/**
* 생성자
* @param name : 사용자 이름
* @param password : 사용자 비밀번호
* @param plannedDate : 사용자가 입력한 일정 날짜
* @param title : 사용자가 입력한 일정의 제목
* @param task : 사용자가 입력한 일정의 상세 정보
*/
public Plan(
String name,
String password,
LocalDate plannedDate,
String title,
String task
) {
this.name = name;
this.password = password;
this.plannedDate = plannedDate;
this.title = title;
this.task = task;
this.createdDateTime = LocalDateTime.now();
this.updatedDateTime = LocalDateTime.now();
}
// 기능
}
원인은 강의 자료를 읽은 끝에 찾아낼 수 있었다. 엔티티(entity)에 해당하는 Plan 클래스(class)에서 나는 기본 생성자를 만드는 @NoArgsConstructor 어노테이션(annotation)을 썼는데, 강의에서는 클래스의 모든 속성을 매개변수로 받는 @AllArgsConstructor 어노테이션을 썼다. 그러니까, 받아야 하는 매개변수가 맞지 않아 문제가 생기고 말았다.
[해결]
package com.spring.weekthree.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDate;
import java.time.LocalDateTime;
/*
[1/2] entity에 해당하는 Plan 클래스 수정하기
[수정 전] @NoArgsConstructor
[수정 후] @AllArgsConstructor
*/
@AllArgsConstructor
@Getter
public class Plan {
// 속성
private Long id;
private String name;
private String password;
private LocalDate plannedDate;
private String title;
private String task;
private LocalDateTime createdDateTime;
private LocalDateTime updatedDateTime;
// 생성자
public Plan(
String name,
String password,
LocalDate plannedDate,
String title,
String task
) {
this.name = name;
this.password = password;
this.plannedDate = plannedDate;
this.title = title;
this.task = task;
this.createdDateTime = LocalDateTime.now();
this.updatedDateTime = LocalDateTime.now();
}
// 기능
}
package com.spring.weekthree.repository;
import com.spring.weekthree.dto.PlanResponseDto;
import com.spring.weekthree.entity.Plan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
// [2/2] Data Access Layer(Repository Layer) 수정하기
@Repository
public class JdbcTemplatePlanRepository implements PlanRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcTemplatePlanRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public PlanResponseDto save(Plan plan) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("planner").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", plan.getName());
parameters.put("password", plan.getPassword());
parameters.put("plannedDate", plan.getPlannedDate());
parameters.put("title", plan.getTitle());
parameters.put("task", plan.getTask());
parameters.put("createdDateTime", plan.getCreatedDateTime());
parameters.put("updatedDateTime", plan.getUpdatedDateTime());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
return new PlanResponseDto(
key.longValue(),
plan.getName(),
plan.getPlannedDate(),
plan.getTitle(),
plan.getTask(),
plan.getCreatedDateTime(),
plan.getUpdatedDateTime());
}
@Override
public List<PlanResponseDto> fetchAllPlans(String name, LocalDate updatedDate) {
return jdbcTemplate.query("SELECT * FROM planner", plannerRowMapper());
}
@Override
public Optional<Plan> fetchPlanById(Long id) {
List<Plan> result = jdbcTemplate.query("SELECT * FROM planner WHERE id = ?", plannerRowMapperEach(), id);
return result.stream().findAny();
}
private RowMapper<PlanResponseDto> plannerRowMapper() {
return new RowMapper<PlanResponseDto>() {
@Override
public PlanResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
return new PlanResponseDto(
rs.getLong("id"),
rs.getString("name"),
rs.getDate("plannedDate").toLocalDate(),
rs.getString("title"),
rs.getString("task"),
rs.getTimestamp("createdDateTime").toLocalDateTime(),
rs.getTimestamp("updatedDateTime").toLocalDateTime()
);
}
};
}
private RowMapper<Plan> plannerRowMapperEach() {
return new RowMapper<Plan>() {
@Override
public Plan mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Plan(
rs.getLong("id"),
rs.getString("name"),
rs.getString("password"),
/*
[수정 전] 비밀번호 부분 누락
[수정 후] 추가: rs.getString("password"),
[주의 사항] 쉼표(,) 누락하지 말자!
*/
rs.getDate("plannedDate").toLocalDate(),
rs.getString("title"),
rs.getString("task"),
rs.getTimestamp("createdDateTime").toLocalDateTime(),
rs.getTimestamp("updatedDateTime").toLocalDateTime()
);
}
};
}
}
이번 문제는 적절한 어노테이션(annotation)을 사용하지 않고, 엔티티와 DTO(Data Transfer Object)를 헷갈려서 생겼다. 일정 목록을 조회할 때 반환하는 값은 PlanResponseDto이며 클라이언트(client)에게 엔티티가 아니라 이 PlanResponseDto를 전달하는데, 이 사실을 순간 잊었다.
'맞다, 생각해 보니까 데이터를 전달할 때 Plan이 아니라 DTO를 주잖아?'
그러니 일정 목록을 조회할 때 비밀번호는 전달하지 말아야 한다는 생각에 사로잡혀 rs.getString("password") 부분 또한 적어야 한다는 점을 금방 떠올리지 못했다. 앞으로는 좀 더 차근차근 코드를 써야겠다.
[결과 수치화]
[수정 전] 자바(Java) 인텔리제이(IntelliJ) 빨간 줄 경고 표시 3개 발생
[수정 후] 자바(Java) 인텔리제이(IntelliJ) 빨간 줄 경고 표시 0개 발생
'Troubleshooting: 무엇이 문제였는가? > 본캠프 3주 차: 일정 관리 앱 만들기' 카테고리의 다른 글
1단계: "아무래도 이름(name)이란 체에 구멍이 뻥 뚫렸나 보다." (0) | 2024.12.08 |
---|---|
2단계: "왜 수정 날짜를 수정하려고 하니!" (0) | 2024.12.08 |
2단계: "500 Internal Server Error라니! 일단 침착해!" (0) | 2024.12.07 |
1단계: "Condition '(name != null) && (updatedDate != null)' is always 'false'" (0) | 2024.12.07 |
2단계: "꿈쩍 안 하는 수정일과 얼음땡!" (0) | 2024.12.06 |