본문 바로가기
Backend/JPA

[JPA] JPA 소개

by 2245 2023. 12. 1.

서론

JPA는 ORM 기술입니다. ORM이란, Object-Relational Mapping (객체 관계 매핑) 의 약자로 객체와 관계를 매핑해주는 기술입니다. 즉, 객체 지향 언어(ex) Java)로 작성된 데이터와 관계형 데이터베이스(RDB) 사이를 매핑을 해주는 기술입니다. 

JPA가 어떻게 객체와 관계형 데이터베이스를 연결해주는지에 관해 작성하도록 하겠습니다.

 

 

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

JPA 설명의 앞서, 객체와 관계형 데이터베이스가 어떻게 다른지 알아본 후, 이를 JPA가 어떻게 해결하는지 알아보도록 하겠습니다. 

 

  1. 상속
  2. 연관 관계
  3. 데이터 타입
  4. 데이터 식별 방법

 

1. 상속

  • 객체 지향 언어에서는 왼쪽과 같이 상속 관계를 사용할 수 있습니다. 
  • 하지만, RDB에선 상속 관계가 없습니다.
  • 오른쪽과 같이 RDB에서 상속 관계를 표현하기 위해선 각각의 테이블을 생성한 후 PK, FK를 사용하여 연관관계를 매핑합니다. 

 

❔RDB에 Album(객체)을 저장하려면?

  1. 객체를 분해한다.
  2. INSERT INTO ITEM ...
  3. INSERT iNTO ALBUM ...

❔RDB에서 Album(객체)을 조회하려면?

  1. 테이블 조인 SQL을 작성한다.
  2. 각각 ITEM, ALBUM 객체를 생성한다.
  3. ...

상상만 해도 복잡한 것을 알 수 있습니다. 따라서, DB에 저장할 객체에는 보통 상속 관계를 사용하지 않습니다. 

 

 

2. 연관 관계

  • 객체는 참조를 사용합니다. ex) member.getTeam()
  • 반면, 테이블은 외래 키를 사용합니다. ex) JOIN ON M.TEAM_ID = T.TEAM_ID

 

❔객체를 테이블에 맞추어 모델링하면?

class Member {
    String id;       //MEMBER_ID (PK)
    Long teamId;     //TEAM_ID (FK)
    String username; //USERNAME
}

class Team {
    Long id;         //TEAM_ID (PK)
    String name;     //NAME
}

 

위와 같이 teamId를 Member가 가지고 있어야 다음과 같은 SQL Query를 작성할 수 있습니다. 

하지만, 이는 객체스러운 모델링이 아닙니다. 

 

 

객체다운 모델링

class Member {
    String id;       //MEMBER_ID
    Team team;       //객체는 참조로 연관관계를 맺는다. 
    String usrename; //USERNAME

    Team getTeam() {  //getTeam()으로 바로 Team을 꺼낼 수 있어야 한다. 
        return team;
    }
}

class Team {
    Long id;      //TEAM_ID (PK)
    String name;  //NAME
}

 

이와 같이 getTeam()을 사용하여 Team을 조회할 수 있어야 합니다. 하지만, 이러한 모델링은 다음과 같은 문제점을 갖습니다. 

 

문제점 - 등록

  • 위와 같이 데이터베이스에 INSERT하기 굉장히 까다롭습니다.
  • TEAM_ID 하나를 구하기 위해서 member.getTeam().getId()를 해야 합니다.

 

문제점 -  조회

조회는 더 번거롭습니다.

SELECT M.*, T.* FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
  • Member와 Team 둘 다 필요하므로 모두 조회합니다.

 

