글 검색

공개된 글의 제목과 본문을 검색합니다.

    닫기 Esc

    1. SQL을 직접 다룰 때의 문제점#

    1.1. 반복 그리고 반복#

    java
    // Member 객체
    public class Member {
        private String memberId;
        private String name;
        ...
    }
    
    // Member용 Data Access Object
    public class MemberDAO {
        public Member find(String memberId) {...}
    }

    위와 같은 DAO에서 회원을 찾기 위한 find 메서드를 구현할 때 아래와 같은 순서로 개발을 진행한다.

    1. 회원 조회용 SQL 작성
      sql
      SELECT MEMBER_ID, NAME FROM MEMBER WHERE MEMBER_ID = ?
    2. JDBC API를 사용해 SQL을 실행
      java
      ResultSet rs = stmt.executeQuery(sql);
    3. 조회 결과를 Member 객체로 매핑
      java
      String memberId = rs.getString("MEMBER_ID");
      String name = rs.getString("NAME");
      
      Member member = new Member(memberId, name);
      ...

    여기서 다른 기능(회원 추가, 삭제 등)을 추가할 경우 위 3가지 경우를 지속적으로 반복하게 된다.

    • 결국 문제는 객체를 DB에 CRUD하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야한다.
    • 테이블이 100개라면 무수히 많은 SQL을 작성해야 하고 이런 비슷한 일을 100번은 더 반복해야 한다.

    1.2. SQL에 의존적인 개발#

    앞서 진행한 예제에서 만약 요구사항이 추가되어 회원 테이블에 전화번호를 저장할 수 있도록 변경되었다고 생각해보자

    java
    // Member 객체
    public class Member {
        private String memberId;
        private String name;
        private String tel; // 추가 됨
        ...
    }

    이렇게 새롭게 요구사항이 추가되어 테이블이 변경될 경우 이에 따른 객체와 SQL문 전부가 수정되어야 한다.

    • 수정되어야 할 코드의 양도 많을 뿐더러 수정되지 않은 SQL로 인해서 버그가 발생할 수도 있다.

    SQL 의존적인 개발은 SQL 계층을 분리해 숨겨도 문제를 찾기 위해서는 어쩔 수 없이 DAO를 열어 실행되는 SQL을 확인해야한다.

    • 진정한 의미의 계층 분할이 어렵고, 엔티티를 신뢰할 수 없다.

    1.3. JPA와 문제 해결#

    JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아닌 JPA가 제공하는 API를 이용하면 된다.

    그러면 JPA가 개발자 대신 적절한 SQL을 생성해 DB에 전달한다.

    1.3.1. 저장기능#

    java
    jpa.persist(member);
    • persist() 메소드는 객체를 데이터베이스에 저장한다.
    • JPA가 객체와 매핑정보를 보고 적절한 SQL을 생성해 DB에 전달한다.

    1.3.2. 조회기능#

    java
    String memberId = "helloId";
    Member member = jpa.find(Member.class, memberId);
    • find() 메소드는 객체 하나를 DB에서 조회한다.

    1.3.3. 수정기능#

    java
    String memberId = "helloId";
    Member member = jpa.find(Member.class, memberId);
    member.setName("이름변경");
    • JPA는 별도의 수정 메소드를 제공하지 않는다.
    • 대신 객체를 조회해 값을 변경하면 트랜잭션을 커밋할 때 적절한 UPDATE SQL이 전달된다.

    1.3.4. 연관된 객체 조회#

    java
    String memberId = "helloId";
    Member member = jpa.find(Member.class, memberId);
    Team team = member.getTeam();
    • JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다.

    2. 패러다임 불일치#

    • 애플리케이션은 발전하며 그 내부의 복잡성이 점점 커진다.
      • 복잡성을 제어하지 못하면 결국 유지보수하기 어려운 애플리케이션이 된다.
    • 객체지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.
    • 비즈니스 요구사항을 정의한 도메인 모델도 객체로 모델링 하면 객체지향 언어가 가진 장점을 활용할 수 있다.
      • But. 저장이 필요한 객체라면 메모리가 아닌 어딘가에 영구 보관해야한다.
      • 자바는 이런 문제를 고려해 객체를 파일로 저장하는 직렬화 기능을 지원한다.
        • But. 보안상의 문제와 더불어 객체를 검색하기가 어렵다.
      • 현실적인 대안은 RDB를 사용하는 것이지만 이는 앞서 이야기한 객체지향적인 개념이 존재하지 않는다.
    • 객체지향과 RDB간의 패러다임 불일치를 개발자가 중간에서 해결해야 한다.

    2.1. 상속#

    객체의 상속 모델
    객체의 상속 모델
    RDB의 슈퍼타입과 서브타입 모델
    RDB의 슈퍼타입과 서브타입 모델
    java
    abstract class Item {
        Long id;
        String name;
        int price;
    }
    
    class Album extends Item {
        String artist;
    }
    
    class Movie extends Item {
        String director;
        String actor;
    }
    
    class Book extends Item {
        String author;
        String isbn;
    }
    객체 모델 코드
    • 객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.
      • 그나마 유사한 모델링은 슈퍼타입과 서브타입 관계라고 말할 수 있다.
    • ITEM 테이블의 DTYPE 컬럼을 사용해 어떤 자식 테이블과 관계가 있는지 정의 했다.
    • JDBC API를 사용해 해당 코드를 완성하려면 ITEM용 INSERT SQL을 작성하고 자식 데이터만 커내 ALBUM용 INSERT SQL을 작성해야 하는데 작성해야 하는 코드량이 만만치 않다.
      • 물론 조회도 쉬운일이 아닌것이 ALBUM을 조회한다면 ITEM과 ALBUM테이블을 조인해 결과로 ALBUM 객체를 생성해야 한다.
    • 자바 컬렉션에 보관한다면 이런 고민없이 그냥 컬렉션에 보관하면 된다.
      java
      list.add(album);
      list.add(movie);
      
      Album album = list.get(albumId);
    • JPA는 상속과 관련된 패러다임 불일치 문제를 개발자 대신 해결해준다.
      • 개발자는 마치 자바 컬렉션에 객체를 저장하듯 JPA에게 객체를 저장하면 된다.
      • jpa.persist(album);

    2.2. 연관관계#

    • 객체는 참조를 사용해 다른 객체와 연관관계를 가지고 참조에 접근해 연관된 객체를 조회한다.
      • 반면 테이블은 외래 키를 사용해 테이블과 연관관계를 가지고 조인을 사용해 연관된 테이블을 조회한다.
    연관관계
    연관관계
    • 참조를 사용하는 객체와 외래 키를 사용하는 RDB 사이의 패러다임 불일치는 객체지향 모델링을 거의 포기하게 말든 정도록 극복하기 어렵다.
    • Member 객체는 참조를 통해서 Tream을 조회할 수 있다.
      java
      // 참조를 사용하는 객체 모델
      class Member {
          String id;
          Team team;
          String username;
      
          Team getTeam() {
              return team;
          }
      }
      
      class Team {
          Long id;
          String name;
      }
      ...
      
      member.getTeam(); // 접근
    • MEMBER 테이블은 MEMBER.TEAM_ID 외래 키를 통해 TEAM 테이블과 관계를 맺고 조인하여 접근할 수 있다.
      sql
      SELECT M.*, T.* FROM MEMBER M JOIN TEAM T M.TEAM_ID = T.TEAM_ID;

    2.2.1. 객체를 테이블에 맞춰 모델링#

    java
    class Member {
        String id;
        Long teamId;
        String username;
    }
    
    class Team {
        Long id;
        String name;
    }
    테이블에 맞춘 객체 모델
    • 여기서 조금 어려운 문제가 존재한다.
      • 객체는 참조가 있는 방향으로만 조회가 가능하지만 외래 키는 반대 방향으로도 조회가 가능하다.
      • 테이블과 동일하게 객체 모델을 수정하게 되면 Member 객체와 연관된 Team 객체를 참조를 통해 조회할 수 없다.
        • 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버리게 된다.

    2.2.2. 객체지향 모델링#

    • 참조를 통한 모델링을 이용할 경우 개발자가 중간에서 변환 역할을 해줘야 한다.
    • 이에는 저장/조회시 개발자가 사용하고 신경써야하는 비용이 된다.

    2.3. 객체 그래프 탐색#

    객체 연관관계
    객체 연관관계
    • 객체에서 회원이 소속된 팀을 조회할 때는 다음처럼 참조를 사용해 연관된 침을 찾으면 된다 ⇒ 이를 그래프 탐색이라 한다.
      • member.getOrder().getOrderItem()... = 그래프 탐색 코드
    • SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.
      • 이는 객체지향 개발자에겐 큰 제약인데 비즈니스 로직에 따라 객체 그래프가 다른데 언제 끊어질지 모를 그래프를 함부로 탐색할 수는 없기 때문
    java
    class MemberService {
    		...
    		public void process() {
    				Member member = memberDAO.find(memberId);
    				member.getTeam();  // 그래프 탐색이 가능한가?
    				member.getOrder().getDelivery();  // ???
    		}
    }
    • member 객체를 조회했지만, 객체와 연관된 Team, Order, Delivery 방향으로 객체 그래프를 탐색할 수 있을지 없을지는 이 코드만으로 전혀 예측할 수 없다.
      • SQL에 논리적으로 종속되어서 발생하는 문제다.

    2.3.1. JPA와 객체 그래프 탐색#

    • JPA를 사용하면 객체 그래프를 마으껏 탐색할 수 있다.
    • JPA는 지연로딩을 투명하게 처리한다.
    java
    Member member = jpa.find(Member.class, memberId);
    
    Order order = member.getOrder();
    order.getOrderDate();   // Order를 사용하는 시점에 SELECT ORDER SQL
    • JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에서 지연해서 조회할지를 간단한 설정으로 정의 가능하다.

    2.4. 비교 (Compare)#

    • DB는 기본 키의 값으로 각 로우를 구분한다.
    • 반면 객체는 동일성과 동성성이라는 두 가지 비교방법이 있다.
      • == 동일성 비교로 인스턴스 주소가 동일한지 비교한다.
      • equals() 동등성 비교로 인스턴스 내부의 값을 비교한다.
    • JDBC API를 이용해서 값을 매핑 시킬 경우 동일한 값으로 조회하더라도 동일한 객체가 아닌 동등한 객체가 된다.
      • 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 쉽지 않다.
      • 트랜잭션이 동시에 실행되는 상황까지 고려하면 문제는 더 어려워진다.
    • JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

    2.5. 정리#

    • 객체 모델과 RDB 모델은 지향하는 패러다임이 서로 다르다.
      • 문제는 이 패러다임의 차를 극복하기 위해 개발자가 너무 많은 시간과 코드를 소비한다는 점이다.
    • 패러다임의 불일치로 객체 모델링은 힘을 잃고 점점 데이터 중심의 모델로 변해간다.
    • JPA는 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

    3. JPA란 무엇인가?#

    JPA
    JPA
    • JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준으로 애플리케이션과 JDBC 사이에서 동작한다.
    • 그렇다면 ORM은 무엇인가?
      • Object-Relational Mapping은 이름 그대로 객체와 RDB를 매핑한다는 뜻이다.
      • ORM 프레임워크를 사용한다면 패러다임 불일치 문제를 개발자 대신 해결해준다.
    JPA 저장
    JPA 저장
    JPA 조회
    JPA 조회
    java
    jpa.persist(member); // 저장
    jpa.find(member); // 조회
    • ORM 프레임워크는 단순히 SQL을 개발자 대신 생성해 데이터베이스에 전달해주는 것뿐만 아니라 앞서 이야기한 다양한 패러다임의 불일치 문제들도 해결해준다.
      • 객체 측면에서는 정교한 객체 모델링 가능
      • 객체와 모델링간 매핑은 ORM에게만 알려주면 된다.
      • 개발자는 데이터 중심인 RDB를 사용해도 객체지향 애플리케이션 개발에 집중 가능
    • 성숙한 객체지향 언어들은 대다수 ORM 프레임워크들이 있고, 단순 CRUD 기능만 제공하거나 패러다임 불일치 문제를 대부분 해결해주는 프레임워크도 존재한다.
      • 자바 진형은 하이버네이트가 거의 대부분의 패러다임 불일치 문제를 해결해준다.

    3.1. JPA 소개#

    • 과거 자바 진영은 자바 빈즈(EJB)라는 표준 기술을 만들었는데 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었다.
      • But. 너무 복잡, 성숙도 떨어짐 J2EE 환경에서만 동작했다는 단점이 존재했다.
    • 이때 하이버네이트가 등장했고 EJB 3.0에서 하이버네이트를 기반으로 새로운 자바 ORM 표준이 만들어진게 JPA다.
    JPA 표준 인터페이스와 구현체
    JPA 표준 인터페이스와 구현체
    • JPA는 자바 ORM 기술에 대한 API 표준 명세다.
      • 쉽게 이야기 인터페이스를 모아놓은 것
      • JPA 2.1 를 구현한 ORM 구현체는 아래와 같다.
        • 하이버네이트, EclipseLink, DataNucleus
    • JPA의 버전별 특징
      • JPA 1.0 (JSR 220) = 2006년 출시된 초기버전, 복합 키와 연관관계 기능이 부족
      • JPA 2.0 (JSR 317) = 2009년 대부분의 orm 기능을 포함하고 JPA Criteria가 추가
      • JPA 2.1 (JSR 338) = 2013년 스토어드 프로시저 접근, 컨버터, 엔티티 그래프 기능 추가

    3.2 왜 JPA를 사용해야 하는가?#

    3.2.1. 생산성#

    • JPA를 사용하면 자바 컬력센에 객체를 저장하듯 JPA에게 저장할 객체를 전달하면 된다.
    • SQL을 작성하고 JDBC API를 사용하는 지루하고 반복적인 작업을 JPA가 대신 처리한다.
    • 더 나아가 JPA는 CREATE TABLE과 같은 DDL문을 자동으로 생성해주는 기능도 있다.
      • 이런 기능을 활용해 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전할 수 있다.

    3.2.2. 유지보수#

    • SQL을 직접 다루면 엔티티에 필드를 하나만 추가해도 관련된 CRUD SQL과 결과를 매핑하기 위한 코드를 모두 변경해야 했다.
    • JPA를 사용하면 이런 과정을 JPA가 대친 처리해주므로 필드를 추가하거나 삭제해도 수정해야할 코드가 줄어든다.
      • 즉 유지보수 해야하는 코드 수가 줄어든다.
    • JPA가 패러다음 불일치를 해결해주므로 객체지향 언어가 가진 장점을 활용해 유연하고 유지보수 하기 좋은 도메인 모델을 설계할 수 있다.

    3.2.3. 패러다임 불일치 해결#

    • JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해준다.

    3.2.4. 성능#

    • JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.
    • 예를 들어 각은 키의 값을 통해 조회시 같은 트랜잭션 내라면 추가적인 통신없이 기존 객체를 재사용할 수 있다.
    • 하이버네이트는 SQL 힌트를 넣을 수 있는 기능도 제공한다.

    3.2.5. 데이터 접근 추상화와 벤더 독립성#

    벤더 독립성
    벤더 독립성
    • RDB는 같은 기능도 벤더마다 사용법이 다른경우가 많은데 SQL을 직접 다루는 경우 다른 벤더로의 변경은 매우 어렵다.
    • JPA를 사용하면 특정 기술에 종속적이지 않기 때문에 손쉽게 변경할 수 있다.

    3.2.6. 표준#

    • JPA는 자바 진영의 ORM 표준 기술이기 때문에 다른 구현 기술로 손쉽게 변경 가능하다.
    전체 글 둘러보기