본문 바로가기
책을 읽어봅시다/자바 ORM 표준 JPA 프로그래밍

[자바 ORM 표준 JPA 프로그래밍] JPA 시작 0

by upswp 2022. 2. 13.

Why JPA?

SQL에 의존적인 개발 해결

JPA는 아래와 같은 문제점을 해결해 줍니다.

  • 진정한 의미의 계층 분할이 어렵다.
  • 엔티티를 신뢰할 수 없다.
  • SQL에 의존적인 개발을 피하기 어렵다.

바로 애플리케이션에서 SQL을 직접 다룰때 발생하는 문제점을 요약한 부분입니다.

패러다임의 불일치

객체 구조를 테이블 구조에 저장하는데는 한계가 있습니다. 객체와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현방법도 다릅니다.

객체지향은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공합니다. 이 부분에서 지금의 개발 단계에서 복잡한 애플리케이션을 다룰때 객체지향은 필수입니다.

관계형 데이터 베이스는 데이터 중심으로 구조화 되어 있고, 집합적인 사고를 요구합니다. 이 부분에서 객체 지향에서 이야기하는 추상화, 상속, 다형성과 같은 개념이 없기 때문에 패러다임의 불일치가 일어나게 됩니다.

상속

객체는 상속이라는 기능이 있지만,

관계형 데이터 베이스에는 상속이라는 기능이 없습니다. ( 일부 데이터 베이스는 상속기능을 지원하지만 객체의 상속과는 약간 다릅니다. 그나마 비슷하다고 볼 수 있는게 테이블의 슈퍼타입, 서브타입 관계라 할 수 있겠네요)

JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해 줍니다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯 JPA에게 객체를 저장하면 됩니다.

jpa.persist(album);

JPA를 사용해서 Item을 상속한 Album 객체를 저장해보면, persist() 메서드를 사용해서 객체를 저장하면 됩니다.

다음으로 조회는 find()메서드를 이용해서 조회를 하며 아래와 같은 쿼리문을 통해서 JPA는 Item과 album 두 테이블을 조인해서 필요한 데이터를 조회하고 그 결과를 반환합니다.

SELECT I.*, A.*
        FROM ITEM I
        JOIN ALBUM A ON I.ITEM_ID

연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회합니다. 반면 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회합니다.

객체의 관계에서는 member.getTeam()은 가능하지만 반대 방향인 team.getMember()는 참조가 없으므로 불가능합니다. 반면에 테이블은 외래키 하나로 MEMBER JOIN TEAM 도 가능하고 TEAM JOIN MEMBER도 가능합니다.

객체 그래프 탐색

객체는 마음껏 객체 그래프를 탐색할 수 있어야 합니다.

하지만 처음 실행하는 SQL 문에 따라 탐색 범위가 결정이 됩니다.

SELECT M.*, T.*
    FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID. = T.TEAM_ID
member.getTeam(); // OK
member.getOrder(); // null
class MemberService {
        ...
        public void process(){
                Member member = memberDAO.find(memberId);
                member.getTeam(); //???
                member.getOrder().getDelivery(); //???
        }
}

위의 부분에서 보면 반환하는 엔티티에 있어서 신뢰해서 쓸 수 없는 문제점이 발생하게 된다.

즉, 레이어드 아키텍쳐의 부분으로 봤을때, 해당 부분에서 엔티티에 대한 신뢰가 가능해야 레이어드 아키텍쳐가 가능한 부분인데 위의 상황을 보면 반환하는 객체의 부분을 정말로 신뢰할 수 있는가에 대한 의문점으로 보게되면서
엔티티에 대한 신뢰 문제가 발생하게 됩니다.

물리적으로는 service, dao 나눠져있는 부분으로 보이지만 논리적으로는 해당 부분에 소스를 직접 까서 어떤 쿼리문으로 가져와서 작업을 하는지 모르기 때문에 신뢰가 되지 않는 문제점이 있습니다.


