지연 로딩과 영속성 전이는 JPA에서 엔티티의 데이터 조회 및 관계 관리를 효율적으로 처리하기 위해 제공하는 기능입니다. 각 개념에 대해 자세히 설명하겠습니다.
1. 지연 로딩 (Lazy Loading)
지연 로딩은 엔티티가 데이터베이스에서 실제로 필요한 시점까지 관련 데이터를 불러오지 않고, 요청이 발생할 때 데이터를 가져오는 방식입니다. 지연 로딩은 JPA에서 @OneToMany
, @ManyToMany
등의 관계 설정에서 기본적으로 사용되며, 필요할 때만 데이터를 가져오므로 성능 최적화에 유리합니다.
- 특징: 실제로 엔티티를 사용할 때까지 데이터베이스 조회를 미루고, 조회 시점에 데이터를 가져옵니다.
- 예제:
Order
엔티티가 여러 개의OrderItem
엔티티와@OneToMany
관계로 설정된 경우,Order
엔티티를 조회할 때OrderItem
리스트는 실제로 접근하기 전까지 조회되지 않습니다.
예시 코드
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> orderItems = new ArrayList<>();
// 기타 필드와 메서드
}
- FetchType.LAZY:
OrderItem
엔티티 리스트는 실제로 필요할 때(접근할 때) 조회됩니다.
주의사항: 지연 로딩 시 프록시 객체가 생성되며, 이 객체는 실제 데이터베이스 조회를 필요할 때 수행합니다. 이로 인해 특정 상황(예: 영속성 컨텍스트 밖에서 접근할 때)에서는 LazyInitializationException
이 발생할 수 있습니다.
2. 즉시 로딩 (Eager Loading)
지연 로딩의 반대 개념으로, 즉시 로딩은 엔티티를 조회할 때 연관된 엔티티도 함께 즉시 조회하는 방식입니다. 즉시 로딩은 FetchType.EAGER
를 사용하며, 작은 규모의 프로젝트나 연관 데이터가 항상 필요한 경우에 유용하지만, 많은 데이터 조회가 동시에 발생할 경우 성능에 영향을 줄 수 있습니다.
예시 코드
@OneToOne(fetch = FetchType.EAGER)
private Profile profile;
이 방식은 지연 로딩과 달리 즉시 모든 데이터를 조회하므로 사용 시 신중해야 합니다.
3. 영속성 전이 (CascadeType)
영속성 전이는 부모 엔티티에서 수행된 작업(저장, 삭제 등)을 자식 엔티티로 자동 전파하는 기능입니다. 관계를 통해 연관된 엔티티들 간의 일관성을 유지하고, 개발자가 수동으로 엔티티 상태를 관리해야 하는 수고를 덜어줍니다.
주요 CascadeType 옵션
- CascadeType.PERSIST: 부모 엔티티가 저장될 때, 자식 엔티티도 함께 저장됩니다.
- CascadeType.MERGE: 부모 엔티티가 병합될 때, 자식 엔티티도 함께 병합됩니다.
- CascadeType.REMOVE: 부모 엔티티가 삭제될 때, 자식 엔티티도 함께 삭제됩니다.
- CascadeType.ALL: 위 모든 전이 작업(PERSIST, MERGE, REMOVE, REFRESH, DETACH)이 자식 엔티티에도 전파됩니다.
예시 코드
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Book> books = new ArrayList<>();
// 기타 필드와 메서드
}
CascadeType.ALL
설정 덕분에 Author
를 저장하거나 삭제하면 Book
도 함께 처리됩니다.
요약
- 지연 로딩은 성능 최적화를 위해 엔티티를 실제로 필요할 때 로드하는 방식입니다.
- 즉시 로딩은 엔티티를 조회할 때 관련 엔티티도 즉시 로드하는 방식으로, 성능에 부담을 줄 수 있습니다.
- 영속성 전이(CascadeType)는 부모 엔티티에 대한 작업이 자식 엔티티로 자동 전파되도록 하는 기능으로, 데이터 일관성을 유지하는 데 유리합니다.
이 두 가지 개념을 적절히 사용하면 JPA에서 효율적인 데이터 관리와 성능 최적화를 도울 수 있습니다.
고아 엔티티는 JPA에서 부모 엔티티와의 관계가 끊어진 자식 엔티티를 의미합니다. 쉽게 말해, 부모 엔티티가 자식 엔티티와의 연관 관계를 제거했을 때, 데이터베이스에 남아있는 자식 엔티티를 가리킵니다.
JPA에서는 orphanRemoval = true
옵션을 사용하여 고아 엔티티를 자동으로 삭제할 수 있습니다. 이 옵션을 설정하면 부모와의 관계가 끊어진 자식 엔티티가 자동으로 삭제되어 데이터베이스에 불필요한 데이터가 남지 않게 됩니다.
고아 엔티티 예시
예를 들어, Parent
와 Child
라는 두 엔티티가 있고 Parent
가 Child
를 리스트로 가지고 있는 일대다(@OneToMany
) 관계라고 가정해 봅시다.
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
// Getter, Setter 및 기타 메서드
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
// Getter, Setter 및 기타 메서드
}
orphanRemoval = true 설명
위 예제에서 orphanRemoval = true
를 설정했기 때문에, Parent
엔티티에서 children
리스트에서 Child
엔티티를 삭제하면 데이터베이스에서도 해당 Child
엔티티가 자동으로 삭제됩니다.
동작 방식 예시
Parent parent = entityManager.find(Parent.class, 1L);
parent.getChildren().remove(0); // 첫 번째 Child 엔티티를 리스트에서 제거
entityManager.getTransaction().commit();
위 코드에서 parent.getChildren().remove(0);
을 통해 Parent
엔티티와 Child
엔티티 간의 관계를 제거하면, 고아가 된 Child
엔티티가 자동으로 데이터베이스에서 삭제됩니다.
주의사항
orphanRemoval = true
는 일대다 또는 일대일 관계에서만 사용 가능합니다.orphanRemoval
과CascadeType.REMOVE
는 자주 함께 사용되지만,orphanRemoval
은 부모-자식 관계에서 참조가 제거될 때 자식 엔티티를 삭제한다는 점에서 더 구체적입니다.
요약
- 고아 엔티티는 부모와의 연관 관계가 끊어져 더 이상 참조되지 않는 자식 엔티티입니다.
orphanRemoval = true
옵션을 사용하면 부모-자식 관계가 제거될 때 고아 엔티티가 자동으로 삭제됩니다.- 데이터 무결성을 유지하고, 불필요한 데이터가 남지 않도록 관리할 수 있는 유용한 옵션입니다.
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)은 애플리케이션의 주요 비즈니스 로직과는 별개의 횡단 관심사(cross-cutting concerns)를 모듈화하는 프로그래밍 기법입니다. 횡단 관심사는 예외 처리, 로깅, 트랜잭션 관리, 보안, 성능 모니터링 등과 같이 여러 클래스나 모듈에 걸쳐 반복적으로 발생하는 공통 기능들을 의미합니다.
AOP 주요 개념
- Aspect (관점): 여러 클래스에서 공통으로 사용하는 기능을 모듈화한 것입니다. 예를 들어, 로깅, 보안, 트랜잭션 관리를 Aspect로 구현할 수 있습니다.
- Advice: 언제 공통 기능을 적용할지 정의하는 코드입니다. AOP에서 제공하는 Advice 종류는 다음과 같습니다.
- Before: 대상 메서드가 호출되기 전에 실행됩니다.
- After: 대상 메서드가 실행된 후에 실행됩니다.
- AfterReturning: 메서드가 정상적으로 실행되고 반환된 이후에 실행됩니다.
- AfterThrowing: 메서드 실행 중 예외가 발생했을 때 실행됩니다.
- Around: 메서드 실행 전후로 실행되며, 메서드의 실행을 감싸는 형태로 동작합니다.
- Pointcut: Advice가 적용될 지점을 정의하는 표현식입니다. 특정 패키지나 클래스, 메서드를 지정해 Advice가 실행되는 위치를 설정할 수 있습니다.
- Join Point: Advice가 적용될 수 있는 지점으로, 메서드 호출, 예외 발생, 필드 접근 등이 Join Point에 해당합니다. Spring AOP에서는 메서드 실행 시점이 Join Point입니다.
- Weaving: Aspect를 실제 코드에 적용하는 과정입니다. Spring AOP에서는 프록시 패턴을 사용하여 런타임에 Weaving이 이루어집니다.
AOP 적용 예시
예를 들어, 모든 서비스 메서드 호출에 대해 로깅을 적용하고 싶을 때 AOP를 사용할 수 있습니다.
1. Aspect 작성하기
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
logger.info("Method is about to be called");
}
}
- @Aspect: 이 클래스가 Aspect임을 나타냅니다.
- @Before: 지정된 Pointcut에 따라 서비스 메서드가 호출되기 전에 로그를 기록합니다.
- execution: 특정 메서드를 지정하는 Pointcut 표현식입니다. 여기서는
com.example.service
패키지의 모든 메서드에 적용됩니다.
2. AOP 설정하기
Spring Boot에서는 @EnableAspectJAutoProxy
어노테이션을 통해 AOP를 활성화할 수 있습니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
AOP 사용의 장점
- 모듈화: 공통 기능을 한 곳에 모아 코드 중복을 줄이고 유지보수를 쉽게 할 수 있습니다.
- 코드 가독성: 비즈니스 로직에서 횡단 관심사를 분리하여 가독성을 높입니다.
- 유연성: 특정 기능을 원하는 위치에 쉽게 적용하거나 제거할 수 있습니다.
AOP의 활용 사례
- 로깅: 메서드 실행 전후에 로그를 남기거나 실행 시간을 측정하는 데 사용됩니다.
- 트랜잭션 관리: 데이터베이스 트랜잭션의 시작과 종료를 자동으로 처리합니다.
- 보안: 특정 메서드에 접근 권한이 있는지 확인하는 데 사용됩니다.
- 예외 처리: 메서드 실행 중 발생하는 예외를 공통적으로 처리합니다.
AOP를 통해 공통적인 기능을 코드 곳곳에 중복 작성하지 않고 중앙에서 관리할 수 있으며, Spring AOP는 런타임에 프록시를 생성하여 성능에 크게 영향을 주지 않도록 합니다.
Mockito는 Java에서 테스트를 위한 모의 객체(Mock Object)를 생성하고 관리하는 프레임워크입니다. 테스트하려는 객체의 의존성을 모의 객체로 대체하여 독립적인 단위 테스트를 수행할 수 있도록 도와줍니다. 주로 메서드 호출의 결과를 미리 설정하거나, 호출 여부를 검증하여 코드의 특정 부분만을 테스트할 수 있게 해 줍니다.
Mockito의 주요 기능
- Mock 객체 생성: Mockito를 사용하면 실제 객체 대신 Mock 객체를 생성할 수 있습니다. 이 객체는 실제 로직을 실행하지 않고, 사전에 설정한 동작만 수행합니다.
- Stubbing (스텁 설정): 특정 메서드가 호출될 때 원하는 결과를 반환하도록 설정할 수 있습니다. 예를 들어,
when(...).thenReturn(...)
을 사용해 특정 조건에 대해 반환 값을 설정할 수 있습니다. - 메서드 호출 검증: 테스트 중에 특정 메서드가 호출되었는지, 몇 번 호출되었는지 등을 검증할 수 있습니다. 이는
verify()
메서드를 통해 이루어집니다. - 인자 매칭: 특정 인자로 메서드가 호출될 때 동작을 정의할 수 있습니다. 예를 들어,
anyString()
,anyInt()
와 같은 매처를 사용하여 다양한 인자 값에 대해 설정할 수 있습니다.
Mockito 사용 예제
아래 예제에서는 UserService
클래스가 UserRepository
에 의존한다고 가정하고, UserRepository
를 Mock 객체로 대체하여 UserService
를 테스트합니다.
1. Mock 객체 생성
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class UserServiceTest {
@Mock
private UserRepository userRepository; // Mock 객체
@InjectMocks
private UserService userService; // 테스트할 객체
public UserServiceTest() {
MockitoAnnotations.openMocks(this); // Mock 초기화
}
@Test
public void testFindUserById() {
// 스텁 설정: userRepository.findById(1L)가 호출되면 "Test User" 반환
when(userRepository.findById(1L)).thenReturn("Test User");
// 테스트 수행
String result = userService.findUserById(1L);
// 결과 검증
assertEquals("Test User", result);
}
}
2. 메서드 호출 검증
메서드가 정확히 호출되었는지 확인할 수 있습니다.
@Test
public void testSaveUser() {
User user = new User("New User");
// 메서드 호출
userService.saveUser(user);
// userRepository.save(user) 메서드가 1회 호출되었는지 검증
verify(userRepository, times(1)).save(user);
}
verify()
: 지정된 메서드가 호출되었는지 검증합니다.times(1)
은 메서드가 한 번 호출되었는지를 확인합니다.
3. 예외 처리 검증
예외 상황이 발생할 때 동작을 검증할 수도 있습니다.
@Test
public void testFindUserByIdThrowsException() {
when(userRepository.findById(1L)).thenThrow(new RuntimeException("User not found"));
// 예외 발생 여부 확인
assertThrows(RuntimeException.class, () -> userService.findUserById(1L));
}
Mockito의 장점
- 의존성 분리: Mock 객체를 사용하여 클래스 간 의존성을 제거하고, 테스트하려는 클래스만 독립적으로 테스트할 수 있습니다.
- 테스트 속도 향상: Mock 객체는 실제 로직을 실행하지 않기 때문에 테스트가 빠르게 진행됩니다.
- 예외 및 엣지 케이스 처리: 특정 예외 상황이나 엣지 케이스를 쉽게 시뮬레이션하여 테스트할 수 있습니다.
Mockito는 Spring Boot와 같은 프레임워크와 통합이 용이하며, 단위 테스트와 통합 테스트 모두에서 널리 사용됩니다.
'Spring' 카테고리의 다른 글
스프링 부트 Test(어노테이션) (0) | 2024.11.16 |
---|---|
스프링 부트 3의 특징(이전 버전과 비교) (0) | 2024.11.16 |
entity 연관관계 (0) | 2024.11.11 |
@Validation에 대하여 (0) | 2024.11.11 |
JPA와 영속성 컨텍스트, 그리고 Bean의 관계 (0) | 2024.11.10 |