객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등의 개념이 있지만 관계형 데이터베이스는 그렇지 못하다.
객체와 관계형 데이터베이스의 차이는 다음과 같이 크게 4가지로 볼 수 있다.
위와 같은 객체 상속 관계가 있을 때 각 물품을 조회하는 상황을 가정하자.
조회를 위해서는 각각의 테이블에 따라 다른 조인 SQL을 작성해야하며, 객체 또한 각각 생성해야 한다. 그래서 DB에 저장할 객체에는 상속 관계를 쓰지 않는다.
객체 연관관계는 참조를 사용할 시 Member에서 Team을 참조하는 것은 가능하지만 Team에서 Member를 읽는 것은 불가능 하다. 반면,
테이블은 연관관계에서는 외래키로 참조가 가능하다.
class Member{
Long id;
Long teamId;
String username;
}
위와 같이 테이블에 맞춰 객체를 저장하여도 teamId로는 아무 참조를 할 수가 없다.
결국 객체답게 모델링을 진행 할수록 개발자들의 매핑 작업만 늘어나는 결과를 초래한다. 이럴 때 사용하기 위해 나온 것이 JPA(Java Persistence API)
다.
하이버네이트
, EclipseLink
, DataNucleus
의 구현체를 가지고 있다.저장 : jpa.persist(member)
조회 : Member member = jpa.find(memberId)
수정 : member.setName("변경할 이름") -> 변경감지(Dirty Checking)
삭제 : jpa.remove(member)
기존 : 필드 변경 시 모든 SQL을 직접 수정해야 했다.
JPA : 필드만 추가하면 된다. SQL은 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
member.setTeam(team);
jpa.persist(member);
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
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();
}
}
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId); // DB에서 가져옴
Member member2 = jpa.find(Member.class, memberId); // 1차 캐시에서 가져옴
member1 == member2; //같다
1차 캐시와 동일성 보장
트랜잭션을 지원하는 쓰기 지연
transaction.begin() // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 아직 Commit되지 않았기에 SQL로 보내지 않는다.
transaction.commit(); // 커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
// 지연 로딩
Member member = memberDAO.find(memberId); // SELECT * FROM MEMBER
Team team = member.getTeam();
String teamName = team.getName(); // SELECT * FROM TEAM
지연 로딩은 위와 같이 team 객체를 실제로 사용할 때 쿼리문이 나간다. 결국 Member와 Team 객체 따로 조회하기 때문에 DB 접근을 두번 하게 된다. (만약, Member 객체를 사용할 때 Team 객체 사용 빈도수가 많다면 즉시 로딩을 사용하는 것이 나음)
// 즉시 로딩
Member member = memberDAO.find(memberId); // SELECT * FROM M.*, T.*
Team team = member.getTeam(); // FROM MEMBER
String teamName = team.getName(); // JOIN TEAM ...
즉시 로딩은 JOIN을 이용해 객체를 동시에 가져온다. 실무에서는 지연 로딩으로 개발을 한 후에, 성능 최적화에 필요하다 판단 시에 즉시 로딩으로 옵션을 추가로 주는 것을 권장한다.