TIL
1. Cascade Types in JPA
Cascade는 부모-자식 관계에 있는 엔티티 간 작업을 전파하는 방식으로, 부모 엔티티의 작업(Insert, Delete 등)이 자식 엔티티에 영향을 미치도록 설정할 수 있습니다.
- CascadeType.PERSIST:
부모 엔티티가 저장(Insert)될 때 자식 엔티티도 함께 저장됩니다.
예시: 부모 엔티티persist
호출 시, 자식 엔티티도 자동으로persist
. - CascadeType.REMOVE:
부모 엔티티가 삭제(Delete)될 때 자식 엔티티도 자동으로 삭제됩니다.
예시: 부모 엔티티remove
호출 시, 관련된 자식 엔티티도 삭제됨. - Orphan Removal:
부모 엔티티와의 관계가 끊어진(고아 상태) 자식 엔티티를 자동으로 삭제하는 설정입니다.
예: 부모 엔티티에서 자식 엔티티를 리스트에서 제거하면, 자식 엔티티는 DB에서도 삭제됩니다.여기서 중요한 점은orphanRemoval = true
와CascadeType.REMOVE
의 차이입니다.CascadeType.REMOVE
: 부모가 삭제될 때 자식도 삭제됩니다.orphanRemoval = true
: 부모와 연결이 끊어진 고아 상태의 자식만 삭제됩니다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) private List<Child> children;
2. Lazy Loading in @OneToMany
@OneToMany
관계의 기본 Fetch 전략은 Lazy
입니다.
이는 부모 엔티티를 조회할 때 자식 엔티티를 즉시 가져오지 않고, 실제로 접근할 때 데이터를 가져오는 방식입니다.
- 장점: 불필요한 데이터를 미리 로드하지 않아 성능이 향상됩니다.
- 단점: 자식 엔티티를 접근할 때 추가적인 쿼리가 발생합니다.
예시:
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children;
- Lazy Loading을 강제로 즉시 로드(Eager)하도록 바꾸려면:
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
private List<Child> children;
3. Transactional 및 Rollback
@Transactional
:
트랜잭션 경계를 지정하는 애너테이션으로, 이 안에서 실행된 작업은 성공 시 자동으로commit
, 실패 시 자동으로rollback
됩니다.- 테스트 메서드에서의 Rollback:
테스트 메서드에서@Transactional
을 사용할 경우, 테스트 실행 후 자동으로rollback
됩니다. 이는 테스트 환경에서 실제 데이터베이스를 오염시키지 않도록 하기 위함입니다. - 예:
-
@Test @Transactional public void testSave() { // 테스트 실행 후 insert된 데이터는 rollback됨 }
4. @Autowired
와 객체 주입
@Autowired
:
스프링이 특정 객체를 자동으로 주입하도록 해주는 애너테이션입니다. 주로 스프링 컨텍스트에서 관리하는 빈(Bean)을 주입받을 때 사용합니다.- 객체 주입 방식:
- 필드 주입:
@Autowired
를 필드에 직접 사용하는 방식입니다.- 장점: 코드가 간결함.
- 단점: 테스트하기 어렵고, 순환 참조 문제가 발생할 수 있음.
@Autowired private QuestionRepository questionRepository;
- Setter 주입: Setter 메서드를 통해 객체를 주입받는 방식입니다.
@Autowired public void setQuestionRepository(QuestionRepository questionRepository) { this.questionRepository = questionRepository; }
- 생성자 주입 (권장): 생성자를 통해 객체를 주입받는 방식입니다. 순환 참조 문제를 방지하고, 의존성 주입이 명확해집니다.
private final QuestionRepository questionRepository; public QuestionService(QuestionRepository questionRepository) { this.questionRepository = questionRepository; }
- 필드 주입:
5. JPA Naming Methods
JPA에서 쿼리를 생성하기 위해 사용하는 메서드 네이밍 규칙입니다.
- 기본 네이밍 규칙: JPA는 메서드 이름에 따라 쿼리를 자동으로 생성합니다.
- 예:
findBy
,existsBy
,countBy
,deleteBy
등.
- 예:
- 사용 예시:
// 엔티티 필드에 따라 검색 List<User> findByName(String name); // 특정 조건을 만족하는 엔티티 확인 boolean existsByEmail(String email); // 특정 필드 조건으로 삭제 void deleteById(Long id);
- 복잡한 쿼리: 여러 조건을 조합할 수도 있습니다.
List<User> findByAgeGreaterThanAndCity(String city, int age);
- 메서드 이름이 너무 길어질 경우: 복잡한 쿼리는 JPQL 또는 네이티브 쿼리를 사용하는 것이 좋습니다.
@Query("SELECT u FROM User u WHERE u.age > :age AND u.city = :city") List<User> findUsersByCityAndAge(@Param("city") String city, @Param("age") int age);
추가 학습 자료
- Cascade와 Orphan Removal: Hibernate Official Documentation
- Spring Data JPA 공식 문서: Spring Data JPA
- Spring @Transactional 이해하기: Baeldung
Page
와 Pageable
개념
Spring Data JPA에서 Page
와 Pageable
은 페이징과 정렬을 처리하는 데 사용됩니다. 페이징은 대규모 데이터셋을 다룰 때 한 번에 모든 데이터를 가져오지 않고, 데이터를 페이지 단위로 나눠 가져오는 기법입니다.
1. Pageable
- 정의:
Pageable
은 페이징을 위한 정보를 전달하는 인터페이스입니다.- 몇 번째 페이지를 요청할지
- 페이지 크기는 얼마인지
- 정렬 기준은 무엇인지 등의 정보를 포함합니다.
주요 메서드
int getPageNumber()
요청하는 페이지 번호를 반환합니다. (0부터 시작)int getPageSize()
페이지 크기(한 페이지에 포함될 데이터 수)를 반환합니다.Sort getSort()
정렬 기준을 반환합니다.
Pageable
구현체: PageRequest
Pageable
은 인터페이스이기 때문에 직접 객체화할 수 없고, 일반적으로 PageRequest
클래스를 사용하여 생성합니다.
예제
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
// 0번째 페이지, 페이지 크기 10, id 기준으로 내림차순 정렬
2. Page
- 정의:
Page
는 데이터셋의 한 페이지를 나타내는 인터페이스입니다.- 페이징된 데이터와 함께, 페이지에 대한 메타데이터(전체 페이지 수, 현재 페이지 번호 등)도 포함합니다.
주요 메서드
List<T> getContent()
현재 페이지의 데이터를 반환합니다.int getTotalPages()
전체 페이지 수를 반환합니다.long getTotalElements()
전체 데이터 수를 반환합니다.boolean hasNext()
다음 페이지가 있는지 확인합니다.boolean isFirst()
현재 페이지가 첫 번째 페이지인지 확인합니다.boolean isLast()
현재 페이지가 마지막 페이지인지 확인합니다.
예제
Page<Post> page = postRepository.findAll(pageable);
List<Post> posts = page.getContent(); // 현재 페이지 데이터
int totalPages = page.getTotalPages(); // 전체 페이지 수
long totalElements = page.getTotalElements(); // 전체 데이터 수
boolean isLast = page.isLast(); // 마지막 페이지 여부
3. 활용 예시
Repository 메서드 정의
public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findByTitle(String title, Pageable pageable);
}
서비스 코드에서 호출
Pageable pageable = PageRequest.of(0, 5, Sort.by("id").descending());
Page<Post> page = postRepository.findByTitle("Example Title", pageable);
List<Post> posts = page.getContent(); // 현재 페이지의 Post 리스트
int totalPages = page.getTotalPages(); // 전체 페이지 수
long totalElements = page.getTotalElements(); // 전체 Post 개수
4. 페이징 API 응답 구조 예시
REST API에서 페이징 데이터를 응답할 때, Page
객체를 활용하여 페이징 정보를 함께 제공할 수 있습니다.
JSON 응답 예시
{
"content": [
{
"id": 1,
"title": "Post 1"
},
{
"id": 2,
"title": "Post 2"
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"pageNumber": 0,
"pageSize": 5,
"offset": 0,
"paged": true,
"unpaged": false
},
"totalPages": 10,
"totalElements": 50,
"last": false,
"first": true,
"size": 5,
"number": 0,
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"numberOfElements": 5,
"empty": false
}
5. 장점
- 효율성: 전체 데이터를 한꺼번에 가져오지 않으므로 메모리 사용량 감소.
- 유연성: 정렬, 페이지 크기, 페이지 번호 등을 동적으로 변경 가능.
- 확장성: 대규모 데이터셋에서도 적은 비용으로 효율적으로 데이터 접근 가능.
Pageable
과 Page
는 Spring Data JPA에서 페이징과 정렬을 간단하게 구현하도록 돕는 강력한 도구입니다.
728x90
'프로그래머스 데브코스' 카테고리의 다른 글
데프코스 TIL - DTO, DIP (0) | 2025.01.08 |
---|---|
데브코스 TIL - Map, StringBuilder, lombok, Files (0) | 2025.01.07 |
데브코스 TIL - TDD, Stream (0) | 2025.01.03 |