[문제]
'일정 관리 앱 Develop' 과제는 JPA를 활용해야 했다. 필수 과제 1단계에서는 일정을 생성, 조회, 수정, 삭제할 수 있도록 CRUD를 구현해야 했는데, 생성(Create)에 해당하는 C를 구현한 다음 프로그램을 실행하자, 바로 500 Internal Server Error 메시지가 떴다. 우선 null이 들어가면 안 되는 곳에 null이 들어가서 생긴 문제라 짐작했다.
[원인]
package com.example.plan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/*
[오답] 어노테이션(annotation) 누락
[정답] 추가: @EnableJpaAuditing
*/
@EnableJpaAuditing
@SpringBootApplication
public class PlanApplication {
public static void main(String[] args) {
SpringApplication.run(PlanApplication.class, args);
}
}
원인은 필요한 어노테이션(annotation)을 빠뜨린 데에 있었다. 과제의 요구 사항 중 하나가 '작성 날짜와 수정 날짜는 JPA Auditing 활용하기'였는데, @EnableJpaAuditing을 쓰지 않아 오류가 발생했다.
[해결]
@EnableJpaAuditing 어노테이션(annotation)을 추가하고 다시 프로그램을 실행한 결과, 일정 생성에 성공했다는 201 Created 메시지가 나왔으나, 생성 날짜와 수정 날짜가 데이터베이스(database)에만 저장되었을 뿐, 반환되지는 않았다. 추가로 응답할 때 두 정보가 전달되도록 아래와 같이 수정했다.
package com.example.plan.plan.repository;
// [1/8] Data Access Layer(Repository Layer)
import com.example.plan.plan.entity.Plan;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PlanRepository extends JpaRepository<Plan, Long> {
}
package com.example.plan.plan.service;
import com.example.plan.plan.dto.response.PlanResponseDto;
// [2/8] Service Layer 구현에 필요한 인터페이스(interface)
public interface PlanService {
public PlanResponseDto save(
String name,
String title,
String task
);
}
package com.example.plan.plan.controller;
import com.example.plan.plan.dto.request.CreatePlanRequestDto;
import com.example.plan.plan.dto.response.PlanResponseDto;
import com.example.plan.plan.service.PlanServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// [3/8] Presentation Layer
@RestController
@RequestMapping("/plans")
@RequiredArgsConstructor
public class PlanController {
private final PlanServiceImpl planService;
@PostMapping
public ResponseEntity<PlanResponseDto> save(
@RequestBody CreatePlanRequestDto requestDto
) {
PlanResponseDto savedPlan = planService.save(
requestDto.getUsername(),
requestDto.getTitle(),
requestDto.getTask()
);
return new ResponseEntity<>(savedPlan, HttpStatus.CREATED);
}
}
package com.example.plan.plan.entity;
import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
/*
[4/8] BaseEntity 클래스(class)
용도: 엔티티(entity)에 작성 날짜와 수정 날짜 반영
*/
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class PlanBaseEntity {
@CreatedDate
@Column (nullable = false, updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt; // 작성 날짜
@LastModifiedDate
@Column (nullable = false)
private LocalDateTime updatedAt; // 수정 날짜
}
package com.example.plan.plan.entity;
import jakarta.persistence.*;
import lombok.Getter;
// [5/8] BaseEntity를 상속한 Plan 엔티티(entity) 클래스
@Getter
@Entity
@Table(name = "plans")
public class Plan extends PlanBaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long planId;
@Column(nullable = false)
private String username; // 작성자 이름
@Column(nullable = false)
private String title; // 일정 제목
@Column(nullable = true, columnDefinition = "longtext")
private String task; // 일정 내용
public Plan() {
}
public Plan(
String username,
String title,
String task
) {
this.username = username;
this.title = title;
this.task = task;
}
}
package com.example.plan.plan.dto.request;
import lombok.Getter;
// [6/8] 일정 생성 요청에 해당하는 request DTO
@Getter
public class CreatePlanRequestDto {
private final String username; // 작성자 이름
private final String title; // 일정 제목
private final String task; // 일정 내용
public CreatePlanRequestDto(
String username,
String title,
String task
) {
this.username = username;
this.title = title;
this.task = task;
}
}
package com.example.plan.plan.service;
import com.example.plan.plan.dto.response.PlanResponseDto;
import com.example.plan.plan.entity.Plan;
import com.example.plan.plan.repository.PlanRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/*
[7/8] Service Layer 수정하기
PlanService 인터페이스를 오버라이딩한 클래스이다.
*/
@Service
@RequiredArgsConstructor
public class PlanServiceImpl implements PlanService {
private final PlanRepository planRepository;
@Override
public PlanResponseDto save(
String username,
String title,
String task
) {
Plan planToSave = new Plan(
username,
title,
task
);
Plan savedPlan = planRepository.save(planToSave);
return new PlanResponseDto(
savedPlan.getPlanId(),
savedPlan.getUsername(),
savedPlan.getTitle(),
savedPlan.getTask(),
savedPlan.getCreatedAt(), // [수정] 추가
savedPlan.getUpdatedAt() // [수정] 추가
// [주의] 추가할 때 쉼표(,)를 누락하지 말자.
);
}
}
package com.example.plan.plan.dto.response;
import lombok.Getter;
import java.time.LocalDateTime;
// [8/8] 응답에 해당하는 response DTO
@Getter
public class PlanResponseDto {
private final Long planId; // 일정 식별자
private final String username; // 작성자 이름
private final String title; // 일정 제목
private final String task; // 일정 내용
private final LocalDateTime createdAt; // [수정] 추가: 작성 날짜
private final LocalDateTime updatedAt; // [수정] 추가: 수정 날짜
/*
[수정] 두 가지 매개변수 추가
[주의] 추가할 때 쉼표(,)를 누락하지 말자.
(1) LocalDateTime createdAt
(2) LocalDateTime updatedAt
*/
public PlanResponseDto(Long planId,
String username,
String title,
String task,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
this.planId = planId;
this.username = username;
this.title = title;
this.task = task;
this.createdAt = createdAt; // [수정] 추가
this.updatedAt = updatedAt; // [수정] 추가
}
}
역시 처음 배운 내용을 바로 완벽하게 적용하기란 불가능했다. 그래도 로직(logic)을, 코드의 흐름을 뜯어고칠 정도로 심각한 문제가 아니라서 안도감이 살짝 들었다.
[결과 수치화]
[수정 전] 500 Internal Server Error 1건 발생
[수정 후] 500 Internal Server Error 0건 발생, 201 Created 메시지 생성 및 데이터베이스 연동 성공
'Troubleshooting: 무엇이 문제였는가? > 본캠프 3주 차: 일정 관리 앱 Develop' 카테고리의 다른 글
2단계: "MySQL Error 1049: Unknown database" (0) | 2024.12.15 |
---|---|
1단계: "200 OK인데 왜 username이 null일까?" (0) | 2024.12.15 |