· 오늘 공부한 것

팀프로젝트 checklist  CREATE, UPDATE, DELETE API 기능

update의 경우 contents, check, priority를 따로 API 생성

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

어제 사전작업을 하고 내가 맡은 checklist에 대한 기능들을 구현하였다. 하지만 아직 정하지 못하고 넘어간 것들이 있어서 팀원이 모여서 회의를 진행하였다. 회의 내용은 다음과 같다.

  • 코드 컨벤션
    • 도메인에 해당하는 하나의 서비스는 본 도메인 관련 레포지토리만 주입
      • 하나의 컨트롤러/서비스/메서드 => 하나의 일만 하자!!!!
        • Ex) 게시글이 존재하는지 검증 메서드 :: 위치 => 본인 도메인인 게시글에 있어야 한다고 생각
      • 다른 도메인에 대한 참조는 서비스를 주입받자!
        • 다른 서비스 참조 시, 순환참조????
          • 서비스 참조하게 될 때, 이를 하나의 엔티티로 분리해서 사용하자
  • branch 전략
    • develop (default)
      • feature-{본인 이름}
        • 각자 작업 내용
  • PR 리뷰원 3명 이상 (팀끼리 만나서 코드리뷰하고 진행)
  • DTO 설정
    • 하나의 API에 각각에 Request와 Response를 만든다.

이것들 외에도 많은 부분을 정하고 넘어갔다. 이러한 규칙들을 정하면 서로 간의 코드 스타일도 통일할 수 있고 각 도메인을 나눠서 진행해도 통일성을 줄 수 있다.

전에 했던 프로젝트에서 항상 해설영상을 따라 하다 보니 많은 규칙을 따라가기 힘든 부분이 많다;;; 

일단 그래도 내가 맡은 API에 대해서 코드를 완벽하게 하지는 못했지만 어느 정도 흐름은 주석으로 잡아놓은 상태이다.

 

 

· 오늘 서칭 한 자료

https://sas-study.tistory.com/445

 

Intellij에 Google Java Style Guide 적용하기.

안녕하세요. 오늘은 제 인텔리제이 프로젝트에 구글 자바 스타일 가이드를 적용시켜 코드를 통일화할 수 있는 방법을 소개하고자 합니다. 평소에는 팀원들간의 약속된 코드를 유지하는게 더 중

sas-study.tistory.com

 

 

· 느낀 점

- 기존 프로젝트와 다르게 많은 부분을 사전에 준비하고 정했는데 아직 따라 하기에는 힘든 부분이 많지만 이렇게 진행을 한다면 코드의 통일성을 줄 수 있다고 생각한다. (빨리 적응하고 코딩을 할 수 있도록 해야겠다!)

- 오늘은 아직 많은 부분을 못했는데 내일 확실하게 하고 진행해야겠다.

- 규칙을 정할 때 따라가기 힘들다 보니 자신감이 많이 없어졌다.

반응형

· 오늘 공부한 것

S.A (Starting Assignments) 작업

  • ERD 작성
  • API명세서 작성
  • 와이어 프레임

 

· 공부한 내용, 궁금한 내용, 부족한 내용

최종 프로젝트전 마지막 팀프로젝트는 Trello와 같은 업무 관리 시스템을 만들어 보는 것이다. 전에 이와 같은 것을 사용해 본 경험으로는 github 레포지토리에서 프로젝트를 생성해서 To do, In progress, Done 영역을 두고 카드를 만들어서 본 것이다. 카드 안에서는 체크리스트와 같은 것을 넣고 주어진 카드를 완료해서 프로젝트가 잘 진행될 수 있도록 사용을 하였다. 그래서 이번 프로젝트에서 보드를 만들고 그 안에 컬럼(To do, In progress, Done 등)을 만들고 그 안에 카드를 넣어서 업무를 관리한다. 그리고 권한에도 3가지를 설정을 하였다.

1. 해당 보드를 만든 유저 (총 관리자)

2. 해당 보드에 초대되어 카드를 만들 수 있는 유저 (매니저)

3. 해당 보드에 초대되어 카드를 부여 받은 유저 (일반 유저)

업무관리 시스템이기 때문에 매니저를 팀장으로 생각하고 일반 유저를 사원으로 보면 이해하기 쉽다. 그래서 권한에 따라 할 수 있는 기능이 정해져 있고 각 권한은 포함관계이기 때문에 일반유저가 할 수 있는 기능은 매니저도 할 수 있고 매니저가 할 수 있는 기능은 총 관리자도 할 수 있다. 이러한 것들을 생각하여 ERD 작성을 해보았다. 관계 설정을 할 때 팀원들과 많은 의논을 거쳤다.

팀프로젝트 ERD

특히 권한을 설정 할 때 중간 테이블을 생각은 했지만 처음에는 유저와 보드, 유저와 칼럼, 유저와 카드, 유저와 체크리스트 등 모두에 중간테이블을 생성해서 관리하려고 접근을 했다. 하지만 생각했을 때 비효율적이라고 판단하여 보드와 유저사이에 중간테이블을 두고 권한테이블을 따로 만들어서 관계설정을 해주었다. 이렇게 함으로써 해당 유저가 어떤 권한을 가졌는지 알 수 있고 그것에 따라 수행할 수 있는 기능을 제한할 수 있다고 판단했다. 또한 Issue를 발행해서 팀원끼리 이슈에 대해서 댓글로 소통할 수 있는 기능도 추가하였다.

그래서 필수적으로 구현할 기능은 다음과 같다.

  • 사용자
  • 권한 설정
  • 보드
  • 컬럼
  • 카드
  • 체크리스트
  • 업무 이슈

기능은 간단해 보이지만 각각에 CRUD와 관계까지 생각해 보면 구현할 부분이 많았다. 당장 준비단계에서 API명세서를 작성한 것만 봐도 꽤 많았다. 이후에도 팀원끼리 정해야 할 것이 많았다.

  • API 호출 시, 발생가능
    • 예외케이스
    • 상태코드
    •  
  •  build.gradle 설정
  •  DTO 를 어떻게 짤 건지
  •  반환 DTO??
  •  package 구성
  •  깃허브 브랜치 전략
  •  Test Config
  •  ( CI/CD 설정 )
  •  예외처리 ::
    • ENUM 처리할 건지
    • 커스텀 클래스를 팔건지

전 프로젝트 진행시 사전단계를 미흡하게 하고 진행했을 때 나중에 코드작업을 할 때 더 힘들다고 느꼈기 때문에 최종프로젝트 전 마지막 팀프로젝트이기 때문에 확실히 하고 진행하려고 팀원끼리 노력하였다. 그리고 내가 구현해야 할 부분은 체크리스트 부분이다.

