article thumbnail image
Published 2022. 3. 24. 00:02
반응형

ORM을 왜 사용하는가?

SQL 의존적 개발


  • 객체지향은 객체를 기반으로 개발을 해야하는데 관계형 데이터베이스를 사용하기 때문에
    반복적인 SQL 작업을 해야한다.

패러다임의 불일치를 해결하자.


상속

  • 객체는 상속관계가 있지만 데이터베이스는 없다.

출처 : 스프링 핵심 원리 - 기본편

하지만 위의 그림처럼 데이터베이스도 슈퍼타입 서브타입의 관계로 상속과 비슷한 구조로 설계할 수는 있다.
그렇다면 이제 어떤 부분이 불일치가 될까?

  • 각각의 테이블에 따른 조인 SQL을 작성하고 각각의 객체에 매핑을 해줘야 한다.
    • Album을 조회한다고 가정 해보자.
      1. 각각의 테이블에 따른 조인 SQL 작성
      2. 매핑할 각각의 객체 생성
      3. 이런 과정을 반복하게 된다.

이게 왜 문제가 될까?

만약 데이터베이스를 사용하지 않고 자바 컬렉션에 저장하여 사용한다고 했을 때와 차이가 있기 때문이다.
Item을 저장한 컬렉션이 있다고 해보자.
list.add(album)과 같이 저장을 하고, 조회할 때는 list.get(albumId)와 같이 조회하면 끝이다.
객체 관점에서 볼 때는 아주 간단하게 처리를 할 수 있다. 하짐란 데이터베이스에 의존하게 되면,
추가적인 작업이 필요하게 된다. 이런 부분이 객체는 객체 탐색을 해야하는데 데이터베이스에 의존하기 때문에
객체 탐색을 하지 못하고, SQL해결 하는 현상이 발생하게 된다.

연관관계

객체는 객체 참조를 한다. 하지만 데이터베이스는 PK와 FK로 관계를 형성한다.

  • 객체는 참조를 사용 : member.getTeam()
  • 테이블은 외래키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID

출처 : 스프링 핵심 원리 - 기본편

위 그림과 같이 객체는 Team에서는 Member를 알 수 없는 상태이지만,
테이블은 항상 양방향 관계이기 때문에 양쪽에서 모두 조회가 가능하다.
일단 여기까지만 하고 뒤에서 더 문제점을 알아보도록 하자.

객체를 테이블에 맞추어 모델링하는 현상

MyBatis같은 프레임워크를 사용하면 이를 손쉽게 할 수 있다.
그래서 보통 프레임워크를 사용해서 객체를 테이블에 맞추어 모델링을 하게 된다.
아래와 같이 모델링이 될 것이다.

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

class Team {
    Long id;     // TEAM_ID PK 사용
    String name; // NAME 컬럼 사용
}

이를 데이터 베이스에 저장하기 위해서는 아래와 같은 쿼리를 MyBatis를 사용해 관리하게 된다.

INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES...

여기까지 보면, 큰 문제는 없어 보인다.

객체지향적으로 개발을 했을 경우 아주 번거로움 추가 작업들

객체 입장에서는 Member가 TeamId를 아는게 아느라 Team객체 자체를 알아서
그래프 탐색을 해야하는 것이 객체 지향을 사용하는 이유 중 하나이다.
그래서 객체 입장에서 모델링을 다시 한다면 아래와 같은 구조가 나오게 된다.
Member객체가 Team 객체를 가지도록 변경이 되었다.

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 컬럼 사용
}

이와 같은 구조가 객체 입장에서는 그래프 탐색을 할 수 있기 때문에 맞다.
하지만 결국은 데이터베이스에 저장을 해야 하므로 추가적은 작업이 발생한다.
member.getTeam().getId()와 같은 코드가 추가 적으로 필요하다.
결국 INSERT를 위해서는 TEAM_ID컬럼이 필요하기 때문이다.

INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES...

여기까지는 어떻게 할 수는 있을 것 같다.(번거롭긴 하지만)

진짜 문제는 조회할 때 발생하게 된다.
만약 아래와 같은 쿼리를 실행 했다고 하자.

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

이런 부분은 데이터베이스 위주의 객체 모델링을 해도 해결되지는 않는다.
그래서 보통 Member와 Team을 합친 객체에 한방 쿼리로 때려박는 식으로 객체를 모델링하고,
MyBatis를 활용하게 된다. 그래서 객체 지향적인 객체 모델링이 나올 수가 없는 것이다.
이렇게 됐을 때 더 큰 문제는 Member만 조회하고 싶은 상황, Team만 조회하고 싶은 상황이 발생했을 때,
Member와 Team을 모두 조회하고 싶을 때의 모든 상황에 대해 모델도 나와야 하고, 쿼리로 나와야 하고, 쿼리를 실행하는
메서드도 나와야 한다. memberDAO.getMember(), memberDAO.getMemberWithTeam() 등과 같은 케이스들이 각각 나오게 된다.

이를 만약 데이터베이스가 아닌 자바 컬렉션에서 관리한다고 치면
패러다임이 완전히 불일치되는 것을 더욱 느낄 수 있다.

list.add(member);

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

이런 식으로 아주 간단하게 해결이 되는 것이다.
그래서 이렇게 객체지향적으로 객체지향 언어를 데이터베이스와 연동해서 사용하기 위해 나온 ORM이
JPA인 것이다.

엔티티 신뢰 문제

객체지향적 모델링을 했을 때 엔티티 신뢰 문제도 발생하게 된다.
이유는 처음 실행하는 SQL에 따라서 탐색 범위가 결정되기 때문이다.

select m.*
     , t.*
  from member m
  join team t
    on m.team_id = t.team_id

member.getTeam();  // OK
member.getOrder(); // null

해당 쿼리는 memberDAO.find()와 같은 메서드에 정의되어 있을 것이다.
이렇게 사용하다가 나중에 Order가 추가 되어서 Member에 Order객체도 추가했을 경우,
다른 개발자는 당연히 Order객체도 참조할 수 있을 거라고 생각할 것이다.
하지만 find() 메서드에서 추가적으로 작업을 잘 해주었는지 100% 신뢰할 수가 없다.
그래서 find()를 했을 때, 모든 참조 객체를 자동으로 채워주도록 하는 것이 JPA가 되는 것이다.
그렇게 되면 엔티티 신뢰 문제를 해결할 수 있는 것이다.

동일성 불일치

데이터베이스에 접근하여 조회를 할 경우 당연히 같은 값을 조회해도 다른 객체가 나올 것이다.

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 = ?";

        ...
        // SQL 실행
        return new Member(...);
    }
}

하지만 컬렉션 입장에서 보면 같은 객체가 반환되게 된다.

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

member1 == member2 // 같다.

결국 정리를 하자면, 한 가지이다.

객체를 자바 컬렉션에 관리하듯이 데이터 베이스도 관리할 수 없을까? FK를 직접 사용하지 않고, 객체를 사용할 수 없을까? 라는 것을 해결하기 위함이다.

  • 이를 해결하기 위해서는 아주 복잡한 작업이 필요하다.
  • 그 결과가 바로 JPA인 것이다.

추가 적으로

  • 데이터베이스 교체 용이
    • 방언 기능
반응형
복사했습니다!