public Member find(String memberId) {
    //SQL 실행
    Member member = new Member(;  //데이터베이서에서 조회한 회원 관련 정보를 사용하여 객체 생성
    Team team = new Team();       //데이터베이스에서 조회한 팀 관련 정보를 사용하여 객체 생성

    //회원과 팀 관계 설정
    member.setTeam(team);
    return member;
}
  • SQL을 실행한 후 member와  team 객체를 각각 생성한 후 member와 team 관계를 설정까지 해야 합니다.

 

❔객체 모델링을 자바 컬렉션에서 관리한다면?

list.add(member);
Member member = list.get(memberId);
Team team = member.getTeam();

자바 컬렉션을 사용하면 단순히 Member를 저장하고, Member만 찾아도 getTeam()을 사용하여 Team을 조회할 수 있습니다. 

 

 

3. 객체 그래프 탐색

만약, 객체가 다음과 같은 연관 관계를 맺고 있다고 가정해 봅시다. 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다.  

  • Member.getTeam()
  • Member.getOrder().getDelivery()
  • 즉, . (참조)를 사용하여 자유롭게 탐색할 수 있어야 합니다. 

 

하지만, SQL에선 실행하는 SQL에 따라 탐색 범위가 결정됩니다. 

SELECT M.*, T.*
From Member M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam();    //OK
member.getOrder();   //null
  • Member에는 Team과 Order라는 필드가 둘 다 있습니다.
  • 하지만, SQL을 통해 Member와 Team만 조회했기 때문에 Order는 탐색이 불가능합니다. 

 

이 경우, 엔티티 신뢰 문제가 발생합니다.

엔티티 신뢰 문제 발생

class MemberService {
    ...
    public void process() {
        Member member = memberDAO.find(memberId);
        member.getTeam();   //???
        member.getOrder().getDelivery();   //???
    }
}
  • 계층형 아키텍쳐(ex) controller - service - dao) 에선 다음 계층을 믿고 쓸 수 있어야 합니다. 즉, 계층에서 넘어온 데이터를 믿고 쓸 수 있어야 합니다. 그래야 계층의 의미가 성립합니다.
  • 하지만, 현재 memberDao에서 memberId로 member를 조회했을 때, getTeam()이나 getOrder() 등 탐색이 불가능한 사태가 발생할 수 있습니다. 즉, 객체 그래프를 자유롭게 탐색할 수가 없습니다.
  • 이런 경우, 현재 Service 계층을 개발하고 있어도, Dao 코드를 확인해서 어떤 SQL을 사용하여 데이터를 가져오는지를 확인해야 합니다.

 

보통, 상황에 따라 동일한 회원 조회 메서드를 여러 개 생성해 놓습니다.

memberDao.getMember();    //Member만 조회
memberDao.getMemberWithTeam();  //Member와 Team 조회
memberDao.getMemberWithOrderWithDelivery();  //Member, Order, Delivery 조회

 

하지만, 모든 객체를 미리 로딩해 놓을 수는 없습니다. 

 

 

4. 동일 객체 비교

String memberId = "100";
Member member1 = memberDao.getMember(memberId);
Member member2 = memberDao.getMember(memberId);

member1 != member2;     //두 객체는 다르다.
class MemberDao {
    public Member getMember(String memberId) {
        String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
        ...   //JDBC API, SQL 실행
        return new Member(...);
}
  • SQL을 사용하여 객체를 조회한다면, 항상 새로운 객체를 생성하여 반환하기 떄문에 반환된 두 객체는 서로 다릅니다.
  • 하지만, 자바 컬렉션에서 조회한다면 두 객체는 같습니다. 
String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

member1 == member2;    //같다.

 

 

결론

  • 객체답게 모델링을 할 수록 SQL 매핑 작업만 늘어난다.
  • 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수 없을까?
  • 해결책 → JPA : Java Persistence API

 


JPA?

  • Java Persistence API
  • 자바 진영의 ORM 기술 표준

 

ORM?

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

 

JPA는 애플리케이션과 JDBC 사이에서 동작한다.

 

JPA 동작 1. 저장