내가 담당한 부분

 

· 오늘 서칭 한 자료

https://www.notion.so/API-Reference-5758b834b93243dd877d69cecb5175e1

 

API Reference | Built with Notion

Notion Tip: Use this page as an example of how you can structure your API Reference and host it on Notion. The Endpoints database also includes an endpoint template for further documentation.

vanillacake369.notion.site

 

 

· 느낀 점

- 사전준비에 확실히 하고 진행하니 든든한 느낌이 든다.

- 필수구현 기능을 빨리 구현하고 챌린지로 둔 것들도 구현해보고 싶다.

- 아직 이해하지 못하고 넘어간 부분이 있는데 코드작업하면서 복습도 확실히 하고 진행해야 할 거 같다. (이거 끝나면 최종프로젝트!!!!)

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-28 TIL 업무 관리 시스템 팀프로젝트(3)  (0) 2023.12.29
2023-12-27 TIL 업무 관리 시스템 팀프로젝트(2)  (0) 2023.12.28
2023-12-21 TIL  (0) 2023.12.21
2023-12-11(BA클럽 KPT 회고)  (0) 2023.12.11
2023-12-07 TIL  (0) 2023.12.07

· 오늘 공부한 것

JPA심화 4일 차

  • JPA와 오해 풀기
  • TestContainer로 배포환경과 동일한 DB에서 테스트하기
  • Transactional 심화

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

쿼리 최적화가 안 되는 경우 아래 3가지를 확인해야 한다.

  1. 먼저 해당 함수나 클래스가 Transaction 안에 포함되고 있는지 봐야 합니다.
    1. **@Transactional** 으로 함께 감싸져 있어야만 쿼리 최적화가 동작합니다.
    2. Transaction으로 포함되어있지 않으면 repository 메서드 내부에서만 Transcation 이 최적화됩니다.
    3. Transaction Propagation (전파) 전략 체크해봐야 합니다. (심화)
  2. 두 번째로, 해당 엔티티의 ID 식별자 생성전략을@GeneratedValue(strategy = GenerationType.IDENTITY)로 사용한 건 아닌지 확인해봐야 합니다.
    1. **GenerationType.IDENTITY** 로 키필드가 설정되어 있으면 데이터베이스에 실제로 저장을 해야 유일한 식별자를 구할 수 있으므로 Insert 쿼리가 즉시 데이터베이스에 전달됩니다.
    2. GenerationType.SEQUENCE 설정하길 추천드립니다. 이렇게 하면 DB에 select nextval('thread_seq') 쿼리만 날아갑니다.
    3. GenerationType.AUTO 설정해서 DB에서 선호하는 방식으로 자동적용시켜 줄 수도 있습니다.
  3. 마지막으로, orphanRemoval() 영속성 전이에 의해 자식의 삭제작업이 이루어지는 건 아닌지 확인해봐야 합니다.
    1. 이런 경우 1차 캐시 최적화가 아닌 삭제쿼리가 쓰기 지연 저장소에 저장되어 동작하게 되므로 후처리로 발생하게 됩니다.

Test 클래스에 애너테이션(@)이 많아서 헷갈리는 경우

1. Spring 전체 빈을 사용해야 하는 Spring 통합 테스트

@SpringBootTest   
// SpringApplication 띄울때의 빈들을 모두 생성해줍니다.
@Transactional    
// 테스트 메소드들이 모두 트랜잭션에 포함되어 최적화 되도록 합니다. 
// 테스트 대상 함수의 실행환경에서는 Transaction이 안걸려 있을 수 있으니 실무에 사용시 주의
@Rollback(value = false) // 테스트 데이터가 롤백되지 않고 실제 DB에 반영되도록 합니다.

2. JPA 관련 빈 만 사용하는 JPA 슬라이스 테스트

@DataJpaTest      
// SpringDataJpa 테스트에 필요한 빈들만 생성해줍니다.
@Transactional    
// 테스트 메소드들이 모두 트랜잭션에 포함되어 최적화 되도록 합니다. 
// 테스트 대상 함수의 실행환경에서는 Transaction이 안걸려 있을 수 있으니 실무에 사용시 주의
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)  
// 테스트 용 DB를 따로 설정하지 않고 main 환경 DB를 그대로 사용하도록 합니다.
@Import(JPAConfiguration.class) 
// JPAQueryFactory와 같이 테스트시 필요한 빈들을 정의해놓은 Configuration 설정합니다.
@Rollback(value = false)
// 테스트 데이터가 롤백되지 않고 실제 DB에 반영되도록 합니다.

위에 두 개중에 슬라이스 테스트를 추천한다.

  • 실제 구동되는 애플리케이션의 설정, 모든 Bean을 로드하기 때문에 시간이 오래 걸리고 무겁다.
  • 테스트 단위가 크기 때문에 디버깅이 어려운 편이다.
  • 결과적으로 웹을 실행시키지 않고 테스트 코드를 통해 빠른 피드백을 받을 수 있다는 장점이 희석된다.

 

TestContainer로 배포환경과 동일한 DB에서 테스트하기 (실습)

  • 단위 테스트
    • 각 계층(클래스) 별로 테스트 케이스 작성
  • 통합 테스트
    • 실행될 때마다 랜덤 하게 변경되는 시나리오를 만들고 그에 따른 데이터를 미리 생성(Docker 환경의 데이터베이스)
    • 모든 엔드포인트에 대해서 테스트
    • 사전에 데이터를 미리 만들어둔 것을 통해서, 결과를 예측하고 검증할 수 있음
  • 단위테스트는 Pull Request에서 검증하는 용도(CI)
  • 통합테스트는 정기 배포 당일 생성한 브랜치에 대해서 검증하고 검증이 완료된다면 자동으로 배포하는 프로세스(CD)

Propagation (전파 전략)

@RunWith(SpringRunner.class)
@SpringBootTest
public class TransTest {

    @Autowired
    Parent parent;


    @Test
    @Transactional(propagation = Propagation.NOT_SUPPORTED) // 트랜잭션 생성 방지
    public void transactionalPropagationTest() {
        parent.parentMethod(); // 부모 메서드 실행
    }

}

@Component
public class Parent {

    @Autowired
    Child child;


    @Transactional
    public void parentMethod() {
        System.out.println("Parent");
        child.calledMethod();
    }
}

@Component
public class Child {

    @Transactional(propagation = ?) // <- 여기에 하나씩 넣어보세요.
    public void calledMethod() {
        System.out.println("Child");
    }
}

