개발/JPA

[JPA] JPA 소개

highright96 2021. 12. 8.

JPA 스터디를 진행하며 작성한 글입니다.

 

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

반복, 반복 그리고 반복

데이터베이스는 객체 구조와는 다른 데이터 중심의 구조를 가지므로 객체를 데이터베이스에 직접 저장하거나 조회할 수 없다. 따라서 아래 예제 코드와 같이 개발자가 객체지향 애플리케이션과 데이터베이스 중간에서 SQL과 JDBC API를 사용해서 변환 작업을 직접 해주어야 한다.

 

String sql="INSERT INTO MEMBER (MEMBER_ID, NAME) VALUES(?,?)";
    psmt.setString(1,member.getMemberId());
    psmt.setString(2,member.getName());
    psmt.excuteUpdate(sql);

 

만약, 테이블이 100개라면 객체를 데이터베이스에 CRUD 하려면 위와 같은 작업을 무수히 해야 한다.

 

엔티티를 신뢰할 수 없다

비즈니스 요구사항을 모델링한 객체를 엔티티라 하는데, SQL에 모든 것을 의존하는 상황에서는 개발자들이 엔티티를 신뢰하고 사용할 수 없다.

 

진정한 의미의 계층 분할이 어렵다

개발자들은 엔티티를 신뢰하고 사용할 수 없기 때문에, 결국 DAO를 열어서 어떤 SQL이 실행되고 어떤 객체들이 함께 조회되는지 일일이 확인해야 한다. 이것은 진정한 의미의 계층 분할이 아니다.

 

SQL에 의존적인 개발을 피하기 어렵다

물리적으로 SQL과 JDBC API를 데이터 접근 계층에 숨기는 데 성공했을지는 몰라도 논리적으로는 엔티티와 아주 강한 의존관계를 갖고 있다.

이런 강한 의존관계 때문에 회원을 조회할 때는 물론이고 회원 객체에 필드를 하나 추가할 때도 DAO의 CRUD 코드와 SQL 대부분을 변경해야 하는 문제가 발생한다.

 

패러다임 불일치

객체와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현 방법도 다르다. 이것을 객체와 관계형 데이터베이스의 패러다임 불일치 문제라 한다.

 

애플리케이션은 자바라는 객체지향 언어로 개발하고 데이터는 관계형 데이터베이스에 저장해야 한다면, 패러다임의 불일치 문제를 개발자가 중간에 해결해야 한다. 문제는 이런 객체와 관계형 데이터베이스 사이의 패러다임 불일치 문제로 해결하는데 너무 많은 시간과 코드를 소비한다.

 

패러다임의 불일치로 인해 발생하는 문제를 자세히 살펴보자.

 

상속

객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.

 

 

그나마 데이터베이스 모델링에서 이야기하는 슈퍼타입 서브타입 관계를 사용하면 객체 상속과 가장 유사한 형태로 테이블을 설계할 수 있다.

 

아래 그림은 ITEM 테이블의 DTYPE 컬럼을 사용해서 어떤 자식 테이블과 관계가 있는지 정의했다.

 

 

만약, Album 객체를 저장하려면 이 객체를 분해해서 다음 두 SQL을 만들어야 한다.

 

INSERT INTO ITEM...
INSERT INTO ALBUM...

 

JDBC API를 사용해서 이 코드를 완성하려면 부모 객체에서 부모 데이터만 꺼내고 자식 객체에서 자식 데이터만 꺼내서 SQL을 작성해야 하기 때문에, 개발자가 작성해야 할 코드량이 많아진다.

 

연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다. 반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.

 

 

객체를 테이블에 맞추어 모델링

객체와 테이블의 차이를 알아보기 위해 아래 코드와 같이 객체를 단순히 테이블에 맞추어 모델링하였다.

 

class Member {

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

class Team {

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

 

객체를 테이블에 맞추어 모델링하면 객체를 테이블에 저장하거나 조회할 때는 편리하다.

그러나 연관된 객체(Team)를 조회할 수 었다. 객체는 연관된 객체의 참조를 보관해야 다음처럼 찾을 수 있기 때문이다.

 

Team team=member.getTeam();

 

결국 관계형 데이터베이스가 사용하는 방식에 맞추면 Member 객체와 연관된 Team 객체를 참조를 통해서 조회할 수 없다. 이런 방식을 따르면 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버리기 된다.

 

객체지향 모델링

객체는 참조를 통해서 관계를 맺는다. 따라서 아래 코드와 같이 참조를 사용하도록 모델링해야 한다.

 

class Member {

