목차

  • 프록시
  • 즉시 로딩과 지연 로딩
  • 지연 로딩 활용
  • 연속성 전이 : CASECADE
  • 고아 객체
  • 연속성 전이 + 고아 객체, 생명주기
  • 실전 예제 - 5. 연관관계 관리

 

 

프록시


Member를 조회할 때 Team도 함께 조회해야 할까?

결론적으로 비즈니스 로직에 따라 다를 것이다. 멤버를 가져왔을 때 팀을 100% 사용한다고 할 때는 함께 조회하는 게 이득일 수 있지만, 팀을 20% 사용한다고 할 때는 필요할 때 팀 데이터를 가져오는게 이득일 수 있다.

 

JPA에서는 위의 문제점을 지연로딩과 프록시로 기가막히게 처리한다고 한다.

 

 

프록시 기초

  • em.find() vs em.getReference()
  • em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
    • DB 쿼리가 나가지 않는데 조회가 되는 것
    • 실제로 해보니 getReference 시점에 쿼리가 나가지 않고 해당 객체를 사용할 때 쿼리가 나감.

getReference로 가져온 객체는 .getClass()로 네이밍 조회 시에 Hibernate에서 제공해주는 프록시 객체인 것을 알 수 있습니다.

 

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐 (hibernate가 내부적으로 프록시 라이브러리를 통해 만들어 냄)
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨. (이론상)

  • 프록시 객체는 실제 객체의 참조(targer)을 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출 (여기서 DB에 쿼리가 나감)

 

프록시 객체의 초기화

Member member = em.getReference(Member.class, "id1");
member.getName();

getName시 프록시 객체가 하는 작업

 

 

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
    • instanceof 사용법 : 객체 instanceof 클래스명 
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
    • getReference 전 find로 같은 객체를 찾아놓았다면 실제 엔티티 반환
    • getReference 전 getReference로 같은 객체를 찾아놓았다면 프록시 객체 반환
    • getReference -> find가 같은 데이터일 때 find에서도 프록시 반환
      • -->> 이렇게까지 하는 이유는 JPA에서는 어떻게든 ==비교 시에 같도록 나오게하려고 함.
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (하이버네이트는 org.hibernate.LazuInitializationException 예외를 터트림) - 영속성 컨텍스트에 없기 때문에 초기화할 수 없다는 뜻
    • em.detach(객체) or em.close()

 

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    • emf.getPersistenceUnitUtil().isLoaded(Object entity)
  • 프록시 클래스 확인 방법
    • entity.getClass().getName() 출력
  • 프록시 강제 초기화
    • org.hibernate.hibernate.initialize(entity)
  • 참고 : JPA 표준은 강제 초기화 없음
    • 강제 호출 : member.getName()

 

 

 

즉시 로딩과 지연 로딩


Member를 조회할 때 Team도 함께 조회해야 할까?

이것은 비즈니스 로직에 따라 다르지만, 단순히 member 정보만 조회하는 비즈니스 로직에서는 Team에 대한 쿼리문을 날릴 필요가 없다.

 

지연 로딩 LAZY를 사용해서 프록시로 조회

fetch = FetchType.LAZY로 조회하는 경우 Member를 조회할 때, Team에 대한 쿼리가 나가지 않는 것을 볼 수 있습니다.

 

findMember.getTeam을 해서 Team 객체를 가져오면 쿼리문을 보내지 않고, 프록시 객체를 가져오는 것을 볼 수 있습니다. 후에 프록시 객체의 접근해 데이터를 가져올 때 프록시 객체가 진짜 객체를 필요로 하기 때문에 쿼리문을 보내고, 데이터를 가져오는 것을 볼 수 있습니다.

 

지연 로딩

지연 로딩으로 했을 때

 

지연 로딩 LAZY를 사용해서 프록시로 조회

Team team = member.getTeam(); // 쿼리가 나가지 않음 (프록시 객체 할당)

team.getName(); // 실제 team을 사용하는 시점에 초기화 (쿼리가 나감)

 

 

즉시 로딩

Member를 가져올 때 Team을 자주 함께 사용할 때 사용하면 쿼리 한번으로 조회가 되기때문에 좋은 방법입니다.

EAGER

LAZY 테스트 할 때와 같은 코드로 EAGER일 때 실행해 보았습니다. 출력된 쿼리문으로 Member와 Team 객체를 한 번의 쿼리로 조회하는 것을 볼 수 있습니다. LAZY는 Member->Team의 플로우에서 두번 쿼리문을 날리는 것에 비해 효율적으로 보입니다. 가져온 Team의 객체는 프록시 객체가 아닌 진짜 객체를 가져온 것을 볼 수 있습니다.

 

즉시 로딩

Member 객체를 가져올 때 진짜 Team 객체를 가져옴

 

즉시 로딩(EAGER), Member조회 시 항상 Team도 조회

JPA 구현체는 가능하면 조인을 사용해서 SQL 한 번에 함께 조회

 

 

프록시와 즉시로딩 주의

  • 가급적 지연 로딩만 사용 (특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생하기 때문 (2개일 땐 별다른 차이가 없지만, 5개 아니 더 많은 구조들에서 문제가 생깁니다.)
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다. (N+1문제란, 처음 쿼리 1개를 날렸는데 추가 쿼리가 N개가 나간다.)
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩
    -> LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

EAGER의 경우 Member를 조회할 때, Member마다 Team 쿼리를 또 날린다.
LAZY의 경우 JPQL으로 요청한 쿼리만 나간다.

 

지연 로딩 활용

  • Member와 Team은 자주 함께 사용 -> 즉시 로딩
  • Member와 Order는 가끔 사용 -> 지연 로딩
  • Order와 Product는 자주 함께 사용 -> 즉시 로딩
  • 위의 설명은 이론적인 것, 실무에서는 모두 지연 로딩으로 해야함.

 

지연 로딩 활용 - 실무

  • 모든 연관관계에 지연 로딩을 사용해라!
  • 실무에서 즉시 로딩을 사용하지 마라!
  • JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라! (뒤에서 설명)
  • 즉시 로딩은 상상하지 못한 쿼리가 나간다.

 

 

영속성 전이 : CASCADE와 고아 객체


영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
  • 예 : 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

 

영속성 전이 : 저장

연관관계의 주인을 저장할 때, 연관된 객체를 모두 저장하는 것을 cascade라고 한다.

 

cascade가 없을 땐 연관관계의 주인을 저장할 때, 연관관계의 주인만 DB에 insert 쿼리문이 나가며 저장된다. 아래는 위의 코드로 실행했을 때의 출력되는 쿼리문.

 

하지만 cascade를 all로 설정하면 연관관계의 주인을 저장할 때에는 연관관계의 주인 뿐만이 아닌 하위 객체들 모두 DB에 insert 쿼리문이 나가며 저장된다. 아래는 위의 코드로 실행했을 때의 출력되는 쿼리문.

 

영속성 전이 : CASCADE - 주의!

  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐 
  • 위의 경우처럼 parent -> child일 때, child와 다른 객체가 연관관계가 없다면 cascade를 사용해도 되지만 child에 또 다른 객체가 연관관계가 있다면 사용하면 안된다고 합니다. (이유는 알게되면 정리)

 

CASCADE의 종류

  • ALL : 모두 적용
  • PERSIST : 영속
  • REMOVE : 삭제
  • MERGE : 병합
  • REFRESH : REFRESH
  • DETACH : DETACH

 

 

고아 객체

  • 고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
  • Parent parent1 = em.find(Parent.class, id);
    parent1.getChildren().remove(0);
    // 자식 엔티티를 컬렉션에서 제거
  • DELETE FROM CHILD WHERE ID = ?

 

고아 객체 - 주의

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야 함!
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능
  • 참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서, 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascaseType.REMOVE처럼 동작한다.

 

영속성 전이 + 고아 객체, 생명주기

  • CascadeType.ALL + orphanRemoval=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용 (나중에 찾아 보자)
 

DDD, Aggregate Root 란?

이번에 알아볼 것은, DDD란? Domain이란? Domain Model이란? DDD가 필요한 이유 DDD와 Aggregate Root DDD의 특징과 이유 간단히 영속성 전이, 고아 객체와의 연관 DDD(Domain Driven Design)란? 도메인 중심으로..

eocoding.tistory.com

 

 

실전 예제 5 - 연관관계 관리


글로벌 패치 전략 설정

  • 모든 연관관계를 지연 로딩으로
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경

 

영속성 전이 설정

  • Order -> Delivery를 영속성 전이 ALL 설정
    • 주문을 생성할 때 배송정보도 함께 저장하겠다는 뜻
  • Order -> OrderItem을 영속성 전이 ALL 설정
    • 주문을 생성할 때 주문아이템도 함께 저장하겠다는 뜻

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

목차

  • 상속관계 매핑
  • @MappedSuperclass
  • 실전 예제

 

상속관계 매핑


상속관계 매핑

  • 관계형 데이터베이스는 상속 관계 X
  • 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사
  • 상속관계 매핑 : 객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
  • 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
    • 각각 테이블로 변환 -> 조인 전략
    • 통합 테이블로 변환 -> 단일 테이블 전략
    • 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

 

 

주요 어노테이션

  • Inheritance(strategy = InheritanceType.XXX)
    • JOINED : 조인 전략
    • SINGLE_TABLE : 단일 테이블 전략
    • TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
  • @DiscriminatorColumn(name = "DTYPE")
    • 해당 컬럼에 DTYPE이 생기게 되고, value는 상속된 객체의 엔티티 명이 들어갑니다.
  • @DiscriminatorValue("XXX")
    • 엔티티 명을 바꿀 수 있음, 자식 객체에 부여함

 

조인 전략

  • 장점
    • 테이블 정규화
    • 외래 키 참조 무결성 제약조건 활용가능
    • 저장공간 효율화
  • 단점
    • 조회시 조인을 많이 사용, 성능 저하
    • 조회 쿼리가 복잡함
    • 데이터 저장시 INSERT SQL 2번 호출

 

단일 테이블 전략

  • 장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
    • 조회 쿼리가 단순함
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null 허용
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

 

구현 클래스마다 테이블 전략

  • 이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천 X
  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적
    • not null 제약조건 사용 가능
  • 단점
    • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
    • 자식 테이블을 통합해서 쿼리하기 어려움

 

 

@MappedSuperclass


@MappedSuperclass

  • 공통 매핑 정보가 필요할 때 사용(id, name)

  • 상속관계 매핑 X
  • 엔티티 X, 테이블과 매핑 X
  • 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공
  • 조회, 검색 불가(em.find(BaseEntity)) 불가)
  • 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
  • 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할
  • 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용
  • 참고 : Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속가능

 

 

 

