spring/스프링 DB 2편

4. 데이터 접근 기술 - JPA (1) JPA 와 JPA 을 사용해야 하는 이유

sh1mj1 2023. 2. 21. 02:09

 

이 글은 배민 기술이사 김영한 이사님의 인프런 강의 "스프링 DB 2편 - 데이터 접근 활용 기술" 을 기반으로 작성되었습니다. 문제 시 삭제 조치하겠습니다.

 

아래와 같은 순서로 공부를 진행합니다.

 

  • JPA 시작
  • ORM 개념 1 - SQL 중심적인 개발의 문제점
  • ORM 개념 2 - JPA 소개

 

1. JPA 시작

 

 

스프링과 JPA는 자바 엔터프라이즈(기업) 시장의 주력 기술입니다.

스프링이 DI 컨테이너를 포함한 애플리케이션 전반의 다양한 기능을 제공한다면, JPA는 ORM 데이터 접근 기술을 제공합니다.

 

스프링 + 데이터 접근기술의 조합을 구글 트랜드로 비교했을 때 아래와 같습니다.

  • 글로벌에서는 스프링+JPA 조합을 80%이상 사용
  • 국내에서도 스프링 + JPA 조합을 50%정도 사용하고, 2015년 부터 점점 그 추세가 증가

 

JPA는 스프링 만큼이나 방대하고, 학습해야 할 분량도 많습니다. 하지만 한번 배워두면 데이터 접근 기술에서 매우 큰 생산성 향상을 얻을 수 있습니다. 대표적으로 JdbcTemplate 이나 MyBatis 같은 SQL 매퍼 기술은 SQL을 개발자가 직접 작성해야 하지만, JPA를 사용하면 SQL도 JPA가 대신 작성하고 처리해줍니다.

 

실무에서는 JPA를 더욱 편리하게 사용하기 위해 스프링 데이터 JPA 와 Querydsl 이라는 기술을 함께 사용합니다.

근본적으로 중요한 것은 JPA입니다. 스프링 데이터 JPA, Querydsl은 JPA를 편리하게 사용하도록 도와주는 도구인 것이죠.

 

2. ORM 개념 1 - SQL 중심적인 개발의 문제점

관계형 DB (Oracle, MySQL)에서는 SQL만 사용할 수 있으므로 SQL 의존적인 개발을 할 수 밖에 없습니다.

관계형 DB의 목적과 객체지향 프로그래밍의 목적이 일치하지 않습니다

.

그러나, 객체를 저장할 수 있는 가장 현실적인 방안은 관계형 DB입니다.

 

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

상속

https://user-images.githubusercontent.com/52024566/132992161-2db24f9c-3106-4d50-bdf1-9b83088d63a3.png

 

DB에서

Album 객체를 저장할 경우 - 객체를 분해하여 artistalbum에 저장, name, price, dtypeitem에 저장합니다.

Album 객체를 조회할 경우 - 각 테이블을 조회하는 Join SQL 을 작성하고, 각각의 객체를 생성하고 …. 이런식으로 복잡합니다. 그래서 DB에 저장할 객체에는 상속 관계를 쓰지 않습니다.

 

자바에서

Album 객체를 저장할 경우

list.add(album);

Album 객체를 조회할 경우

Album album = list.get(albumId);

⬇️

Item item = list.get(albumId);

부모 타입으로 조회 후에 다형성을 활용할 수도 있습니다.

 

연관관계

객체 연관관계와 테이블의 연관관계를 비교해봅시다.

https://user-images.githubusercontent.com/52024566/132992295-e91aa5be-9080-47de-b9e5-efa4ab6a40a2.png

객체는 참조를 사용 - member.getTeam()

테이블은 외래 키를 사용 - JOIN ON M.TEAM_ID = T.TEAM_ID

 

객체를 테이블에 맞추어 모델링했을 때 형태는 아래와 같습니다.

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

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

하지만 객체답게 모델링했을 때는 아래와 같습니다.

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

  Team getTeam() {
      return team;
  }
}

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

 

객체 모델링 조회

SELECT M.*, T.*
  FROM MEMBER M
  JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
public Member find(String memberId) {
    // SQL 실행
    Member member = new Member();
    // 데이터베이스에서 조회한 회원 관련 정보 입력
    Team team = new Team();
    // 데이터베이스에서 조회한 팀 관련 정보 입력

    // 회원과 팀 관계 설정
    member.setTeam(team);
    return member;
}

이런 식으로 굉장히 번거로운 작업을 거쳐야 합니다.

 

객체 그래프 탐색

객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다. 하지만 실행하는 SQL 에 따라 탐색 범위가 결정되기 때문에 SQL 중심적인 개발에서는 객체 그래프 탐색이 불가능합니다.

 

엔티티 신뢰 문제

class MemberService {
    public void process() {
        Member member = memberDAO.find(memberId);
        member.getTeam(); // 사용 가능한가?
        member.getOrder().getDelivery(); // 사용 가능한가?
    }
}

SQL에서 탐색된 객체 이외에는 사용할 수 없으므로 엔티티를 신뢰할 수 없습니다. 신뢰할 수 없다는 것은 get 문법을 통해서 불러왔을 때 null 이 아닌지 등의 문제를 말합니다.

 

