JPA 적용 이유
프로젝트 초기에는 JDBC Template을 사용해 데이터베이스와의 연동 작업을 진행했다. 하지만 객체지향적 언어로 도메인을 구성하며 프로그래밍을 하던 중, 객체와 데이터베이스 간의 연결 방법에 대한 고민이 생겼다. 객체지향 설계와 관계형 데이터베이스 사이의 패러다임 불일치로 인해 코드가 복잡해지는 문제를 겪었다. 이러한 문제를 해결하기 위해 ORM(Object-Relational Mapping) 기술인 JPA를 도입하게 되었고, 이를 통해 객체와 데이터베이스 간의 매핑을 더 자연스럽게 처리하고자 했다.
✔️ ORM이란?
: Object-Relational Mapping의 약자로, 객체와 관계형 데이터를 매핑하기 위한 기술. 매핑이 필요한 이유는 객체지향 언어와 관계형 데이터베이스 사이의 패러다임 불일치가 있기 때문이다
✔️ 패러다임 불일치
: 객체지향 프로그래밍과 관계형 데이터베이스 사이의 데이터 표현 방식이 달라서 생기는 문제를 패러다임 불일치라고 한다.
❇️ 객체 지향 : 데이터를 객체와 클래스의 형태로 다룬다.
❇️ 관계형 데이터베이스 : 데이터를 테이블과 행으로 관리한다.
✔️ JPA
: Java Persistence API의 약자로, 자바 ORM 기술에 대한 API 표준 명세이다. 인터페이스의 모음으로 이러한 JPA 인터페이스를 구현한 대표적인 프레임워크가 하이버네이트(Hibernate)이다.
Spring Data JPA
스프링 프레임워크의 일부로 스프링에서 JPA를 쉽게 사용할 수 있도록 도와주는 기술이다. JPA를 사용할때의 반복적인 코드들을 대신 작성하여 코드를 줄여준다. JPA를 한단계 추상화한 모듈로써, Repository라는 인터페이스를 제공함으로써 이루어진다.
Spring Data JPA 인터페이스 적용하기
public interface MemberV2JpaRepository extends JpaRepository<MemberV2, Long> {
}
✔️ 구현체가 없는 이유 : 구현체를 Spring Data JPA가 대신 생성한다. Spring Data JPA 관련 인터페이스를 가지고 있으면 구현 클래스를 대신 생성한다.
✔️ @Repository 어노테이션 생략 가능 : 컴포넌트 스캔을 Spring Data JPA가 자동으로 처리한다.
다양한 구조로 변경 가능한 Repository 만들기
💡 도메인 계층의 Repository를 JpaRepository로 바로 확장하여 구현할때의 유의할 점
도메인 모델을 구성하는 방법은 추후에 변경이 될 수 있다. 현재로써는 JPA만 사용하지만 추후에 도메인 모델을 구성하는 방법이 JPA + NoSQL 또는 JPA + Redis Cache 등으로 성능 향상이 필요한 구조로 변경될 필요가 있다면 현재 설계는 변경에 취약한 구조가 된다.
✅ 해결 방법 : 의존성 주입
도메인 계층에서 Repository를 정의하고 해당 구현체에 JpaRepository 등 필요한 의존성을 주입하는 방식으로 변경에 유연한 모델을 만들 수 있다.
✔️ MemberV2Repository 인터페이스 정의
public interface MemberV2Repository {
MemberV2 save(MemberV2 member);
boolean isEmailExists(String email);
boolean isNicknameExists(String nickname);
Optional<MemberV2> findByEmail(String email);
}
✔️ JpaRepository
public interface MemberV2JpaRepository extends JpaRepository<MemberV2, Long> {
boolean existsByEmail(String email);
boolean existsByNickname(String nickName);
Optional<MemberV2> findByEmail(String email);
}
✔️ MemberV2Repository 구현체에 주입
@Repository
@RequiredArgsConstructor
public class MemberV2RepositoryImpl implements MemberV2Repository{
private final MemberV2JpaRepository memberV2JpaRepository;
@Override
public MemberV2 save(MemberV2 member) {
return memberV2JpaRepository.save(member);
}
@Override
public boolean isEmailExists(String email) {
return memberV2JpaRepository.existsByEmail(email);
}
@Override
public boolean isNicknameExists(String nickname) {
return memberV2JpaRepository.existsByNickname(nickname);
}
@Override
public Optional<MemberV2> findByEmail(String email) {
return memberV2JpaRepository.findByEmail(email);
}
}
테스트 코드 작성
@DataJpaTest
@DataJpaTest
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MemberV2JpaRepositoryTest {
...
}
@DataJpaTest를 사용하여 JPA 관련 컴포넌트에 대한 슬라이스 테스트를 수행할 수 있다. 즉, 애플리케이션의 전체 컨텍스트를 로드하지 않고 데이터 레이어에 집중된 테스트를 수행할 수 있다.
1. JPA 관련 컴포넌트만 로드 : JPA와 관련된 빈들만 로드한다. 따라서 서비스 레이어나 컨트롤러 같은 다른 레이어와는 독립적으로 데이터 접근 로직을 테스트 할 수 있다.
2. 내장 데이터베이스 사용 : 기본적으로 내장된 인메모리 데이터베이스를 사용하여 테스트를 수행한다. 테스트를 실행할 때 별도의 데이터베이스 설정이 필요없다.
3. 트랜잭션 관리 및 롤백 : 각 테스트 메서드는 트랜잭션 내에서 실행되며, 기본적으로 테스트 메서드가 끝나면 자동으로 롤백된다. 이를 통해 테스트 간 데이터의 격리가 보장된다.
4. 엔티티, 리포지토리 자동 설정 : @Entity, @JpaRepository 인터페이스 등이 자동으로 설정된다.
@AutoConfigurTestDatabase
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
...
}
@DataJpaTest 어노테이션 사용시 자동으로 설정해주는 옵션들이 여럿 존재한다. 그중에서도 @AutoConfigureTestDatabase 는 스프링부트에서 테스트 시 사용될 데이터베이스를 자동으로 구성하기 위해 사용되는 어노테이션이다. 테스트 환경에서 어떤 데이터베이스를 사용할지 제어할 수 있게 해준다.
기본적으로는 Spring Boot는 테스트 시 인메모리 데이터베이스를 사용하도록 설정되어 있다. 만약 내가 설정한 property를 사용하고 싶다면 해당 어노테이션을 통해 설정할 수 있다.
✔️ Replace
데이터베이스를 대체할 방식을 지정한다. 열거형을 사용하며, 다음과 같은 옵션이 존재한다.
❇️ANY (Default) : 데이터 소스가 임베디드(인메모리) 데이터베이스로 대체된다.
❇️ NONE : 데이터소스를 대체하지 않고, 애플리케이션에서 제공한 데이터 소스 그대로 사용한다.
사용 예시
기본 설정
@DataJpaTest
@AutoConfigureTestDatabase
public class UserRepositoryTest {
// 테스트 코드
}
Spring Boot 테스트 시 자동으로 인메모리 데이터베이스(H2 등)를 사용한다.
애플리케이션에서 설정한 데이터베이스 사용
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserRepositoryTest {
// 테스트 코드
}
이 경우, application.yml 또는 application-test.yml 에서 설정한 데이터베이스가 테스트에 적용된다.
'Side Project > Study Together' 카테고리의 다른 글
[동시성 제어] DB 락 vs Redis vs 비동기 (1) | 2025.02.04 |
---|---|
[Test Code] Repository 단위 테스트로 다져가는 도메인 중심 설계의 기초 (4) | 2024.10.26 |
[Event 기반 설계] 다양한 이벤트 확장 : 이벤트 추상화와 동적 매핑 구현 방법 (2) | 2024.09.26 |
[Event 기반 설계] Transactional Outbox 패턴이 필요한 이유 : 프로젝트 적용 사례 (2) | 2024.09.26 |
[JPA] 일대다(OneToMany) 단방향 매핑의 성능 이슈 (1) | 2024.09.25 |