실전 예제


요구사항 추가

  • 상품의 종류는 음반, 도서, 영화가 있고 이후 더 확장될 수 있다.
  • 모든 데이터는 등록일과 수정일이 필수다.

 

 

모데인 모델

 

 

 

 

테이블 설계를 보면 ITEM이라는 테이블에 DTYPE으로 하위 테이블에 대한걸 명시해줍니다. 이처럼 설계하기 위해서는

1.

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

SINGLE_TALBE로 해주면 ITEM 테이블에 모든 하위 테이블에 대한 정보가 저장됩니다.

 

2.

@DiscriminatorColumn

DIscriminatorColumn을 추가해주면 해당 컬럼 데이터가 어떤 하위 테이블의 데이터인지 DTYPE으로 명시해줍니다.

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

목차

  • 연관관계 매핑시 고려사항 3가지
  • 다대일 [N:1]
  • 일대다 [1:N]
  • 일대일 [1:1]
  • 다대다 [N:N]
  • 실전 예제

 

연관관계 매핑시 고려사항 3가지

  • 다중성
  • 단방향, 양방향
  • 연관관계의 주인

 

다중성

  • 다대일 : @ManyToOne
  • 일대다 : @OneToMany
  • 일대일 : @OneToOne
  • 다대다 : @ManyToMany

 

단방향, 양방향

  • 테이블
    • 외래 키 하나로 양쪽 조인 가능
    • 사실 방향이라는 개념이 없음
  • 객체
    • 참조용 필드가 있는 쪽으로만 참조 가능
    • 한쪽만 참조하면 단방향
    • 양쪽이 서로 참조하면 양방향

 

연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺는다.
  • 객체 양방향 관계는 A -> B, B -> A 처럼 참조가 2곳. 둘 중 테이블의 외래 키를 관리할 곳을 지정해햐 함.
  • 연관관계의 주인 : 외래 키를 관리하는 참조
  • 주인의 반대편 : 외래 키에 영향을 주지 않음, 단순 조회기능

 

 

다대일 [N:1]


다대일 단방향

DB 설계 상 1:N의 경우 N 쪽에 외래키가 있어야 합니다. 

 

 

다대일 양방향

기존에 했던 것과 다를 것 없이 양방향의 경우 TEAM 객체에 조회하하는 list만 추가하면 됩니다. 

 

 

 

일대다 [1:N]


일대다 단방향

권장하지 않는 모델링 방법 (무조건 N 쪽에 외래 키가 있기 때문)

 

 

일대다 단방향 정리

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이 구조
  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함 (중간에 테이블을 하나 추가함)

 

일대다 

JoinColumn은 꼭 해주어야함!! JoinColumn을 하지 않게되면 두 테이블을 연결시킬 새로운 테이블을 JPA에서 자동으로 만든다.

JoinColumn을 하지 않으면 생기는 테이블

 

일대다 단방향 단점

  • 엔티티가 관리하는 외래 키가 다른 테이블에 있음
  • 연관관계 관리를 위해 추가로 UPDATE SQL 실행

그렇기때문에 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자. (다(N)쪽에 외래 키가 있도록)

 

 

일대다 양방향

 

insertable = false, updatable = false를 하게되면 읽기만 가능하게 해줍니다. 

 

 

일대다 양방향 정리

  • 일대다 양방향 매핑은 공식적으로 존재 X
  • @JoinColumn(insertable = false, updatable = false)
  • 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법
  • 다대일 양방향을 사용하자

 

 

일대일 [1:1]


일대일 관계

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능
    • 주 테이블에 외래 키
    • 대상 테이블에 외래 키
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

 

일대일 : 주 테이블에 외래 키 단방향

외래 키는 Member, Locker 어디에 넣어도 상관없다. 위의 예시에서는 Member에 외래 키를 넣었습니다. 다대일(@ManyToOne) 단방향 매핑과 유사

 

 

일대일 : 주 테이블에 외래 키 양방향

양방향으로 하고 싶다면 Member, Member 모두 필드를 추가해주면 됩니다. 

 

 

일대일 : 주 테이블에 외래 키 양방향 정리

  • 다대일 양방향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인
  • 반대편은 mappedBy 적용

 

일대일 : 대상 테이블에 외래 키 단방향

일대일의 경우 대상 테이블에 외래 키가 있으면 작동하지 않습니다.

 

일대일 : 대상 테이블에 외래 키 단방향 정리

  • 단방향 관계는 JPA 지원 X
  • 양방향 관계는 지원

 

일대일 : 대상 테이블에 외래 키 양방향

일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같음

 

 

일대일 정리

  • 주 테이블에 외래 키
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼
      주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호
    • JPA 매핑 관리
    • 장점
      • 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점
      • 값이 없으면 외래 키에 null 허용
  • 대상 테이블에 외래 키
    • 대상 테이블에 외래 키가 존재
    • 전통적인 데이터베이스 개발자 선호
    • 장점
      • 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    • 단점
      • 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩 됨 (프록시는 뒤에서 설명)

 

 

 

다대다 [N:M]


실무에서 다대다 관계는 쓰면 안된다.

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음.
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 함.

 

 

다대다

  • @ManyToMany 사용
  • @JoinTable로 연결 테이블 지정
  • 다대다 매핑 : 단방향, 양방향 가능

 

다대다 매핑의 한계

  • 편리해 보이지만 실무에서 사용 X
  • 연결 테이블이 단순히 연결만 하고 끝나지 않음
  • 주문 시간, 수량 같은 데이터가 들어올 수 있음

 

다대다 한계 극복

  • 연결 테이블용 엔티티 추가 (연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne

 

 

실전 예제


배송, 카테고리 추가 - 엔티티

  • 주문과 배송은 1:1 (@OneToOne)
  • 상품과 카테고리는 N:M (@ManyToMany)

 

배송, 카테고리 추가 - ERD

 

배송, 카테고리 추가 - 엔티티 상세

 

N:M 관계는 1:N, N:1로

  • 테이블의 N:M 관계는 중간 테이블을 이용해서 1:N, N:1
  • 실전에서는 중간 테이블이 단순하지 않다.
  • @ManyToMany는 제약 : 필드 추가 X, 엔티티 테이블 불일치
  • 실전에서는 @ManyToMany 사용 XX

 

@JoinColumn

  • 외래 키를 매핑할 때 사용

 

@ManyToOne

  • 다대일 관계 매핑

 

@OneToMany

  • 다대일 관계 매핑

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

목표

  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑
  • 용어 이해
    • 방향 (Direction) : 단방향, 양방향
    • 다중성 (Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N) 이해
    • 연관관계의 주인 (Owner) : 객체 양방향 연관관계는 관리 주인이 필요

 

예제 시나리오

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다.

 

객체를 테이블에 맞추어 모델링 (연관관계가 없는 객체)

 

객체를 테이블에 맞추어 모델링 (참조 대신에 외래 키를 그대로 사용)

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @Column(name = "TEAM_ID")
    private Long teamId;
    ...
    }
    