    String id;
    Team team;
    String username;

    Team getTeam() {
        return team;
    }
}

class Team {

    Long id;
    String name;
}

 

이제는 회원과 연관된 팀을 조회할 수 있다. 그러나 MEMBER 테이블은 TEAM_ID 외래 키로 연관관계를 맺기 때문에, 위와 같이 객체지향 모델링을 사용하면 객체를 테이블에 저장하거나 조회하기가 쉽지 않다.

결국, 개발자가 중간에서 변환 역활을 해야 한다.

 

객체 그래프 탐색

객체에서 회원이 소속된 팀을 조회할 때는 아래 코드처럼 참조를 사용해서 연관된 팀을 찾으면 되는데. 이것을 객체 그래프 탐색이라 한다.

 

Team team=member.getTeam();

 

객체 연관관계가 아래와 같이 설계되어 있다고 가정해보자.

 

 

SELECT M.*, T.*
FROM MEMBER M
         JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 

MemberDAO에서 member 객체를 조회할 때 위와 같은 SQL을 실행했다면. member.getTeam()은 성공하지만 다른 객체 그래프는 데이터가 없으므로 탐색할 수 없다.

 

member.getOrder();  //null

 

SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해지며, 상황에 따라 DAO 메소드를 추가해야 한다.

 

memberDAO.getMember();
memberDAO.getMemberWithTeam();
memberDAO.getMemberWithOrderWithDelivery();

 

비교

데이터베이스는 기본 키의 값으로 각 로우를 구분한다. 반면에 객체는 동일성 비교와 동등성 비교라는 두 가지 비교 방법이 있다.

 

  • 동일성 비교 : == 비교이다. 객체의 인스턴스의 주소 값을 비교한다.
  • 동등성 비교 : equals() 메소드를 사용해서 객체 내부의 값을 비교한다.

 

String memeberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

member1 == member2; // 다르다

 

위 코드를 보면 기본 키 값이 같은 회원 객체를 두 번 조회했다. member1과 member2는 같은 데이터베이스 로우에서 조회했지만, 객체 측면에서 볼 때 둘은 다른 인스턴스이다.

 

이런 패러다임의 불일치 문제를 해결하기 위해 데이터베이스의 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 쉽지 않다. 여기에 여러 트랜잭션이 동시에 실행되는 상황까지 고려하면 문제는 더 어려워진다. 그러나 JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

 

JPA란 무엇인가?

JPA (Java Persistence API)는 자바 진영의 ORM 기술 표준이다. JPA는 아래와 같이 애플리케이션과 JDBC 사이에서 동작한다.

 

 

ORM이란
ORM (Object-Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻이다. 객체와 테이블을 매핑해서 패러다임 불일치 문제를 개발자 대신 해결해준다.

 

JPA 소개

JPA는 자바 ORM 기술에 대한 API 표준 명세이며, 쉽게 이야기하면 인터페이스를 모아둔 것이라고 생각하면 된다. 따라서 JPA를 사용하기 위해서 JPA를 구현한 ORM 프레임워크를 선택해야 하는데, 주로 하이버네이트를 사용한다.

 

 

왜 JPA를 사용해야 하는가?

1) 유지보수와 생산성 향상

  • JPA에서 제공하는 메서드를 통해 CRUD를 수행하기 때문에 생산성이 향상된다.
  • 필드의 변경이나 수정이 일어나도 JPA가 대신 처리해주므로 별도의 SQL 수정 과정이 사라진다.

2) 패러다임 불일치 해결

  • JPA는 상속, 연관 관계, 객체 그래프 탐색, 비교 등 패러다임의 불일치를 해결해 준다.

3) 성능

  • JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.

4) 데이터 접근 추상화와 벤더 독립성

  • JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 특정 데이터베이스 기술에 종속되지 않도록 한다.

 

 

5) 표준

  • JPA는 자바 진영의 ORM 기술 표준이므로 다른 구현 기술로 손쉽게 변경이 가능하다.

 

참고

  • 자바 ORM 표준 JPA 프로그래밍 - 김영한

 

예상 면접 질문 및 답변

링크 참고

댓글