240511~12 Spring 숙련 - 주말 추가 공부

노재원·2024년 5월 11일
0

내일배움캠프

목록 보기
37/90

챗봇으로 Claude 3가 무료치고 좋길래 계속 물어보며 공부하느라 쓰고 있는데 메시지 제한이 너무 빨리 걸린다. $22를 결제할 수 밖에 없나 싶다.

Cascade

  • SQL에 있는 Cascade 처럼 연관관계에 있는 애들의 영속성을 어떻게 할지 정할 수 있다. 영속성 전파라고 하면 된다.
    • 부모-자식이 있으면 부모에서 Cascade를 정의해주면 된다.
    • ALL, Persist, Merge, REMOVE, REFRESH, DETACH 로 옵션이 나뉘고 상황에 따라 쓰면 된다.
      • 실무에서는 주로 ALL, Persist 를 쓴다.
      • 강의에서 쓰는 Course-Lecture는 붙어 다니므로 ALL을 썼다.

고아 자식 개체

부모-자식의 표현을 보면 맞긴 한데 이름 참 섬뜩하다.

  • JPA에서 삭제를 할 때 DB에 Delete가 아니라 Update 쿼리를 날려서 연관관계만 끊어버리는 일이 발생하고, 이는 정책 위반이고 데이터의 무결성을 해치는 고아 자식이 생겼다고 한다.
    • 이를 방지하기 위해 orphanRemoval 속성이 존재한다. 설정하면 JPA가 Update가 아니라 Delete를 실행해준다.
    • 자식에 Remove를 하면 Entity manager는 기본적으로 연관관계를 끊는 Remove로 처리를 하기 때문에 orphanRemoval 을 적어줘야 레코드를 삭제하는 Remove로 생각한다.
      • 부모에 날리면 그런 거 필요없이 Remove 쿼리를 날리고 Cascade에 따라 자식도 날려준다.

결합

  • 강의에서는 Entity들이 연관관계의 객체를 그대로 들고 있는데 이러면 결합이 강해지니까 id만 들고 있는게 유지보수가 더 쉬울 때도 있다.
    • Aggregate 가 여러 개일때 관계가 있다면 두 Aggregate를 별도로 발전시키기 위해 관계를 명시적으로 사용하지 않는다.
    • 자식이 너무 많거나 하면 객체로 관리할 시 메모리를 너무 많이 잡아먹을 수도 있다.
    • 객체로 주면 프론트에서 작업하기 편하긴 했다.

JPA Repository

  • 기본적으로 findById는 못찾아도 null이 아니라 Optional 객체를 던져주는데 findByIdOrNull을 쓰면 Nullable T로 사용 가능할 수 있다.
    • Swift는 이 방식이 좋긴 한데 Kotlin은 guard 문이 없어서 Optional unwrapping이 좀 귀찮지 않나? 생각이 든다.
  • 내장함수중 update는 없고 save만 있는데 Dirty checking을 통해서 쿼리를 판별하기 때문에 수정이면 그냥 수정만 알아서 잘 해준다.
  • findAllById을 쓸 때 없는 Id는 아예 Optional 이든 Null 이든 무시하고 객체 자체를 반환하지 않는다.
    • 4개의 id를 찾았을때 3개만 실제 id라면 반환되는 리스트의 사이즈도 3개다.
  • delete는 soft delete를 지원하지 않기 때문에 상태를 DELETED로 업데이트 하는 방식의 관리를 할 거면 따로 Extension 함수를 만드는게 좋다.
  • Repository는 상속받아서 객체의 Type, 객체 Id의 Type만 넘겨주면 알아서 구현되기 때문에 파일 작성이 쉽다.
  • Repository를 쓰면 기본 CRUD를 쓰기 정말 편하지만 복잡한 Where 조건을 커버치려면 Method를 네이밍해서 Query를 작성할 수 있다.
    • find는 SELECT, By는 WHERE을 가리키고 그 외에도 다른 연산자가 존재해서 Method 이름을 Repository에 작명만 하면 사용이 가능해진다. 개똑똑한듯
    • @Query 어노테이션을 써서 직접 작성도 가능한데 이때 작성하는 query는 SQL이 아니라 JPQL (Java Persistence Query Language) 라서 약간 다르다.
      • 항상 'p from Post p' 처럼 별칭을 써야하고 Table name 대신 Entity name을 써야하며 변수에 :을 붙이면 @Param 어노테이션으로 해당 argument를 지정한다.
    • Method 네이밍이 너무 길어지고 복잡해지면 JPQL 쓴다고 생각하면 된다.
  • save를 쓸 때 내부적으로 비영속, 준영속을 구분해 persist, merge를 사용하는데 준영속 Entity는 Merge를 통해 기존과 변경분을 추적하고 동기화한다.