@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    ...
    }

 

test code

            //팀 저장
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            //회원 저장
            Member member = new Member();
            member.setName("member1");
            member.setTeamId(team.getId());
            em.persist(member);

 

결과

테이블에 잘 저장되고 Join도 잘 동작하는 것을 볼 수 있습니다.

 

 

객체를 테이블에 맞추어 모델링 (식별자로 다시 조회, 객체 지향적인 방법이 아니다.)

이전에 보았던 것처럼 DB를 통해서 계속해서 끄집어 내야한다.

 

객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이러한 간격이 존재한다.

 

 

단방향 연관관계


객체 지향 모델링 (객체 연관관계 사용)

 

변경된 코드

    //@Column(name = "TEAM_ID")
    //private Long teamId;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
  • @ManyToOne
    • Member N : Team 1 이기 때문에 Member 객체에선 Team과 연관되는 컬럼에는 본인이 Many 팀이 One이라는 어노테이션을 추가해주어야 합니다. (연관관계 사용할 때 이러한 일대일, 다대일, 일대다, 다대다 관계의 어노테이션을 추가해야함)
  • @JoinColumn
    • 조인해야하는 컬럼이 어떤 컬럼인지 명시해주는 어노테이션입니다. DB에 실제 존재하는 값을 찾아서 매핑합니다.

이렇게 관계와 join 컬럼만 추가해주면 끝입니다.

 

 

객체 지향 모델링

 

조회

            //조회
            Member findMember = em.find(Member.class, member.getId());
            //참조를 사용해서 연관관계 조회
            Team findTeam = findMember.getTeam();

Member 객체를 가져온 뒤, getTeam() 메소드만 호출하면 원하는 연관된 Team 객체를 받아올 수 있습니다. (다시 DB를 조회할 필요가 없음)

 

 

 

양방향 연관관계와 연관관계의 주인


양방향 매핑

이전엔 멤버 -> 팀 으로만 가능했다면 양방향 연관관계는 멤버 <-> 팀 으로 이동이 가능하게 매핑하는 것 입니다.

 

코드

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

Team 객체에 해당 컬럼만 추가해주면 됩니다.

OneToMany는 일대다 관계라는 것을 프로그램에게 알려 주는 것이고, mappedBy는 Member객체에 있는 team이라는 변수와 매핑이 된다는 것을 나타내는 것 입니다.

 

테스트

            //조회
            Team findTeam = em.find(Team.class, team.getId());
            int memberSize = findTeam.getMembers().size(); //역방향 조회

위의 테스트를 해보았는데 문제가 발생했다.

기존 데이터를 em.flush()를 통해 DB에 저장한 뒤, 탐색하면 정상적으로 데이터를 가져올 수 있을 거라고 생각했지만 그렇지 못했습니다.

em.flush후 em.clear까지 해줘야지 정상적으로 데이터를 갖고올 수 있었습니다. 

 

em.flush후 em.clear를 해야하는 이유는? em.flush를 하면 DB에 데이터가 저장이 됩니다. 그렇기 때문에 DB에 데이터를 가져올 것이라고 생각했지만, 영속성 컨텍스트에 가져오려는 데이터가 있기때문에 DB에 저장된 데이터가 아닌 영속성 컨텍스트에 있는 데이터를 가져옵니다. 여기서 문제점은 영속성 컨텍스트에 있는 Team 테이블에 members 컬럼에는 아무것도 없습니다. DB에서 조회를 하여 가져올 때는 @OneToMany를 통해 DB에서 연관된 테이블을 같이 가져오지만, 영속성 컨텍스트에 있는 Team 테이블은 그렇지 않기 때문입니다. 그렇기 때문에 em.clear를 통해 영속성 컨텍스트를 초기화 시켜준 뒤, 원하는 객체를 DB로부터 받아올 수 있도록 해줘야합니다.

 

 

연관관계의 주인과 mappedBy

  • mappedBy = JPA의 멘탈붕괴 난이도
  • mappedBy는 처음에 이해하기 어렵다.
  • 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.

 

객체와 테이블이 관계를 맺는 차이

  • 객체 연관관계 = 2개
    • 회원 -> 팀 연관관계 단방향 1개
    • 팀 -> 회원 연관관계 단방향 1개
  • 테이블 연관관계 = 1개
    • 회원 <-> 팀 연관관계 양방향 1개

테이블의 경우 fk 값 하나로 테이블의 연관관계가 끝이 납니다. 하지만 객체의 경우 참조가 객체마다 있어야합니다.

 

사실상 객체의 양방향 관계서로 다른 단방향 관계 2개입니다.

 

한명의 멤버가 팀을 바꾸려고 할 때 테이블에서는 fk 값만 바꿔주면 끝이납니다. 하지만, 객체의 경우 Member에 있는 team을 바꿔야할 지, Team에 있는 members를 바꿔야할지 딜레마에 빠지기 때문입니다. 

그렇기때문에 객체를 매핑할 때 테이블이 fk 값하나로 연관관계를 맺는 것처럼 객체의 참조도 하나일 필요가 있습니다.

 

그래서 결론은 "객체도 둘 중 하나로 외래 키를 관리해야 한다"로 됩니다. 이 개념이 바로 연관관계의 주인입니다.

 

 

연관관계의 주인

  • 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리 (등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용 X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

이전에 짰던 코드를 비유하면 연관관계의 주인은 Member의 team입니다.

 

 

누구를 주인으로?

  • 외래 키가 있는 곳을 주인으로 정해라
  • 여기서는 Member.team이 연관관계의 주인

외래 키가 있는 곳을 주인으로 정하는 이유는 아래와 같다.

  • 예를들어, Team.members를 외래키로 정했다고 가정해보자. members의 값을 변경하게되면 연관된 다른 테이블의 업데이트 쿼리도 나가게되는데 이렇게되면 헷갈리게 된다.
  • 성능 이슈

 

 

양방향 연관관계와 연관관계의 주인 - 주의점, 정리


양방향 매핑시 가장 많이 하는 실수 (연관관계의 주인에 값을 입력하지 않음)

실제 코드만 보면 아무런 문제가 없어보이는 코드입니다. member 객체를 만들었고, team에 member를 추가해주었습니다. 한번 더 기억을 더듬어 보자면 저희는 이전에 mappedBy로 연관관계의 주인을 Member로 설정했습니다. 또 등록, 수정이 member를 통해서만 이루어진다고 말했었습니다. 하지만, 해당 코드에서 등록을 연관관계의 주인이 아닌 역방향으로 했습니다. 결과는 어떨까요??

위를 보다시피 매핑이 정상적으로 이루어지지 않은 모습을 볼 수 있습니다. 

다시 한번 기억합시다! 연관관계 주인에 설정을 해야합니다.

 

그렇다면 연관관계의 주인에만 설정을 하는 것이 맞는 걸까요?? 이것도 무언가 객체지향스럽지 못합니다. 이유는 데이터를 넣지도 않았는데 이미 데이터는 들어가있기 때문입니다. 이것은 아래와 같은 두가지에서 문제가 생깁니다.

  • em.clear()를 하지 않은 경우 (위에 해당의 문제는 설명한 바 있습니다. 간단하게 설명하자면 영속성 컨텍스트에서 데이터를 가져오기 때문입니다. 이해되지 않는 분들은 em.clear를 하고 안하고의 차이를 주석처리해보며 직접 느껴보시길 추천합니다.)
  • 테스트케이스를 작성할 때, member.getTeam은 되지만 team.getMembers는 동작하지 않음.

위의 이유로 "양쪽 다 데이터를 세팅하는 것이 맞는 방법이다"라고합니다.

 

 

양방향 연관관계 주의 - 실습

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메소드를 생성하자
  • 양방향 매핑시에 무한 루프를 조심하자
    • 예) toString(). lombok, JSON 생성 라이브러리 

 

연관관계 편의 메소드

연관관계 편의 메소드

연관관계 편의 메소드를 생성해 한번의 메소드로 양쪽에 값을 설정해줍니다. 위 코드는 setTeam -> changeTeam을 통해 양쪽의 연관관계를 설정해주는 코드입니다. 양쪽 연관관계를 맺는 방법은 다양한 방법이 있으니 편한 방법대로 해주시면됩니다. 

 

 

양방향 매핑시 무한 루프

Member, Team toString 메소드
toString 사용
StackOverFlow

