Weekly I Learned (4월 4주차)

YEON·2022년 4월 23일
0

Weekly I Learned (2022)

목록 보기
1/15
그 주의 화요일, 금요일을 지정 날짜로 업데이트 합니다

1. JPA 객체 연관관계

객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 양방향 연관관계에서 안전하지만, cascade.ALL 을 설정해주면 연관관계의 주인에서만 연결을 해주어도 양방향 연관관계가 설정된다.

cascade 학습테스트

 @Test
 @DisplayName("cascade 학습테스트. 부모가 영속 상태이면, 자식도 영속 상태가 된다")
 void cascade() {
     //given
     User newWriter = new User("id", "pwd", "user", "email@slipp.net");
     Question newQuestion = new Question("question", "is");
     Answer newAnswer = new Answer(newWriter, newQuestion, "answer");

     newQuestion.writeBy(newWriter);         // 연관관계의 주인으로 양방향 연관관계 설정 (연관관계를 안해주면 연관관계가 실패하여 save 가 실패한다.)
     newQuestion.addAnswer(newAnswer);       // 연관관계의 주인이 아니지만, cascade 를 통해서 Answer 도 영속성
     //newAnswer.setQuestion(newQuestion);   // 연관관계의 주인이지만, 이렇게 하려면 question 도 save 하고, answer 도 save 해야 한다.
     //newWriter.addAnswer(newAnswer);       // 연관관계의 주인이 아니고, Answer 에서 User 를 넣어서 연관관계 맺어주었으므로 안해줘도 성공한다.
     //newWriter.addQuestion();              // 연관관계의 주인이 아니고, newQuestion.writeBy 으로 맺어주었으므로 안해줘도 성공한다.

     //when
     userRepository.save(newWriter);
     Question actualQuestion = questionRepository.save(newQuestion);

     //then
     //userRepository, questionRepository, answerRepository 모두 같은 영속성 컨텍스트에서 관리된다.
     assertThat(actualQuestion.getAnswer().size()).isEqualTo(1);
     assertThat(answerRepository.findById(newAnswer.getId()).get().getWriter()).isEqualTo(newWriter);
     assertThat(answerRepository.findById(newAnswer.getId())).isNotEmpty();
 }

ERROR: The given id must not be null!

연관관계가 실패하면 save 가 되지 않고 때문에 DB에 값이 insert 되지 않고 때문에 IDENTITY 전략을 사용하게 된다면 ID 값이 존재하지 않아서, The given id must not be null! 오류 메시지가 발생하게 된다.

save 관점에서 다시 생각해보면, 영속성 컨텍스트에 저장되려면 id(PK)가 필요하고 GenerationType.IDENTITY 전략을 사용할 경우 현재 id 는 존재하지 않는다. 때문에 전략상 id 값을 넣고 가져오기 위해 데이터베이스에 우선 값을 insert해서 id(기본 키)를 가져와 이때부터 insert 쿼리가 영속화된다. (save는 부모 트랜잭션이 없으면 바로 @Transactional에 의해서 트랜잭션이 되기 때문에 가능하다.)





2. JPA 영속성 컨텍스트 save

ID가 존재하지 않는 객체를 이용해 JPA의 save 메서드를 복수로 사용하는 경우, 영속성 컨텍스트에서 같은 객체를 반환한다.

또한, GenerationType.IDENTITY 전략을 사용할 경우 임시의 ID 값을 넣어서 저장을 하더라도 generatedValue 타입으로 아이디가 생성되어 객체가 반환된다.

save 학습테스트

  @Test
  @DisplayName("save 학습테스트. 같은 객체를 중복으로 저장하였을 때 결과 확인")
  void duplicateSave() {
      final User writer = userRepository.save(UserTest.TESTUSER);
      final Question question1 = questionRepository.save(QuestionTest.Q1.writeBy(writer));
      final Question question2 = questionRepository.save(QuestionTest.Q1.writeBy(writer));
      //db 에는 없고 persist 로 영속성에만 저장
      
      assertThat(question1).isEqualTo(question2); //같은 객체인지 확인
      assertThat(question1.getId()).isEqualTo(question2.getId());
      assertThat(question1.getId()).isEqualTo(QuestionTest.Q1.getId());
  }// test 하나 끝나면 commit 되어 db에 반영




3. 비지니스 로직 구현 위치

layered architecture 기반에서 TDD, OOP를 적용하려면
핵심 비지니스 로직을 서비스 레이어가 아닌 도메인 객체가 담당하도록 구현하는 것이 좋다.
(데이터베이스 등 테스트 하기 어려운 부분들이 해결될 수 있다)

TDD 를 할때 초점은 서비스 레이어가 아니라 도메인이다.
서비스 레이어는 데이터베이스와의 인터페이스를 담당하고 엮어서 로직을 구현하기 때문에 TDD가 어렵다.

서비스 레이어의 초점은 thin layer 이다.
로직이 없고, 리포지토리나 다른 서비스 레이어들과의 의존관계만 연결하면서 도메인 객체에 메시지만 보내는 역할을 하는 것이다.
(모키토를 사용하여 TDD를 사용할 수 있지만, 구현 비용이 드는 단점이 존재한다.)

결국, 도메인에서 로직을 구현하게 되면 좋은 점은 데이터베이스등 테스트하기 어려운 부분들의 의존관계가 깨지게 되어 JUnit 기반의 순수한 TDD 가 가능하다.

Q. 만약 DB 관련하여 테스트를 구현해보고 싶다면 어떻게 하면 좋을까?

DB에 값이 들어가는지를 체크해보기 위하여, JDBC 템플릿을 통해 서버쪽에서 접속이 되는지를 구현해본 후 이후에 JPA 등의 맵핑으로 리팩터링을 진행할 수 있다.

Q. 서비스 레이어도 관통해서 테스트해보고 싶다면 어떻게 하면 좋을까?

서비스 레이어를 관통하여 함께 테스트를 진행하기 위해서는 DB와의 연결을 일단 끊어야한다.
그러기 위해 DB 소스를 Bean 등록 후에 JUnit 테스트를 진행할 때에는 fake 객체를 바라보도록 구현하면 된다.

profile
- 👩🏻‍💻

0개의 댓글