계층 아키텍쳐에서는 이전 계층에서 넘어온 내용을 신뢰할 수 있어야 하는데 SQL 중심적인 개발에서는 그것이 불가능하므로 객체가 SQL에 의존하게 됩니다.

그렇다고 모든 객체를 미리 로딩할 수 없으므로 상황에 따라 동일한 회원 조회 메서드를 여러 번 생성해야 합니다.

 

생성한 객체 비교

SQL 중심적인 개발

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

member1 == member2; // 다르다.

 

자바 컬렉션

String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

member1 == member2; // 같다.

SQL 중심적인 개발에서는 getMember()을 호출할 때 New Member()로 객체를 생성하기 때문에 member1member2가 다릅니다.

 

그러나 자바 컬렉션에서 조회할 경우 member1member2의 참조 값이 같기 때문에 두 객체는 같습니다.

 

이렇게 객체를 객체답게 모델링할수록 불필요해보이는 매핑 작업만 늘어납니다.

 

3. ORM 개념 2 - JPA 소개

이제 JPA 을 개념적으로 설명하겠습니다.

JPA 이란?

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

 

ORM 이란?

ORMObject-relational mapping(객체 관계 매핑)의 약자입니다.

 

객체는 객체대로 설계하고 관계형 데이터베이스는 관계형 데이터베이스대로 설계하는 것이 핵심입니다.

ORM 프레임워크가 중간에서 매핑을 담당해줍니다. 대중적인 언어에는 대부분 ORM 기술이 존재합니다. 당연히 파이썬에도 ORM 기술이 따로 있습니다.

 

JPA 동작

저장

https://user-images.githubusercontent.com/52024566/132992893-ccfa7103-2a55-4f81-80c2-4e4bd269fefd.png

조회

https://user-images.githubusercontent.com/52024566/132992894-d55e1e4b-5833-44cc-bb30-4a1006a840ac.png

JPA는 표준 명세

JPA는 인터페이스의 모음입니다.

JPA 2.1 표준 명세를 구현한 3가지 구현체가 아래 그림처럼 존재합니다.

https://user-images.githubusercontent.com/52024566/132992940-b11dc52d-524e-4897-8b8a-ff101b5af5d4.png

보통 Hibernate 을 가장 대중적으로 사용합니다.

 

JPA를 왜 사용해야 하는가?

SQL 중심적인 개발에서 객체 중심으로 개발이 가능해집니다.

 

JPA 을 사용하여 생산성이 향상됩니다.

간단하게 아래 코드로 저장, 조회, 수정, 삭제를 구현할 수 있습니다.

  • 저장: jpa.persist(member)
  • 조회: Member member = jpa.find(memberId)
  • 수정: member.setName(“변경할 이름”)
  • 삭제: jpa.remove(member)

 

또한 유지보수에도 이점이 있습니다.

  • 기존에는 필드 변경시 모든 SQL 문을 수정해야 했지만, JPA에서는 필드만 추가하면 SQL은 JPA가 처리해줍니다.

 

패러다임의 불일치를 해결할 수 있습니다.

  1. JPA와 상속
    특정 객체를 저장할 경우 상속 관계를 JPA가 분석하여 필요한 쿼리를 JPA가 생성해줍니다.
  2. JPA와 연관관계, JPA와 객체 그래프 탐색
    지연 로딩을 사용하여 신뢰할 수 있는 엔티티, 계층을 제공해줍니다.
  3. JPA와 비교하기
    동일한 트랜잭션에서 조회한 엔티티는 같음을 보장합니다.

 

또한 성능 최적화 기능도 있습니다.

1차 캐시와 동일성(identity)을 보장합니다.

  1. 같은 트랜잭션 안에서는 같은 엔티티를 반환하여 약간의 조회 성능이 향상됩니다.
  2. DB Isolation Level 이 Read Commit 이어도 애플리케이션에서 Repeatable Read 을 보장합니다.
  3. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
    1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모읍니다.
    2. JDBC BATCH SQL 기능을 사용해서 모았던 INSERT SQL 을 한 번에 전송합니다.
    3. UPDATE, DELETE로 인한 로우(ROW)락 시간을 최소화합니다.
    4. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋해줍니다.
    5. 지연 로딩(Lazy Loading) 기능을 수행해줍니다.(즉시 로딩: JOIN SQL 로 한번에 연관된 객체까지 미리 조회)
    6. (지연 로딩: 객체가 실제 사용될 때 로딩 )
![https://user-images.githubusercontent.com/52024566/132993188-add758c8-5c57-4be8-ae05-ee5a6b6ea74b.png](https://user-images.githubusercontent.com/52024566/132993188-add758c8-5c57-4be8-ae05-ee5a6b6ea74b.png)

 

또한 데이터 접근 추상화을 제공하며 벤더 독립성을 제공합니다.

 

그리고 결국 JPA 는 자바 진영에서의 표준입니다!

 

이렇게 JPA, ORM 을 소개하고 사용해야 하는 이유를 설명했습니다. 다음 글에서는 본격적으로 JPA 을 프로젝트에 직접 적용해 볼 것입니다.