1. PROPAGATION_REQUIRED ( JpaTransactionManager Default )

  • 부모 트랜잭션이 존재할 경우
    • 부모 트랜잭션에 참여한다.
  • 부모 트랜잭션이 없을 경우
    • 새 트랜잭션을 시작한다.

일반적으로 사용되는 트랜잭션의 전파유형이다.

즉, 어떻게 해서든 (상속받서든, 내가 새로 만들든) Transaction을 시작하는 전파 유형

2. PROPAGATION_SUPPORTS

  • 부모 트랜잭션이 존재할 경우
    • 부모 트랜잭션에 참여한다.
  • 부모 트랜잭션이 없을 경우
    • non-transactional 하게 동작한다.

부모 따라서 전파되는 유형

3. PROPAGATION_MANDATORY

  • 부모 트랜잭션이 존재할 경우
    • 부모 트랜잭션에 참여한다.
  • 부모 트랜잭션이 없을 경우
    • Exception이 발생한다.

트랜잭션에 참여하도록 강제하는 유형

4. PROPAGATION_REQUIRES_NEW

  • 부모 트랜잭션 유무에 상관없이
    • 새 트랜잭션을 시작한다.
  • 부모 트랜잭션이 존재할 경우
    • 부모 트랜잭션을 중지시킨다.

무조건 새 트랜잭션을 생성하도록 강제하는 유형

5. PROPAGATION_NOT_SUPPORTED

  • 부모 트랜잭션 유무에 상관없이
    • non-transactional 하게 동작한다.
  • 부모 트랜잭션이 존재할 경우
    • 부모 트랜잭션을 중지시킨다.

6. PROPAGATION_NEVER

  • 부모 트랜잭션이 존재할 경우 Exception이 발생한다.

항상 non-transactional 하게 동작하는 유형

 

 

 

 

 

 

· 오늘 서칭 한 자료

강의 자료 대체

https://github.com/thesun4sky/jpa/tree/day-4

 

GitHub - thesun4sky/jpa

Contribute to thesun4sky/jpa development by creating an account on GitHub.

github.com

 

· 느낀 점

- JPA를 가지고 DB를 다룰 때 실행문에서 어떤 쿼리가 나가는지 항상 확인을 했었는데 내가 적은 코드가 어떤 쿼리를 발생하냐를 자주 보았다.

- 예상하지 못한 쿼리가 나간 경험이 있었는데 이번에 그 의문을 해결할 수 있었다.

- 그리고 역시나 @(애너테이션)은 알고 사용하면 좋기는 하지만 역시 많아서 헷갈리기도 하다. 그렇기 때문에 자주 사용해 보고 직접 모르는 부분을 찾아서 정리해야 할 거 같다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-27 TIL 업무 관리 시스템 팀프로젝트(2)  (0) 2023.12.28
2023-12-26 TIL 업무 관리 시스템 팀프로젝트(1)  (0) 2023.12.26
2023-12-11(BA클럽 KPT 회고)  (0) 2023.12.11
2023-12-07 TIL  (0) 2023.12.07
2023-12-06 TIL  (1) 2023.12.06

이승준

 

중요한 SA 작업을 확실히 하고 넘어가지 않음

⬇️

각자 맡은 부분 구현하면서 API 작성

⬇️

기능 구현 후 합치는 작업

 ⬇️

많은 충돌 발생

 

Keep 구현할 수 있는 기능을 최소 목표로 잡고 진행해서 좋았던 거 같습니다. 그리고 역할 분담도 잘해서 각자 맡은 부분이 있어서 좋았습니다.
Problem 초반 SA작업을 하기는 했지만 꼼꼼히 진행하지 못해서 아쉬웠습니다. 그리고 필수 구현 기능을 빠르게 하고 추가기능을 시도했으면 좋았을 거 같습니다. 다양한 API 구현을 하지 못한게 아쉬웠습니다. github에 각자 구현한 기능을 모으는 과정에서 시행착오가 있었습니다.
Try git에 대한 이해도를 높이고 모두모여서 pr날린것을 확인하고 머지를 진행하였습니다. 추가 API 구현을 하기 위한 초석은 만들어 놓았기 때문에 개인적으로 시도해 보면 좋을 거 같습니다. 초반 SA작업에 맞게 진행하지 못해 충돌이 났지만 겹치는 부분을 진행한 팀원끼리 모여서 해결을 할 수 있었습니다. 이러한 과정을 통해 초반 SA작업의 중요성을 확실히 알 수 있었습니다.

 

 

장하람

  1. 처음에 이전 팀에서 배운 Mapper 기능을 사용해서 게시글에 관한 매핑을 하려고 했으나, 머지할 때 충돌이 많이 발생했고, 다른 팀원과의 컨벤션이 일치하지 않는 것이 그 원인 중에 하나라는 것을 발견함. 처음부터 다시 코드를 짜서 머지에 성공함. 익숙지 않은 기능은 팀프로젝트에서는 자제해야 되는 것과 컨벤션의 중요성을 새삼 느낌.
  2. 게시판 기능 중 카테고리 항목을 추가했는데, 구현하는 걸 너무 어렵게 생각해서 팀장님의 도움을 받음. 의외로 entity등에 한두 줄 추가하면 되는 간단한 문제였음.
  3. 자바파일들을 세세하게 나눠 코드를 작성해서 중복되거나 불필요한 부분이 많았는데, 삭제하거나 덜어내는 과정을 통해서 가독성 좋고 간결한 코드의 중요성을 새삼 느낌.
