SQL을 직접 다룰 때 발생하는 문제점

관계형 데이터베이스에 데이터를 관리하려면 SQL을 사용해야 한다.
(자바 애플리케이션 -- JDBC API) ----SQL---> (DB)

반복, 반복 그리고 반복

  • 데이터베이스는 객체 구조와는 다른 데이터 중심의 구조를 가지므로 객체를 데이터베이스에 직접 저장하거나 조회할 수는 없고 개발자가 객체지향 애플리케이션과 데이터베이스 중간에서 SQL과 JDBC API를 사용해서 변환 작업을 직접 해주어야 한다.
  • 문제점: 객체를 데이터베이스에 CRUD하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야 한다.

SQL에 의존적인 개발

  • 데이터 접근 계층을 사용해서 SQL을 숨겨도 어쩔 수 없이 DAO를 열어서 어떤 SQL이 실행되는지 확인해야 한다.
  • SQL에 모든 것을 의존하는 상황에서는 개발자들이 엔티티를 신뢰하고 사용할 수 없다.
    • 엔티티: Member, Team 처럼 비즈니스 요구사항을 모델링한 객체
  • DAO를 열어서 어떤 SQL이 실행되고 어떤 객체들이 함께 조회되는지 일일이 확인해야 한다. 이는 진정한 의미의 계층 분할이 아니다.

JPA와 문제 해결

  • JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 JPA가 개발자 대신에 적절한 SQL을 생성해서 데이터베이스에 전달한다.

JPA는 SQL을 개발자 대신 작성해서 실행해주는 것 이상의 기능들을 제공한다.

패러다임의 불일치

특정 유저가 시스템에 회원 가입하면 회원이라는 객체 인스턴스를 생성한 후에 이 객체를 메모리가 아닌 어딘가에 영구 보관해야 한다.

객체가 단순하면 객체의 모든 속성 값을 꺼내서 파일이나 데이터베이스에 저장하면 되지만, 부모 객체를 상속받았거나, 다른 객체를 참조하고 있다면 객체의 상태를 저장하기는 쉽지 않다.

관계형 데이터베이스는 데이터 중심으로 구조화되어 있고, 집합적인 사고를 요구한다. 그리고 객체지향에서 이야기하는 추상화, 상속, 다형성 같은 개념이 없다.

객체와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현 방법도 다르다. 따라서 객체 구조를 테이블 구조에 저장하는 데는 한계가 있다.

이 패러다임의 불일치 문제를 개발자가 중간에서 해결해야하는 데 많은 시간과 코드를 소비해야 한다.

상속

  • 객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.(일부 데이터베이스는 상속 기능을 지원하지만 객체의 상속과는 약간 다르다)

  • JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다.

  • 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.

  • Item을 상속한 Album 객체를 저장해보자.

      // JPA는 다음 SQL을 실행해서 따로 저장한다.
      jpa.persist(album);
    
      INSERT INTO ITEM ...
      INSERT INTO ALBUM ...
    
      // JPA는 ITEM과 ALBUM 두 테이블을 조인해서 필요한 데이터를 조회하고 그 결과를 반환한다.
      String albumId = "id100";
      Album album = jpa.find(Album.class, albumId);
    
      SELECT I.*, A.*
          FROM ITEM I
          JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

연관관계

  • 객체: 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.

  • 테이블: 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.

  • 객체는 참조가 있는 방향으로만 조회할 수 있다.

    • member.getTeam() 은 가능하지만 반대 방향인 team.getMember()는 참조가 없으므로 불가능하다.
  • 테이블은 외래키 하나로 둘 다 조인이 가능하다.

  • 객체를 테이블에 맞추어 모델링하면 객체에 외래키를 저장해야하고 이런 방식을 따르면 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버리게 되므로 객체지향 모델링이 필요하다.

  • 히지만 객체지향으로 모델링을 사용하면 객체를 테이블에 저장하거나 조회하기가 쉽지 않다.

  • 객체 모델은 외래키 필요 없고 참조만 있으면 되고, 테이블은 참조 필요 없고 외래 키만 있으면 되므로 개발자가 중간에서 변환 역할을 해야한다.

    member.setTeam(team); //회원과 팀 연관관계 설정
    jpa.persist(member); //회원과 연관관계 함께 저장
  • 개발자는 회원과 팀의 관계를 설정하고 회원 객체를 저장하면 된다. JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다.

  • 객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리해준다.

