[JPA]JPA가 탄생한 이유

신창호·2023년 3월 13일
0

jpa

목록 보기
1/2
post-thumbnail

나에게 하는 JPA 질문

  • Q: JPA란 무엇인가?
    • A : 간단하게 자바진영의 ORM 기술이라고 할 수 있다.
  • Q: JPA 를 사용할 줄 아는가?
    • A: 단순한 기능이라면 “할 줄 안다!”라고 대답할 수 있지만, 깊게 들어가면 두루뭉실하게 알고 있다는 것이 이 글을 시작한 이유다.
  • Q: 그럼 JPA을 할 줄 아려면 어떻게 해야되나?
    • JPA의 표준 스펙들을 공부하면 된다! 물론 맞는 말이지만, 적은 양이 아니다.
  • Q: 그럼 다른 방법이 있나?
    • 가장 효율적인 방법은 상황에 따라 맞춰서 사용할 줄 알면 된다!
    • 그리고 그 상황에 따라 맞추려면 JPA의 근본적인 원리를 알면 된다!
  • Q: JPA의 근본적인 원리가 뭔데?
    • 근본적인 원리는 보통 탄생한 이유가 답을 해준다.

JPA의 탄생이유를 알아보기 위해 JPA가 없었던 시대로 돌아가보자






SQL 중심적인 개발

  • JPA가 없던 시절에는 SQL중심적으로 개발을 했었다고 한다.
    • 애플리케이션의 경우 객체지향 언어로 개발을 진행했다.
    • 데이터베이스의 경우 관계형 DB를 사용했다.

  • 즉, 객체를 관계형 DB에 넣어 관리해야된다는 것이다.
    • insert, update등 DB에 CRUD를 수행하기 위해선 수많은 SQL을 사용해야 됐다.

객체 CRUD

public class Member{
	private String memberId;
	private String name;
	// 생략
}
# CRUD
INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES
SELECT MEMBER_ID, NAME FROM MEMBER M
UPDATE MEMBER SET  ... # 생략
  • 위와 같이, 하나의 객체에도 여러개의 SQL문이 필요하다.

객체에 필드가 추가된 상황

  • 만약 기존 객체에 전화번호라는 필드가 추가되었다고 가정하자,
  • 그럼 Application의 java진영에서는 객체 지향 프로그래밍으로 설계 하였기 때문에, 간단하게 수정이 가능하다.
public class Member{
	private String memberId;
	private String name;
	private String phoneNumber;
	// 생략
}
  • 하지만, 해당 객체에 필요한 SQL문은 하나가 아니다.
  • 객체 하나가 바뀌면 이에 해당하는 모든 SQL문이 바뀌어야했다.
# CRUD
INSERT INTO MEMBER(MEMBER_ID, NAME, PHONE_NUMBER) VALUES // 수정
SELECT MEMBER_ID, NAME, PHONE_NUMBER FROM MEMBER M // 수정
UPDATE MEMBER SET  ...PHONE_NUMBER  // 수정

이렇기때문에 SQL에 의존적인 개발을 피하기 어려웠다.
즉, SQL의 변환하는 SQL 매퍼 = 개발자 였다.






객체와 관계형 데이터베이스의 차이

  1. 상속
  2. 연관관계
  3. 데이터 타입
  4. 데이터 식별방법



상속

  • 상품을 판매한다고 가정하자.

객체의 상속관계

  • Item이라는 공통적인 부분을 추상화하여 부모로 만들고
  • 앨범, 영화, 책은 item을 상속받아 만들면된다.

데이터베이스의 상속관계

  • RDB에서 객체의 상속같은 관계는 없다.
  • 대신, 부모같은 릴레이션(테이블)과 자식같은 릴레이션(테이블)을 만들 수가 있다.
    • 보통 Table 슈퍼타입 서브타입 관계라고 한다.

상속관계의 패러다임이 다르다는 것은 알았다. 그럼 이제 이게 무슨 연관이 있는지 알아보자

예시 상황

  • 객체 저장(앨범을 저장한다고 가정)
    • 객체 분해 → INSERT INTO ITEM , INSERT INTO ALBUM
    • 두 테이블에 저장해야된다.
  • 객체 조회(앨범을 조회해보자)
    • 두 테이블(ITEM, ALBUM)에 따른 조인 SQL 작성 → 각각의 객체 생성 → 객체에 데이터를 매핑해준다 → 생성된 객체를 서로 참조. .. 생략
    • 조회기능하나에 엄청 복잡해진다.
    • 그래서 DB에 저장할 객체에는 상속관계를 쓰지 않는다.