Keep 강의에서 배운 방식 외에 새롭게 배운 방식으로 코딩을 하는 도전정신과 끈기를 가지고 수정하는 노력은 유지할 부분이라 생각합니다. merge할 때 오류가 나지 않도록 각자 local에서 branch를 만들어 코딩을 하고 최종적으로 개발 브랜치에 합치는 방식이 바람직했다고 봅니다. 특히 git에서 충돌이 나지 않도록 단계적으로 merge하고, 큰 충돌이 발생한 경우는 다시 백업자료로 돌아가 처음부터 시작하는 등 오류를 수정하는 전반적인 피드백 과정에서 배울 점이 많았습니다.
Problem 처음 SA에서 정한 부분들을 제대로 숙지하고 코딩했어야 했는데, 막상 제가 코딩할 때는 놓친 부분이 많아 미흡했습니다. 또한 호기롭게 이전 팀에서 배웠던 Mapper를 쓰다가 연쇄적으로 충돌이 발생해 수정에 이틀 가까이 소요됐으나, 결국 해결하지 못하고 전체적으로 재코딩을 하게 됐습니다. 결과적으로 프로젝트를 지연시키게 되어 죄송한 마음이 컸습니다. 연쇄적으로 컨벤션 문제와도 이어졌는데, 제 방식에만 익숙해있다 보니 어려워서 방식을 통일하는데 많은 도움이 필요했습니다.
Try SA를 짤 때 팀의 컨벤션 및 제가 맡은 부분을 확실하게 구체화하고, 아예 개인코딩 시작시 SA를 옆에 띄워놓고 제 코딩이 컨벤션과 목적에 부합하는지 실시간 체크를 해야겠습니다. 익숙하지 않은 기능을 사용할 시에는 반드시 팀원들이 그 기능에 대해 알고 있는지 확인한 후 팀원의 동의를 미리 구하고, 혹 전체 프로젝트에 지장이 갈 것 같으면 과감히 빼야겠습니다. 새 기능에 능숙해지기 전까지는 기존의 방식을 고수해야겠습니다. 제 부족함으로 인해 프로젝트가 지연되지 않도록, 부족한 결과가 나오더라도 먼저 시간을 맞추고 팀원들의 도움을 얻는 식의 프로젝트 진행이 되도록 노력해야겠습니다.

 

 

전성준

  1. 테스트 코드를 작성하며 객체의 값을 검증하기 위해 if문을 사용해야 하나 고민하다가 지난 팀의 팀장님이 사용하신 assertThat이 생각나서 assertThat을 사용하였는데 다른 기능들과 달리 자동으로 import가 되지 않아 많이 애먹었다. 수동으로 임포트 작성해 주니 간단하게 해결되었다.
  2.  mysql 다운로드를 해결하지 못하고 현재까지 mariaDB를 사용하고 있었는데 이번에 유섭 님이 mariaDB도 삭제 후 진행해 보는 게 어떻냐고 말씀해 주셔서 진행하였더니 해결이 되었다.
Keep 무리하게 많은 기능을 구현하지 않고 복습과 추가적인 기능을 더 구현해보는 방식이 탁월한 선택이었다고 생각됩니다.
Problem git사용에 큰 문제를 느꼈습니다. commit, push, stash 를 하는 동안 코드를 몇번이나 날려먹었고 클론을 수 없이 많이 해왔습니다. 또한 설계단계에서 개인 사정으로 인해 참여하지 못하여 유섭님과 더 자세한 설계를 해야 함에도 그러지 못한 것에 굉장히 죄송스러운 마음입니다.
Try 개인사정이 생겨 자세한 설계를 하지 못하였을 때 다른 시간이라도 먼저 대화하는 시간을 가져야겠다고 느꼈습니다. git관련 TIL을 작성하여 git 숙련도를 높여야겠습니다.

 

 

신유섭

기존 세션 방식과는 다르게, jwt토큰을 사용하여 로그아웃을 구현할 때 서버가 발급된 토큰을 인위적으로 만료할 수 없어 애로사항이 있었습니다.

제가 택한 방식은 새로운 MySQL DB를 만들어 Refresh 토큰을 서버가 저장하고, Access토큰의 제한시간을 짧게 설계하였습니다.

이렇게 설계하면 jwt토큰의 장점인 무상태성을 어느 정도 포기하게 되지만, Access토큰이 해커에 의해 탈취되더라도 아주 적은 시간밖에 사용할 수 없어 보안이 강화되는 효과를 기대할 수 있습니다.

 

개선점은 DB를 MySQL을 사용하는 것이 아닌 Redis를 사용하는 것입니다.

Redis는 메모리에서 데이터를 처리하기 때문에 속도가 빠르고,

Refresh 토큰만 저장하면 되기 때문에 Key Value형태의 Redis가 관계형 데이터베이스 MySQL보다 효율적일 것으로 예상됩니다.

 

Keep 새롭게 배운내용을 적용하고 익숙치 않은 방법에 대해 두려워하지 않는 마인드는 계속 이어나가야겠습니다. 팀원들과 편안한 분위기를 조성하기위해 지금까지와 같이 앞으로도 따뜻한 말투로 대화를 해나가도록 하겠습니다.
Problem 필수 구현에 맞춰서 배우지 않은 방법을 구현해야할 때 시간이 많이 소요됐습니다. 팀원들에게 먼젖 조언을 구하고 같이 해결하는 방식을 취하는데 어려움을 가졌습니다.
Try 테스트 코드를 작성해서 제가 만든 웹서버의 안정성을 테스트 해보고 싶습니다. 웹서버를 구축하면서 생기는 예외들의 상태코드들에 대한 고찰도 다시한번 생각해봐야겠습니다.

 

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-26 TIL 업무 관리 시스템 팀프로젝트(1)  (0) 2023.12.26
2023-12-21 TIL  (0) 2023.12.21
2023-12-07 TIL  (0) 2023.12.07
2023-12-06 TIL  (1) 2023.12.06
2023-12-05 TIL  (1) 2023.12.05

· 오늘 공부한 것

댓글 작성, 수정, 삭제 기능에서 로그인한 유저 정보를 활용하기

좋아요 기능 추가하기

 

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

요청할 때 받은 Authorization 코드를 사용해서 로그인한 유저의 정보를 가지고 댓글 작성자인지 판별하는 과정을 추가했다. 또한 댓글 작성을 하게 되면 response로 댓글내용뿐만 아니라 해당 사용자의 닉네임도 같이 보내주기 때문에 이곳에서도 사용을 했다.

댓글 좋아요 기능을 추가하기 위해서 댓글 좋아요 entity를 따로 두었다. 좋아요를 댓글 entity에 두고 사용할 경우 해당 댓글에 여러 사람이 좋아요를 하게 돼서 관리가 어렵다고 판단했다. 그리고 좋아요를 누른 유저가 다시 좋아요를 누르게 되면 취소로 간주해서 좋아요 DB에서 삭제하는 과정을 거쳤다. 따로 상태를 두고 true/false로 두고 하려 했지만 좋아요를 누른 유저가 취소를 한 후 다시 좋아요를 누를 때까지 DB에 남겨두기에는 비효율적이라고 생각했다.

아래 부분은 좋아요 기능을 추가한 것을 정리한 것이다.

// 좋아요 정리 부분 추가 예정 //

 

 

 

아직 프로젝트과정이 복습과정과 다르지 않다고 느껴서 이번에는 외부 API를 이용하는 기능을 추가해 보려 한다. 네이버 or카카오 로그인 기능이나 검색 API를 외부를 통해서 가져온 후 전달해 주는 기능을 생각 중에 있다. 이 부분은 강의를 다시 보며 복습이 필요하다.

 

 

