# Object와 RDB의 패러다임 불일치
## 1. 상속
- Album을 조회하려면?
- 각각의 테이블에 따른 조인 SQL 쿼리 작성, 각각 객체 생성… → 개발 코드보다 SQL코드가 더 커지게 된다.
- 자바 컬렉션에서 조회하려면?
list.add(album);
Album album = list.get(album);
Item item = list.get(albumId);
→ 매우 간단하다. 그럼, 자바의 컬렉션처럼 쓸 수 없을까..?
## 2. 연관관계
- 테이블에 맞춰서 모델링 할 경우
class Member {
String id;
Long teamId;
Sring username;
}
class Team {
Long id;
String name;
}
- 객체지향 다운 모델링 할 경우
class Member {
String id;
Team team;
Sring username;
}
class Team {
Long id;
String name;
}
- 객체지향 모델링의 경우, 조회는…?
SELECT M.T, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_UID;
public Member find(String memberId) {
// SQL 실행
Member member = new Member();
Team team = new Team();
member.setTeam(team);
return member;
}
→ Member를 가져올 때 마다 Team도 가져와서 setTeam()을 호출해야한다. 만약 그러지 않을 경우, 엔티티 신뢰문제가 발생한다.
## 3. 엔티티 신뢰문제
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // ??
member.getOrder().getDelivery(); // ??
}
}
- Member의 Team이 진짜로 SQL을 통해서 가져왔는지, Order도 가져왔는지, Order의 Delivery가 가져와져 있는지 확인할 방법이 없다.
- 이걸 확인하려면 DAO를 까봐야한다.
- 또는 Member를 SQL로 조회할 때 모든 객체를 다 끌어와야한다. 당연히 문제가 발생한다.
## 4. 비교하기
- SQL로 조회한 DAO에서 가져오기
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다.
- 컬렉션을 통해서 가져오기
String memberId = "100";
Member member1 = list.get(memberId);
Member member2 = list.get(memberId);
member1 == member2; //같다.
# JPA의 장점
## 1. 생산성
- 저장: jpa.persist(member)
- 조회: Member member = jpa.find(memberId)
- 수정: member.setName(“변경할 이름”)
- 삭제: jpa.remove(member)
- 를 하면 바로 쿼리들이 날라간다.
## 2. 유지보수
- 필드 추가시 SQL을 다시 작성할 필요 없이, 관련된 모든 SQL들을 JPA가 자동으로 수정해준다.
## 3. 패러다임 불일치 해결
### 3-1. 상속관계
- 저장
jpa.persist(album);
=>
INSERT INTO ITEM ...
INSERT INTO ALBUM...
→ persist만 하면 insert문을 자동으로 2개 만들어서 날려준다.
- 조회
Album album = jpa.find(Album.class, albumId);
=>
SELECT I.*, A.*
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
→ find만 하면 알아서 join해서 찾아온다.
### 3-2. 연관관계
member.setTeam(team);
jpa.persist(member);
===
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
→ 연관관계에 있는 객체를 자동으로 세팅해준다.
### 3-3. 엔티티 신뢰문제
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); //자유로운 객체 그래프 탐색
member.getOrder().getDelivery();
}
}
→ 지연로딩을 이용해서 실제 조회하는 시점에 자동으로 SQL을 날려서 사용할 수 있다. 즉, 엔티티를 항상 신뢰할 수 있다.
### 3-4. 비교하기
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.
→ 같은 트랜잭션에서 조회한 엔티티는 항상 같음을 보장한다.
# JPA의 성능
## 1. 1차 캐시와 동일성 보장
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1 == m2) //true
- 같은 트랜잭션 내에서는 SQL을 1번만 실행하게 된다.
- DB Isolation Level이 Read Commit이어도 애플리케이션단에서 Repeatable Read를 보장한다.
## 2. 트랜잭션을 지원하는 쓰기 지연 - INSERT
transaction.begin();
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
transaction.commit();
- 버퍼링을 통해서 한 번에 SQL을 날린다. 네트워크에 1번만 SQL을 태우기 때문에 성능이 올라간다.
## 3. 트랜잭션을 지원하는 쓰기 지연 - UPDATE
transaction.begin();
changeMember(memberA);
deleteMember(memberB);
비지니스_로직_수행(); // 비지니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.
// 커밋 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.
transaction.commit();
- 역시, 버퍼링을 통해서 한 번에 SQL을 날리기 때문에 애플리케이션 비지니스 로직 수행 동안에 DB 로우 락이 걸리지 않는다.
## 4. 지연 로딩과 즉시 로딩
Member member = memberDAO.find(memberId);
Team team = member.getTeam();
String teamName = team.getName();
- 지연로딩을 통해 실제 팀을 가져올 때 그 때 SQL을 날려서 필요할 때 SQL을 날릴 수 있다.
- 단점으로 네트워크에 SQL을 2번 태우기 때문에 네트워크 오버헤드로 인해 더 느릴 수도 있다.
- 즉시로딩을 사용하면, member를 find할 때 바로 team을 가져오므로 오히려 빠를 수도 있다.
- 비지니스 로직상 member를 건들 때 항상 team을 건드는 경우 사용하면 성능상 유리하다.