강의 정리를 시작하기 앞서
왜?? 라는 질문에 대답하기 위해선 해당 분야에 많은 지식이 있어야지 대답을 해줄 수 있습니다.'
앞으로 스스로에게 왜?? 라는 질문을 던지며 정리를 하려고합니다. 왜?? 라는 질문은 강의와 책을 보며 찾은 해답을 정리할 것이고, 아직 해답을 못찾은 왜?라는 질문에 대한 대답은 후에 정리하도록 하겠습니다.
JPA 왜 공부해야 하는가?
1. 현재 JPA를 공부하는 이유는 점유율입니다. JPA, Mybatis 등 중에서 세계적으로 앞도적인 점유율을 보여주는 JPA를 사용할 줄 알고, 이해하고 있어야지 취업에 도움이 될 것이라고 생각하기 때문입니다.
2. 무한 반복, 지루한 코드를 적어야하는 SQL의 문제점을 보완해주기 때문입니다. (간단하게 쿼리문 작성이 필요하지 않습니다.) 이에따라서, 개발 시간이 줄어듬으로써 설계나 테스트 등에 시간을 더 사용할 수 있습니다.
3. 시간을 줄여준다고 성능이 입증되지 않은 것도 아닙니다. 현재 조 단위의 거래 금액이 발생하는 다양한 서비스에서 사용하고 있으며 문제없이 사용되기 때문에 문제없다고 생각했습니다.
SQL 중심적인 개발의 문제점
무한 반복, 지루한 코드를 적어야하는 예를 한번 들어봅시다. JPA가 없는 SQL만으로 작성된 아래의 코드로 작성된 프로젝트가 있다고 가정해봅시다.
여기서, 기획자에 요청에 의해서 객체에 번호 필드를 추가해야되는 상황이 주어졌습니다. 객체에 tel이란 필드를 생성하면 끝나는 것이 아닌 모든 쿼리문에 tel을 추가해야되는 상황이 발생합니다.
결국, 관계형 데이터베이스를 사용하는 환경에서는 SQL에 의존적인 개발을 피하기 어렵습니다.
그리고 단순한 SQL 문을 작성하는 문제를 넘어서서 또 하나의 문제점이 있습니다. 바로 패러다임의 불일치 입니다.
패러다임의 불일치
객체 vs 관계형 데이터베이스
관계형 데이터베이스의 사상과 객체 지향 사상이 다릅니다.
객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공합니다. 반면, 관계형 데이터베이스는 데이터를 잘 정규화해서 보관하는 것을 목표로합니다.
하지만 생각을 뒤집어 봅시다. 먼저 객체를 잘 정규화해서 저장한다고 봅시다. 선택지는 RDB, NoSQL, File 등 여러가지가 있습니다. 여기서 데이터를 저장하는 다양한 방법 중 현실적인 대안은 RDB입니다. 이유는 데이터가 백만건 있다고 할 때 파일에 집어 넣는 경우는 검색이 불가능하고 백만건 모두를 Object를 만든 뒤 검색하기에는 답이 없기 때문입니다. NoSQL도 대안이 될 수 있기도 하지만 아직까지 Main은 RDB를 쓰기 때문에 힘들다고 합니다. 그렇기 때문에 RDB를 사용해야합니다.
결론적으로, RDB를 사용함으로써 객체를 SQL로 바꿔야하기 때문에 SQL은 필수로 사용해야 합니다. 이러한 작업은 개발자가 해야합니다.
객체와 관계형 데이터베이스의 차이
- 상속
- 객체 : 상속 관계 O
- RDB : 상속 관계 X
- 연관관계
- 객체 : 레퍼런스 ex) ArrayList에서의 get
- RDB : PK, FK
- 데이터 타입
- 데이터 식별 방법
상속
위와 같은 상속 관계가 있다고 가정해봅시다.
삽입
- 객체 분해
- INSERT INTO ITEM ...
- INSERT INTO ALBUM ...
insert의 경우 3번의 작업을 거치긴하지만 가능합니다.
조회
- 각각의 테이블에 따른 조인 SQL 작성 ..
- 각각의 객체 생성 ..
- 상상만 해도 복잡합니다.
그렇기때문에 DB에 저장할 객체에는 상속 관계를 쓰지 않습니다.
하지만, DB가 아닌 자바 컬렉션에 저장한다면 어떻게 될까요??
삽입
list.add(album);
조회
Album album = list.get(albumId);
// 부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumID);
자바 컬렉션에 넣으면 심플해지지만, RDB에 넣고 빼는 순간 중간에 SQL 매핑 작업을 개발자가 한땀한땀 해야합니다.
연관관계
- 객체는 참조를 사용 : member.getTeam()
- 테이블은 외래 키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID
객체의 경우 Member에서 Team을 찾을 수 있고, Team에서 Member를 찾을 순 없습니다. 하지만 테이블의 경우 MEMBER에서 TEAM을 join할 수 있고, TEAM에서 MEMBER를 join할 수 도 있습니다.
객체를 테이블에 맞추어 모델링
class Member {
String id; // MEMBER_ID 컬럼 사용
Long teamId; // TEAM_ID FK 컬럼 사용 // **
String username;// USERNAME 컬럼 사용
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
테이블에 맞춘 객체 저장
하지만 이는 객체다운 모델링 같지 않습니다. Member 클래스에 있는 teamId의 타입이 Team이 아닌 Long으로 되어있기 때문입니다.
객체다운 모델링
class Member {
String id; // MEMBER_ID 컬럼 사용
Team teamId; // 참조로 연관관계를 맺는다. // **
String username;// USERNAME 컬럼 사용
Team getTeam() {
return team;
}
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
객체 모델링 저장
이렇게 설계하면 정상적으로 저장이 가능합니다. 하지만 조회할 때 문제가 생깁니다.
객체 모델링 조회
위와 같이 주석처리 된 부분의 코드가 상당할 것으로 예상됩니다. 결론적으로 매우 번거롭습니다.
객체 모델링, 자바 컬렉션에 관리
훨씬 편리해진 것을 볼 수 있습니다.
객체 그래프 탐색
객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다. 아래의 그래프를 예시로 들어 봅시다.
여기서 자유롭게 그래프를 탐색한다는 것은 자바에서 get을 통해 모든 객체로 갈 수 있다는 것을 의미합니다.
처음 실행하는 SQL에 따라 탐색 범위 결정
이처럼 SELECT로 MEMBER와 TEAM을 가져오면 getTeam은 가능하지만, getOrder은 불가능합니다. 이것을 해결하기 위해선 일일이 코드를 다 살펴봐야하는 문제가 발생하게 됩니다. 즉, 엔티티에 신뢰할수 없다는 것 입니다.
엔티티 신뢰 문제
위에 코드가 있다고 가정합시다. Member를 받아와서 getTeam(), getOrder().getDelivery()를 수행하려 합니다. 하지만 엔티티를 모두 가져올 수 있을지는 장담할 수 없습니다. 이를 알아보기 위해선 Member를 가져올 때 TEAM, ORDER, DELIVERY 데이터를 모두 긁어 오는지를 확인해야 합니다.
모든 객체를 미리 로딩할 수는 없다.
그렇다면 모든 객체를 미리 모두 로딩해놓으면 되는 것 아닌가라는 의문점이 생깁니다. 하지만 사실상 현업에서 사전에 미리 로딩하는 작업을 하게되면 서비스를 이용하기 직전에 불필요한 데이터까지 조회를 하기때문에 너무 오랜 시간을 기다릴 수 있어 서비스적인 측면에서 좋지 못합니다. (개인적인 생각입니다.) 그렇기때문에 위와같이 테이블을 조회하는 모든 경우의 수를 메소드화 시키는 것이 좋지만 이또한 코드가 지저분해지고 지루한 코드를 오랜시간 작성해야하는 번거로움이 있습니다.
이렇듯 SQL을 직접 다루게 되면 계층형 아키텍처에서 진정한 의미의 계층 분할이 어렵습니다. (진정한 게층 분할은 뭘까??)
비교하기
SQL에서 조회
member1과 member2가 식별자는 똑같지만 다른 이유는 다른 쿼리문으로 받아온 값을 new Member를 통해 반환하기 때문에 당연히 다릅니다. 그렇다면 자바 컬렉션에서는 어떨까요?
자바 컬렉션에서 조회
이것은 당연하게 같은 참조값으로 조회하기때문에 같은 Member를 반환합니다.
이처럼 자바 컬렉션에서 객체를 다룰 때와 SQL(RDB)에서 객체를 다룰 때와 중간에 많은 미스매치가 나는 것을 볼 수 있습니다. 그렇기때문에 RDB를 객체답게 모델링 할수록 매핑 작업만 늘어나게 됩니다.
이러한 무한 반복, 지루한 코드를 계속해서 작성하던 선배 개발자분들은 "객체를 자바 컬렉션에 저장 하듯이 DB에 저장"하고 싶어집니다. 이러한 고민의 결과가 바로 JPA이고, 공부를 해야하는 이유 입니다.
JPA 소개
JPA란?
Java Persistence API의 약자로 자바 진영의 ORM 기술 표준입니다.
ORM이란?
Object-Relational Mapping (객체 관계 매핑)의 약자로 객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계하는 것을 의미합니다. 각각 설계된 객체와 RDB를 ORM 프레임워크가 중간에서 매핑합니다.
JPA 동작
JPA는 JAVA 애플리케이션과 JDBC 사이에서 동작합니다. 그렇기때문에 개발자가 직접 JDBC API를 쓰는 것이 아닌 JPA에게 명령하면 JPA가 JDBC API를 사용해서 SQL을 호출하고 만들어서 보내고 결과를 받는 등의 동작을 하게 됩니다.
JPA 동작 - 저장
예를들어, MemberDAO에서 객체를 저장하고 싶다고 가정해봅시다. 과거에는 JDBC API나 JDBC Template, Mybatis를 썼다면 JPA를 사용하게 될 경우에는 JPA에게 Member 객체를 넘깁니다. 그렇게되면 JPA가 Member 객체를 분석하고 적절한 INSERT SQL을 생성하고 JDBC API를 사용해서 쿼리를 DB에 보냅니다. 여기서 JPA의 장점은 쿼리를 개발자가 작성하는 것이 아닌 JPA가 작성해줍니다. 그렇기때문에 패러다임의 불일치가 해결됩니다. (아직까지는 어떻게 패러다임의 불일치가 해결되는지 이해가지 않지만 뒤에서 해결해준다고합니다.)
JPA 동작 - 조회
조회할 때도 마찬가지로 JPA에게 PK값만 넘기면 JPA가 알아서 쿼리문을 작성하고 JDBC API를 사용해서 DB를 보내고 결과를 받은 다음에 ResultSet 결과를 객체에 다 매핑해줍니다. 그렇기때문에 코드한줄로 조회기능을 애플리케이션 단에서 가능하게 됩니다.
JPA 역사
해당 역사는 기본편에서 한번 보았던 내용인 것 같아 따로 정리하지않고 링크를 아래 추가했습니다.
JPA는 표준 명세
- JPA는 인터페이스의 모음
- JPA 2.1 표준 명세를 구현한 3가지 구현체
- 하이버네이트(대표적), EclipseLink, DataNucleus
JPA를 왜 사용해야 하는가?
- SQL 중심적인 개발에서 객체 중심으로 개발
- 생산성
- 유지보수
- 패러다임의 불일치 해결
- 성능
- 데이터 접근 추상화와 벤더 독립성
- 표준
생산성 - JPA와 CRUD
- 저장
- jpa.persist(member)
- 조회
- Member member = jpa.find(memberId)
- 수정 (fantastic)
- member.setName("updateName")
- 삭제
- jpa.remove(member)
코드가 모두 만들어져있고 가져다 쓰기만해서 기존 CRUD를 하기위해 SQL 문을 작성하던 번거로움이 사라졌습니다.
유지보수 - 기존 : 필드 변경시 모든 SQL 수정
위에서 느꼈던 기획자에 요청에 의해 필드가 변경될 때 모든 SQL 문을 수정해야하는 번거로움이 있었습니다. 하지만 아래와 같이 JPA에서는 JAVA 애플리케이션 단에서 컬럼만 추가하면 됩니다. 따라서 유지보수 측면에서 훨씬 우월하다고 볼 수 있습니다.
JPA와 패러다임의 불일치 해결
- JPA와 상속
- JPA와 연관관계
- JPA와 객체 그래프 탐색
- JPA와 비교하기
JPA와 상속
이전에 보았던 상속 관계입니다.
JPA와 상속 - 저장
이전에 SQL 문을 작성했던 번거로움이 사라진 것을 볼 수 있습니다. 또한, 상속관계에 있는 album을 보고 알아서 ITEM의 INSERT 쿼리문도 작성해주는 것을 볼 수 있습니다.
JPA와 상속 - 조회
기가 막힙니다. JPA에 앨범의 PK 값을 넘기면 JPA에서 ITEM과 ALBUM을 조인해서 데이터를 가져오는 것을 볼 수 있습니다. 패러다임이 맞지 않는 부분을 JPA가 해결해줍니다.
JPA와 연관관계, 객체 그래프 탐색
member에 team을 set한 뒤 member를 jpa에 보내 DB에 저장합니다. 후에 조회하여 member로 team을 조회하면 가져올 수 있습니다. 마치 자바 컬렉션에 넣었던 것 처럼!!
신뢰할 수 있는 엔티티, 계층
JPA를 통해서 member 객체를 가져온거면 객체 그래프를 자유롭게 모두 다 탐색할 수 있습니다. JPA는 지연로딩이라는 기능덕분에 member.getTeam()이나 member.getOrder()를 해서 실제 객체를 사용해서 조회하는 시점에 SQL을 보내 채워주는 기능이 있습니다. 그렇기때문에 자유롭게 탐색할 수 있습니다. 그렇기때문에 신뢰할 수 있는 엔티티가 될 수 있습니다.
JPA와 비교하기
동일한 트랙잭션에서 조회한 엔티티는 같음을 보장합니다. 이것이 가능한 이유는 강의를 보다보면 이해할 수 있다고 합니다. (아직 이해 안됨)
JPA의 성능 최적화 기능
- 1차 캐시와 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 지연 로딩 (Lazy Loading)
JPA를 사용하면 성능이 떨어질 수도 있다고 생각할 것 입니다. 이렇게 생각하는 이유는 제 생각으론 기존에는 JAVA 애플리케이션에서 JDBC API로 바로 사용했지만, JPA를 사용하면 중간에 JPA를 거치기때문에 구조가 한단계 더 깊어졌기 때문입니다.
계층 사이에 중간 계층이 있으면 항상 할 수 있는 것이 있습니다. 바로 모아서 쏘는 버퍼링과 읽을 때 캐싱하는 것을 할 수 있습니다. (마치 CPU와 메모리 사이 캐시기능) JAVA 애플리케이션과 JDBC API의 중간계층으로 JPA가 있기 때문에 SQL을 사용하는 것보다 이러한 부분들을 최적화 할 수 있습니다.
1차 캐시와 동일성 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장 (DB 지식이 많이 있어야하기 때문에 나중에 이해하고 이해해야할 부분, 현재는 알아두기만 할 것)
아까 조회했던 코드입니다. 첫번째 조회는 SQL을 통해 조회하고 두번째는 캐시를 통해 데이터를 가져옵니다. 그렇기때문에 m1과 m2는 같고 조회 성능을 향상시킬 수 있습니다. 실제로 캐싱기능을 지원해주는 것이 아닌 트랜잭션을 통해 제공해주는 기능이기 때문에 굉장히 짧은 시간에만 사용이 가능해서 실무에서 큰 도움은 되지 않는다고 합니다.
트랜잭션을 지원하는 쓰기 지연 - INSERT
- 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
- JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
JDBC BATCH 기능인 한번에 여러 SQL 기능을 사용해서 한번에 SQL 전송을 하는 기능이 있습니다. JPA를 사용하지 않는 경우에는 상당히 복잡하다고 하는데 JPA를 사용하면 위와같이 간단하게 사용할 수 있습니다.
트랜잭션을 지원하는 쓰기 지연 - UPDATE
- UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
- 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋
지연 로딩과 즉시 로딩
- 지연 로딩 : 객체가 실제 사용될 때 로딩
- 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
JPA에서 중요한 부분 중 하나입니다. 위 그림과 같이 지연 로딩은 객체가 사용될 때 로딩을 합니다. 지연 로딩의 많은 쿼리가 발생하는 단점을 보완한 기능인데 Member 조회해서 사용할 때 team을 거의 같이 사용한다고 가정될 때 member를 로딩할 때 team도 같이 조회해서 가져오는 것을 말합니다. 옵션을 통해 지연 로딩과 즉시 로딩을 껐다 켤 수 있다고 합니다. 김영한님은 지연 로딩으로 코드를 다 작성한 후 최적화할 때 즉시 로딩이 필요한 부분만 최적화를 진행한다고 합니다.
마지막으로
ORM이라는 기술은 객체와 RDB 두 기둥위에 있는 기술입니다. JPA만 잘 안다고해서 잘 할 수 있는 것이 아니고 RDB만 잘 안다고해서 잘할 수 있는 것이 아닙니다. JPA는 객체와 RDB 두 가지 사이에서 밸런스를 잘 맞춰야합니다. 한 가지에 편향하지 않고 JPA를 공부하더라도 RDB도 꾸준하게 같이 공부해야 한다. 꼭! 명심해!
출처
'spring > 인프런 강의 정리' 카테고리의 다른 글
[JPA 기본편] 3. 영속성 관리 - 내부 동작 방법 (0) | 2022.03.05 |
---|---|
[JPA 기본편] 2. JPA 시작하기 (0) | 2022.02.21 |
[HTTP 웹 기본 지식] 8. HTTP 헤더 - 캐시와 조건부 요청 (0) | 2022.01.15 |
[HTTP 웹 기본 지식] 7. HTTP 헤더 - 일반 헤더 (0) | 2022.01.13 |
[HTTP 웹 기본 지식] 6. HTTP 상태코드 (0) | 2022.01.13 |