김영한 강사님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의내용을 정리하는 글이다.
현재 데이터베이스 세계의 헤게모니는 관계형 DB이다! (Oracle, MySQL)
헤게모니 : 우두머리의 자리에서 전체를 이끌거나 주동할 수 있는 권력.
그렇기 때문에, 현재는 객체를 관계형 DB에 저장하고 관리해야 되는 시대다.
근데, DB는 SQL만 알아들을 수 있기 때문에 결국 계속해서 SQL을 작성해야 된다.
그렇다면, SQL 중심적인 개발의 문제점은?
- 무한 반복, 지루한 코드
- 객체 CRUD - 필드 추가 : 객체에 필드를 추가하면 각 sql문에도 이를 모두 추가해줘야 된다.
→ SQL에 의존적인 개발을 하기 어렵다.
패러다임의 불일치
객체가 나온 사상과, 관계형 데이터베이스가 나온 사상이 너무나도 다르다.
- 관게형 데이터베이스 : 데이터를 잘 정규화해서 보관하는 것이 목표
- 객체 : 필드와 메서드를 묶어서 캡슐화하는 것이 목표
그렇지만, 위에서 말했듯이 현재는 관계형 DB를 메인으로 사용하는 시대이기 때문에 가장 현실적인 대안은 객체를 관계형 DB에 저장하는 것이다.
즉, 객체를 관계형 DB에 저장해야 되고, 객체를 SQL로 변환해야 된다. 그렇다면, 이러한 작업을 SQL 매퍼( == 개발자)가 하는 것이다.
객체와 관계형 데이터베이스의 차이
- 상속 : 관계형 DB에는 상속 관계가 없다.
- Table 슈퍼타입, 서브타입이라는 비슷한 개념은 존재한다.
- 연관관계 : 객체는 참조를 통해 필요한 정보를 가져오고, 관계형 DB에는 PK와 FK를 통해 조인을 해서 정보를 찾는다.
- A라는 객체가 B라는 객체를 필드로 가지고 있을 때, A.getB()를 통해 B에 접근할 수는 있지만 B에서 A로는 접근할 수 없다. 즉, 한 방향으로만 접근할 수 있다.
- 하지만, 테이블은 FK를 통해 조인하기 때문에 양방향으로 접근이 가능하다.
객체 그래프 탐색 : 객체는 .get()
이나 .
을 통해서 관계가 있는 객체를 자유롭게 접근할 수 있어야 한다.
하지만, DB에서는 처음 실행하는 SQL에 따라 탐색 범위가 결정된다.
- Member와 Team과 Order가 연관관계를 가지고 있는데, SQL을 작성할 때, Member와 Team으로만 조인한 결과값을 가져오게 되면 Member는 Order와 연관관계를 가지고 있음에도 Member를 통해 Order에 자유롭게 접근할 수 없게 된다.
계층형 아키텍처 : 다음 계층에 대해서 신뢰를 가지고 있어야 하는데, DB에서 가져온 결과값이 어떤 탐색 범위를 가지고 있는지 직접 코드를 까보기 전까지는 알 수 없다. 그렇기 때문에 진정한 계층형 아키텍처
에서의 진정한 의미의 계층 분할이 어렵다.
- 비교하기 : 같은 식별자로 두 객체를 테이블에서 조회했을 때, 두 객체가 같은지 확인하면 다르다는 결과를 확인할 수 있다.
하지만, Java Collection에서 두 객체를 get 해서 같은지 검사하면 참조값이 같기 때문에, 같다는 결과를 확인하게 된다.
1. 즉, Java Collection에서 객체를 다룰 때와 SQL에서 객체를 다룰 때 중간에 Mismatch가 발생한다는 것을 알 수 있다.
객체답게 모델링 할수록 매핑 작업만 늘어나기 때문에 SQL에 맞춰서 객체를 설계할 수 밖에 없게 된다.
그렇다면 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수는 없을까? → JPA
JPA란? (Java Persistence API)
자바 진영의 ORM 기술 표준
ORM?
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 DB는 관계형 DB대로 설계
- ORM 프레임워크가 중간에서 매핑!
- 대중적인 언어에는 대부분 ORM 기술이 존재
JPA 동작
JPA는 애플리케이션과 JDBC 사이에서 동작하며, 객체와 관계형DB의 패러다임 불일치를 해결한다.
- 저장
- Java 애플리케이션에서 JPA에게 Member 객체를 넘긴다.
- JPA가 Member 객체를 분석해서 적절한 Insert 쿼리를 생성한다.
- JPA가 JDBC API를 사용해서 DB에 쿼리를 보내고, 결과를 받는다.
- 조회
- JPA가 Member 객체를 보고 적절한 Select 쿼리를 생성한다.
- JDBC API를 통해서 DB에게 결과를 받은 뒤에 이를 객체에 매핑한다.
JPA의 역사
과거에 EJB라는 자바 표준 ORM 기술이 있었다. 이는 인터페이스를 매우 많이 구현해야 하고, 속도가 느리고 성능이 좋지 않다는 여러 단점이 있었다.
이를 해결하기 위해 하이버네이트라는 오픈 소스 프레임워크가 개발되었다. 자바 측에서 이러한 하이버네이트를 복사-붙여넣기 하듯 만든 표준 ORM이 JPA다.
JPA 표준 명세
JPA는 인터페이스의 모음이다.
이를 구현한 3가지 대표적인 구현체는 다음과 같다.
- 하이버네이트(8~90% 사용), EclipseLink, DataNucleus
JPA 2.0 이후로는 대부분의 기능이 잘 작동한다.
JPA를 왜 사용해야 하는가?
- 생산성 : CRUD 기능이 이미 만들어져 있어서 한 줄로 구현 가능하다.
- 수정의 경우에는
member.setName(”변경할 이름”)
만 작성하더라도 DB에 변경 사항이 적용된다.
- 마치 Java Collection에서 객체를 다루듯이
- 유지보수
- 기존 : 필드 변경시 모든 SQL이 수정해야 된다.
- JPA : 필드만 추가하면 된다. SQL은 JPA가 알아서 처리한다.
- 객체-관계형DB 패러다임 불일치 해결
- 상속 : Item 클래스를 상속하고 있는 Album 클래스 객체를 저장하려고 할 때,
jpa.persist(album)
코드 하나만 작성하면 나머진 JPA가 알아서 처리해준다.(ITEM 테이블과 ALBUM 테이블에 Insert 쿼리 각각 날려준다.) → 조회 등 나머지 작업에서도 마찬가지
- 연관관계, 객체 그래프 탐색 : 테이블에서 JPA를 통해 가져온 객체로 연관관계를 가지는 모든 객체에 자유롭게 접근할 수 있다.
- Member 객체를 가져왔고, 이를 통해서 Order 객체에 접근하려고 하면 그 시점에 JPA가 알맞는 쿼리를 날려준다(지연 로딩) → 다음 계층을 신뢰할 수 있게 된다.
- 비교하기 : 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다
- JPA를 통해 같은 식별자로 두 객체를 조회하면 두 객체는 같은 객체다.
JPA의 성능 최적화 기능
중간 계층에서는 다음과 같은 일을 할 수 있다.
중간 계층에 존재하는 JPA 역시 이와 같은 기능을 통해 성능을 더욱 끌어올릴 수 있다.
-
1차 캐시와 동일성 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환한다. - 캐싱과 비슷한 개념. 하지만, 매우 짧은 시간(한 트랜잭션)만 유지되므로 큰 성능 향상을 기대하긴 어렵다.
-
트랜잭션을 지원하는 쓰기 지연(버퍼링)
Insert 쿼리를 3개 보내고 싶을 때, 이를 하나씩 3번 보내는 것은 네트워크를 3번 타야 되기 때문에 한 번에 3개를 모아서 보내도록 할 수 있다.
-
지연 로딩과 즉시 로딩
- 지연 로딩 : 객체가 실제 사용될 때 로딩
- 즉시 로딩 : JOIN SQL로 한 번에 연관된 객체까지 미리 조회
JPA에서는 지연 로딩, 즉시 로딩을 옵션을 통해 선택할 수 있다. → 상황에 따라 전략적으로 설계 가능하다.
ORM은 객체와 RDB 두 기둥 위에 있는 기술이다. 그렇기 때문에, 두 개념을 모두 밸런스 있게 알고 있어야 한다. (김영한님은 관계형 DB가 더 중요하다고 생각한다.)
References
김영한 님 '자바 ORM 표준 JPA 프로그래밍 - 기본편'