JPA) 사용해야하는 이유 (SQL 의존적 개발의 문제)

Date:    Updated:

카테고리:

SQL에 의존적인 개발

  • 주로 어플리케이션을 개발할 때 관계형 데이터베이스(RDB)를 사용한다. (Oracle, MySQL, PostgreSQL,..)
  • image
  • 객체를 영구 보관하는 다양한 저장방안이 존재하지만 현실적인 대안은 관계형 데이터베이스(RDB)다.

  • 반복적이며 지루한 SQL 코드를 매번 작성해야 한다.
    • INSERT INTO
    • UPDATE
    • SELECT
    • DELETE
    • 자바 객체를 SQL로 변환 작업 (혹은 SQL 결과를 자바 객체로 변환)
  • 유지보수에 용이하지 않다
    • EX) 예를 들어 SQL에 필드를 하나 추가한다고 했을 때..
    • image
    • SQL에 의존적인 개발을 피하기 어렵다!

패러다임의 불일치 발생

객체지향의 사상과 RDB의 사상이 다르다!

  • RDB
    • 객체를 잘 정규화 하여 저장소에 보관하는 것이 목표
  • 객체
    • 필드 및 메서드를 묶어서 잘 캡슐화 하는것이 목표
    • ‘객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.’ - 어느 객체지향 개발자

사상이 다른 두 가지를 연결(매핑)지으려다 보니 여러 문제가 발생한다.

image

  • RDB는 SQL밖에 인식하지 못하므로 결국 객체를 SQL 형태로 구현해야함.
  • 결국 RDB와 관련된 기능을 구현할 때 마다 매번 SQL을 구현해야하는 처지에 놓임
  • 나는 개발자인가 SQL 작성자인가? (개발자 ≓ SQL 매퍼)

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

상속

image

  • 객체
    • 상속관계가 존재한다.
  • RDB
    • 상속관계가 존재하지 않지만 유사한 방식의 슈퍼타입-서브타입 물리 모델이 있다.
    • Album 객체를 RDB에 저장할 때
      • 객체를 분해한다.
        • Album이 Item을 상속받고 있으므로
      • INSERT 쿼리를 2번 날린다.
        • INSERT INTO ITEM ...
        • INSERT INTO ALBUM ...
    • Album 객체를 RDB에서 조회할 때
      • Album 과 Item 테이블을 JOIN해서 SQL 결과로 가져온다.
      • Album 과 Item 객체를 생성하고 각 필드에 set 한다
      • Album이 아닌 Movie, Book 등을 조회하려면 위 과정을 반복해야한다..
      • 너무 복잡하며 불편하다. 그래서 DB에 저장할 객체에는 상속관계를 사용하지 않는다!
    • 만약 DB가 아니고 자바 컬렉션에서 관리를 한다면? (해결 가능성)
      • 저장
        • list.add(album)
          • INSERT 쿼리를 2번 날릴 필요 없이 너무 간단하다!
      • 조회
        • Album album = list.get(albumId);
        • 부모 타입으로 조회 (다형성 활용)
          • Item item = list.get(albumId);
        • JOIN 쿼리 작성, 각각의 객체 생성 등 복잡한 과정을 거칠 필요가 없다!

연관관계

image

  • 객체
    • 참조를 사용한다.
      • member.getTeam();
    • 단방향으로만 관계가 존재한다.
      • Member의 경우 Team 타입의 속성이 존재하지만 Team은 Member 타입의 속성이 없다.
  • RDB
    • 외래키를 사용한다.
      • JOIN ON M.TEAM_ID = T.TEAM_ID
    • 양방향으로 관계가 존재한다.
      • MEMBER 테이블의 TEAM_ID 외래키를 통해서 가능하다!
        • MEMBER.TEAM_ID = TEAM.TEAM_ID (MEMBER 테이블 기준 조회)
        • TEAM.TEAM_ID = MEMBER.TEAM_ID (TEAM 테이블 기준 조회)

객체의 모델링 과정 중 문제

객체를 테이블에 맞춰서 모델링 한 뒤 테이블에 저장

