본문 바로가기

개발 일지/TIL

[ #34 ] TIL

✏️ 0603      


Spring 숙련 과제 피드백 반영


Spring 숙련 과제 피드백

중에 찾아본 내용들 정리

 

 

튜터님 피드백

 

 

 

요청과 응답은 Entity 대신 DTO 

 

// 변경전 (Service)
Comment comment = new Comment(commentRequestDto, schedule, user);
commentRepository.save(comment);
return new CommentResponseDto(comment);
// 변경후
Comment comment = new Comment(commentRequestDto, schedule, user);
Comment savedComment = commentRepository.save(comment);
return new CommentResponseDto(savedComment);

 

  • 일관된 데이터 상태
    • 데이터베이스에 저장된 객체는 실제로 저장된 데이터베이스의 상태를 반영
    • 자동으로 생성된 ID, 타임스탬프 등 저장 과정에서 변경되거나 추가된 필드를 가질 수 있
  • 정확한 응답 데이터
    • 클라이언트에게 반환되는 DTO는 항상 데이터베이스에 저장된 최종 상태를 반영
    • 저장된 객체의 정확한 상태를 알 수 있다
  • 확장성 및 유지보수성
    • 코드의 유지보수성이 높아진다
    • 객체가 저장되는 과정에서 추가적인 처리가 필요한 경우, 저장된 객체에서만 정확하게 반영될 수 있다
    • 변경되거나 추가적인 로직이 들어갈 수 있는 상황에서도 코드가 확장 가능

 

📝 요약

저장된 객체를 기반으로 DTO를 생성하는 것이 더 정확하고 안전한 방법

 

데이터 일관성

➖ 데이터베이스에 실제로 저장된 최종 상태를 사용하여 응답을 생성함으로써 데이터의 일관성을 유지할 수 있다
정확한 응답

➖ 클라이언트가 항상 정확하고 최신의 데이터를 받을 수 있다
유지보수 용이성

➖ 향후 변경 사항에 대해 더 유연하고 쉽게 대응할 수 있다

 

 

@CreatedDate & @CreationTimestamp

 

이건 과제 해설 영상에서 튜터님이 사용하신 어노테이션

@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdAt;

 

@CreationTimestamp : 엔터티가 생성될 때 자동으로 생성 시간을 기록하는 데 사용

 

 

이건 과제에서 내가 사용한 어노테이션

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;

 

@CreatedDate : 엔터티가 생성될 때 자동으로 날짜 및 시간을 기록하는 데 사용

 

 

그럼 둘의 같은걸까? 다른걸까?

@CreationTimestamp와 @CreatedDate는 유사한 기능을 하지만, 사용하는 방식과 의존하는 라이브러리가 다르다

 

  • @CreationTimestamp
    • Hibernate 의 어노테이션
    • 엔터티가 생성될 때 자동으로 타임스탬프를 기록
    • Hibernate 가 관리하는 엔터티 필드에 사용
  • @CreatedDate
    • Spring Data JPA 의 어노테이션
    • 엔터티가 생성될 때 자동으로 타임스탬프를 기록
    • Spring Data JPA 가 관리하는 엔터티 필드에 사용
    • @EntityListeners 로 변화를 감지하는 기능을 활성화
    • 애플리케이션 클래스나 구성 클래스에 @EnableJpaAuditing 어노테이션을 추가

 

두 어노테이션은 비슷한 역할을 하지만, 사용하는 라이브러리와 설정 방식이 다르므로

사용하는 프로젝트에 따라 선택하면 될 것 같다

 

 

다시 보니 중복된 메서드

 

CommentService 에 존재하는 일정 아이디가 DB에 존재하는지 확인하는 메서드

private Schedule findScheduleById(Long scheduleId) {
    return scheduleRepository.findById(scheduleId).orElseThrow(() ->
            new NullPointerException("해당 일정은 존재하지 않습니다."));
}

 


ScheduleSerivce 에 존재하는 일정 아이디가 DB에 존재하는지 확인하는 메서드

private Schedule findScheduleById(Long id) {
    return scheduleRepository.findById(id).orElseThrow(() ->
            new IllegalArgumentException("선택한 일정은 존재하지 않습니다."));
}

 

 

피드백에 존재하진 않았지만 해설 영상을 보면서 내 코드를 하나하나 다시 뜯어보니 중복된 코드를 발견했다

둘다 동일한 메서드 이름을 가진 메서드인 동시에 같은 기능을 처리하는 메서드이고
이는 코드 중복에 해당한다

 

 

CommentService 에 존재하던 findScheduleById 메서드를 삭제하고

ScheduleService 의 findScheduleById 메서드를 CommentService에서 사용 가능하도록 변경

public Schedule findScheduleById(Long id) {
    return scheduleRepository.findById(id).orElseThrow(() ->
            new IllegalArgumentException("선택한 일정은 존재하지 않습니다."));
}

--

private final ScheduleService scheduleService;

Schedule schedule = scheduleService.findScheduleById(scheduleId);

 

 

설계 하기 힘들다면?

 

코드를 짜기 힘들다면 사용자의 흐름을 생각해보기

 

  1. API 요청 (Controller 필요)
  2. 비지니스 로직필요 (Serivce 필요)
  3. 데이터를 저장할 DB 필요 (Repository 필요)
  4. 데이터를 이동시켜줄 요청, 응답이 필요 (DTO 필요)

 

 

 

튜터님이 알려주신 방법이 좋은 것 같아서 메모 삼아 올려본다

기능명: 일정 기능

Controller (API)
    API 명세서
   일정 컨트롤러 (/api/schedule)
        조회 - Get
            전체 조회
            단건 조회
        생성 - Post
        수정 - Path or Put
        삭제 - Delete

Service (로직)
    findAll - 전체
    findById - 단건
    create - 생성
    update - 수정
    delete - 삭제

Repo (DB 작업)
    repository.findAll - 전체
    repository.findById - 단건
    repository.save - 생성
    update with Transactional - 수정
    repository.delete - 삭제

Entity (Model)
    일정
        아이디
        제목
    BaseEntity (공통)
        생성일자
        수정일자

DTO (요청, 반환시 사용하는 용도)
    일정 응답 DTO
        아이디
        제목
        생성일자
    일정 생성 요청 DTO
    일정 수정 요청 DTO

 

 

private 생성자

 

튜터님의 코드 중에 private 으로 된 생성자가 있었다

보통 public 인데 저건 왜 private 인가 궁금해서 찾아봤다

private JwtTokenProvider() { }

 

=> 밖에서 새로운 객체를 생성할 수 없게 막는 것

이러한 생성자를 만드는 이유
주로 Singleton 패턴을 구현하기 위해서
Singleton 패턴은 애플리케이션에서 특정 클래스의 인스턴스가 딱 하나만 존재하도록 보장하는 디자인 패턴

 

생성자를 private으로 선언하면 외부에서 해당 클래스의 인스턴스를 직접 생성할 수 없으므로, 외부에서는 해당 클래스의 인스턴스를 얻기 위해 정적 메서드나 getInstance()와 같은 팩토리 메서드를 통해 인스턴스를 요청해야 한다
이를 통해 단일 인스턴스를 보장할 수 있다

애플리케이션 전역에서 해당 클래스의 인스턴스가 하나만 생성되고,

모든 곳에서 그 인스턴스를 공유하여 사용할 수 있게 되는 것

 

메모리 낭비를 줄일 수 있고, 일관된 상태를 유지할 수 있다

 

 

@PathVariable

 

@GetMapping("/api/resource/{id}")
public ResponseEntity<Resource> getResource(@PathVariable("id") Long id) {
    // id를 이용한 작업 수행
}

 

@PathVariable 를 사용하면 null 값이 들어오지 않는다
경로 변수에 대응되는 값이 없는 경우에는 예외가 발생하지 않고 null이 아니라 빈 문자열("")이 들어온다

 

http://example.com/api/resource/123

원래라면 이렇게 들어오는 주소일 때

http://example.com/api/resource/

빈값으로 들어오게 된다면 이렇게 된다

 

id 에 null 이 아니라 빈 문자열("")이 전달
이러한 상황을 처리해야 한다면 메서드 내에서 적절한 예외 처리를 해줘야 한다

 

if (id.isEmpty()) {
    return ResponseEntity.badRequest().body("ID가 필요합니다.");
}

 

빈 문자열이 들어온 경우에 대한 처리

 

 

orphanRemoval

 

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();

 

엔티티 관계 설정 시 부모 엔티티에서 자식 엔티티를 제거할 때 자식 엔티티도 자동으로 삭제되도록 설정하는 기능

@OneToMany 또는 @OneToOne 관계에서 주로 사용

 

JPA에서 부모 엔티티와 자식 엔티티 간의 관계를 설정할 때

부모 엔티티에서 자식 엔티티를 제거해도 자식 엔티티는 데이터베이스에서 삭제되지 않는다

 

이때 orphanRemoval = true를 설정하면

부모 엔티티에서 자식 엔티티를 제거할 때 자식 엔티티도 데이터베이스에서 자동으로 삭제할 수있다

부모 엔티티와 자식 엔티티 간의 관계를 유지하면서, 자식 엔티티의 생명 주기를 부모 엔티티와 동일하게 관리

 

 

'개발 일지 > TIL' 카테고리의 다른 글

[ #36 ] TIL  (0) 2024.06.05
[ #35 ] TIL  (0) 2024.06.04
[ #33 ] TIL  (0) 2024.05.31
[ #32 ] TIL  (0) 2024.05.30
[ #31 ] TIL  (0) 2024.05.29