· 오늘 서칭 한 자료

프로젝트 진행으로 대체

 

 

· 느낀 점

- entity끼리 관계가 설정되어 있다보니 다른 팀원과의 코드가 아직 모이지 않아 생략하고 진행한 부분이 있어 제대로 동작할지 걱정이다.

- 좋아요 기능을 진행하면서 entity관계가 복잡해지다 보니 코드를 작성할 때도 많은 부분을 생각하게 된다.

- 반복적인 코드를 입력하다 보니 익숙해지기는 했지만 확실히 기억하기 위해 글로 정리를 해둬야겠다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-21 TIL  (0) 2023.12.21
2023-12-11(BA클럽 KPT 회고)  (0) 2023.12.11
2023-12-06 TIL  (1) 2023.12.06
2023-12-05 TIL  (1) 2023.12.05
2023-12-04 TIL  (0) 2023.12.04

· 오늘 공부한 것

댓글 CRUD 기능 작업

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

프로젝트 필수 구현 기능 중에서 내가 맡은 댓글 CRUD를 진행하였다. 전 프로젝트에서 댓글은 아니었지만 CRUD를 경험했기에 코드는 작성할 수 있었다. 하지만 코드를 쓰면서 댓글을 작성, 수정, 삭제에는 로그인한 유저의 정보가 필요했는데 이것은 USER부분에서 토큰 작업한 것이 필요했다. 또한 댓글이 게시글에 들어가기에 POST 작업도 필요했다. 하지만 이것을 각각 팀원이 나눠서 진행했기에 아직 합치지 못한 상태라 postman으로 테스트를 할 수 없었다. 전 시간에 배운 테스트코드를 작성하는 것도 진행할 수 없었다. 오늘까지 작업하기로 했던 것은 진행 못한 팀원들이 있어서 내일 오후에 github에 모두 모아서 이후에 진행하기로 했다. 먼가 댓글 API만 담당해서 독립적으로 작성하려 했지만 entity에서부터 관계설정이 들어가기에 배제하고 진행하기 까다로웠다. 그래서 일단 작성할 수 없는 부분은 주석으로 남겨두고 진행할 예정이다.

전체적인 흐름의 이해를 아래와 같이 그려보았다. 물론 코드를 반복작업하다 보니 요청한 데이터가 어디로 가서 가공되어 반환되는지 익힐 수 있었다.

 

API 요청에 따른 흐름

 

CRUD는 어느 정도 이해는 했지만 아직 로그인 부분이 이해되지 않았다. 일단 전프로젝트에서 로그인한 유저 정보만 가져다 계속 사용해서 진행을 할 수는 있었는데 이번에도 다른 사람이 USER를 맡아서 그냥 넘어가게 될 거 같아 혼자서 학습해보려고 한다. JWT을 이용한 로그인과 인증, 인가등을 복습해야겠다. 그래야 내일 다 합쳤을 대 팀원의 코드도 이해할 수 있을 것이다.

 

 

· 오늘 서칭 한 자료

프로젝트 진행으로 대체

 

 

· 느낀 점

- 만든 코드가 제대로 작동하는지 테스트하는 것이 중요하다. 어느 부분에서 오류가 있는지 알 수 있기 때문이다.

- 협업을 할 때 각 기능을 나눠서 진행하기는 했지만 맞물리는 부분이 있기 때문에 개인으로 진행하기는 힘들다.

- 독립적인 기능이 아닌 이상 팀원들과 꾸준한 의사소통이 필요하다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-11(BA클럽 KPT 회고)  (0) 2023.12.11
2023-12-07 TIL  (0) 2023.12.07
2023-12-05 TIL  (1) 2023.12.05
2023-12-04 TIL  (0) 2023.12.04
2023-11-27 TIL  (0) 2023.11.27

· 오늘 공부한 것

아웃소싱 팀프로젝트 시작

S.A (Starting Assignments) 작성 (ERD, API 명세서, 와이어프레임)

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

나의 역할은 필수 구현 중에서 댓글 CRUD를 맡았다. 전에 했던 작업이기도 해서 수월하게 진행할 수 있었다. 그래도 아직 팀원 모두가 각자 맡은 기능을 구현하고 합쳤을 때 제대로 작동할지는 의문이다. 그 이후에 추가적으로 하고 싶은 기능은 전 프로젝트에서 다른 팀원이 맡았던 좋아요 기능을 댓글에 추가해 보고 싶다. 이것이 완성된다면 네이버 or카카오 로그인을 진행할 생각이다. 아직 추가기능을 진행하기 위해서는 부족한 부분이 있기 때문에 복습을 진행할 예정이다. 사실 못 들은 강의도 있기에 이번 프로젝트에서 확실히 하고 넘어가야겠다. 이번 프로젝트가 끝나면 개인강의 시간이 진행되기에 이전 것을 습득하지 못하고 넘어가면 나중에 벅찰 것 같다.

 

 

 

· 오늘 서칭 한 자료

https://teamsparta.notion.site/e25db75d053c487899d4bcb49ede4c78

 

비포애프터

API 명세

teamsparta.notion.site

 

 

· 느낀 점

- 전에 프로젝트 진행 시 사전 작업의 중요성을 느꼈기 때문에 꼼꼼히 작성하였다.

- 역할 분담을 해서 진행하기로 했는데 과연 각자 작업하고 합쳤을 때 제대로 작동할지 모르겠다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-07 TIL  (0) 2023.12.07
2023-12-06 TIL  (1) 2023.12.06
2023-12-04 TIL  (0) 2023.12.04
2023-11-27 TIL  (0) 2023.11.27
2023-11-16 TIL  (0) 2023.11.16

· 오늘 공부한 것

스프링심화 개인과제 테스트 코드 작성하기

해설영상 참고해서 다시 코드 작성하기

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

  • DTO, Entity Test 추가하기
    • @Test를 사용해서 DTO와 Entity Test를 추가합니다.
    • User, Todo, Comment, DTO에 존재하는 메서드들에 대해서 테스트를 추가합니다.
  • Controller Test 추가하기
    • @WebMvcTest를 사용하여 Controller Test를 추가합니다.
    • Todo, Comment Controller에 대해서 테스트를 추가합니다.
  • Service Test 추가하기
    • @ExtendWith를 사용하여 Service Test를 추가합니다.
    • User, UserDetails, Todo, Comment Service에 대해서 테스트를 추가합니다.
  • Repository Test 추가하기
    • @DataJpaTest를 사용하여 Repository Test를 추가합니다.
    • User, Todo, Comment Repository에 대해서 테스트를 추가합니다.
  1. 통합 테스트와 단위 테스트의 차이점에 대해서 설명해 주세요.
  2. 통합 테스트와 단위 테스트의 장/단점에 대해서 설명해 주세요.
  3. 레이어별로 나누어서 Slice Test를 하는 이유에 대해서 설명해 주세요.
  4. 테스트 코드를 직접 짜보고 나서 느낀 테스트 필요성을 적어주세요.
  5. 테스트 코드를 짜면서 어려웠던 점을 적어주세요\

 

