Project
[Project] Lv_1 스케줄 프로젝트(심화)
kimyongjun0129
2025. 5. 19. 21:05
목차
요구 사항
일정을 생성, 조회, 수정, 삭제할 수 있습니다.
일정은 아래 필드를 가집니다.
- ✅ 작성 유저명, 할일 제목, 할일 내용, 작성일, 수정일 필드
- ✅ 작성일, 수정일 필드는 JPA Auditing을 활용합니다. → 3주차 JPA Auditing 참고!
요구 구현
ScheduleController
더보기
@RestController
@RequestMapping("/api/schedules")
public class SchedueController {
private final ScheduleService scheduleService;
public SchedueController(ScheduleService scheduleService) {
this.scheduleService = scheduleService;
}
@PostMapping
public ResponseEntity<CreateScheduleResponseDto> createSchedule (@RequestBody CreateScheduleRequestDto requestDto) {
return new ResponseEntity<>(scheduleService.saveSchedule(requestDto), HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<FindScheduleResponseDto> findSchedule(
@PathVariable Long id
) {
FindScheduleResponseDto schedule = scheduleService.findSchedule(id);
return new ResponseEntity<>(schedule, HttpStatus.OK);
}
@PatchMapping("/{id}")
public ResponseEntity<UpdateScheduleResponseDto> updateSchedule(
@PathVariable Long id,
@RequestBody UpdateScheduleRequestDto requestDto
) {
UpdateScheduleResponseDto schedule = scheduleService.updateSchedule(id, requestDto);
return new ResponseEntity<>(schedule, HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteSchedule(
@PathVariable Long id,
@RequestBody DeleteScheduleRequestDto requestDto
) {
scheduleService.deleteSchedule(id, requestDto);
return new ResponseEntity<>(HttpStatus.OK);
}
}
ScheduleService
더보기
public interface ScheduleService{
CreateScheduleResponseDto saveSchedule(CreateScheduleRequestDto requestDto);
FindScheduleResponseDto findSchedule(Long id);
UpdateScheduleResponseDto updateSchedule(Long id, UpdateScheduleRequestDto requestDto);
void deleteSchedule(Long id, DeleteScheduleRequestDto requestDto);
}
ScheduleImpl
더보기
@Service
public class ScheduleServiceImpl implements ScheduleService{
private final ScheduleRepository scheduleRepository;
public ScheduleServiceImpl(ScheduleRepository scheduleRepository) {
this.scheduleRepository = scheduleRepository;
}
public CreateScheduleResponseDto saveSchedule(CreateScheduleRequestDto requestDto) {
if (requestDto.getUsername() == null || requestDto.getTitle() == null || requestDto.getContent() == null || requestDto.getPassword() == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "There is no content.");
}
Schedule schedule = new Schedule(requestDto.getUsername(), requestDto.getTitle(), requestDto.getContent(), requestDto.getPassword());
Schedule savedSchedule = scheduleRepository.save(schedule);
return new CreateScheduleResponseDto(savedSchedule);
}
public FindScheduleResponseDto findSchedule(Long id) {
Schedule schedule = scheduleRepository.findByIdOrElseThrow(id);
return new FindScheduleResponseDto(schedule);
}
@Override
public UpdateScheduleResponseDto updateSchedule(Long id, UpdateScheduleRequestDto requestDto) {
Schedule schedule = scheduleRepository.findByIdOrElseThrow(id);
if(requestDto.getTitle() == null || requestDto.getContent() == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "There is no content.");
}
if(!schedule.getPassword().equals(requestDto.getPassword())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "This password does not the same as schedule password.");
}
schedule.updateTitle(requestDto.getTitle());
schedule.updateContent(requestDto.getContent());
Schedule savedSchedule = scheduleRepository.save(schedule);
return new UpdateScheduleResponseDto(savedSchedule);
}
@Override
public void deleteSchedule(Long id, DeleteScheduleRequestDto requestDto) {
Schedule schedule = scheduleRepository.findByIdOrElseThrow(id);
if(!schedule.getPassword().equals(requestDto.getPassword())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "This password does not the same as schedule password.");
}
scheduleRepository.delete(schedule);
}
}
ScheduleRepository
더보기
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
default Schedule findByIdOrElseThrow(Long id) {
return findById(id).orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + id)
);
}
}
BaseEntity
더보기
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private Date createdAt;
@LastModifiedDate
private Date updatedAt;
}
- abstract : 추상 클래스이며 직접 인스턴스를 생성할 수 없습니다.
- 다른 Entity가 이 클래스를 상속하여 공통 필드(생성/수정 날짜)를 공유하기 위해 사용됩니다.
- `@MappedSuperClass` : JPA의 상속 매핑을 위해 사용됩니다. (DB에는 상속 개념이 없기 때문에 사용)
- 공통 필드만 상속하고 싶을 때 사용합니다. (엔티티가 아님 -> 테이블이 생성되지 않음)
- BaseEntity는 엔티티로 직접 테이블에 매핑되지 않지만, 이 클래스를 상속하는 다른 엔티티 클래스에 필드(createdAt, updatedAt)를 전달합니다.
- 즉, BaseEntity를 상속한 클래스는 이 필드들을 자신의 테이블 컬럼처럼 사용할 수 있습니다.
- `@EntityListeners(AuditingEntitiyListner.class) : JPA의 감사 기능(Auditing)을 활성화합니다.
- `@CreatedDate`, `@LastModifiedDate` 같은 어노테이션이 동작하기 위해 필요합니다.
- `AuditingEntityListener`는 엔티티의 생성 및 수정 시점을 자동으로 감지해 필드 값을 설정해줍니다.
Schedule
더보기
@Entity
@Table(name = "schedule")
@Getter
public class Schedule extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private String password;
public Schedule() {}
public Schedule(String username, String title, String content, String password) {
this.username = username;
this.title = title;
this.content = content;
this.password = password;
}
public Schedule(Long id, String username, String title, String content) {
this.id = id;
this.username = username;
this.title = title;
this.content = content;
}
public void updateTitle(String title) {
this.title = title;
}
public void updateContent(String content) {
this.content = content;
}
}
CreateScheduleRequestDto
더보기
@Getter
@AllArgsConstructor
public class CreateScheduleRequestDto {
private String username;
private String title;
private String content;
private String password;
}
CreateScheduleResponseDto
더보기
@Getter
public class CreateScheduleResponseDto {
private final Long id;
private final String title;
private final String username;
private final String content;
private final Date createdAt;
private final Date updatedAt;
public CreateScheduleResponseDto(Schedule schedule) {
this.id = schedule.getId();
this.title = schedule.getTitle();
this.username = schedule.getUsername();
this.content = schedule.getContent();
this.createdAt = schedule.getCreatedAt();
this.updatedAt = schedule.getUpdatedAt();
}
}
FindScheduleResponseDto
더보기
@Getter
public class FindScheduleResponseDto {
private final Long id;
private final String title;
private final String username;
private final String content;
private final Date createdAt;
private final Date updatedAt;
public FindScheduleResponseDto(Schedule schedule) {
this.id = schedule.getId();
this.title = schedule.getTitle();
this.username = schedule.getUsername();
this.content = schedule.getContent();
this.createdAt = schedule.getCreatedAt();
this.updatedAt = schedule.getUpdatedAt();
}
}
UpdateScheduleRequestDto
더보기
@Getter
@AllArgsConstructor
public class UpdateScheduleRequestDto {
@Column(nullable = false)
private String password;
private String title;
private String content;
}
UpdateScheduleResponseDto
더보기
@Getter
public class UpdateScheduleResponseDto {
private final Long id;
private final String title;
private final String content;
public UpdateScheduleResponseDto(Schedule schedule) {
this.id = schedule.getId();
this.title = schedule.getTitle();
this.content = schedule.getContent();
}
}
DeleteScheduleRequestDto
더보기
@Getter
public class DeleteScheduleRequestDto {
@Column(nullable = false)
private String password;
}
회고
BaseEntity : 다른 Entity들이 공통 필드를 공유하기 위해 사용하 추상 클래스입니다.
JpaRepository : CRUD 기능을 포함한 다양한 JPA 기능을 제공하는 인터페이스입니다.
이전 프로젝트에서 진행했던 CRUD 기능을 직접 구현해보고 JpaRepository에서 기본적으로 제공하는 CRUD 기능을 사용했을 때, 얼마나 편하고 수고스러움이 적은지 한 번에 느껴졌습니다.