객체를 마치 자바 컬렉션에 저장하고 불러오듯이 관계형 데이터베이스에 저장하고 불러올 수 있는 방법이 없을까?
고민 끝에 등장한 것이 JPA 이다.
- JPA(Java Persistence API)
- 자바 진영의 ORM 기술 표준
- ORM(Object-relational mapping) - 객체 관계 매핑
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORM 프레임워크가 중간에서 매핑하는 역할
즉, 객체지향 개발자는 객체지향스럽게 개발하고 관계형 데이터베이스는 관계형 데이터베이스 답게 처리 진행
나머지는 ORM 프레임 워크한테 맡긴다!
- JPA는 자바 애플리케이션과 JDBC 사이에서 동작한다.
- 개발자는 JDBC API를 직접 사용하는 것이 아닌 JPA에게 명령한다.
- JPA는 개발자의 명령에 따른 적절한 JDBC API를 사용하여 SQL을 호출하고 결과를 받는다.
- MemberDAO에서 객체를 저장하고 싶을 경우 JPA에게 멤버객체를 넘겨준다.
- JPA는 분석 후에 INSERT 쿼리를 생성한다.
- JDBC API를 사용해서 쿼리를 디비에 보내고 그 결과값을 받아준다.
- 조회할 때에도 마찬가지로 MemberDAO는 JPA에게 PK값만 보내고 조회 값을 요구한다.
- JPA는 요구사항(멤버객체)을 받고 SELECT 쿼리를 만들어서 JDBC API를 사용하여 DB에 보내고 결과값을 받는다.
- 결과값을 받아서 객체에다 전부 매핑해준다.
JPA의 표준 명세
- JPA는 인터페이스의 모음
- JPA 2.1 표준 명세를 구현한 3가지 구현체
- JPA 2.0 버전 이후로는 왠만한 기능은 다 되니 2.0 이상 버전을 가져다가 사용 권함
- 하이버네이트, EclipseLink, DataNucleus
쉽게 설명해서 어플리케이션에서 JPA를 사용하려면 Hibernate, EclipseLink, DataNucleus에서 구현을 할 수 있다
JPA를 쓰려면 세가지 방법에 구축되어있으니 가져다가 사용해라! 이말이다.
하지만 요즘은 대부분 Hibernate를 사용하니 별다른 이유없다면 Hibernate를 써서 사용하자!!
JPA를 왜 사용해야 하는가?
- 생산성적인 측면 (JPA와 CRUD) / JPA에서 다 만들어져있으니 그냥 가져다가 써라 이말이다!
- 저장 : jpa.persist(member)
- 조회 : Member member = jpa.find(memberId)
- 수정 : member.setName("변경할 이름")
- 삭제 : jpa.remove(member)
- 유지보수적인 측면 / tel이라는 멤버변수가 추가되는 상황에서 필드만 추가하면, SQL은 따로 변경할 필요 없이 JPA가 알아서 처리해준다.
public class Member {
private String memberId;
private String name;
private String tel;
...
}
JPA와 패러다임의 불일치 해결
- JPA와 상속
- 오른쪽 그림과 같이 테이블 관계를 정규화하게 설계하여 나타내고 있다.
- 앨범 객체를 디비에 저장하고 싶을 경우 (저장) | - 앨범을 조회하고 싶을 경우 (조회) |
- 연관관계 및 객체 그래프 탐색 방법 | |
|
- persist를 통해 member를 넣어줬을 때 - 자바 컬렉션에 넣은것 처럼 member.getTeam()을 통해 가져온다. |
- JPA와 비교하기
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; // 같다.
// 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장
JPA의 성능 최적화 기능
JPA를 사용하면 성능이 더 떨어지지 않을까?
- 계층 사회에 어떤 하나의 중간 계층이 존재하면 읽어드리는 과정에서 한번에 모아서 쏘는 버퍼링과 읽을 때 캐싱하는 것을 할 수 있다.
- JPA에서도 JPA가 중간계층 역할을 하기 때문에 이런 부분을 최적화 하므로 단순히 SQL 쓰는거보다 성능을 끌어올릴 수 있다.
- 1차 캐시와 동일성(identity) 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
/**
만약에 비즈니스 로직이 복잡해서
중간마다 똑같은 멤버를 여러번 조회하는 경우가 생길 경우
*/
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); // SQL
Member m2 = jpa.find(Member.class, memberId); // 캐시
println(m1 == m2) // true
// JPA는 처음에 쿼리가 날라가서 m1을 가져오고 그걸 캐싱하여 가지고 있다가
// 두번째 동일한 PK 값이 들어가면 메모리에 첫번째 m1값을 반환해준다.
- 트랜잭션을 지원하는 쓰기 지연 (버퍼링 기능)
- 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
- JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// JPA는 INSERT SQL을 데이터베이스에 보내지 않고 메모리에 보관하고 있는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); 트랜잭션 커밋
- 지연 로딩과 즉시 로딩
- 지연 로딩 : 객체가 실제 사용될 때 로딩
- 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
- JPA에는 옵션이 있어서 지연 로딩 형태로 가져올 것인지 즉시 로딩 형태로 가져와서 쓸껀지 껐다 켤 수 있다.
Member를 사용할 때는 거의 Member만 쓰고 Team은 어쩌다가 사용한다? 할 경우 지연 로딩 방법처럼 사용
// 지연 로딩
Member member = memberDAO.find(memberId);
Team team = member.getTeam();
String teamName = team.getName();
SELECT * FROM MEMBER
SELECT * FROM TEAM
99%가 Member 를 조회하면 Team 은 같이 사용하는 경우 즉시 로딩방법으로 사용
// 즉시 로딩
Member member = memberDAO.find(memberId);
Team team = member.getTeam();
String teamName = team.getName();
SELECT M.*, T.*
FROM MEMBER
JOIN TEAM ...
결국 지연로딩인지 즉시 로딩인지의 여부는 JPA에서는 스위치처럼 옵션을 껐다 켰다 함으로써 컨트롤을 할 수 있다! 라는 의미
TIP
어플리케이션을 개발할 때에는 지연 로딩 형태로 쭈욱 개발해 놓은 다음에 최적화가 필요한 경우에만 딱 맞춰서 하는 방식이 좋다!
JPA를 공부하더라도 / 관계형 데이터베이스는 중요하니 꾸준하게 공부해야한다!
'Dev > JPA' 카테고리의 다른 글
다양한 연관관계 매핑 (0) | 2020.07.28 |
---|---|
연관관계 매핑 기초 (0) | 2020.07.15 |
엔티티 매핑 (0) | 2020.07.12 |
JPA내부 구조(영속성 관리) (0) | 2020.07.06 |
SQL중심개발의 문제점 (0) | 2020.06.30 |