· 오늘 서칭 한 자료

https://learnote-dev.com/java/jacoco-%EC%A0%81%EC%9A%A9%ED%95%98%EC%97%AC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80-%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0/

 

[Spring] JaCoCo 적용하여 테스트 커버리지 체크하기

서론 테스트 코드는 개발 시간을 굉장히 단축시켜주는 강력한 도구입니다. 물론 코드를 작성하는데 시간도 많이 들어갈 뿐더러, 눈에 보이는 성과를 주지 않는 코드이기 때문에, 괜히 작성하는

learnote-dev.com

 

https://galid1.tistory.com/735

 

Spring Boot - 스프링 부트 통합테스트 방법과 팁(Spring boot Integration Test)

Spring Boot 테스트 이번 포스팅에서는 Spring Boot에서 통합테스트하는 방법에 대해서 알아보려고 합니다. https://medium.com/@ssowonny/%EC%84%A4%EB%A7%88-%EC%95%84%EC%A7%81%EB%8F%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%9

galid1.tistory.com

 

https://devs0n.tistory.com/25

 

Github Actions로 PR 시 테스트를 돌려보자

PR을 올릴 때 Github Actions로 테스트를 돌려서 해당 PR에 대해 깨지는 테스트가 없는지 확인하는 세팅을 해보자. 코드는 여기에 예제 PR은 여기에서 확인할 수 있다. Github Actions에 Gradle Test 추가 .githu

devs0n.tistory.com

 

· 느낀 점

- 통합 테스트보다 단위 테스트를 진행하면서 빠르고 정확한 오류를 파악하기 쉬웠다.

- 각 과정에서 진행하는 테스트코드를 작성해 보면서 간단한 부분도 있었지만 테스트하기 어려운 부분이 있었다. (repository test 관련)

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-06 TIL  (1) 2023.12.06
2023-12-05 TIL  (1) 2023.12.05
2023-11-27 TIL  (0) 2023.11.27
2023-11-16 TIL  (0) 2023.11.16
2023-11-15 TIL  (0) 2023.11.15

· 오늘 공부한 것

뉴스피드 팀프로젝트 회고

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

프로젝트 주제

맛집을 서로 공유하는 뉴스피드 사이트

 

목표

회원기능 및 각 도메인별 CRUD를 능숙하게 구현

멤버 간 원활한 의사소통

 

1. Keep

  1. 팀원이 같이 하는 프로젝트이기 때문에 모두가 이해하고 발맞춰 진행한 부분이 잘했다고 생각합니다. 만약 처음부터 분업을 해서 각자 진행을 했더라면 지금보다 완성도 있는 프로젝트를 할 수 없었을 것이라고 생각합니다.
  2. 스프링부트환경에서 코드를 많이 작성해보려는 습관
  3. 팀원간의 원할한 의사소통을 통해 서로 모르는 부분을 배우고 알려주려는 자세
  4. 초반 목표한 구현 목표를 넘어 추가 구현까지 해보려는 도전적인 자세
  5. 마감기한을 생각해서 미루지 않고 미리미리 해결하려는 자세

2. Problem

  1. 협업에 꼭 필요한 git을 잘 관리하지 못했다고 생각합니다. git을 commit할 때 명칭이나 기능을 완성하고 pr을 날리는 부분에서 먼가 정한 규칙이 없었기 때문에 기록이 잘 남지 못했습니다.
  2. 연결관계 이해가 부족해서 ERD를 설계하고 엔티티를 실제로 작성하는데 어려움이 있었음
  3. 트러블 슈팅 기록을 잘 정리해놓지 않아 프로젝트를 완성하고 배운것이 잘 기억나지 않음
  4. 만든 api 테스트를 하나하나 포스트맨으로 테스트를 해서 서비스 로직 자체만 검증하기가 번거로움
  5. 강의만 듣고 실습을 많이 해보지 않아 프로젝트 코드 작성시 어려움

3. Try

  1. git에 대한 이해도를 높이고 잘 활용하기 위해 연습이 필요하다고 생각합니다. 그래도 하루를 끝낼 때 모두가 모여서 각자 한 부분을 pr날리고 merge를 했습니다. merge 할 때도 모두의 승인이 떨어져야 가능하도록 설정하여 누군가 임의로 merge하는 것을 방지했습니다.
  2. 복습도 복습인데 다음 프로젝트 들어가기전 최소한 강의를 한 번이라도 다 보자
  3. 공부하고 알게 된 내용을 잘 기록하고 정리해놓자
  4. 다음 주차 강의에 테스트 관련 내용이 있던데 적극 활용해서 단위테스트를 해보자
  5. 알게 된 내용들로 실습을 많이 해보자
  6. 사전계획(API명세 작성, ERD작성, 팀원 간 컨벤션 정하기 등)을 보다 꼼꼼히 점검하면 프로젝트를 보다 수월하고 빠르게 진행할 수 있을 것 같습니다.

 

· 오늘 서칭 한 자료

생략

 

 

· 느낀 점

- 스프링을 시작하고 나서 첫 번째 팀프로젝트였는데 다행히 못 따라간 부분을 팀원들과 해결할 수 있었다.

- 바로 프로젝트를 시작하는 대신 모두가 이해하고 넘어가는 단계를 거쳐서 좋은 기회였다고 생각한다.

- 지식 공유를 할 수 있는 팀원이 되도록 노력해야겠다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-05 TIL  (1) 2023.12.05
2023-12-04 TIL  (0) 2023.12.04
2023-11-16 TIL  (0) 2023.11.16
2023-11-15 TIL  (0) 2023.11.15
2023-11-14 TIL  (0) 2023.11.14

· 오늘 공부한 것

Entity 연관 관계

  • 1 대 1 관계
  • N 대 1 관계
  • 1 대 N 관계
  • N 대 M 관계

지연 로딩과 즉시로딩 (LAZY, EAGER)

영속성 전이 (cascade)