class Member {
  String id; //MEMBER_ID 컬럼 사용
  Long teamId; //TEAM_ID FK 컬럼 사용
  String username;//USERNAME 컬럼 사용
}
class Team {
  Long id; //TEAM_ID PK 사용
  String name; //NAME 컬럼 사용
}

// insert SQL
/* INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ... */

위 방법은 객체지향 관점에서 봤을때 객체지향스럽지 않다. (객체다운 모델로 수정하자.)

class Member {
  String id; //MEMBER_ID 컬럼 사용
  Team team; //참조로 연관관계를 맺는다. (Long 타입의 id가 아닌 Team 타입의 객체가 들어가는게 객체지향 답다.)
  String username;//USERNAME 컬럼 사용
  
  Team getTeam() {
    return team;
  }
}

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;
    }
}

// insert SQL (TEAM_ID를 넣기 위해 `member.getTeam().getId()` 를 통해 호출)
/* INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ... */

수정한 객체 모델로 테이블에서 조회할 때 (문제발생)

// select SQL
/*
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 결과를 사용하기위해 객체를 생성하고.. 객체에 set하고.. 너무 번잡하다.*/

이러한 과정을 자바 컬렉션에서 관리한다면? (해결 가능성)

  • 조회
    • list.add(member);
  • 저장
    • Member member = list.get(memberId);
    • Team team = member.getTeam();
  • 전부 코드 한줄로 되어서 매우 간편하다!
  • 하지만 해당 모델링을 RDB에 넣는 순간 매우 복잡해지며 생산성이 나오지 않는다.

객체 그래프 탐색에서의 문제

image

  • 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다.
    • member.getTeam(), member.getOrder().getDelivery()

그러나 처음 실행하는 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 객체는 불가능 (온전한 객체 그래프 탐색 불가능)

  • 결과적으로 엔티티에 대한 신뢰 문제가 발생함.
    class MemberService {
     ...
    public void process() {
      Member member = memberDAO.find(memberId);
      member.getTeam(); //???
      member.getOrder().getDelivery(); // ???
    }
    }
    
    • member.getTeam();, member.getOrder().getDelivery();
      • 맞는 결과를 가져올 수 있다고 장담할 수 없다!
      • memberDAO.find(memberId);
        • 해당 메소드 내에서 어떤 동작(쿼리, 객체 세팅 등)이 이루어 지는지 직접 눈으로 확인하기 전까지 알 수 없기 때문이다.
        • 신뢰할 수 없다!
  • 그렇다고 모든 객체를 미리 로딩할 수는 없다.
    emberDAO.getMember(); //Member만 조회
    memberDAO.getMemberWithTeam();  //Member와 Team 조회
    memberDAO.getMemberWithOrderWithDelivery(); //Member,Order,Delivery 조회
    
    • 다음과 같이 상황에 따라 해당 객체의 조회 메서드를 여러개 생성한다?
      • 너무 많은 메소드를 생성해야 하며 복잡하다.
      • 이런식으로 많은 메소드를 만들어 SQL을 직접 다루게 되면 계층형 아키텍처 진정한 의미의 계층 분할이 어렵다.
      • 물리적으로는 Service, DAO등으로 계층이 나뉘어져 있지만 논리적으로는 강결합 되어있다.

객체 간 비교할 때 문제

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(...);
  }
}
  • member1 == member2false다.
    • 쿼리로 가져온 결과를 new Member(..); 로 다시 반환하기 때문에 당연히 결과가 다르다.
  • 자바 컬렉션에서 조회하여 비교한다면? (해결 가능성)
    String memberId = "100";
    Member member1 = list.get(memberId);
    Member member2 = list.get(memberId);
    member1 == member2; //같다.
    
  • 컬렉션에서 조회하여 갖고 오기 때문에 참조값이 같아서 true 반환

결과적으로..

  • 이렇듯 객체답게 모델링 하면 할수록 매핑 작업만 늘어난다.
  • 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수 없을까??
    • 그 고민의 결과로 자바 진영에서 JPA (Java Persistence API) 가 탄생하게 된다.

📣 Reference

본 포스팅은 김영한님의 강의를 듣고 스스로 정리 및 추가한 내용입니다.

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

댓글남기기