🦾JPA?

ORM?

  • Object relational mapping( 객체 관계 매핑 )
  • 객체는 객체대로 설계
  • 관계형 데이터 베이스는 관계형 데이터 베이스대로 설계
  • ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어에는 대부분 ORM 기술이 존재

애플리케이션과 JDBC 사이에서 동작하는 JPA

Java 개발자가 애플리케이션을 통해서 JPA에 명령을 진행하면, JPA가 JDBC API를 호출해서 SQL을 통해 DB에 접근 후 결과를 반환하는 구조입니다.

JPA는 표준명세

  • JPA는 인터페이스의 모음
  • JPA 2.1 표준명세를 구현한 3가지 구현체
  • Hibernate, EclipseLink, DataNucleus

WHY JPA?

SQL 중심적인 개발에서 객체 중심으로 개발

생산성

유지보수

  • 각각의 쿼리를 수정할 일이 줄어들면서 수정하는 부분이 적어집니다.

패러다임의 불일치 해결

  • JPA 상속
    • 저장
    // 개발자가 할일
    jpa.persist(album);
    
    // 나머지는 JPA가 처리
    INSERT INTO ITEM ...
    INSERT INTO ALBUM ...
    • 조회
    // 개발자가 할일
     Album album = jpa.find(Album.class, albumId);
    
    // 나머지는 JPA가 처리
    SELECT I.*, A.*
            FROM ITEM I
            JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
  • JPA와 연관관계
    • 연관관계 저장
    member.setTeam(team);
    jpa.persist(member);
  • JPA와 객체 그래프 탐색
    • 객체 그래프 탐색
    • Member member = jpa.find(Memeber.class, memberId); Team team = member.getTeam();`
  • JPA와 비교하기
  • String memberId = "100"; Member member1 = jpa.find(Member.class, memberId); Member member2 = jap.find(Member.class, memberId); >>> member1 == member2; //같다 *동일한 트랜잭션에서 조회한 엔티티는 같음을 보장* // 신뢰할 수 있는 엔티티 class MemberService{ ... public void process(){ Member member = memberDAO.find(memberId); member.getTeam(); // 자유로운 객체 그래프 탐색 -> JPA 지연로딩(Lazy Loading) 기능 덕분 member.getOrder().getDelivery(); } }

성능

  • 1차 캐시와 동일한 (identity) 보장
    • 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
    • DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
    String memberId = "100";
    Member m1 = jpa.find(Member.class,memberId); // SQL
    Member m2 = jpa.find(Member.class,memberId); // 캐시
    
    println(m1 == m2) // true
    
    *SQL 한번만 실행*
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
    • 트랜잭션을 커밋할 때 까지 INSERT SQL을 모음
    • JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
    transaction.begin(); // transaction start
    
    em.persist(memberA);
    em.persist(memberB);
    em.persist(memberC);
    //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
    
    //커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
    transaction.commit();
    • UPDATE, DELETE로 인한 ROW락 시간 최소화
    • 트랜잭션 커밋 시 UPDATE,DELETE SQL 실행하고, 바로 커밋
    transaction.begin();
    
    changeMember(memberA);
    deleteMember(memberB);
    ...//비즈니스 로직 수행, 이때동안 DB 로우 락이 걸리지 않는다.
    
    //커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
    transaction.commit();
  • 지연로딩(Lazy Loading)
    • 지연로딩 : 객체가 실제 사용될 때 로딩
    • 즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
    // 지연 로딩
    Member member = memberDAO.find(memberId); // SELECT * FROM MEMBER
    Team team = member.getTeam();
    String teamName = team.getName(); // SELECT * FROM TEAM
    
    // 즉시 로딩
    Member member = memberDAO.find(memberId); // SELECT M.* , T.* FROM MEMBER JOIN TEAM ...
    Team team = member.getTeam();
    String teamName = team.getName();

데이터 접근 추상화와 벤더 독립성

표준