Service와 연결

  • Entity를 DTO로 변환하는 과정은 코드 중복의 여지가 많으니 Entity에 확장 함수 작성을 추천한다. (ABC.toResponse() -> ABCResponse)
  • 관계 사이의 Entity는 자식측에서 부모를 정확히 지정해줘야 JPA에서 연관 관리가 된다.
  • 부모 Entity에 함수는 적극 활용해줌이 좋다.
    • 강의 기준 numAppl >= maxAppl 체크하는 isFull(), CourseStatus 체크하는 isClosed, CourseStatus 변경하는 close() 등으로 함수로 구분해주는게 명시적으로 변경을 추적하기 좋다.
  • Lecture를 추가할 때 Course 에 addLecture를 구현해서 lectures에 add 처리를 하고 save를 Course에 주면 CascadeType을 통해 영속성을 전파해서 하위에 존재하는 Lecture가 저장이 된다.
    • 왜 그렇게 했는가?: Course 가 Aggregate 최상위라서 Course 를 통해서만 Lecture의 라이프사이클을 변경하는 개념이라고 생각한다. Save를 Course에 하는 것이 그 라이프사이클의 핵심이라고 생각하면 된다. (부모가 변경된 후 자식에 전파되어 자식도 Persist한다.)
      • 이렇게 안하고 Lecture 에 save를 줘도 결과가 다르진 않다. DDD 기반에서 Aggregate가 어떤 개념을 취하는지 보여주기 위한 예시이자 이렇게도 구현된다는 걸 보여주는 용도인듯?
      • 그냥 보기엔 예측이 어렵지 않나 싶은데 챗봇 피셜로는 Aggregate 루트에서만 관리하는 건 무결성, 일관성, Cascade를 통한 그래프 유지, 변경 추적 용이(이 쪽이 더 편한가보다), 참조 무결성을 보장할 수 있다고 한다.
  • Lecture를 가져올 때 Course.lectures 에서 find 하면 Lazy loading으로 모든 Lecture를 조회하는 쿼리를 날리고 거기서 찾는 오버헤드가 발생해서 LectureRepository 에서 한번에 courseId, Id를 통해 조회하는게 더 효율이 좋다
    • 다만 Lecture List를 가져올 땐 그냥 Course.lectures 로 찾으셨다.
  • removeLecture 에서는 Lecture를 가져와서 Course의 lectures 에서 하나를 빼는 방식을 썼는데 쿼리상으로 비효율적이지만 영속성이 전파되고 Lecture의 라이프 사이클을 루트에서만 관리한다는 측면에서는 좋을 수 있다.
    • 챗봇 기준 Lecture repository를 삭제하면 Course의 Lectures에 즉시 반영되진 않는다고 했지만 이는 어차피 Course.lectures를 사용할 때 JPA에서 새로운 조회 쿼리를 통해 최신 상태로만 조회하니 개체 무결성은 보장이 된다.
      • 하지만 이건 추천하는 방식은 아니라고 한다.

0개의 댓글