객체 그래프 탐색

  • SQL을 직접 다루면 처음 실행하는 SQL에 따라(JOIN을 어떻게 하는지) 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.
  • 비즈니스 로직에 따라 사용하는 객체 그래프가 다른데 언제 끊어질지 모를 객체 그래프를 함부로 탐색할 수는 없다.
  • 어디까지 객체 그래프 탐색이 가능한지 알아보려면 결국 DAO를 열어서 SQL을 직접 확인해야 한다.
  • 결국 DAO에 회원을 조회하는 메소드를 상황에 따라 여러 개 만들어서 사용해야 한다.
  • JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행하기 떄문에 연관된 객체를 신뢰하고 마음껏 조회할 수 있다.
    • 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서 지연 로딩이라 한다.
  • JPA는 연관된 객체를 즉시 함께 조회할지(JOIN) 아니면 지연해서 조회할지를 간단한 설정으로 정의할 수 있다.

비교

  • 데이터베이스는 기본 키의 값으로 각 로우를 구분하는 반면, 객체는 동일성 비교와 동등성 비교라는 두 가지 비교 방법이 있다.
  • 동일성:
    • == 비교다. 객체 인스턴스의 주소 값을 비교한다.
  • 동등성:
    • equals() 메소드를 사용해서 객체 내부의 값을 비교한다.
  • 같은 로우를 각각 SQL로 조회하면(호출할 때마다 인스턴스가 새로 생성) 동일성 비교에 실패하지만 객체를 컬렉션에 보관했다면 동일성 비교에 성공한다.
  • JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

정리

  • 객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 서로 다르다.
  • 이 패러다임의 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다.
  • 더 어려운 문제는 객체지향 애플리케이션답게 정교한 객체 모델링을 할수록 패러다임의 불일치 문제가 더 커지는데 이 틈을 메우기 위해 개발자가 소모해야 하는 비용도 점점 더 많아진다.
  • 결국, 객체 모델링은 힘을 잃고 점점 데이터 중심의 모델로 변해간다.
  • 자바 진영에서 나온 JPA는 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

JPA란 무엇인가?

JPA는 자바 진영의 ORM 기술 표준이다.
(ORM: Object-Relational Mapping 의 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻)

자바 진영에는 다양한 ORM 프레임워크들이 있는데 그중에 하이버네이트 프레임워크가 가장 많이 사용된다. 하이버네이트는 거의 대부분의 패러다임 불일치 문제를 해결해주는 성숙한 ORM 프레임워크다.

JPA 소개

  • JPA는 자바 ORM 기술에 대한 API 표준 명세다. 쉽게 말해서 인터페이스를 모아둔 것이다. 따라서 JPA를 사용하려면 JPA를 구현한 ORM 프레임워크를 선택해야 한다.
  • JPA라는 표준 덕분에 특정 구현 기술에 대한 의존도를 줄일 수 있고 다른 구현 기술로 손쉽게 이동할 수 있는 장점이 있다.

왜 JPA를 사용해야 하는가?

  • 생산성:
    • 지루하고 반복적인 코드와 CRUD용 SQL을 개발자가 직접 작성하지 않아도 된다.
    • JPA에는 CREATE TABLE 같은 DDL 문을 자동으로 생성해주는 기능도 있다.
  • 유지보수:
    • SQL을 직접 다루면 엔티티에 필드를 하나만 추가해도 SQL과 JDBC API 코드를 모두 변경해야 한다.
    • 반면에 JPA를 사용하면 이런 과정을 JPA가 대신 처리해주므로 유지보수해야 하는 코드가 줄어든다.
    • 패더라임 불일ㅇ치 문제를 해결해주므로 객체지향 언어가 가진 장점들을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있다.
  • 패러다임의 불일치 해결:
    • 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해준다.
  • 성능
    • JPA는 애플리케이션과 데이터베이스 사이에서 동작하기 때문에 다양한 성능 최적화 기회를 제공한다.
    • 하이버네이트는 SQL 힌트를 넣을 수 있는 기능도 제공한다.
  • 데이터 접근 추상화와 벤더 독립성
    • 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많다.
    • JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 특정 데이터베이스 기술에 종속되지 않도록 한다. 만약 데이터베이스를 변경하면 JPA에게 다른 데이터베이스를 사용한다고 알려주기만 하면 된다.
  • 표준
    • JPA는 자바 진영의 ORM 기술 표준이다. 앞서 말했듯이 표준을 사용하면 다른 구현 기술로 손쉽게 변경할 수 있다.

+ 따끈한 최근 게시물