고아 Entity 삭제 (orphanRemoval)

· 공부한 내용, 궁금한 내용, 부족한 내용

entity를 작성을 전에 했을 때는 1개를 가지고 진행했는데 이번에는 entity끼리의 관계설정하는 방법을 학습했다. entity관계 설정 시 단방향인지 양방향인지에 따라서 참조하여 조회를 할 수 있고 없고를 정할 수 있다. 하지만 DB 테이블 간의 관계에서는 이러한 방향의 개념은 없다. FK로 연결되어 있으면 JOIN으로 어느 테이블에서든지 참조해서 가져올 수 있다. 그렇기에 지금 하는 것은 entity끼리의 관계이기 때문에 DB테이블 관계와 약간은 분리해서 생각해야 할 부분들이 있다. 아래가 정리한 부분이다.

  • DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회가 가능합니다.
  • Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있습니다.
  • 하지만 상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법이 없습니다.
  • 따라서 Entity에서는 DB 테이블에는 없는 방향의 개념이 존재합니다.

1 : 1 관계

@OneToOne 애너테이션을 사용한다.

단방향 관계

보통 FK의 주인은 N(다)의 관계인 Entity이지만 1 대 1 관계에서는 FK의 주인을 직접 지정해야 한다.

외래 키 주인만이 외래 키 를 등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 외래 키를 읽기만 가능합니다.

 

@JoinColumn() 은 FK 주인이 활용하는 애너테이션이다.

  • 칼럼명, null 여부, unique 여부 등을 지정할 수 있다.

음식 Entity가 FK의 주인인 경우

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

양방향 관계

  • FK의 주인을 지정해 줄 때 mappedBy 옵션을 사용한다.
    • mappedBy의 속성값은 FK의 주인인 상대 Entity의 필드명을 의미한다.
  • FK의 주인 Entity에서 @JoinColumn() 애너테이션을 사용하지 않아도 defalut 옵션이 적용되어 생략이 가능하다.
    • 그러나 1 대 N 관계에서 외래 키의 주인 Entity가 @JoinColumn() 애너테이션을 생략한다면 JPA가 외래 키를 저장할 컬럼을 파악할 수가 없어서 의도하지 않은 중간 테이블이 생성된다.
    • 그러니 생략하지 말고 활용을 해주자!
  • 양방향 관계에서 mappedBy 옵션도 위와 같은 문제가 생길 수 있기 때문에 설정해주는게 좋다.

 

음식 Entity가 외래 키의 주인인 경우

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToOne(mappedBy = "user")
    private Food food;
}

 

N 대 1 관계

@ManyToOne 애너테이션을 사용한다.

단방향 관계

음식 Entity가 N의 관계로 외래 키의 주인

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

양방향 관계

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>();
}

 

1 대 N 관계

@OneToMany 애너테이션을 사용한다.

단방향 관계

FK를 관리하는 주인은 음식 Entity이지만 실제 FK는 고객 Entity가 가지고 있다.

  • 1 : N에서 N 관계의 테이블이 FK를 가질 수 있기 때문에 FK는 N관계인 고객 테이블에 FK칼럼을 만들어서 추가하지만 FK의 주인인 음식 Entity를 통해 관리한다.
  • 그렇기에 실제 DB에서 FK를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재한다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany
    @JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
    private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

양방향 관계

  • 1 대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않는다.
  • 양방향 관계를 맺으려면 음식 Entity를 FK의 주인으로 정해주기 위해 고객 Entity에서 mappedBy 옵션을 사용해야 하지만 @ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않는다.
  • N관계의 Entity인 고객 Entity에서 @JoinColumn의 insertable과 updatable 옵션을 false로 설정하여 양쪽으로 JOIN 설정을 하면 양방향처럼 설정할 수는 있다. (굳이?)
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "food_id", insertable = false, updatable = false)
    private Food food;
}

 

N 대 M 관계

@ManyToMany 애너테이션을 사용한다.

단방향 관계

  • N : M 관계를 풀어내기 위해 중간 테이블(orders)을 생성하여 사용한다.
  • 생성되는 중간 테이블을 컨트롤하기 어렵기 때문에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToMany
    @JoinTable(name = "orders", // 중간 테이블 생성
    joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
    inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

양방향 관계

  • 반대 방향인 고객 Entity에 @ManyToMany로 음식 Entity를 연결하고 mappedBy 옵션을 설정하여 FK의 주인을 설정하면 양방향 관계 맺음이 가능하다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToMany
    @JoinTable(name = "orders", // 중간 테이블 생성
    joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
    inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "userList")
    private List<Food> foodList = new ArrayList<>();
}

 

중간테이블을 직접 생성해서 N : M 관계 설정하기

  • 중간 테이블 orders를 직접 생성하여 관리하면 변경 발생 시 컨트롤하기 쉽기 때문에 확장성에 좋다.
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany(mappedBy = "food")
    private List<Order> orderList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Order> orderList = new ArrayList<>();
}
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

 

지연로딩과 즉시로딩

JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지 필요할 때 가져올지 정할 수 있다.

  • 즉, 가져오는 방법을 정하게 되는데 JPA에서는 Fetch Type이라 부릅니다.
  • Fetch Type의 종류에는 2가지가 있는데 하나는 LAZY, 다른 하나는 EAGER입니다.
  • LAZY는 지연 로딩으로 필요한 시점에 정보를 가져옵니다.
  • EAGER는 즉시 로딩으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를 즉시 가져옵니다.

기본적으로 @OneToMany 애너테이션은 defalut값이 LAZY로 지정되어 있고 @ManyToOne 애너테이션은 EAGER로 되어 있다.

다른 애너테이션도 default 값이 있는데 이를 구분하는 방법은 아래와 같다.

  • 애너테이션 이름에서 뒤쪽에 Many가 붙어있으면 설정된 해당 필드가 Java 컬렉션 타입일 것입니다.
    • 즉, 해당 Entity의 정보가 여러 개 들어있을 수 있다는 것을 의미합니다.
    • 따라서 효율적으로 정보를 조회하기 위해 지연 로딩이 default로 설정되어 있습니다.
  • 반대로 이름 뒤쪽이 One일 경우 해당 Entity 정보가 한 개만 들어오기 때문에 즉시 정보를 가져와도 무리가 없어 즉시 로딩이 default로 설정되어 있습니다.

JPA에서 영속성 콘텍스트의 기느은 1차 캐시, 쓰기 지연 저장소, 변경 감지이다. 지연 로딩도 마찬가지로 영속성 컨텍스트의 기능 중 하나이다. 그렇기 때문에 트랜젝션이 적용되어 있어야 한다.

