ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 1. JPA 소개 - SQL 중심적인 개발의 문제점
    ComputerScience/JPA 2022. 6. 2. 23:38

    시작하기에 앞서

    • Java의 ORM 표준 기술인 JPA에 대해서 알아보도록 합니다.
    • 해당 게시글은 Infrean의 김영한 개발자의 '자바 ORM 표준 JPA 프로그래밍'을 참고하여 작성됐습니다.

    SQL 중심적인 개발의 문제점

    • 현재 시중의 프로그램 중 객체 지향 언어로 개발된 프로그램들이 정말 많습니다. Java, Scala 등의 객체 지향 언어 중 Java는 아직까지도 많은 개발자들에게 사랑받는 언어 중 하나입니다.
    객체지향언어
    객체지향언어란 프로그램을 다수의 객체로 만들고, 이들끼리 서로 상호작용하도록 만드는 프로그래밍 언어
    • 그리고 애플리케이션에서 데이터베이스 세계의 패권은 아직 RDB(관계형 데이터베이스)가 쥐고 있죠. NoSQL도 많이 뜨고 있지만 NoSQL은 대용량 데이터분석 등에 용이한 DBMS입니다.
    • 따라서 객체 지향 언어와 관계형 데이터베이스를 사용하는 개발은 "객체"를 "관계형DB"에 저장하는 모습을 띄고 있습니다.
    • 이러한 형식은 SQL 중심적인 개발이 될 수 밖에 없으며 이는 많은 문제점을 안게 됩니다.

    무한 반복, 지루한 코드

    • CRUD... INSERT INTO, UPDATE, SELECT, DELETE... TABLE을 하나 만들고 관리하기 위해선 반복적인 SQL 중심의 개발이 진행되게 됩니다.

    1. 다음과 같은 코드가 있다고 합시다. memberId와 name 속성을 가진 Member Class를 위해 다음과 같이 SQL을 작성합니다.

     

    2.  만약 객체에 tel이라는 필드가 추가된다면 Member Class를 다루고 있던 모든 쿼리문을 수정해줘야 하는 번거로움이 생깁니다.

    • 이러한 예시와 같이 이러한 개발은 SQL에 의존적인 개발을 불가피하게 만듭니다.

     

    패러다임의 불일치

    • 사실 객체를 영구 보관하는 다양한 저장소는 RDB말고도 많은 저장소가 있습니다.
    • 위에서 언급했던 NoSQL에 저장할 수도 있고, File구조에 저장할수도 있습니다.
    • 하지만 현실적인 방안은 객체를 관계형 데이터베이스에 저장해줘야 하는것이고 객체를 SQL로 변환하는 과정이 필요합니다.

    • 이 과정을 도대체 누가 진행해줄까요? 맞습니다. 개발자가 직접 진행해줘야 합니다. 개발자가 거의 SQL Mapper 역할을 진행하는거죠.
    • 이러한 과정중에 객체와 관계형 데이터베이스의 차이가 발생하게 됩니다. 중요한 차이들은 다음과 같습니다.
      • 상속
      • 연관관계
      • 데이터 타입
      • 데이터 식별 방법

     

     

    상속

    • Item이라는 객체와 그를 상속받는 Album, Movie, Book 객체가 있습니다. 
    • 그리고 그것과 상응하는 DB에서의 ITEM, ALBUM, MOVIE, BOOK Table가 있습니다.
    • 언뜻보면 두 모습은 비슷해보이지만 실제 언어의 작동은 다르게 나타납니다.

     

    먼저, SQL에서의 과정을 살펴보겠습니다.

     

    SQL에서의 Album 저장

      1. 객체를 분해해야 합니다.

      2. 분해한 뒤 먼저 INSERT문을 수행해줍니다. 'INSERT INTO ITEM'...

      3. ITEM 뿐 아니라, ALBUM의 INSERT도 수행해줘야 합니다. 'INSERT INTO ALBUM'...

     

    이와 같이, 슈퍼타입과 서브타입의 Table 모두 INSERT를 수행해줘야 정상적으로 작동하는 모습입니다. 그렇다면 조회는 어떨까요?

     

    SQL에서의 Album 조회

      1. 각각 테이블에 따른 조인 SQL을 작성해주고...

      2. 가지고 온 결과를 바탕으로 Album 객체를 생성해주고...

     

    만약 Book을 조회한다고 하더라도 똑같은 작업을 반복해줘야 합니다. 엄청 복잡한 작업이죠

     

    그렇다면 자바 컬렉션에서 조회한다면 어떻게 될까요?

    자바 컬렉션에 저장하기 위해선 단순히 다음 코드를 입력해주면 됩니다.

    list.add(album);

    위의 SQL의 작업과는 다르게 add 메소드 한줄로 쉽게 작성할 수 있죠.

     

    조회도 간단합니다.

    Album album = list.get(albumId);

    코드를 이용해서 원하는 Album에 대해 조회할 수 있습니다.

     

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

    Item item = list.get(albumId)

    다형성(polymorphism)이란
    하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미합니다.
    자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.

     

    이처럼 상속이 된 객체의 경우 객체 지향 언어와 SQL의 작동이 차이가 나게 됩니다.

     

     

     

    연관관계

    연관관계가 있는 두 객체를 살펴봅시다.

    Member 객체와 Team 객체가 연관관계에 있습니다.

     

    객체는 이 연관관계를 참조를 사용하여 표현합니다 : member.getTeam()

    테이블은 외래 키를 사용하여 연관관계를 표현합니다. : JOIN ON M.TEAM_ID = T.TEAM_ID

     

    여기서 객체 연관관계를 보시면 Member에서 Team을 참조할 수 있지만 Team에선 Member를 참조할 수 없습니다.

    하지만 테이블에선 Key를 이용해서 서로가 서로를 참조할 수 있죠.

     

    객체를 테이블에 맞춰 모델링하는 것은 어떻게 이뤄질까요?

     

    연관관계에 맞게 Member Class에는 teamId를 선언해주어 연관관계를 설정합니다.

     

     

     

     

    이는 보기에는 테이블에 맞춘 객체 저장이라고 할 수 있죠.

     

     

    그런데 다시 생각해보니, 연관관계를 맺을때는 단순 Key값이 아니라 그 객체 참조값을 가져야 한다고 생각해서 Team team;과 같이 설계할 수 있습니다.

     

     

     

    위 처럼 된다면 저장할 때는 어떻게 해야 할까요? 조금 복잡하지만 member.getTeam().getId() 메소드 호출을 통해 team의 ID를 찾을 수 있고 이를 INSERT에 활용할 수 있게 됐습니다. 

    하지만 번거로운 문제는 조회에서 발생합니다.

    SQL에서는 JOIN을 통해 Member와 Team을 조회할 수 있습니다.

    하지만 이를 코드로 만들기 위해선 Member 객체를 생성하고 이에 대한 정보를 모두 입력한 뒤 Team도 그 절차를 거치고, 회원과 팀의 관계를 설정해주고 member를 return해줘야 이 작업이 끝이 납니다. 정말 복잡한 작업입니다.

     

    그렇다면 자바 컬렉션에서의 연관관계 관리는 어떨까요?

     

    객체저장은 add로 쉽게 가능합니다.

    list.add(member);

     

    Member와 Team의 조회도 간단하게 수행할 수 있죠.

    Member member = list.get(memberId);

    Team team = member.getTeam();

     

    이처럼 객체지향적으로 설계하다보면 두 방법간의 큰 차이가 발생하게 됩니다.

     

     

     

    객체 그래프 탐색

    기본적으로 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다.

    다음과 같은 그래프가 있다고 합시다.

    Member는 Team을 연관하고 있고 Order를 연관하고 있는 그래프입니다.

     

     

    처음 SQL에 의해 탐색범위가 결정이 됩니다.

    만약 처음 SQL이 Member와 Team에 대한 값만 채워주고 반환한다고 합시다. 이러한 상태라면

    member.getTeam()을 호출했을 땐 그 값을 반환 받을 수 있지만

    member.getOrder()을 호출했을때는 그 값을 반환받을 수 없게 됩니다.

    필드가 있지만 값이 없는 null이 되는 상태가 발생하게 됩니다.

     

     

     

    이는 엔티티 신뢰 문제를 발생시키게 됩니다. 

    코드를 한번 살펴본 결과로는 MemberTeam과 연관을 맺고 있고, OrderDelivery와 연관을 맺는것처럼 보이지만 실제 코드를 뜯어보지 않는 이상 어떤 값이 채워져 있는지 알 수 없습니다.

     

     

     

    그렇다고 모든 객체를 미리 로딩할 수는 없기 때문에 다음과 같이 조회 메소드를 여러개 만들수 있습니다.

    이러한 방법을 사용해서 위의 문제를 해결할 순 있겠지만... 궁극적으로는...

    점점 복잡한 코드가 쓰여지게 되고 궁극적으로는 진정한 의미의 계층 분할이 어렵게 됩니다. 

     

     

     

    비교하기

     

    memberId를 100으로 설정하고 DAI에서 Member 객체를 두번 조회하게 되면 그 멤버는 같을까요? 그 결과는 다르다입니다.

    실제 DAO 코드를 뜯어보게 되면 JDBC API를 통해 SQL을 실행하고 새로운 Member 인스턴스를 생성하기 때문에 같은 Id로 호출하더라도 다른 값이 나오게 됩니다.

     

     

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

    자바 컬렉션에서는 어떨까요? 같은Id로 조회한 결과 같은 값이 나오게 됩니다.

     

    그래서,

    객체답게 모델링을 할수록 매핑 작업만 늘어나게 되며 개발자들은 객체답게 모델링을 하지 않고 그냥 단순히 데이터를 전달하는 방식을 택할 수 밖에 없게 됐습니다.

     

    그렇다면, 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 순 없을까요?

     

    이러한 고민을 거듭한 결과 자바 진영에서는 그 결과 JPA를 사용하게 됩니다.

     

    다음 장에서는 JPA 소개에 대해 말씀드리겠습니다.

     

     

    댓글

Designed by Tistory.