양방향 연관관계에서 두 객체 모두 toString을 생성한 뒤, 한쪽 객체의 toString을 사용하는 경우 member <-> team을 계속해서 부르기 때문에 stackoverflow가 발생하게 됩니다. (toString하는 경우 한쪽 방향만 이용하던가, 양방향 연관하는 컬럼의 경우 제외해주어야 합니다.) 특히, lombok으로 toString 쓰는 경우 보이지 않기때문에 조심해야합니다.

 

 

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)

김영한님의 추천으로 단방향 매핑으로 설계를 완료하고 필요에 의해서 탐색하는 경우에만 조회용으로만 양방향으로 사용하는 것을 권장한다고 합니다.

 

 

 

연관관계 매핑 시작


테이블 구조

 

객체 구조

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

객체와 테이블 매핑


엔티티 매핑 소개

  • 객체와 테이블 매핑
    • @Entity, @Table(name = "테이블 명")
  • 필드와 컬럼 매핑
    • @Column(name = "컬럼 명")
  • 기본 키 매핑
    • @Id
  • 연관관계 매핑
    • @ManyToOne, @OneToMany, @ManyToMany, @OneToOne, @JoinColumn

 

 

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리하는 엔티티
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
  • 주의
    • 기본 생성자 필수 (파라미터가 없는 public 또는 protected 생성자)
    • final 클래스, enum, interface, inner 클래스 사용 X
    • 저장할 필드에 final 사용 X

 

@Table

  • @Table은 엔티티와 매핑할 테이블 지정

 

 

 

데이터베이스 스키마 자동 생성


데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 DB 자동 생성
  • 테이블 중심 -> 객체 중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
  • 이렇게 생성된 DDL은 개발 장비에서만 사용
  • 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

 

데이터베이스 스키마 자동 생성 - 속성

hibernate.hbm2ddl.auto

 

 

데이터베이스 스키마 자동 생성 - 주의

  • 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
  • 개발 초기 단계는 create 또는 update
  • 테스트 서버는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

운영 같은 경우 몇천만건 있는 상태에서 alter를 잘못치면 시스템이 중단상태가 될 수 있다. 애플리케이션 로딩시점에 시스템에서 자동으로 alter 친다는 것은 매우 위험하다. 

 

 

DDL 생성 기능

  • 제약조건 추가 : 회원 이름은 필수, 10자 초과X
    • @Column(nullable = false, length = 10)
  • 유니크 제약조건 추가
    • @Table(uniqueConstraints = {@uniqueConstraint( name = "NAME_AGE_UNIQUE",
      columnNames = {"NAME", "AGE"})})
  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

 

 

 

필드와 컬럼 매핑


요구사항 추가

  • 회원은 일반회원과 관리자로 구분해야 한다.
  • 회원가입일과 수정일이 있어야 한다.
  • 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.

 

Member 클래스 수정

@Entity
public class Member {
    @Id
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob                    // 데이터베이스에 varchar를 능가하는 큰 문자열을 넣을 때 Lob 사용
    private String description;

    public Member(){}
}

테이블이 create된 출력

  • description이 clob인 이유는 String으로 선언했기 때문
  • enum은 varchar로 매핑

 

매핑 어노테이션 정리

 

@Column

 

@Enumerated

자바 enum 타입을 매핑할 때 사용

주의! ORDINAL 사용 X (순서로 하게되면 1,2,3 이런식으로 저장되는데 문제는 Enum의 데이터를 추가할 경우 기존의 데이터가 바뀌지 않아 문제가 생긴다.)

 

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용

참고 : LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)

 

@Lob

데이터베이스 BLOB, CLOB 타입과 매핑

  • @Lob에는 지정할 수 있는 속성이 없다..
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB : String, char[], java.sql.CLOB
    • BLOB : byte[], java.sql.BLOB

 

@Transient

  • 필드 매핑 X
  • 데이터베이스 저장 X, 조회 X
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용

 

 

기본 키 매핑


기본 키 매핑 방법

  • 직접 할당 : @ID만 사용
  • 자동 생성(@GeneratedValue)
    • IDENTITY : 데이터베이스에 위임, MYSQL
    • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
      • @SequenceGenerator 필요
    • TABLE : 키 생성용 테이블 사용, 모든 DB 에서 사용
      • @TableGenerator 필요
    • AUTO : 방언에 따라 자동 지정, 기본값

 

Identity 전략 - 특징

  • 기본 키 생성을 데이터베이스에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용 (예, MySQL의 AUTO_INCREMENT)
  • JPA는 보통 트랜잭션 머킷 시점에 INSERT SQL 실행
  • AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
  • JDBC 드라이브에 insert 쿼리를 날릴 때 return을 받는 것이 내부적으로 되어있기 때문에 select 쿼리문을 날리지 않고 insert 만으로 기본 키를 넣을 수 있음
  • persist 시점에 insert SQL을 실행하기 때문에 버퍼링을 이용한 성능 최적화를 할 수 없다.

 

