Tech/Spring

[Spring] 스프링 주요 어노테이션, Update(수정) 시 save() 메서드를 호출하는 것이 좋을까?

0m1n 2023. 4. 10. 21:29
728x90
반응형

어노테이션

  • @Repository : 스프링 빈으로 등록되도록 해주고, JPA Exception을 Spring 기반 Exception으로 변환
  • @Service : 비즈니스 로직을 처리하는 객체를 스프링 빈으로 등록
  • @Transactional : 트랜잭션을 보내고, 트랜잭션의 성질을 기준으로 commit할 지, rollback할 지 판단해줍니다.
    • readOnly = true : 데이터 변경이 없는 메서드에 사용
    • 영속성 context를 flush하지 않으므로 약간의 성능 향상을 기대할 수 있음
    • 기본값은 false이므로 메서드의 용도에 맞게 annotation을 정해야 함
  • @RequiredArgsConstructor : Spring boot에서 final 처리된 필드를 파라미터로 갖는 생성자를 만들어줌
    • 따라서 @Autowired annotation을 쓰지 않고, 생성자로 주입하는 코드 없이도 그 역할을 수행할 수 있음
    • 대부분 권장하는 방법 또한 @Autowired를 통한 필드 주입보다는 생성자 주입을 권장하므로 이 방법을 사용

생성자 주입의 장점

  • 의존관계 설정이 되지 않으면 객체생성 불가 -> 컴파일 타임에 인지 가능
  • 의존성 주입이 필요한 필드를 final 로 선언가능 -> 불변성
  • (스프링에서) 순환참조 감지가능 -> 순환참조시 앱구동 실패
  • 테스트 코드 작성 용이

Flush란?

  • 영속성 컨텍스트의 변경 내용을 DB 에 반영하는 것(영속성 컨텍스트를 비우는 것이 아님!)
    • 영속성 컨텍스트 : 엔티티를 영구 저장하는 환경
  • Transaction commit 이 일어날 때 flush가 동작하는데, 이때 쓰기 지연 저장소에 쌓아 놨던 INSERT, UPDATE, DELETE SQL들이 DB에 날라감
  • 영속성 컨텍스트의 변경 사항들과 DB의 상태를 맞추는 작업

테스트 코드

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class OrderServiceTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    OrderService orderService;

    /* test code */
    @Test
    public void 상품주문() throws Exception {}

}
  • @RunWith(SpringRunner.class) : Spring과 테스트를 통합하겠다는 의미
  • @SpringBootTest : 테스트 코드를 실행하기 전에 Spring Boot를 띄우고 하겠다는 의미. 없을 경우 @Autowired와 같은 annotation들은 모두 실패
  • @Transactional : 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 강제로 rollback하여 반복 가능한 테스트를 지원

save()

  • id 가 없으면 신규로 보고 persist() 실행
  • id 가 있으면 이미 데이터베이스에 저장된 엔티티를 수정한다고 보고, merge() 를 실행

Update(수정) 시 save() 메서드를 호출하는 것이 좋을까?

  • JPA를 사용하면 트랜잭션 범위 안에서 Dirty Checking이 동작
  • save() 메서드를 호출하지 않아도 값이 알아서 수정되고 반영

@Transactional만을 사용한 예제

@Transactional
public Noticeupdate(Long noticeId, String content) {
    Notice notice = noticeRepository.findById(noticeId).get();
    notice.setContent(content);
}

repository.save() 메서드를 사용한 예제

public Noticeupdate(Long noticeId, String content) {
    Notice notice = noticeRepository.findById(noticeId).get();
    notice.setContent(content);
    noticeRepository.save(notice);
}

위 두 코드의 최종 상태는 동일하다.

  • 1번 코드의 경우 객체가 자기 할일만 하는 코드이고,
  • 2번 코드의 경우 객체의 관점에서 자신의 상태를 변경한 후에, DB에도 따로 반영을 해주는 코드이다.

즉, 2번 코드는 외부 인프라인 DBMS를 우려한 코드이고, 객체 지향 관점에서 좋은 형태로 보긴 힘들다.

테스트 관점?

  • Service 클래스에 대한 단위 테스트를 진행하기 위해서는 Repository Mocking 필요
  • 서비스단에서 save() 메서드를 의미없이 호출하게 되면, 불필요하게 Mocking할 메서드가 하나 더 늘어남
  • 따라서 테스트 코드가 복잡해짐

결론

  • 새로운 엔터티를 추가할 때는 repository.save() 메서드 사용
  • 기존 엔티티를 수정하는 작업은 사용안하는 것이 더 깔끔
728x90
반응형