Goal
- ORM이란
- JPA란
- JPA의 동작 과정
- JPA를 사용해야 되는 이유
ORM(Object-relational mapping) 이란
- Object-relational mapping (객체 관계 매핑)
- 객체는 객체대로 설계하고, 관계형 데이터베이스는 관계형 데이터베이스대로 설계한다.
- ORM 프레임워크가 중간에서 매핑해준다.
- 대중적인 언어에는 대부분 ORM 기술이 존재한다.
- ORM은 객체와 RDB 두 기둥 위에 있는 기술 이다.
JPA(Java Persistence API) 란
- EJB
- 과거의 자바 표준 (Entity Bean)
- 과거의 ORM
- 문제?
- 코드가 매우 지저분하다.
- API의 복잡성이 높다. (interface를 많이 구현해야 함)
- 속도가 느리다.
- Hibernate
- ORM 프레임워크, Open Source SW
- ‘Gavin King’ 과 시러스 테크놀로지스 출신 동료들이 EJB2 스타일의 Entity Beans 이용을 대체할 목으로 개발하였다.
- JPA (Java Persistence API)
- 현재 자바 진영의 ORM 기술 표준으로, 인터페이스의 모음이다.
- 즉, 실제로 동작하는 것이 아니다.
- JPA 인터페이스를 구현한 대표적인 오픈소스가 Hibernate라고 할 수 있다.
- JPA 2.1 표준 명세를 구현한 3가지 구현체: Hibernate, EclipseLink, DataNucleus
- 버전
- JPA 1.0(JSR 220) 2006년 : 초기 버전. 복합 키와 연관관계 기능이 부족
- JPA 2.0(JSR 317) 2009년 : 대부분의 ORM 기능을 포함, JPA Criteria 추가
- JPA 2.1(JSR 338) 2013년 : 스토어드 프로시저 접근, 컨버터(Converter), 엔티티 그래프 기능이 추가
- Cf) Spring Framework
- Application 프레임워크, Open Source SW
- ‘Rod Johnson’ 이 EJB의 여러 문제를 해결하고, 엔터프라이즈 애플리케이션 개발을 좀 더 쉽게 하기 위한 목적으로 만들었다.
JPA의 동작 과정
- JPA는 애플리케이션과 JDBC 사이에서 동작한다.
- 개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.
- 즉, 개발자가 직접 JDBC API를 쓰는 것이 아니다.
저장 과정
- Ex) MemberDAO에서 객체를 저장하고 싶을 때
- 개발자는 JPA에 Member 객체를 넘긴다.
- JPA는
- Member 엔티티를 분석한다.
- INSERT SQL을 생성한다.
- JDBC API를 사용하여 SQL을 DB에 날린다.
조회 과정
- Ex) Member 객체를 조회하고 싶을 때
- 개발자는 member의 pk 값을 JPA에 넘긴다.
- JPA는
- 엔티티의 매핑 정보를 바탕으로 적절한 SELECT SQL을 생성한다.
- JDBC API를 사용하여 SQL을 DB에 날린다.
- DB로부터 결과를 받아온다.
- 결과(ResultSet)를 객체에 모두 매핑한다.
- 쿼리를 JPA가 만들어 주기 때문에 Object와 RDB 간의 패러다임 불일치를 해결할 수 있다.
JPA를 왜 사용해야 하는가?
1. SQL 중심적인 개발에서 객체 중심으로 개발
2. 생산성
- JPA를 사용하는 것은 마치 Java Collection에 데이터를 넣었다 빼는 것처럼 사용할 수 있게 만든 것이다.
- 간단한 CRUD
- 저장: jpa.persist(member)
- 조회: Member member = jpa.find(memberId)
- 수정: member.setName("변경할 이름")
- 삭제: jpa.remove(member)
- 특히, 수정이 굉장히 간단하다.
- 객체를 변경하면 그냥 알아서 DB에 UPDATE Query가 나간다.
3. 유지보수
- 기존: 필드 변경 시 모든 SQL을 수정해야 한다.
- JPA: 필드만 추가하면 된다. SQL은 JPA가 처리하기 때문에 손댈 것이 없다.
4. Object와 RDB 간의 패러다임 불일치 해결
- JPA와 상속
- 저장
- 조회
-
개발자가 할 일
Album album = jpa.find(Album.class, albumId);
-
나머진 JPA가 처리
SELECT I.*, A.* FROM ITEM I JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
- JPA와 연관관계
- 객체의 참조로 연관관계 저장 가능
- member.setTeam(team);
- jpa.persist(member);
- JPA와 객체 그래프 탐색
class MemberService {
...
public void process() {
Member member1 = memberDAO.find(memberId);
member1.getTeam();
member1.getOrder().getDelivery();
Member member2 = jpa.find(Member.class, memberId);
member2.getTeam();
member2.getOrder().getDelivery();
}
}
- 내가 아닌 다른 개발자가 직접 구현한 DAO에서 가져오는 경우
- DAO에서 직접 어떤 쿼리를 날렸는지 확인하지 않는 이상, 그래프 형태의 관련된 객체들을 모두 잘 가져왔는지 알 수가 없다.
- 즉, 반환한 엔티티를 신뢰하고 사용할 수 없다.
- JPA를 통해서 가져오는 경우
- 객체 그래프를 완전히 자유롭게 탐색할 수 있게 된다.
- 지연 로딩 전략(Lazy Loading) 사용
- 관련된 객체를 사용하는 그 시점에 SELECT Query를 날려서 객체를 가져오는 전략
- JPA와 비교하기
- 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2;
- JPA의 성능 최적화 기능
- 중간 계층이 있는 경우 아래의 방법으로 성능을 개선할 수 있는 기능이 존재한다.
- 모아서 쓰는 버퍼링 기능
- 읽을 때 쓰는 캐싱 기능
- JPA도 JDBC API와 DB 사이에 존재하기 때문에 위의 두 기능이 존재한다.
1) 1차 캐시와 동일성(identity) 보장 - 캐싱 기능
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상 (크게 도움 X)
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId);
Member m2 = jpa.find(Member.class, memberId);
println(m1 == m2)
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
2) 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) - 버퍼링 기능
transaction.begin();
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
transaction.commit();
- [트랜잭션]을 commit 할 때까지 INSERT SQL을 메모리에 쌓는다.
- 이렇게 하지 않으면 DB에 INSERT Query를 날리기 위한 네트워크를 3번 타게 된다.
- JDBC Batch SQL 기능을 사용해서 한 번에 SQL을 전송한다.
- JDBC Batch를 사용하면 코드가 굉장히 지저분해진다.
- 지연 로딩 전략(Lazy Loading) 옵션을 사용한다.
*
transaction.begin();
changeMember(memberA);
deleteMember(memberB);
비즈니스_로직_수행();
transaction.commit();
- UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
- 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋
3) 지연 로딩(Lazy Loading)
- 지연 로딩
- 객체가 실제로 사용될 때 로딩하는 전략
- memberDAO.find(memberId)에서는 Member 객체에 대한 SELECT 쿼리만 날린다.
- Team team = member.getTeam()로 Team 객체를 가져온 후에 team.getName()처럼 실제로 team 객체를 건드릴 때!
- 즉, 값이 실제로 필요한 시점에 JPA가 Team에 대한 SELECT 쿼리를 날린다.
- Member와 Team 객체 각각 따로 조회하기 때문에 네트워크를 2번 타게 된다.
- Member를 사용하는 경우에 대부분 Team도 같이 필요하다면 즉시 로딩을 사용한다.
- 즉시 로딩
- JOIN SQL로 한 번에 연관된 객체까지 미리 조회하는 전략
- Join을 통해 항상 연관된 모든 객체를 같이 가져온다.
- 애플리케이션 개발할 때는 모두 지연 로딩으로 설정한 후에, 성능 최적화가 필요할 때에 옵션을 변경하는 것을 추천한다.
6. 데이터 접근 추상화와 벤더 독립성
7. 표준