1. JPA를 시작하기 전에
1-1. 개요
(1) 실무에서는 적어도 수 십개의 테이블과 객체가 존재하고 서로 복잡하게 연관되어 객체 간 상호 통신이 이루어진다.
(2) JPA를 실무에 적용하기 어려운 이유 중 하나가 바로 이러한 객체들과 데이터베이스의 테이블을 정확하게 매핑하는 부분에 있어서 어려움을 느끼기 때문이다.
(3) 핵심적으로 JPA 내부 동작 방식을 이해하는 것이 중요하다.
1-2. SQL(Structured Query Language) 중심 개발의 문제점 - (1)
(1) 무한 반복, 지루한 코드
(2) 변경에 따라 관계형 데이터베이스의 SQL에 대한 의존적 개발을 피하기 어려워진다.
(3) 객체와 관계형 데이터베이스 사이의 패러다임 불일치 문제를 해결하기 어렵다.
- 상속, 연관관계, 데이터 타입, 데이터 식별 방법 등
1-3. SQL(Structured Query Language) 중심 개발의 문제점 - (2)
(1) 객체끼리는 서로 참조로 연관 관계를 맺고 있다.
(2) 데이터베이스의 테이블은 Foreign key(FK) 전략을 사용한다.
(3) 이 부분에서 데이터베이스의 객체를 다룬다는 것은 객체와 데이터베이스 간의 연관 관계를 바라보는 방법(패러다임)이 다르기 때문에 상당히 번거롭다.
(4) 객체 간 그래프 탐색 부분도 문제가 되는데, 특정 상황에서 처음 실행하는 SQL에 다라 객체 간 탐색 범위가 결정되며 이에 따라 탐색 범위에 존재하지 않는 객체들은 조회하기 어렵다는 특징이 있다. (엔티티에 대한 신뢰 문제가 발생할 수 있다.)
(5) 또한 모든 객체를 런타임 시점에 미리 로딩할 수는 없다.
- SQL 중심 개발의 경우 상황에 따라 동일한 조회 메서드를 여러 번 생성해 줘야 할 수도 있다. 대부분 각 상황에 맞는 객체를 필요로 할 때 관련 메서드를 만들어두게 두는 방식을 사용하기 때문에 모든 객체를 런타임 시점에 로딩해 줄 수는 없다.
(6) 얻을 수 있는 결론
- 객체답게 모델링 하는 과정(상속, 연관관계, 참조 ... 등)에서 객체와 테이블 간 매핑 작업이 무수히 늘어나게 된다.
(위의 문제들만 봐도 모델링된 객체를 SQL로 변환하는 과정에서 많은 비용(Cost)가 소모된다.)
- 객체 지향 프로그래밍(OOP)과 관계형 데이터베이스가 나오게 된 시점부터 이러한 고민은 존재했으며 SQL를 직접 작성하고 객체와 데이터베이스 간의 패러다임 불일치, 연관관계 설정 등 복잡한 문제들을 해결하기 위해 과거에 많은 고민들이 있었다. 여기서 마치 "자바 컬렉션에 객체에 데이터를 넣고 조회하고 수정할 수는 없을까?" 라는 고민에서부터 JPA가 시작되었다고 해도 과언은 아니다.
2. JPA(Java Persistence API)
2-1. 정의
(1) JPA는 자바 진영의 ORM 기술 표준을 의미한다.
2-2. ORM(Object Relational Mapping)
(1) JPA의 정의에서 언급된 ORM은 객체와 관계형 데이터베이스를 매핑해주는 기법을 의미한다.
(2) 이러한 ORM 표준 기술 덕분에 객체는 객체대로, RDB는 RDB 컨셉에 맞게 설계한다.
(3) 대중적인 언어에 대부분 ORM 표준 기술이 존재한다.
2-3. JPA는 애플리케이션과 JDBC 사이의 계층에서 동작한다.
(1) JPA 동작 - 저장(persist)
(2) JPA 동작 - 조회(find)
2-4. JPA의 간단한 탄생 배경
(1) 당시 EJB Entity Bean은 기능이 복잡하고 성능이 충분하지 않아 실용성이 떨어지는 문제가 있었다.
(2) 이후에 하이버네이트라는 오픈 소스가 등장했다.
(3) 자바 진영에서 하이버네이트를 자바 표준 ORM 명세에 맞게 재설계하게 되었다.
(4) 이를 통해 JPA가 탄생했고 JPA는 ORM 표준 인터페이스의 모음이다.
(표준 명세를 구현한 3가지 구현체 : Hibernate, EclipseLink, DataNucleus)
3. 그렇다면 JPA를 왜 사용해야 할까?
(1) 아래와 같은 다양한 문제들을 해결하고 SQL 중심 개발에서 객체 중심 개발로 전환함에 따라 여러 가지 이점을 얻을 수 있다.
- 개발 생산성
- 유지보수
- 패러다임 불일치 문제(상속, 연관관계, 객체 그래프 탐색, 값 비교하기 등)
- 성능
- 데이터 접근 추상화, 벤더 독립성 문제
- 표준
3-1. 개발 생산성
(1) 아래와 같이 CRUD 작업 시 SQL를 직접 작성하지 않아 개발 생산성이 높고 유지보수에서 큰 이점을 얻는다.
// 저장
jpa.persist(member);
// 조회
Member member = jpa.find(memberId);
// 수정
member.setName("변경할 데이터");
// 삭제
jpa.remove(member);
3-2. 유지보수
(1) 객체의 필드 변경 시 관련된 SQL도 모두 수정해야 했지만 JPA를 사용할 경우 객체의 필드에서 변경된 부분만 별도로 수정해주면 관련된 SQL은 모두 JPA가 작성해준다.
3-3. JPA와 패러다임 불일치 문제 해결 - 상속 (저장과 조회를 하는 상황)
(1) 패러다임 불일치 문제 해결로 인해 개발자가 해야 하는 일이 상당히 줄어들었다.
jpa.persist(album);
jpa.find(Album.class, albumId);
(2) 아래와 같은 쿼리를 JPA가 직접 작성
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
SELECT I.*, A.* FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID;
3-4. 연관관계와 객체 그래프 탐색
(1) 연관관계 저장
member.setTeam(team);
jpa.persist(member);
(2) 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
3-5. JPA에서의 비교
(1) 아래의 코드는 마치 자바 컬렉션에서 동일한 인스턴스가 반환되는 것과 같다고 생각할 수 있다.
- JPA가 동일한 트랜잭션 내부에서 조회한 엔티티는 서로 같음을 보장해 준다.
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; // 같다
3-6. JPA와 성능 최적화
(1) 1차 캐시와 동일성 보장
- 동일 트랜잭션 내부에서 항상 같은 엔티티를 반환하기 때문에 약간의 조회 성능 향상을 기대해 볼 수 있다.
- DB isolation level이 Read commit이어도 애플리케이션에서 Repeatable read을 보장받을 수 있다.
(2) 트랜잭션을 지원하는 쓰기 지연 - INSERT SQL
- 트랜잭션은 커밋 시점까지 INSERT SQL를 모두 모아두고 JDBC Batch SQL 기능을 사용해서 한 번에 SQL을 전송한다.
transaction.begin() // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 여기까지 insert sql를 날리지 않는다
// 커밋 시점에 insert sql를 한 번에 전송한다
transaction.commit();
(3) 트랜잭션을 지원하는 쓰기 지연 - UPDATE SQL
- UPDATE, DELETE SQL로 인한 Row 락 시간을 최소화한다.
- 트랜잭션 커밋 시점에 UPDATE, DELETE SQL를 실행하고 바로 커밋하게 된다.
transaction.begin();
changeMember(memberA);
deleteMember(memberB);
// 비즈니스 로직 수행 : 로직이 수행되는 동안 DB 로우 락이 걸리지 않음
// 커밋 시점에 update, delete sql를 전송한다
transaction.commit();
3-7. 지연 로딩(Lazy loading), 즉시 로딩(Eager loading)
(1) 지연 로딩(Lazy loading)
- 자신과 관련된 엔티티를 실제로 사용할 때 연관된 엔티티를 조회하는 방식
(2) 즉시 로딩(Eager loading)
- 엔티티를 조회할 때 자신과 연관된 엔티티를 조인(Join)을 통해 함께 조회하는 방식
(3) 예를 들어 특정 작업을 수행할 때 어떤 객체를 사용할 빈도가 매우 낮아서 그 객체를 미리 끌고 올 필요가 없다고 판단될 때 지연 로딩 방식을 사용할 수 있다. 반대로 작업에 필요한 연관 객체를 미리 조회하는 것을 즉시 로딩이라고 한다.
- 이 부분은 성능 최적화 관점에서 매우 중요하기 때문에 JPA를 사용할 때 대부분 지연 로딩 전략을 사용하고 예외적인 상황에서 즉시 로딩을 적용한다.
4. Reference
※ 해당 포스팅은 InFlearn에서 (전) 우아한형제들, 배달의 민족 서비스 개발팀장(기술이사)으로 재직하셨던 김영한님의 "자바 ORM 표준 JPA 프로그래밍(기본편)" 강의를 듣고 공부한 내용을 정리하였습니다.
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
※ 해당 포스팅에 대해 내용 추가가 필요하다고 생각되면 기존 포스팅 내용에 다른 내용이 추가될 수 있습니다.
개인적으로 공부하며 정리한 내용이기에 오타나 틀린 부분이 있을 수 있으며, 이에 대해 댓글로 알려주시면 감사하겠습니다!
댓글