본문 바로가기
JPA

JPA를 왜 써야 하는가?

by 성건희 2020. 4. 3.
반응형
JPA를 왜 써야 하는가

대부분의 기업에서는 MyBatis 인 SQL 매퍼를 사용한다.

하지만 최근 트랜드는 JPA 사용량이 점점 늘어나고 있는 추세이다.


jp

(출처 : https://www.slideshare.net/dongmyo/mybatis-jpa-123381168)


IT 기업으로 유명한 우아한형제들이나 쿠팡등에서는 이미 JPA를 사용하고 있다.

왜 사람들이 JPA에 열광하는 것일까?

사실 필자의 경우에는 학원에서 처음 JPA를 접했지만,

MyBatis를 사용해 본 경험은 없어서 JPA가 편하기는 했지만 크게 와닿지는 않았다.

따라서 이번에 학습을 하면서 왜 JPA를 써야 하는지 알아 볼 예정이다.

 

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

대부분 개발 언어객체지향 언어를 사용한다.

데이터베이스는 대부분 관계형 DB를 사용한다.

지금 시대는 객체관계형 DB에 저장해서 관리해야 하는 시대다.

 

SQL 중심적인 개발의 문제점..

CRUD의 무한 반복, 지루한 코드

마이바티스JDBC Template 등의 SQL 매퍼가 등장해서 약간은 편해졌다지만

여전히 반복적인 SQL을 작성해야 한다.

 

패러다임의 불일치

관계형 DB는 데이터를 잘 정규화해서 보관하는것이 목표

객체는 속성과 기능을 잘 묶어서 캡슐화해서 사용하는 것이 목표

둘 간의 차이가 발생한다.

 

객체를 관계형 DB에 반영하기 위해서는..

객체를 SQL로 변환해서 관계형 DB에 저장해야 한다.

 

SQL로 변환 하는 건 누가함?

개발자 = SQL 매퍼

 

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

객체와 관계형 DB는 크게 다음과 같은 차이가 있다.

  1. 상속
  2. 연관관계

상속

객체는 상속이 있지만, 관계형 DB는 상속이 없다. (그나마 유사한 슈퍼타입, 서브타입 관계가 있긴하다.)

 

예를 들어보자.

부모 클래스 Item이 있고, 자식 클래스 Album, Movie, Book이 있다고 가정하자.

 

Album 생성

INSERT INTO item ...
INSERT INTO album ...

Album을 생성하는건 그나마 insert 쿼리 2번으로 해결했다. 문제는 조회할 때 발생한다.

 

Album 조회

  1. Item과 Album을 JOIN 해서 결과를 만든다.
  2. 만들어진 결과로 Album, Item의 객체를 생성한다.

Movie를 조회하려면? 위 과정 반복해서 또 객체 생성..

 

연관관계

객체는 참조를 사용 : member.getTeam() (단방향)
테이블은 외래 키를 사용 : JOIN ON m.team_id = t.team_id (양방향)

 

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

class Member {
    private Long id;
    private Long teamId;
}
class Team {
    private Long id;
}

이것을 가지고 Member 테이블의 INSERT 쿼리를 짤때는 쉽게 가능하다.

INSERT INTO member (member_id, team_id) VALUES ...

 

근데 위의 방식은 뭔가 객체지향 스럽지 않은 것 같다.

Member를 다음과 같이 수정해보자.


객체다운 모델링

class Member {
    private Long id;
    private Team team;
    
    public getTeam() {
        return team;
    }
}

Member 테이블의 INSERT 쿼리를 보자.

INSERT INTO member (member_id, team_id) VALUES ...

team_id를 넣어주어야 하는데 Member에는 Team을 가지고 있기 때문에 다음과 같이 넣어줄 수 있겠다.

member.getTeam().getId();

정상적으로 INSERT가 된다.

 

문제는 조회할 때 발생한다.

SELECT m.*, t.* FROM member m JOIN team t ON m.team_id = t.team_id;

위처럼 member와 team을 조인한 결과를 가져온다.

그 후 Member, Team 객체에 맞게 관련 정보를 넣어 생성한다.

public Member find(Long memberId) {
    // SQL 실행
    Member memeber = new Member();
    // DB에서 조회한 회원 관련 정보를 모두 입력
    Team team = new Team();
    // DB에서 조회한 팀 관련 정보를 모두 입력
    
    member.setTeam(team);
    // 회원과 팀 관계 설정
    return member;
}

상당히 번거롭다.

실무에서는 이런 번거로움 때문에 SuperMemberTeam DTO와 같은 클래스를 만들어서 사용한다.

객체답게 모델링 할수록 매핑 작업만 늘어난다.

 

만약, 객체를 자바 컬렉션에 넣듯이 관리한다면?

Member member = new Member();

list.add(member); // INSERT

Member findMember = list.get(memberId); // SELECT
Team team = findMember.getTeam();

상당히 편안해진다.

 

객체 그래프 탐색

객체는 .을 통해 자유롭게 객체 그래프를 탐색할 수 있어야 한다.

ex) member.getTeam();

 

하지만 실상은 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.

SELECT m.*, t.* FROM member m JOIN team t ON m.team_id = t.team_id;
member.getTeam(); // 정상 조회 됨
member.getOrder(); // null

memberteam만 조회했으므로 실제 member에는 order가 있더라도 null이 되어

엔티티의 신뢰 문제가 발생함.

 

엔티티 신뢰 문제

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

memberDAO.find(memberId);에서 무슨짓을 했는지 확인하지 않고서는 엔티티를 신뢰할 수 없다.

레이어드 아키텍쳐는 그 다음 계층에 대해서 신뢰를 하고 있어야 한다.

하지만 위 코드는 신뢰할 수 없다.

물리적으로는 Service / DAO 로 쪼개져 있지만,

논리적으로는 코드를 직접 보지 않는 이상 신뢰가 안된다.

 

엔티티 신뢰 문제 대안

상황에 따라 동일한 회원 조회 메서드를 여러개 만든다.

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

 

SQL을 직접 다루게 되면

계층형 아키텍처에서 진정한 의미의 계층 분할이 어렵다.

 

== 비교하기

JDBC Template을 이용하는 경우

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

member1 == member2; // 다르다.
class MemberDAO {
    public Member getMember(Long memberId) {
        String sql = "SELECT * FROM member WHERE member_id = ?";
        ...
        // JDBC API, SQL 실행
        return new Member(..);
    }
}

매번 새로운 Member를 만들기 때문에 동일하지 않다.

 

자바 컬렉션에서 조회하는 경우

Long memberId = 100;
Member member1 = list.getMember(memberId);
Member member2 = list.getMember(memberId);

member1 == member2; // 같다.

컬렉션에서 조회하면 참조 값이 같기 때문에 동일하다.

 

정리

위의 예제에서 봤다시피, 객체답게 모델링 할수록 매핑 작업만 늘어난다.

그래서 많은 개발자들이 '객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까?'를 고민해왔다.

그 고민의 결과가 바로 JPA다.

 

참고

- 자바 ORM 표준 JPA 프로그래밍 - 기본편

반응형

'JPA' 카테고리의 다른 글

JPA 를 공부하면서 알게 된 내용 정리 1  (0) 2022.01.26
다양한 연관관계 매핑  (0) 2020.04.09
연관관계 매핑 기초  (0) 2020.04.06
엔티티 매핑  (0) 2020.04.06
영속성 관리 - 내부 동작 방식  (0) 2020.04.03

댓글