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 기능을 사용했을 때, 얼마나 편하고 수고스러움이 적은지 한 번에 느껴졌습니다.