Spring으로 개발을 하게 되면 자연스럽게 접하게되는 ORM 프레임워크인 Jpa에 핵심 기능 및 주의사항에 대해서 정리해 보았습니다.
JPA를 사용하는 이유?
- JPA를 사용하면 SQL을 객체 지향 프로그래밍에 특징인 "추상화, 상속, 다형성, 캡슐화"에 이점을 살려 코드의 재사용성, 유지보성을 높여 생산성이 좋은 프로그래밍 할 수 있다.
- 반복적인 CRUD SQL작성을 줄일 수 있고, 특정 DB에 대한 종속성을 줄이고 JPA에서 제공하는 인터페이스를 사용해 다른 DB로 변경해도 큰 수정 없이 그대로 사용할 수 있어 유지보수성이 향상 된다.
- JPA는 애플리케이션과 데이터베이스 사이에서 동작 하기에 캐시 기능 등. 다양한 성능 최적화 기회를 제공한다.
영속성 컨텍스트
영속성 컨텍스란?
영속성 컨텍스트는 "엔티티를 영구 저장하는 환경" 이다. 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라 한다. 엔티티의 상태 변화를 감지해 데이터베이스와 동기화 시킨다.
영속성 컨텍스트가 엔티티를 관리하면 아래와 같은 장점이 있다.
- 영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다. 영속 상태의 엔티티는 모두 이곳에 저장된다. JPA로 조회를 하면 우선 1차 캐시에서 식별자 값으로 엔티티를 찾아서 반환하기에 이미 한 번 조회되었거나, 영속성 컨텍스트로 관리되고 있는 엔티티는 데이터베이스를 다시 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회해 성능에 대한 이점이 있다.
- 엔티티를 반복해서 조회해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다. 그렇기에 엔티티의 동일성을 보장한다.('동일성'은 참조 값을 비교(== 연산)했을 때 같다, '동등성'은 인스턴스는 다를 수 있지만 가지고 있는 값이 같다. 동등성을 비교하기 위해 자바에서는 equals()와 hashCode()를 Override해야 한다.)
- 영속성 컨텍스트는 1차 캐시에 엔티티를 저장하면서 동시에 엔티티 정보로 등록 쿼리를 만든다. 이렇게 만들어진 등록 쿼리를 쓰기 지연 SQL 저장소에 보관한다. 이 기능으로 쿼리를 모았다가 한 번에 처리해 성능을 최적화할 수 있다.(엔티티가 영속 성태가 되려면 식별자가 필요한데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있다. 그래서 저장하는 즉시 SQL을 전달하며, 이 전략은 쓰기 지연이 동작하지 않는다. 하이버네이트는 JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하여 데이터를 저장하고 동시에 생성된 기본키를 얻어오는 메소드를 사용해 데이터베이스와 한 번만 통신한다.)
- JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장한다. 이것을 스냅샷이라 한다. 플러시할 때 스냅샷과 엔티티를 비교해서 변경된 엔티티를 감지해서 자동으로 업데이트한다.
기본 키 생성 전략
- 직접 할당 - 애플리케이션 레이어에서 식별자 값을 영속성 컨텍스트에 직접 저장
- IDENTITY - 데이터베이스에 엔티티를 저장한 후. 식별자 값을 얻어 영속성 컨텍스트에 저장
- SEQUENCE - 데이터베이스 시퀀스에서 식별자 값을 먼저 가져온 후. 영속성 컨텍스트에 저장
- TABLE - 이름과 값을 가지고 있는 시퀀스 생성용 테이블에서 식별자 값을 가져온 후. 영속성 컨텍스트에 저장
@GeneratedValue에 strategy 값을 AUTO로 하면 사용하는 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 중 자동으로 선택해 생성한다.
연관 관계
- 다대일(@ManyToOne)
- 일대다(@OneToMany)
- 일대일(@OneToOne)
- 다대다(@ManyToMany)
- 일대다, 다대일 관계에서 외래 키는 항상 '다'쪽 테이블에서 관리하며, 연관관계의 주인은 외래 키가 있는 곳으로 정해야 한다.(연관관계 주인은 mappedBy 속성을 사용하지 않는다.)
- 양방향 매핑 시 무한 루프에 빠지지 않게 조심해야 한다. 특히 Lombok에 @ToString을 그대로 사용하지 말고 직접 재정의 해서 사용한다.
- 일대다 단방향 보다는 다대일 양항뱡을 사용한다. 일대다 단방향 매핑은 외래 키가 다른 테이블에 있어 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다. 반면 본인 테이블에 외래 키가 있으면 INSERT SQL 한 번으로 처리가 가능 하다.
- 주 테이블 외래키 - 주 테이블에 왜래 키를 두고 대상 테이블을 참조하는 방식. 객체지향적 설계에 선호 된다.
- 대상 테이블 왜래키 - 대상 테이블에 외래 키를 두는 방식. 데이터베이스 관점에서 사용하는 설계 방식으로 장점은 테이블 관계를 일대일에서 일대다로 변경해도 이 구조를 그대로 유지할 수 있다.
- 정규화된 관계형 데이터베이스 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 관계 테이블을 만들어 일대다, 다대일로 풀어낸다. 하지만 객체 설계에서는 테이블과 다르게 @JoinTable 어노테이션을 활용해 객체 2개로 다대다 관계를 만들 수 있다. 이외에도 @IdClass, @EmbeddedId를 이용해 복합 키 식별자 클래스를 만들어 관계 테이블에 컬럼을 추가해 확장할 수 있다. 주의사항은 equals와 hashCode 메소드를 꼭 구현해야 한다. 또한 기본 키를 같는 객체를 하나더 만들어 사용할 수 도 있으며 이러한 관계를 비식별 관계라 하고, 받아온 식벽자를 기본 키 + 외래 키로 사용하는 것을 식별 관계라 한다. 식별 관계는 부모 테이블 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어나기 때문에 보통 객체 관점에서 볼 때 비식별 관계를 선호 한다.
상속 관계 매핑
- 조인 전략 - 각각의 자식 테이블에 해당하는 엔티티가 부모 테이블의 기본 키를 받아서 기본 키 + 왜래 키로 사용한다.
- 장점
- 테이블 정규화
- 왜래 키 참조 무결정 제약조건 활용
- 저장공간을 효율적으로 사용
- 단점
- 조회할 때 쿼리가 복잡하고 많은 조인으로 성능 저하
- INSERT SQL 두 번 실행
- 장점
- 단일 테이블 전략 - 테이블을 하나만 사용하며, 구분 컬럼으로 어던 자식 데이터가 저장되었는지 구분한다.
- 장점
- 쿼리가 단순 하고 조인이 필요 없어 빠르다.
- 단점
- 매핑한 자식 엔티티 컬럼은 모두 NULL을 허용해야 한다.
- 단일 테이블에 모든 것을 저장해 테이블이 비대해 질 수 있다. 그래서 조회 성능이 오히려 느려질 수 있다.
- 장점
- 테이블 전략 - 자식 엔티티 마다 부모 엔티티 값을 상속하여 각각 테이블을 만든다.
- 장점
- 서브 타입을 구분해서 처리할 때 효과적이다.
- NOT NULL 제약조건을 사용할 수 있다.
- 단점
- 여러 자식 테이블을 같이 조회할 때 느리다.(SQL에 UNION을 사용해야 한다)
- 자식 테이블을 통합해서 쿼리하기 힘들다.
- 장점
프록시
객체는 객체 그래프로 연관된 객체들을 탐색하는데 실데 데이터가 데이터베이스에 있기 때문에 연관된 객체를 마음껏 탐색하는데 부담이 많다. JPA 구현체들은 이 문제를 해결하고자 프록시라는 기술을 사용한다.
프록시를 사용하면 연관된 객체를 바로 조회하는게 아니라 필요한 시점에 조회할 수 있다. 하지만 거희 필수적으로 같이 사용되는 객체들은 조인을 사용해서 같이 조회하는 것이 효과 적이다. 그렇기에 JPA는 즉시 로딩과 지연 로딩이라는 두 가지 방식을 제공 한다.
프록시 초기화 과정
엔티티 값 호출 -> 영속성 컨텍스트에 데이터 조회 없으면 초기화 요청 -> 데이터베이스 조회 -> 실제 엔티티 생성 -> 엔티티 호출 결과 반환
즉시 로딩(FetchType.EAGER) - 엔티티를 조회할 때 연관된 엔티티도 같이 조회.
지연 로딩(FetchType.LAZY) - 연관된 엔티티를 실제 사용할 때 조회
NULL 제약조건과 JPA 조인 전략
JPA는 외래 키가 NULL 값을 허용할 경우 OUTER JOIN을 사용한다. 하지만 외부 조인 보다 내부 조인이 성능 최적화에
더 유리하다. 그래서 필요에 따라 외래 키에 NOT NULL 제약 조건을 설정하고 아래와 같이 설정하면 INNER JOIN을 사용하게 된다.
@JoinColumn(nullable = true) - NULL 허용(기본 값), 외부 조인 사용
@JoinColumn(nullable = flase) - NULL 허용하지 않음, 내부 조인 사용
또는 @ManyToOne(fetch = FetchType.EAGER, optional = false) - 연관 관계 어노테이션에 직접 설정하여 내부 조인 사용
즉 JPA는 선택적 관계면 OUTER JOIN을 사용하고 필수 관계면 INNER JOIN을 사용한다.
Many에 해당하는 컬렉션 FetchType.EAGER 사용 시 주의사항
즉시 로딩 하는 것을 권장하지 않는다. - 얼마나 많은 데이터가 조회될지 모르기에 성능 저하.
즉시 로딩은 항상 외부 조인을 사용한다. - 외래 키가 NULL이면 엔티티가 조회가 안된다.
영속성 전이 CASADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 같이 영속 상태로 만들고 싶으면 영속성 전이를 사용하면 된다.
고아 객체 제거
orphanRemoval = true 옵션을 이용해 컬렉션에서 연관된 엔티티를 제거하면 실제 데이터베이스 데이터도 삭제된다.
이를 고아 객체 제거라 한다. 다만 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 객체일 경우만 해당 된다.
'java & spring' 카테고리의 다른 글
spring boot multi data source(jpa query dsl로 DB 다중 연결) (0) | 2022.10.25 |
---|
댓글