연관관계/데이터타입

객체

  • 참조를 사용한다.
    • Member 객체안에, Team객체를 가질 수 있다.
    • member.getTeam()

RDB

  • 테이블은 외래키를 사용하고 JOIN 을 해야한다.
    • JOIN ON M.TEAM_ID = T.TEAM_ID

예시상황

  • 객체는 SQL에 저장하기위해서, 아래와 같이 코드를 짤 수 밖에 없다.(SQL에 의존적)
    • Team 객체가 아닌 teamId 값이 들어간다.(객체간의 참조를 포기하게 됨)
class Member {
 String id; //MEMBER_ID 컬럼 사용
 Long teamId; //TEAM_ID FK 컬럼 사용 //**
 String username;//USERNAME 컬럼 사용
}
class Team {
 Long id; //TEAM_ID PK 사용
 String name; //NAME 컬럼 사용
}
  • 만일 객체 답게 코드를 짠다고 해도, SQL에 저장하고 조회할 때 문제가 발생한다.
    • 저장시, DB은 Team처럼 객체형 데이터 타입이 없고, team_id를 얻어야한다.
    • 조회시, DB에서, 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 컬럼 사용
}

문제는 이것뿐만이 아니다.



객체 그래프 탐색

  • 객체는 객체간에 참조로 연관관계가 되어있기때문에 자유롭게 탐색할 수 있다.
    • ex) Member.getOrder().getOrderItem().getItem()

  • 하지만 이걸 SQL로 옮기려면 머리가 아프다..
  • 예를 들어 Member와 Team을 조회하는 쿼리문을 작성을 했다고 가정하자.
SELECT M.*, T.*
 FROM MEMBER M
 JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
  • 그럼 객체는 Member에서 Team을 조회할 수 있지만, 참조된 다른 객체인 Order는 조회할 수 없게 된다
    member.getTeam(); //OK
    member.getOrder(); //null
    member.getOrder().getDelivery(); // null
    • 이렇게 되면, 엔티티의 신뢰가 깨지게된다.
    • 그렇다고, 모든 객체를 가져오기에는 매우 방대한 양이다.
    • 물론 DAO를 사용하여 해결할 수 있지만, 이것도 만만치 않다.
      memberDAO.getMember(); //Member만 조회
      memberDAO.getMemberWithTeam();//Member와 Team 조회
      memberDAO.getMemberWithOrderWithDelivery();//Member,Order,Delivery 조회
      • 필요한 DAO를 전부 만들어 줘야된다.

물리적으로는 분할이 되어 있으나, 논리적으로는 연관되어있는 상태
즉, 진정한 의미의 계층 분할이 어렵다.



다른 인스턴스

  • 같은 id로 조회를 한다고 해도, 새로운 인스턴스가 생기기 때문에, 다른 인스턴스가 두개가 생긴다.
    • 즉, 재사용에 대한 불편함이 있었다.
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다.
  • getMember()메소드의 내부 참고
    class MemberDAO {
    		public Member getMember(String memberId) {
    				String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
    				 ...
    				 //JDBC API, SQL 실행
    				 return new Member(...); // 새로만들어서 데이터 매핑
    	  }
    }

객체답게 모델링 할수록 매핑 작업만 늘어난다.





JPA의 탄생

  • 위 문제는 자바 컬렉션으로 해결이 가능했다.
    • 컬렉션에 저장해서 조회하는 형태로 되면, 같은 인스턴스를 사용할 수 있게 된다.

      String memberId = "100";
      Member member1 = list.get(memberId);
      Member member2 = list.get(memberId);
      member1 == member2; //같다.
  • 또한, 상속문제도 해결할 수 있다.
    • 부모 타입으로 조회 후 다형성을 활용하여 해결가능!
Album album = list.get(albumId);
Item item = list.get(albumId);

즉, 쿼리문에 대한 결과를 list에 보관만 잘해주면된다.

그래서

  • 많은 선대 개발자분들이 고민을 했다.
  • 객체를 자바 컬렉션에 저장하듯 DB에 저장할 수는 없을까?
  • 이것이 JPA의 시작이다.

참고 자료

profile
한단계씩 올라가는 개발자

0개의 댓글