자바 ORM 표준 JPA
영속성 컨텍스트
엔티티의 생명주기
비영속(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태1
Member member = new Member(); // 객체를 생성 (비영속)
영속(managed)
- 영속성 컨텍스트에 관리되는 상태1 2 3 4 5 6
Member member = new Member(); EntityManager em = emf.createEntityManager(); // 엔티티 매니저 생성 em.getTransaction.begin(); // 트랜잭션 시작 (JPA는 트랜잭션 위에서만 실행) em.persist(member); // 객체를 저장 (영속)
준영속(detached)
- 영속성 컨테스트에 저장되었다가 분리된 상태삭제(removed)
- 삭제된 상태
영속성 컨텍스트의 이점
1차 캐시
동일성(identity) 보장
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
변경 감지 (Dirty Checking)
지연 로딩 (Lazy Loading)
flush
영속성 컨텍스트의 변경 내용을 DB에 반영
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터 베이스에 전송
영속성 컨텍스트를 플러시하는 방법
주의! 플러시는 영속성 컨텍스트를 비우지 않음, 영속성 컨텍스트의 변경내용을 데이터베이스와 동기화해주는 것
Entity Mapping
@Entity
- @Entity가 붙은 클래스는 JPA가 관리, DB 테이블과 매핑할 클래스는 @Entity 필수
주의!
:기본 생성자가 필수
, pulic, protected 접근제어자만 허용, 보통 protected로 캡슐화
@Table
- 엔티티와 매핑할 테이블 지정
DB 스키마 자동 생성
JPA는 자동으로 DDL을 만들고 자동으로 생성해준다.
각 DB에 맞는 DB 방언을 활용해 적절한 DDL을 만들어준다.
보통 이렇게 만들어진 DDL은 개발에서만 사용하고 운영에는 수정한 DDL을 사용해야함.
속성
운영 서버에서는 절대 create, create-drop, update를 사용하지 않고, 개발 초기에는 create나 update, 테스트 서버는 update나 validate, 스테이징과 운영서버는 validate나 none을 사용한다.
DDL 생성 기능 : DDL 생성시에만 사용되고 런타임에는 영향 X
- 칼럼 제약조건 추가 : @Column(….)
- 테이블 제약조건 추가 @Table(…..)
매핑 어노테이션
기본 키 매핑 어노테이션
- @Id
- @GeneratedValue
- IDENTITY : DB에 위임, 주로 MySQL에서 사용
- JPA는 트랜잭션 커밋 시점에 insert SQL 실행함
- Mysql의 auto_increment는 DB에 insert SQL 실행 후 ID 값을 알 수 있음
- IDENTITY 전략은 em.persist() 시점에 즉시 insert SQL 실행하고 DB에서 식별자 조회
- SEQUENCE : 데이터베이스 시퀸스 오브젝트 사용, 주로 Oracle에서 사용
- TABLE : 키 생성용 테이블 사용
- AUTO : 방언에 따라 자동 지정
- IDENTITY : DB에 위임, 주로 MySQL에서 사용
연관관계 매핑 중 양방향 매핑에 관해
- 양방향으로 설계시 연관관계 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능
- 양방향 매핑은 보통 하지 않는게 좋음
- 양방향 매핑은 객체 그래프 탐색이나 fetchJoin을 용이하게 하기 위함인데 했을 때 순환참조나 여러 위험이 있음
- 왠만하면 하지 말자….
상속관계 매핑
RDB에는 상속 관계 X
슈퍼 타입과 서브 타입 관계라는 모델링 기법이 객체 상속과 유사
슈퍼 타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법 3가지
- 각각 테이블로 변환 -> 조인 전략
- 통합 테이블로 변환 -> 단일 테이블 전략
- 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
@Inheritance(strategy=InheritanceType.XXX)와 같이 사용(JOINDE, SINGLE_TABLE, TABLE_PER_CLASS)
조인 전략
- 장점
- 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용가능
- 저장공간 효율화
- 단점
- 조회시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡
- 데이터 저장시 insert SQL 2번 호출
- 장점
단일 테이블 전략
- 장점
- 조인이 필요 없어 조회 성능이 빠름
- 조회 쿼리가 단순
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커짐. 상황에 따라서 조회 성능이 느려질 수 있음
- 장점
구현 클래스마다 테이블 전략 - 안 씀
장점
서브 타입을 명확하게 구분해서 처리할 때 효과적
not null 제약조건 사용 가능
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림 (UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움
@MappedSuperClass
공통 매핑 정보가 필요할 때 사용
상속관계 매핑 X
엔티티X, 테이블과 매핑 X
부모 클래스를
상속받은 자식 클래스에 매핑 정보만 제공
조회, 검색 불가
직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할
등록일, 수정일 등 전체 엔티티에서 공통으로 적용할 정보를 모을 때 사용 (BaseEntity)
프록시
em을 이용해 DB에서 데이터를 가져오는 방법으로는 em.find(), em.getReference() 두 가지 방식이 있다.
- em.find() : DB를 통해 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 프록시 엔티티 객체 조회
em.getReference()와 같이 프록시 엔티티 객체는 실제 객체를 사용시 초기화를 요청한다.
초기화를 요청하면 영속성 컨텍스트에서는 해당 엔티티와 같은 identity를 가진 객체를 조회하고 없다면 DB에서 조회한다.
특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 객체를 초기화 시 프록시 -> 실제 엔티티로 바뀌는 것이 아니라, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해진다. (영속성 컨텍스트에 1차 캐싱이 됨)
- 이때 주의할 점은 프록시 객체는 원본 엔티티를 상속 받았기 때문에 타입 체크시 ==을 해서는 안되고 instance of 사용해야된다.
- 또 처음 조회 시 영속성 컨텍스트에 프록시로 조회하면 포록시가 담기고, 실제 엔티티를 조회하면 엔티티가 담긴다. 그 후 같은 트랜잭션 안이라면 처음 담긴 객체가 리턴된다.
지연로딩
- 연관관계 매핑 시 지연로딩과 즉시로딩을 설정할 수 있다.
- 즉시 로딩은 해당 엔티티가 로딩 시 바로 같이 로딩되고, 지연로딩은 바로 로딩되지 않는다.
- 여기서 중요한 점은 항상 지연로딩만을 사용해야된다는 것이다.
- 즉시 로딩 사용시 예상치 못한 SQL이 발생하고 N + 1 문제를 일으킨다.
- 지연 로딩은 위에서 살펴본 것과 같이 연관관계 매핑이 되어있는 엔티티를 프록시로 조회한다.
- 그렇기 때문에 연관관계에 걸려있는 객체를 사용시 로딩되어 즉시 로딩의 문제들을 해결 가능하다.
영속성 전이 - CASCADE
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
주의!
영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음, 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공하는 것일 뿐
고아 객체
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
주의!
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 때 사용해야함
- @OneToOne, @OneToMany만 가능
- CascadeType.REMOVE처럼 동작
영속성 전이 + 고아 객체
- CascadeType.ALL + orphanRemoval=true
- 이와 같이 설정하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다. 보통 부모 엔티티와 자식 엔티티의 생명 주기가 똑같을 때 사용할 수 있음
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
값 타입
- 값타입이란 식별자가 없는 객체를 뜻함
기본 값 타입
- ex) int, String, Integer
- 생명 주기를 엔티티에 의존
- 다른 객체와 공유하면 안됨
임베디드 타입
새로운 값 타입을 직접 정의 가능
JPA는 임베디드 타입(embedded type)이라 함
주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 함
사용법
- @Embeddable : 값 타입을 정의한 곳에 표시
- @Embedded : 값 타입 사용하는 곳에 표시
- 기본 생성자 필수
- 두 어노테이션 중 하나만 붙여도 사용 가능한데 보통 둘 다 붙임 (명확하게 하기 위해)
장점
- 재사용
- 높은 응집도
- 의미 있는 메소드를 만들 수 있음
테이블 매핑
- 임베디드 타입은 엔티티의 값일 뿐, DB 테이블은 임베디드 타입을 사용하든 안 하든 상관 X
- 하지만 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능해진다.
한 엔티티에 같은 값 타임을 여러개 사용할 시
- @AttributeOverrides, @AttributeOverride를 사용해서 컬러 명 속성을 재정의 할 수 있음
값 타입과 불변 객체
- 값 타입은 단순하고 안전하게 다뤄야 함
- 하나의 값 타입을 여러 엔티티에서 공유해서는 안됨 (side effect 발생)
- 실제 인스턴스를 공유하는 대신 값을 복사해서 사용해야 함
- 한계
- 자바는 항상 call by value이기 때문에 참조 값을 직접 대입하는 것을 막을 수 없다.
- 값 타입도 객체 타입이기 때문에 메모리에 있는 주소가 대입된다.
- 해결
- 객체 타입을 수정할 수 없게 만들어야하기 때문에 값 타입은 항상
불변 객체(immutable object)
로 설계해야한다. - 생성자로만 값을 설정하고 setter를 만들지 않는 등
- 객체 타입을 수정할 수 없게 만들어야하기 때문에 값 타입은 항상
- 값 타입의 비교
- 값 타입은 객체이기 때문에 == 비교를 사용해서는 안된다. equals()를 사용해 동등성 비교를 해야한다.
- 값 타입 내부에 equals() 메소드를 적절히 재정의 해야한다.
JPQL 문법
출처)
[자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런 (inflearn.com)](https://www.inflearn.com/course/ORM-JPA-Basic) |