Sequence 전략 - 특징

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 (예 : 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
  • em.persist() 일 때 DB에서 Sequence만 갖고와서 영속성 컨텍스트에 저장하면서, insert 쿼리문도 함께 SQL저장소에 저장한 뒤, 트랜잭션 commit 시점에 insert 쿼리문을 DB에 전송
  • 따라서, 버퍼링 기능을 사용 가능
  • 하지만, persist에서 SEQ 값을 갖고 오기위해 네트워크를 이용하는 것 때문에 성능에 문제가 생길 수 있습니다. SEQ값을 갖고오는 것보다 Insert 쿼리문을 날리는게 성능에 더 좋아진다고 생각할 것 입니다.
  • 이를 해결하기 위해, allocationSize를 이용하면됩니다. allocationSize에 할당한 수만큼 데이터가 모일 때 SEQ 값을 증가시키는 아래와 같은 쿼리문을 보냅니다.

Sequence 전략 - 매핑

Sequence - @SequenceGenerator

 

Table 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 성능
    • 최적화가 되어있지 않기 때문에 성능이 떨어진다.

Table 전략 - 매핑

알아서 테이블과 컬럼이 생성되고, select와 update문을 날리면서 동작

테이블을 하나 만들어서 관리하는게 있어보이지만, 성능상 좋지 않기 때문에 잘 사용하지는 않는다고 합니다.

 

 

권장하는 식별자 전략

  • 기본 키 제약 조건 : null 아님, 변하면 안된다.
  • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
  • 예를들어, 주민등록번호도 기본키로 적절하지 않다.
  • 권장 : Long형 + 대체키 + 키 생성전략 사용

 

 

실전 예제 1 - 요구 사항 분석과 기본 매핑


요구사항 분석

  • 회원은 상품을 주문할 수 있다.
  • 주문 시 여러 종류의 상품을 선택할 수 있다.

 

기능 목록

  • 회원 기능
    • 회원 등록
    • 회원 조회
  • 상품 기능
    • 상품 등록
    • 상품 수정
    • 상품 조회
  • 주문 기능
    • 상품 주문
    • 주문 내역 조회
    • 주문 취소

 

도메인 모델 분석

  • 회원과 주문의 관계 : 회원은 여러 번 주문할 수 있다. (일대다)
  • 주문과 상품의 관계 : 주문할 때 여러 상품을 선택할 수 있다. 반대로 같은 상품도 여러 번 주문될 수 있다. 주문상품이라는 모델을 만들어서 다대다 관계를 일다대, 다대일 관계로 풀어냄

 

테이블 설계

 

엔티티 설계와 매핑

엔티티 설계를 보며, 프로젝트에 Entity로 추가합니다. (Order 클래스에 status는 enum 타입입니다.)

 

 

참고

    <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
    </dependency>

자바 11이상 버전을 이용하면 위의 라이브러리를 pom.xml에 추가해주어야합니다. java 11부터는 Java ee 모듈이 제거되어 jaxb 라이브러리를 꼭 추가해줘야 된다고 합니다.

 

 

엔티티를 추가한 뒤, 코드를 돌렸을 때 create table 로그가 출력되고 콘솔에 테이블이 만들어지면 잘 작동된 것 입니다.

 

근데 자세히보면 Member와 Order의 관계를 만들어주는 Order엔티티에 memberId의 자료형은 Long입니다. 조금 더 객체지향적으로 설계하기 위해선 자료형을 Member로 선언해야 할 것입니다. 그렇다면 객체지향적으로 설계된 엔티티와 그렇지 않은 엔티티의 차이점은 어떨까요? 

 

자료형을 Long, 클래스로 하는 것의 차이점은 아래의 예시를 들어 확인해보도록 하겠습니다.

자료형을 Member로 하면 order.getMember()를 통해 한번의 작업으로 원하는 Member 엔티티를 얻어올 수 있습니다. 하지만, 객체지향적으로 설계하지 않은 Long의 자료형order.getMemberId()를 통해 memberid를 받아오고 해당 memberid를 통해 다시 또 엔티티를 찾아주는 작업을 해야합니다. 즉, Long 자료형의 경우 두번의 작업을 해야합니다. 

 

위의 같은경우는 Order -> Member 한번의 이동의 차이점을 본 것인데요. 이러한 엔티티간의 이동이 많은 경우 객체지향적으로 설계한 것이 더 편리하다는 것을 느낄 수 있을 것 입니다.

 

 

데이터 중심 설계의 문제점

  • 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
  • 테이블의 외래키를 객체에 그대로 가져옴
  • 객체 그래프 탐색이 불가능
  • 참조가 없으므로 UML도 잘못됨

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

영속성 컨텍스트


JPA에서 가장 중요한 2가지

  1. 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
    • DB를 어떻게 설계하고, 객체를 어떻게 설계해서 중간에서 JPA를 어떻게 사용해서 mapping 할 것 인지
  2. 영속성 컨텍스트
    • 실제 JPA가 내부에서 어떻게 동작하는지 
    • 영속성 컨텍스트를 이해하면 JPA가 내부적으로 어떻게 돌아가는지 이해할 수 있을 것

 

 

Entity Manager Factory와 Entity Manager

예를들어 웹 어플리케이션을 개발할 때 사용자의 요청에 따라서 EMF를 통해 EM을 생성합니다. EM은 내부적으로 DB 커넥션을 통해 DB를 사용합니다.

 

그렇다면 영속성 컨텍스트는 무엇일까??

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • "엔티티를 영구 저장하는 환경"이라는 뜻
  • EntityManager.persist(entity);
    • 해당 코드의 의미는 DB에 저장되지만 DB에 저장한다는 의미가 아닌 영속성 컨텍스트에 저장한다는 의미입니다. (아직 어떤 느낌인지 모르겠습니다... ?!?!?!?)   => 실제 확인해보니 em.persist할 때 쿼리문이 나가지 않고 쓰기 지연 SQL 저장소에 쿼리가 저장되고 commit이나 flush를 할 경우 DB에 쿼리문을 보내어 DB를 동기화 시켜줌


EM? 영속성 컨텍스트?

  • 영속성 컨텍스트는 논리적인 개념
  • 눈에 보이지 않기때문에 이해하기 쉽지 않다.
  • EM을 통해 영속성 컨텍스트에 접근

 

  • EM과 영속성 컨텍스트는 J2SE 환경에서 1:1로 매핑됩니다.
  • 스프링 프레임워크같은 환경에선 N:1로 매핑되는데 해당 부분은 다음에 이해하는 것을 권장

 

Entity의 생명주기

  • 비영속 (new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속 (managed)
    • 영속성 컨텍스트에 관리되는 상태
  • 준영속 (detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed)
    • 삭제된 상태

 

비영속

객체만 생성하고 em에 persist하지 않은 상태

 

 

영속

객체를 EM에 저장한 상태 (트랜잭션에 commit하지 않았기때문에 DB에는 올라가지 않는다.)

 

 

준영속, 삭제

 

 

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(Identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

애플리케이션과 DB 사이에 중간 계층이 있음으로서 위와같은 이점을 얻을 수 있다. 영속성 컨텍스트 뿐만 아니라 중간에 하나를 더 두면서 얻는 이점은 버퍼링, 캐시 등과 같은 이점을 얻을 수 있다.

 

 

엔티티 조회, 1차 캐시

이렇게 @Id로 member1이 있고, Entity로 객체가 있음으로서 얻는 이점은 1차 캐시에서 조회할 때 있습니다.

 

1차 캐시에서 조회

em.persist로 1차 캐시에 저장이 되었다고 가정한뒤, 조회한다고 합시다. 조회를 할 경우 DB를 먼저 조회하는 것이 아닌 1차 캐시에서 먼저 조회를 하게 됩니다. 그때 1차 캐시에서 원하는 객체가 있는 경우 DB를 조회하지 않고 바로 객체를 반환 받을 수 있습니다.

 

데이터베이스에서 조회

조회할 때, 1차 캐시에 없는 경우 DB를 조회하게 됩니다. 이때, DB에 원하는 객체가 있는 경우 1차 캐시에 저장한 뒤, 1차 캐시에 있는 객체를 반환하게 됩니다.

 

EM을 사용함으로서 위와같은 1차캐시의 이점을 얻을 수 있지만, 현업자분의 말로는 큰 이점은 없다고 합니다. 이유는 EM은 DB 트랜잭션 단위로 만들고 트랜잭션 종료시 종료됩니다. 이 말은 고객의 요청에 따라 비즈니스가 끝나게 될 경우 영속성 컨텍스트를 지운다는 뜻 입니다. 이렇듯 굉장히 찰나에 순간에만 이점을 얻을 수 있기 때문에 사실상 큰 이점은 없다고 합니다.

 

 

영속 엔티티의 동일성 보장

영속성 컨텍스트에서 같은 객체를 불러와 비교해보면 자바 컬렉션에서 똑같은 레퍼런스 두개를 가져와 비교할 때 같다고 나오는 것 처럼 같다고 나옵니다.

 

 

엔티티 등록

트랜잭션을 지원하는 쓰기 지연

트랜잭션 commit을 하기전까진 1차 캐시에 변경된 내용은 쓰기 지연 SQL 저장소에 쿼리문이 차곡차곡 쌓이게 됩니다. Queue와 비슷한 형식인 것 같습니다. 후에 트랜잭션 commit을 한 경우 쌓인 쿼리문은 flush가 되며 DB에 저장하게 됩니다.

 

 

 

엔티티 수정

변경 감지

데이터를 수정할 때 객체를 가져와 수정 후 update나 persist를 다시 해줘야하는 것 아닌가라는 의문이 생길 수 있습니다. (저도 그랬습니다) 하지만, JPA를 사용하는 목적은 자바에서 컬렉션을 사용하는 것처럼 사용하기 위함 입니다.  컬렉션을 수정할 때, 수정 후 다시 데이터를 집어넣는 작업을 해야하는가를 생각해보면 그렇지 않습니다. 그렇기때문에 JPA에서도 데이터를 수정 후 집어넣는 작업을 하지 않아도 됩니다. 이것이 가능한 이유는 JPA에서 Dirty Checking을 하기 때문입니다. 

 

 

변경 감지 (Dirty Checking)

JPA는 DB 트랜잭션 커밋하는 시점에 내부적으로 flush()가 호출됩니다. 후 엔티티와 스냅샷을 비교합니다. 1차 캐시 안에는 사실 PK값, 객체 뿐만아니라 스냅샷도 있습니다. 스냅샷은 값을 읽는 최초 시점을 저장한 객체입니다. 그렇기 때문에 flush()가 호출 되어 엔티티와 스냅샷을 비교할 때 최초 시점에 객체와 commit된 객체의 값을 비교 후 변경된 데이터에 대한 update 쿼리문을 자동으로 생성하여 쓰기지연 SQL 저장소에 생성하여 저장합니다. 이러한 내부적인 로직때문에 수정할 때 persist나 update를 안해줘도 됩니다.

 

 

엔티티 삭제

위의 메커니즘과 같이 remove(객체)를 하여 삭제할 수 있습니다. 

 

 

플러시 


위 내용을 보다보면 flush라는 단어를 보았을 것 입니다. 

 

flush

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

 

 

플러시 발생

  • 변경 감지 (Dirty Checking)
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

 

 

영속성 컨텍스트를 플러시하는 방법

  • 직접 호출
    • em.flush()
  • 플러시 자동 호출
    • 트랜잭션 커밋
    • JPQL 쿼리 실행

 

플러시 정리

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨

 

 

 

준영속 상태


준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리 (detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

 

준영속 상태로 만드는 방법

  • em.detach(entity)
    • 특정 엔티티만 준영속 상태로 전환
  • em.clear()
    • 영속성 컨텍스트를 완전히 초기화
  • em.close()
    • 영속성 컨텍스트를 종료

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

H2 데이터베이스는 이미 설치했기 때문에 넘어가도록 하겠습니다.

 

아래 링크에 H2 데이터베이스 설치했던 내용이 담겨있는 내용이 있습니다.

 

[JPA 활용 1] 1. 프로젝트 환경설정

프로젝트 환경설정 프로젝트 생성 라이브러리 살펴보기 View 환경 설정 H2 데이터베이스 설치 JPA와 DB 설정, 동작확인 프로젝트 생성 스프링 부트 스타터(https://start.spring.io/) 사용 기능 : web, thymelea

qazyj.tistory.com

 

 

 메이븐 소개

  • https://maven.apache.org/
  • 자바 라이브러리, 빌드 관리
  • 라이브러리 자동 다운로드 및 의존성 관리
  • 최근에는 그레들(Gradle)이 점점 유명 (저도 그레들로 많이 사용했습니다.)

 

 

프로젝트 생성

  • 자바 8 이상(8 권장)
  • 메이븐 설정
    • groupId : jpa-basic
    • artifactId : ex1-hello-jpa
    • version : 1.0.0

intelliJ를 킨 후 new Project에서 maven을 선택한 뒤 위의 그림대로 작성 후 프로젝트를 생성해주시면 됩니다.

 

코드추가 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>jpa-basic</groupId>
    <artifactId>ex1-hello-jpa</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <!-- JPA 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.3.10.Final</version>
        </dependency>
        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
        </dependency>
    </dependencies>
</project>

 

 

JPA 설정하기 - persistence.xml

jpa를 쓰기위해선 몇가지 설정을 해야합니다.
  • JPA 설정 파일
  • /META-INF/persistence.xml 위치 (표준 위치가 정해져 있습니다.)
  • persistence-unit name으로 이름 지정 (JPA이름, DB마다 만든다고 합니다.)
  • javax.persistence로 시작 : JPA 표준 속성
  • hibernate로 시작 : 하이버네이트 전용 속성

 

코드 추가 (METE-INF/persistence.xml)

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>
</persistence>

 

 

데이터베이스 방언

  • JPA는 특정 데이터베이스에 종속 X
  • 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다름
    • 가변 문자 : MySQL은 VARCHAR, Oracle은 VARCHAR2
    • 문자열을 자르는 함수 : SQL 표준은 SUBSTRING(), Oracle은 SUBSTR()
    • 페이징 : MySQL은 LINIT, Oracle은 ROWNUM
  • 방언 : SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능
  • hibernate.dialect 속성에 지정
    • H2 : org.hibernate.dialect.H2Dialect
    • Oracle 10g : org.hibernate.dialect..Oracle10gDialect
    • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect
  • 하이버네이트는 40가지 이상의 데이터베이스 방언 지원

 

애플리케이션 개발

 

JPA 구동 방식

  1. Persistence라는 클래스를 통해 설정 정보를 조회합니다.
  2. 설정 정보를 조회해서 EntityManagerFactory라는 클래스를 만듭니다. 
  3. EntityManagerFactory 클래스에서 필요할 때마다 EntityManager를 찍어냅니다.

 

 

실습 - JPA 동작 확인

  • JpaMain 클래스 생성
  • JPA 동작 확인

 

 

객체와 테이블을 생성하고 매핑하기

  • @Entity
    • JPA가 관리할 객체
  • @Id
    • 데이터베이스 PK와 매핑

localhost:8082를 url로 검색한 뒤, JDBC URL에 persistence.xml에 입력했던 아래의 코드의 value값을 넣어야지 정상적으로 연결이 됩니다.

<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>

Entity 클래스를 만들고, 트랜잭션을 보내주면 log로 쿼리문을 볼 수 있습니다. 위와 같은 쿼리문이 로그에 출력된다면 정상적으로 jpa가 작동하는 것 입니다. 쿼리문이 로그로 출력이 되는 이유는 persistence.xml 코드에서 옵션 첫번째 줄에 show_sql의 value값을 true로 주었기 때문입니다. format_sql은 로그를 위와같이 가독성이 좋게 출력을 해줍니다. use_sql_comments주석된 부분을 로그로 보여줄 것인지 아닌지를 설정해줍니다. h2 콘솔로 확인해보면 입력했던 데이터가 저장된 것을 확인할 수 있습니다.

트랜잭션 정석코드로 변경했습니다. 트랜잭션을 보냈을 때, 에러가 발생할 시 rollback하고 정상적으로 종료되면 EntityManager와 EntityManagerFactory를 종료합니다. 지금은 jpa 정석 코드를 보여주기 위해 적었지만 실제로는 spring 위의 코드가 다 없어지고 em.persist(member)만 적으면 작업을 완료할 수 있습니다. 

 

JPA 조회

조회는 위와 같이 find 메소드를 통해 찾을 수 있습니다. 파라미터로 엔티티 클래스명, PK를 넘겨주면 DB에 저장한 데이터를 가져올 수 있습니다. 가져온 Entity의 값을 출력해보면 이전에 넣었던 데이터가 출력되는 것을 볼 수 있습니다. 

 

JPA 삭제

삭제는 위처럼 remove에 찾은 Entity 클래스 필드를 넣어주면 됩니다. 

 

JPA 수정 (신기함)

수정이 진짜 신기합니다. 마치 자바 컬렉션을 사용하는 것처럼 사용만하면 수정이됩니다. 단지, setter를 활용해 데이터를 수정만해준 뒤 트랜잭션만 보내면 JPA가 자동으로 DB 데이터를 수정해줍니다. 이것이 가능한 이유는 JPA를 통해 Entity를 가져오면 해당 Entity는 JPA가 관리하기 때문입니다. 그리고 JPA가 변경이 되는지 안되는지 트랜잭션을 commit하는 시점에 체크합니다. 그 후 바뀐 데이터가 있을 시 업데이터 쿼리를 작성하여 DB에 날려줍니다. 

 

 

주의

  • EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유
    • 웹 애플리케이션 서비스를 실행한다고하면 EntityManagerFactory는 DB 당 하나만 생성한다고 보면 됩니다.
  • EntityManager는 쓰레드간에 공유 X (사용하고 버려야 한다.)
    • 고객의 요청에 따라서 사용한 뒤 close, 사용한 뒤 close 반복한다고 보면 됩니다.
    • 이유는 후에 이해하면 정리하도록!
  • JPA의 모든 데이터 변경은 트랙잭션 안에서 실행
    • 정말 중요합니다!
    • RDB 데이터 변경은 트랜잭션안에서 할 수 있도록 설계가 되어있기 때문입니다.

 

 

JPQL 소개

  • 가장 단순한 조회 방법
    • EntityManager.find(Entity 클래스명, PK 값)
    • 객체 그래프 탐색 (a.getB().getC())
  • 나이가 18살 이상인 회원을 모두 검색하고 싶다면?
    • JPQL을 사용해야 합니다.

현업에서의 개발고민은 여러 테이블이 정말 많은데 필요하면 조인도해야하고, 원하는 데이터를 최적화 해서 가져와야하고, 필요하면 통계성 쿼리도 날려야하고 이러한 작업들을 JPA에서는 JPQL로 도와준다고 보면 됩니다. 쿼리를 어떻게 쓰지라는 고민이 드실텐데 JPA에서는 대안이 있다고 합니다.

 

 

실습 - JPQL 소개

  • JPQL로 전체 회원 탐색
  • JPQL로 ID가 2 이상인 회원만 검색
    • 쿼리문에 where m.id =
  • JPQL로 이름이 같은 회원만 검색
  • JPQL에 대해 자세한 내용은 객체지향 쿼리에서 학습

 

JPQL로 전체 회원 탐색

log를 잘 보면 실제 전송한 쿼리랑 다르게 모든 필드를 나열한 것을 볼 수 있습니다. 

 

 

JPQL

  • JPA를 사용하면 엔티티 객체 중심으로 개발할 수 있다.
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체로 탐색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
  • JPQL은 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
    • SQL을 추상화했기 때문에 특정 데이터베이스에 의존 X
  • SQL 문법과 유사, SELECT FROM, GROUP BY, WHERE, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리
  • SQL은 데이터베이스 테이블을 대상으로 쿼리
  • JPQL은 뒤에서 아주 자세히 다룸

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

 

강의 정리를 시작하기 앞서

왜?? 라는 질문에 대답하기 위해선 해당 분야에 많은 지식이 있어야지 대답을 해줄 수 있습니다.'
앞으로 스스로에게 왜?? 라는 질문을 던지며 정리를 하려고합니다. 왜?? 라는 질문은 강의와 책을 보며 찾은 해답을 정리할 것이고, 아직 해답을 못찾은 왜?라는 질문에 대한 대답은 후에 정리하도록 하겠습니다.

 

JPA 왜 공부해야 하는가?

1. 현재 JPA를 공부하는 이유는 점유율입니다. JPA, Mybatis 등 중에서 세계적으로 앞도적인 점유율을 보여주는 JPA를 사용할 줄 알고, 이해하고 있어야지 취업에 도움이 될 것이라고 생각하기 때문입니다.

2. 무한 반복, 지루한 코드를 적어야하는 SQL의 문제점을 보완해주기 때문입니다. (간단하게 쿼리문 작성이 필요하지 않습니다.) 이에따라서, 개발 시간이 줄어듬으로써 설계나 테스트 등에 시간을 더 사용할 수 있습니다.

3. 시간을 줄여준다고 성능이 입증되지 않은 것도 아닙니다. 현재 조 단위의 거래 금액이 발생하는 다양한 서비스에서 사용하고 있으며 문제없이 사용되기 때문에 문제없다고 생각했습니다.

 

SQL 중심적인 개발의 문제점

 

무한 반복, 지루한 코드를 적어야하는 예를 한번 들어봅시다. JPA가 없는 SQL만으로 작성된 아래의 코드로 작성된 프로젝트가 있다고 가정해봅시다.

여기서, 기획자에 요청에 의해서 객체에 번호 필드를 추가해야되는 상황이 주어졌습니다. 객체에 tel이란 필드를 생성하면 끝나는 것이 아닌 모든 쿼리문에 tel을 추가해야되는 상황이 발생합니다. 

결국, 관계형 데이터베이스를 사용하는 환경에서는 SQL에 의존적인 개발을 피하기 어렵습니다.

 

그리고 단순한 SQL 문을 작성하는 문제를 넘어서서 또 하나의 문제점이 있습니다. 바로 패러다임의 불일치 입니다.

 

 

패러다임의 불일치

객체 vs 관계형 데이터베이스

관계형 데이터베이스의 사상과 객체 지향 사상이 다릅니다.

 

객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공합니다. 반면, 관계형 데이터베이스는 데이터를 잘 정규화해서 보관하는 것을 목표로합니다.

 

하지만 생각을 뒤집어 봅시다. 먼저 객체를 잘 정규화해서 저장한다고 봅시다. 선택지는 RDB, NoSQL, File 등 여러가지가 있습니다. 여기서 데이터를 저장하는 다양한 방법 중 현실적인 대안은 RDB입니다. 이유는 데이터가 백만건 있다고 할 때 파일에 집어 넣는 경우는 검색이 불가능하고 백만건 모두를 Object를 만든 뒤 검색하기에는 답이 없기 때문입니다. NoSQL도 대안이 될 수 있기도 하지만 아직까지 Main은 RDB를 쓰기 때문에 힘들다고 합니다. 그렇기 때문에 RDB를 사용해야합니다.

결론적으로, RDB를 사용함으로써 객체를 SQL로 바꿔야하기 때문에 SQL은 필수로 사용해야 합니다. 이러한 작업은 개발자가 해야합니다. 

 

 

객체와 관계형 데이터베이스의 차이

  1. 상속
    • 객체 : 상속 관계 O
    • RDB : 상속 관계 X
  2. 연관관계
    • 객체 : 레퍼런스 ex) ArrayList에서의 get
    • RDB : PK, FK
  3. 데이터 타입
  4. 데이터 식별 방법

 

상속

위와 같은 상속 관계가 있다고 가정해봅시다.

 

삽입

  1. 객체 분해
  2. INSERT INTO ITEM ...
  3. INSERT INTO ALBUM ...

insert의 경우 3번의 작업을 거치긴하지만 가능합니다.

 

조회

  1. 각각의 테이블에 따른 조인 SQL 작성 ..
  2. 각각의 객체 생성 ..
  3. 상상만 해도 복잡합니다.

그렇기때문에 DB에 저장할 객체에는 상속 관계를 쓰지 않습니다.

 

하지만, DB가 아닌 자바 컬렉션에 저장한다면 어떻게 될까요??

삽입

list.add(album);

조회

Album album = list.get(albumId);

// 부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumID);

 자바 컬렉션에 넣으면 심플해지지만, RDB에 넣고 빼는 순간 중간에 SQL 매핑 작업을 개발자가 한땀한땀 해야합니다.

 

 

연관관계

  • 객체는 참조를 사용 : member.getTeam()
  • 테이블은 외래 키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID

객체의 경우 Member에서 Team을 찾을 수 있고, Team에서 Member를 찾을 순 없습니다. 하지만 테이블의 경우 MEMBER에서 TEAM을 join할 수 있고, TEAM에서 MEMBER를 join할 수 도 있습니다.

 

객체를 테이블에 맞추어 모델링

class Member {
    String id;		// MEMBER_ID 컬럼 사용
    Long teamId;	// TEAM_ID FK 컬럼 사용 // **
    String username;// USERNAME 컬럼 사용
}

class Team {
    Long id;		// TEAM_ID PK 사용
    String name;	// NAME 컬럼 사용
}

 

테이블에 맞춘 객체 저장

하지만 이는 객체다운 모델링 같지 않습니다. Member 클래스에 있는 teamId의 타입이 Team이 아닌 Long으로 되어있기 때문입니다. 

 

객체다운 모델링

class Member {
    String id;		// MEMBER_ID 컬럼 사용
    Team teamId;	// 참조로 연관관계를 맺는다. // **
    String username;// USERNAME 컬럼 사용
    
    Team getTeam() {
    	return team;
    }
}

class Team {
    Long id;		// TEAM_ID PK 사용
    String name;	// NAME 컬럼 사용
}

 

객체 모델링 저장

이렇게 설계하면 정상적으로 저장이 가능합니다. 하지만 조회할 때 문제가 생깁니다.

 

 

객체 모델링 조회

위와 같이 주석처리 된 부분의 코드가 상당할 것으로 예상됩니다. 결론적으로 매우 번거롭습니다.

 

 

객체 모델링, 자바 컬렉션에 관리 

훨씬 편리해진 것을 볼 수 있습니다. 

 

 

객체 그래프 탐색

객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다. 아래의 그래프를 예시로 들어 봅시다.

여기서 자유롭게 그래프를 탐색한다는 것은 자바에서 get을 통해 모든 객체로 갈 수 있다는 것을 의미합니다.

 



처음 실행하는 SQL에 따라 탐색 범위 결정

이처럼 SELECT로 MEMBER와 TEAM을 가져오면 getTeam은 가능하지만, getOrder은 불가능합니다. 이것을 해결하기 위해선 일일이 코드를 다 살펴봐야하는 문제가 발생하게 됩니다. 즉, 엔티티에 신뢰할수 없다는 것 입니다.

 

 

엔티티 신뢰 문제

위에 코드가 있다고 가정합시다. Member를 받아와서 getTeam(), getOrder().getDelivery()를 수행하려 합니다. 하지만 엔티티를 모두 가져올 수 있을지는 장담할 수 없습니다. 이를 알아보기 위해선 Member를 가져올 때 TEAM, ORDER, DELIVERY 데이터를 모두 긁어 오는지를 확인해야 합니다.

 

 

모든 객체를 미리 로딩할 수는 없다.

그렇다면 모든 객체를 미리 모두 로딩해놓으면 되는 것 아닌가라는 의문점이 생깁니다. 하지만 사실상 현업에서 사전에 미리 로딩하는 작업을 하게되면 서비스를 이용하기 직전에 불필요한 데이터까지 조회를 하기때문에 너무 오랜 시간을 기다릴 수 있어 서비스적인 측면에서 좋지 못합니다. (개인적인 생각입니다.) 그렇기때문에 위와같이 테이블을 조회하는 모든 경우의 수를 메소드화 시키는 것이 좋지만 이또한 코드가 지저분해지고 지루한 코드를 오랜시간 작성해야하는 번거로움이 있습니다. 

 

이렇듯 SQL을 직접 다루게 되면 계층형 아키텍처에서 진정한 의미의 계층 분할이 어렵습니다. (진정한 게층 분할은 뭘까??)

 

 

비교하기

SQL에서 조회

member1과 member2가 식별자는 똑같지만 다른 이유는 다른 쿼리문으로 받아온 값을 new Member를 통해 반환하기 때문에 당연히 다릅니다. 그렇다면 자바 컬렉션에서는 어떨까요?

자바 컬렉션에서 조회

이것은 당연하게 같은 참조값으로 조회하기때문에 같은 Member를 반환합니다.

 

이처럼 자바 컬렉션에서 객체를 다룰 때와 SQL(RDB)에서 객체를 다룰 때와 중간에 많은 미스매치가 나는 것을 볼 수 있습니다. 그렇기때문에 RDB를 객체답게 모델링 할수록 매핑 작업만 늘어나게 됩니다.

 

이러한 무한 반복, 지루한 코드를 계속해서 작성하던 선배 개발자분들은 "객체를 자바 컬렉션에 저장 하듯이 DB에 저장"하고 싶어집니다. 이러한 고민의 결과가 바로 JPA이고, 공부를 해야하는 이유 입니다.

 

 

JPA 소개

JPA란?

Java Persistence API의 약자로 자바 진영의 ORM 기술 표준입니다. 

 

ORM이란?

Object-Relational Mapping (객체 관계 매핑)의 약자로 객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계하는 것을 의미합니다. 각각 설계된 객체와 RDB를 ORM 프레임워크가 중간에서 매핑합니다.

 

 

JPA 동작

JPA는 JAVA 애플리케이션과 JDBC 사이에서 동작합니다. 그렇기때문에 개발자가 직접 JDBC API를 쓰는 것이 아닌 JPA에게 명령하면 JPA가 JDBC API를 사용해서 SQL을 호출하고 만들어서 보내고 결과를 받는 등의 동작을 하게 됩니다.

 

 

JPA 동작 - 저장

예를들어, MemberDAO에서 객체를 저장하고 싶다고 가정해봅시다. 과거에는 JDBC API나 JDBC Template, Mybatis를 썼다면 JPA를 사용하게 될 경우에는 JPA에게 Member 객체를 넘깁니다. 그렇게되면 JPA가 Member 객체를 분석하고 적절한 INSERT SQL을 생성하고 JDBC API를 사용해서 쿼리를 DB에 보냅니다. 여기서 JPA의 장점은 쿼리를 개발자가 작성하는 것이 아닌 JPA가 작성해줍니다. 그렇기때문에 패러다임의 불일치가 해결됩니다. (아직까지는 어떻게 패러다임의 불일치가 해결되는지 이해가지 않지만 뒤에서 해결해준다고합니다.)

 

JPA 동작 - 조회

조회할 때도 마찬가지로 JPA에게 PK값만 넘기면 JPA가 알아서 쿼리문을 작성하고 JDBC API를 사용해서 DB를 보내고 결과를 받은 다음에 ResultSet 결과를 객체에 다 매핑해줍니다. 그렇기때문에 코드한줄로 조회기능을 애플리케이션 단에서 가능하게 됩니다.

 

 

JPA 역사

해당 역사는 기본편에서 한번 보았던 내용인 것 같아 따로 정리하지않고 링크를 아래 추가했습니다.

 

 

[Spring/기본편] 1. 객체 지향 프로그래밍 및 스프링 개념 정리

목표 스프링을 왜 만드는가? 이유와 핵심원리 스프링 기본 기능 학습 스프링 본질 깊은 이해 객체 지향 설계에 대한 고민을 할 수 있게 해줌 서론 2000년대 초반에는 자바 정파 기술에는 EJB(Enterpr

qazyj.tistory.com

 

 

JPA는 표준 명세

  • JPA는 인터페이스의 모음
  • JPA 2.1 표준 명세를 구현한 3가지 구현체
  • 하이버네이트(대표적), EclipseLink, DataNucleus

 

 

JPA를 왜 사용해야 하는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 패러다임의 불일치 해결
  • 성능
  • 데이터 접근 추상화와 벤더 독립성
  • 표준

 

생산성 - JPA와 CRUD

  • 저장
    • jpa.persist(member)
  • 조회
    • Member member = jpa.find(memberId)
  • 수정 (fantastic)
    • member.setName("updateName")
  • 삭제
    • jpa.remove(member)

코드가 모두 만들어져있고 가져다 쓰기만해서 기존 CRUD를 하기위해 SQL 문을 작성하던 번거로움이 사라졌습니다.

 

 

유지보수 - 기존 : 필드 변경시 모든 SQL 수정

위에서 느꼈던 기획자에 요청에 의해 필드가 변경될 때 모든 SQL 문을 수정해야하는 번거로움이 있었습니다. 하지만 아래와 같이 JPA에서는 JAVA 애플리케이션 단에서 컬럼만 추가하면 됩니다. 따라서 유지보수 측면에서 훨씬 우월하다고 볼 수 있습니다.

 

JPA와 패러다임의 불일치 해결

  1. JPA와 상속
  2. JPA와 연관관계
  3. JPA와 객체 그래프 탐색
  4. JPA와 비교하기

 

JPA와 상속

이전에 보았던 상속 관계입니다. 

 

JPA와 상속 - 저장

이전에 SQL 문을 작성했던 번거로움이 사라진 것을 볼 수 있습니다. 또한, 상속관계에 있는 album을 보고 알아서 ITEM의 INSERT 쿼리문도 작성해주는 것을 볼 수 있습니다.

 

JPA와 상속 - 조회

기가 막힙니다. JPA에 앨범의 PK 값을 넘기면 JPA에서 ITEM과 ALBUM을 조인해서 데이터를 가져오는 것을 볼 수 있습니다. 패러다임이 맞지 않는 부분을 JPA가 해결해줍니다. 

 

 

JPA와 연관관계, 객체 그래프 탐색

member에 team을 set한 뒤 member를 jpa에 보내 DB에 저장합니다. 후에 조회하여 member로 team을 조회하면 가져올 수 있습니다. 마치 자바 컬렉션에 넣었던 것 처럼!!

 

 

신뢰할 수 있는 엔티티, 계층

JPA를 통해서 member 객체를 가져온거면 객체 그래프를 자유롭게 모두 다 탐색할 수 있습니다. JPA는 지연로딩이라는 기능덕분에 member.getTeam()이나 member.getOrder()를 해서 실제 객체를 사용해서 조회하는 시점에 SQL을 보내 채워주는 기능이 있습니다. 그렇기때문에 자유롭게 탐색할 수 있습니다. 그렇기때문에 신뢰할 수 있는 엔티티가 될 수 있습니다.

 

JPA와 비교하기

동일한 트랙잭션에서 조회한 엔티티는 같음을 보장합니다. 이것이 가능한 이유는 강의를 보다보면 이해할 수 있다고 합니다. (아직 이해 안됨)

 

 

JPA의 성능 최적화 기능

  1. 1차 캐시와 동일성(identity) 보장
  2. 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  3. 지연 로딩 (Lazy Loading)

JPA를 사용하면 성능이 떨어질 수도 있다고 생각할 것 입니다. 이렇게 생각하는 이유는 제 생각으론  기존에는 JAVA 애플리케이션에서 JDBC API로 바로 사용했지만, JPA를 사용하면 중간에 JPA를 거치기때문에 구조가 한단계 더 깊어졌기 때문입니다. 

계층 사이에 중간 계층이 있으면 항상 할 수 있는 것이 있습니다. 바로 모아서 쏘는 버퍼링과 읽을 때 캐싱하는 것을 할 수 있습니다. (마치 CPU와 메모리 사이 캐시기능) JAVA 애플리케이션과 JDBC API의 중간계층으로 JPA가 있기 때문에 SQL을 사용하는 것보다 이러한 부분들을 최적화 할 수 있습니다. 

 

 

1차 캐시와 동일성 보장

  1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
  2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장 (DB 지식이 많이 있어야하기 때문에 나중에 이해하고 이해해야할 부분, 현재는 알아두기만 할 것)

아까 조회했던 코드입니다. 첫번째 조회는 SQL을 통해 조회하고 두번째는 캐시를 통해 데이터를 가져옵니다. 그렇기때문에 m1과 m2는 같고 조회 성능을 향상시킬 수 있습니다. 실제로 캐싱기능을 지원해주는 것이 아닌 트랜잭션을 통해 제공해주는 기능이기 때문에 굉장히 짧은 시간에만 사용이 가능해서 실무에서 큰 도움은 되지 않는다고 합니다. 

 

 

트랜잭션을 지원하는 쓰기 지연 - INSERT

  1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
  2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

JDBC BATCH 기능인 한번에 여러 SQL 기능을 사용해서 한번에 SQL 전송을 하는 기능이 있습니다. JPA를 사용하지 않는 경우에는 상당히 복잡하다고 하는데 JPA를 사용하면 위와같이 간단하게 사용할 수 있습니다. 

 

 

트랜잭션을 지원하는 쓰기 지연 - UPDATE

  1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
  2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

 

 

지연 로딩과 즉시 로딩

  • 지연 로딩 : 객체가 실제 사용될 때 로딩
  • 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

JPA에서 중요한 부분 중 하나입니다. 위 그림과 같이 지연 로딩은 객체가 사용될 때 로딩을 합니다. 지연 로딩의 많은 쿼리가 발생하는 단점을 보완한 기능인데 Member 조회해서 사용할 때 team을 거의 같이 사용한다고 가정될 때 member를 로딩할 때 team도 같이 조회해서 가져오는 것을 말합니다. 옵션을 통해 지연 로딩과 즉시 로딩을 껐다 켤 수 있다고 합니다. 김영한님은 지연 로딩으로 코드를 다 작성한 후 최적화할 때 즉시 로딩이 필요한 부분만 최적화를 진행한다고 합니다.

 

 

 

 

마지막으로

ORM이라는 기술은 객체와 RDB 두 기둥위에 있는 기술입니다. JPA만 잘 안다고해서 잘 할 수 있는 것이 아니고 RDB만 잘 안다고해서 잘할 수 있는 것이 아닙니다. JPA는 객체와 RDB 두 가지 사이에서 밸런스를 잘 맞춰야합니다. 한 가지에 편향하지 않고 JPA를 공부하더라도 RDB도 꾸준하게 같이 공부해야 한다. 꼭! 명심해!

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

+ Recent posts