@Test
@DisplayName("아보카도 피자 조회")
void test1() {
    Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);

    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());

    System.out.println("아보카도 피자를 주문한 회원 정보 조회");
    System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
@Test
@Transactional
@DisplayName("Robbie 고객 조회")
void test2() {
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    System.out.println("Robbie가 주문한 음식 이름 조회");
    for (Food food : user.getFoodList()) {
        System.out.println(food.getName());
    }
}

위에 코드는 실행 시 left join까지 실행하여 한 번에 연관된 모든 Entity의 정보를 가져오는데 아래에 있는 코드는 user.getFoodList()가 호출될 때 select문을 사용하여 정보를 가져온다. 그리고 지연로딩이기 때문에 @Transactional 애너테이션이 사용된 모습이다.

 

영속성 전이

  • 영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황을 뜻한다.
  • 이를 적용하여 해당 Entity를 저장할 때 연관된 Entity까지 자동으로 저장하기 위해서는 자동으로 저장하려고 하는 연관된 Entity에 추가한 연관관계 애너테이션에 CASCADE의 PERSIST 옵션을 설정하면 된다.

옵션을 사용하지 않으면 아래코드와 같이 save() 메서드를 여러 번 사용하게 된다.

@Test
@DisplayName("Robbie 음식 주문")
void test1() {
    // 고객 Robbie 가 후라이드 치킨과 양념 치킨을 주문합니다.
    User user = new User();
    user.setName("Robbie");

    // 후라이드 치킨 주문
    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);

    user.addFoodList(food);

    Food food2 = new Food();
    food2.setName("양념 치킨");
    food2.setPrice(20000);

    user.addFoodList(food2);

    userRepository.save(user);
    foodRepository.save(food);
    foodRepository.save(food2);
}
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
          this.foodList.add(food);
          food.setUser(this);// 외래 키(연관 관계) 설정
    }
}

하지만 User entity에 cascade PERSIST 옵션을 추가하게 되면 userRepository.save(user); 를 한 번만 쓰게 되면 영속성 전이로 food도 저장이 된다.

 

반대로 이렇게 연관된 Entity를 쉽게 삭제하는 방법인 cascade REMOVE 옵션이 있다.

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this);// 외래 키(연관 관계) 설정
    }
}

cascade에 중괄호({})를 사용하면 여러 개의 옵션을 사용할 수 있다.

cascade의 REMOVE 옵션을 하고 안하고의 차이는 아래와 같다.

@Test
@Transactional
@Rollback(value = false)
@DisplayName("Robbie 탈퇴")
void test3() {
    // 고객 Robbie 를 조회합니다.
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    // Robbie 가 주문한 음식 조회
    for (Food food : user.getFoodList()) {
        System.out.println("food.getName() = " + food.getName());
    }

    // 주문한 음식 데이터 삭제
    foodRepository.deleteAll(user.getFoodList());

    // Robbie 탈퇴
    userRepository.delete(user);
}

원래는 user를 삭제하기 전에 연관된 Entity Food에서 해당 user가 주문한 음식 데이터를 삭제하는 작업이 필요하다. 하지만 위에서 REMOVE옵션을 걸어줬기 때문에 foodRepository.deleteAll(user.getFoodList()); 와 같은 작업 없이 userRepository.delete(user); 만 하면 연관된 데이터도 삭제가 된다.

 

고아 Entity 삭제

위에서 알아본 REMOVE옵션의 경우 해당 Entity 객체를 삭제했을 때 연관된 Entity객체들을 자동으로 삭제할 수 있었다. 하지만 연관된 Entity와 관계를 제거했다고 해서 자동으로 해당 Entity가 삭제되지는 않는다. 그래서 사용하는 옵션이 orphanRemoval이다.

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this);// 외래 키(연관 관계) 설정
    }
}

@OneToMany 애너테이션에 orphanRemoval = true 옵션을 준 모습이다.

@Test
@Transactional
@Rollback(value = false)
@DisplayName("연관관계 제거")
void test1() {
    // 고객 Robbie 를 조회합니다.
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    // 연관된 음식 Entity 제거 : 후라이드 치킨
    Food chicken = null;
    for (Food food : user.getFoodList()) {
        if(food.getName().equals("후라이드 치킨")) {
            chicken = food;
        }
    }
    if(chicken != null) {
        user.getFoodList().remove(chicken);
    }

    // 연관관계 제거 확인
    for (Food food : user.getFoodList()) {
        System.out.println("food.getName() = " + food.getName());
    }
}

만약 orphanRemoval 옵션을 주지 않고 위에 코드를 실행하게 되면 Delete SQL이 수행되지 않는다. 하지만 옵션을 주면 Delete가 수행되는 것을 확인할 수 있었다.

 

그런데 여기서 REMOVE, orphanRemoval 옵션을 사용할 때 주의할 부분이 있다. 그것은 삭제하려고 하는 연관된 Entity를 다른 곳에서 참조하고 있는지 아닌지를 꼭 확인해야 하는 것이다.

  • A와 B에 참조되고 있던 C를 B를 삭제하면서 같이 삭제하게 되면 A는 참조하고 있던 C가 사라졌기 때문에 문제가 발생할 수 있습니다.
  • 따라서 orphanRemoval 같은 경우 @ManyToOne 같은 애너테이션에서는 사용할 수 없습니다.
    • ManyToOne이 설정된 Entity는 해당 Entity 객체를 참조하는 다른 Entity 객체들이 있을 수 있기 때문에 속성으로 orphanRemoval를 가지고 있지 않습니다.

 

· 오늘 서칭 한 자료

강의자료 대체

 

 

· 느낀 점

- 전에 node.js로 서버 만들 때 express 사용해서 구현했는데 관계 설정이 어느 정도 기억이 났다.

- 방식은 당연히 다르지만 관계설정을 하는 개념이 같았고 특히 다대다 관계에서 중간테이블을 생성해서 관리하기 편하게 해주는 방식이 같았다.

- entity 작성이 코드 초반작업이기 때문에 관계설정까지 확실히 해두지 않으면 DB에서 무슨 작업을 할 때 어려움이 생길 수 있다고 생각했다.

반응형

'Today I Learned' 카테고리의 다른 글

2023-12-04 TIL  (0) 2023.12.04
2023-11-27 TIL  (0) 2023.11.27
2023-11-15 TIL  (0) 2023.11.15
2023-11-14 TIL  (0) 2023.11.14
2023-11-13 TIL  (0) 2023.11.13

+ Recent posts