  • JPA에서 사용하는 객체를 Entity라고 부릅니다. (ex) Member)
  • 개발자는 member.persist 라고만 작성하면, JPA가 Entity 분석, Insert SQL 생성, JDBC API 사용, 패러다임 불일치 해결의 일을 대신 해줍니다. 

 

JPA 동작 2. 조회

 

 

JPA를 왜 사용해야 하는가?

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

 

생산성 (JPA와 CRUD)

  • 저장: jpa.persist(member)
  • 조회: Member member = jpa.find(memberId)
  • 수정: member.setName(”변경할 이름”) (트랜잭션 안에서 데이터를 변경하면, 트랜잭션이 끝나는 시점에 자동으로 update query가 db에 실행됩니다.)
  • 삭제: jpa.remove(member)

 

유지보수

[기존 - 필드 변경 시 모든 SQL 수정]

 

 

[JPA - 필드만 추가하면 된다. SQL은 JPA가 처리]

 

JPA와 패러다임 불일치 해결

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

 

JPA와 상속

 

 

저장

조회

 

 

 

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

 

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

class MemberService {
    ...
    public void process() {
        Member member = memberDAO.find(memberId);
        member.getTeam();   //자유로운 객체 그래프 탐색
        member.getOrder().getDelivery();
    }
}

 

 

JPA와 동일 객체 비교

String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);

member1 == member2;    //같다.
  • 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장합니다.

 

 

JPA의 성능 최적화

  1. 1차 캐시와 동일성(identity) 보장
  2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  3. 지연 로딩(Lazy Loading
참고 
두 애플리케이션 중간에 기술이 하나 추가되면, 다음 2가지의 이점이 있습니다. (애플리케이션과 DB 사이에 추가된 JPA)
1. Buffer Writing : 모아서 보낼 수 있다.
2. 캐싱 : 조회할 때 중간에서 이미 조회된 객체를 가져올 수 있다.

 

 

1차 캐시와 동일성 보장

  1. 같은 트랜잭션 안에서는 같은 엔티티를 반환합니다.(캐싱) → 약간의 조회 성능 향상
  2. 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을 한 번만 실행합니다.

 

 

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

  1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모읍니다.
  2. JDBC BATCH SQL 기능을 사용해서 한 번에 SQL 전송합니다.
transaction.begin();  //트랜잭션 시작

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

transaction.commit();  //트랜잭션 커밋
//커밋하는 순간에 데이터베이스에 INSERT SQL을 모아서 보낸다.

 

 

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

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

changeMember(memberA);
deleteMember(memberB);
비지니스_로직_수행();   //비지니스 로직 수행동안 dB 로우 락이 걸리지 않는다.

transaction.commit();  //트랜잭션 커밋
//커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
참고 update나 delete는 commit을 하기 전까지 해당 로우에 락이 걸립니다.

 

 

지연 로딩과 즉시 로딩

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

  • member의 team을 자주 사용하지 않는다면, member만 조회하는 것이 성능 상 더 나을 수 있습니다. 
  • 성능을 보고 일단 지연 로딩으로 설정을 해놓았다가, 이 부분은 즉시 로딩으로 하나의 쿼리를 사용하는 것이 좋다고 판단되면 그 부분만 즉시 로딩으로 설정할 수 있습니다.

 

 

결론

ORM은 객체와 RDB 두 기둥 위에 있는 기술입니다.

 

 


출처

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

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

저는 야생형이 아니라 학자형인가봐요^^ 활용편 넘어갔다 30% 정도 듣고 도저히 답답해서 기본편을 들어버렸네요^^. 한주 한주 김영한님 강의 들으니 렙업되는 모습을 스스로 느낍니다. 특히 실

www.inflearn.com

 

'Backend > JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계 매핑  (0) 2023.12.18
[JPA] 연관관계 매핑  (0) 2023.12.18
[JPA] 엔티티 매핑  (0) 2023.12.18
[JPA] 영속성 컨텍스트  (0) 2023.12.18
[JPA] Hello JPA  (0) 2023.12.02