[자바 ORM 표준] JPA 소개
JPA 왜 사용하는가?
과거에 객체를 DB에 저장하려면 복잡한 JDBC API, SQL을 등록해야했다.
JDBCTemplate에서 개발코드는 많이 줄었지만 sql은 다 작성해야했다.
JPA는 이 sql조차 작성할 필요가 없다.
그 밖에 객체를 자바 컬렉션에 저장하듯이 DB에 저장하기 위해서 사용한다.
객체/관계형 데이터베이스 차이
- 상속
- 연관관계
- 데이터 타입
- 데이터 식별 방법
상속
Album 저장
- 객체 분해
- INSERT INTO ITEM …
- INSERT INTO ALBUM …
Album 조회
- 각각의 테이블에 따른 조인 SQL 작성
- 각각의 객체 생성
(그 밖에 복잡한 과정 생략…)
자바 컬렉션
저장
list.add(album);
조회
Album album = list.get(albumId);
연관관계
- 객체는 참조를 사용: member.getTeam()
- 테이블은 외래 키를 사용: JOIN ON M.TEAM_ID = T.TEAM_ID
테이블에 맞추어 모델링
class Member {
String id;
Long teamId;
String username;
}
class Team {
Long id;
String name;
}
테이블에 맞춘 객체 저장
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ...
JPA
- Java Persistence API
- 자바 진영의 ORM 기술 표준
- EJB - 엔티티 빈(자바 표준) -> 하이버네이트(오픈 소스) -> JPA(자바 표준)
- JPA는 인터페이스의 모음
- JPA 2.1 표준 명세를 구현한 3가지 구현체
- 하이버네이트, EclipseLink, DataNucleus
ORM
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORM 프레임워크가 중간에서 매핑
- 대중적인 언어에는 대부분 ORM 기술이 존재
JPA 동작
JPA는 애플리케이션과 JDBC 사이에서 동작한다.
동작 예시(저장)
JPA 사용 이유?
- SQL 중심적인 개발에서 객체 중심으로 개발
- 생산성
- 유지 보수
- 패러다임의 불일치 해결
- 성능
- 데이터 접근 추상화와 벤더 독립성
- 표준
생산성
JPA와 CRUD
- 저장: jpa.persist(member)
- 조회: Member member = jpa.find(memberId)
- 수정: member.setName(“변경할 이름”)
- 삭제: jpa.remove(member)
유지 보수
- 기존: 필드 변경시 모든 SQL 수정
- JPA: 필드만 추가하면 SQL은 JPA가 자동으로 처리
public class Member { private String memberId; private String name; private String tel; // 추가 코드 }
SQL은 JPA가 자동으로 처리해 주기 때문에 유지 보수가 간단하다.
패러다임 불일치 해결
- JPA와 상속
- JPA와 연관관계
- JPA와 객체 그래프 탐색
- JPA와 비교하기
JPA와 상속
저장
- 개발자가 할 일
jpa.persist(album);
- JPA가 자동 처리
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
조회
- 개발자가 할 일
Album album = jpa.find(Album.class, albumId);
- JPA가 자동 처리
- SELECT I. *, A. *
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
- SELECT I. *, A. *
연관관계, 그래프 탐색
- 연관관계 저장
member.setTeam(team); jpa.persist(member);
- 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId); Team team = member.getTeam();
JPA와 비교
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
// 결론
member1 == member2; // 같다
동일한 트랜잭션에서 조회한 엔티티는 같음을 보장
JPA의 성능 최적화
- 1차 캐시와 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 지연 로딩(Lazy Loading)
1차 캐시와 동일성 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상(거의 효과X)
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
println(m1 == m2); // true
결론 : SQL 한번만 실행
쓰기 지연
- 트랜잭션을 커밋할 때 까지 INSERT SQL을 모음
- JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
// 커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // 트랜잭션 커밋
지연 로딩과 즉시 로딩
- 지연 로딩: 객체가 실제 사용될 때 로딩
- 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
- 로딩 설정은 코드로 변경할 수 있다.
실무에서는 우선 지연 로딩으로 설계를 해 놓은 뒤에 최적화시 필요한 부분을 즉시 로딩으로 설정
댓글남기기