JPA 소개
현재 주로 사용되는 언어는 객체 지향 언어, 주로 사용되는 데이터베이스는 관계형 DB라고 볼 수 있다.
이런 환경에서 애플리케이션 개발을 할 때는 결국, 객체를 관계형 DB에서 관리하게 된다.
때문에 각 객체마다 일일히 SQL을 사용해서 객체를 관계형 DB에 매핑해주어야 한다.
그런데 객체와 관계형 DB의 구조는 같지 않기 때문에 매핑하는 것 자체도 어렵다.
예를 들어 객체는 상속관계가 있지만 관계형 DB는 상속관계라는 개념이 없다.
수많은 객체를 관계형 DB와 매핑하는 것도 힘든데 그 과정도 어렵고, 매핑 후에도 객체와 관계형 DB 구조의 괴리 때문에 많은 애로사항이 발생한다.
이를 해결하기 위해 JAVA진영에서 ORM 기술 표준으로 나온 게 JPA(Java Persistence API)이다.
1. SQL 중심적 개발의 문제점
■ 반복과 SQL 의존적 개발
객체와 관계형 DB를 매핑하기 위해, 모든 객체에 대해 SQL로 CURD를 작성해야 한다.
게다가 이런 단순 반복 작업인 CURD를 하는 것조차 SQL에 매우 종속적이게 돼서 객체를 수정해야 할 때 많은 어려움이 발생한다.
[CURD]
public class Member {
private String memberId;
private String name;
...
}
INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES
SELECT MEMBER_ID, NAME FROM MEMBER M
UPDATE MEMBER SET …
Member라는 객체에 CURD를 작성했다.
[CURD - 객체 변경]
public class Member {
private String memberId;
private String name;
private String tel;
...
}
INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES
SELECT MEMBER_ID, NAME, TEL FROM MEMBER M
UPDATE MEMBER SET … TEL = ?
Member에 String tel 이라는 필드를 추가하기 위해서는 관련된 모든 SQL에 이를 반영해주어야 한다.
■ 구조 차이에서 오는 매핑의 어려움
결국 개발자는 SQL을 사용해서 객체와 관계형 DB(RDB)를 매핑해주어야 한다.
그러나 객체와 관계형 데이터 베이스의 구조적 차이 때문에 이마저도 쉽지 않다.
예를 들어, 객체는 상속이 있지만 RDB에는 상속이라는 개념이 없다.
객체의 상속이 가지는 특성을 RDB에 억지로 반영하기 위해서는 많은 어려움이 있을 것이다.
반대로, RDB에서는 각 테이블을 외래 키(FK)를 통해 연결하고 관계를 구성하지만 객체는 참조를 사용한다.
[객체와 RDB 테이블 연관관계]
객체에서 Member는 Team을 조회하기 위해서 참조(member.getTeam())을 사용하지만 테이블은 외래 키를 사용한다.
그렇다고 테이블에 맞추기 위해 Member 객체에 저장된 Team team을 Long teamId 같이 변경하면 객체의 특징을 버리게 되는 꼴이 된다.
이처럼 구조 차이에서 오는 괴리 때문에 객체와 RDB를 매핑하는 데는 많은 어려움이 따른다.
■ 엔티티 신뢰 & 동일성 보장 문제
Order와 Team이라는 객체를 필드로 가진 Member 객체가 있다고 하자.
RDB에서 Member 객체를 조회했을 때 member.getTeam(), member.getOrder()와 같이 Team이나 Order를 조회할 수 있을까?
만약 Member를 조회하는 SQL이 다음과 같다면 member.getTeam()은 조회가 가능할 것이다.
[Member와 Team을 조회하는 SQL]
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
위 예제의 SQL로 Member를 조회하면 member.getTeam()은 조회할 수 있지만, member.getOrder()는 조회할 수 없다.
같은 member임에도 불구하고 SQL에 따라 조회할 수 있는 객체가 달라질 수 있다.
즉, 객체와 RDB를 매핑했을 때, 엔티티가 어느 필드를 참조할 수 있을지 신뢰할 수 없는 문제가 발생한다.
또한 RDB에서 데이터를 가지고 올 때, 결국에는 새로운 객체를 생성하기 때문에 동일성이 보장되지 않는다.
[동일성 보장 문제]
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
...
//JDBC API, SQL 실행 return new Member(...);
}
}
같은 member를 조회해도 SQL로 조회한 데이터로 새로운 객체를 반환하기 때문에 결국 다른 객체로 취급된다.
2. JPA
객체를 자바 컬렉션에 저장하듯이 DB에 저장하기 위해, 객체의 특성을 유지하면서 RDB와 매핑하기 위해 나온 것이 JPA이다.
JPA는 자바 진영의 ORM 기술 표준이다.
■ ORM
● Object-relational mapping(객체 관계 매핑)
● 객체는 객체대로 설계하고 RDB는 RDB대로 설계할 수 있도록 ORM 프레임워크가 중간에서 매핑
● 대중적인 언어네는 대부분 ORM 기술이 존재한다.
[JPA 동작]
JPA는 애플리케이션과 JDBC사이에서 동작하며 JDBC를 사용해서 대신 SQL을 사용하고 결과를 조회한다.
이 과정에서 자동으로 SQL을 생성하고 객체와 DB 간 패러다임 불일치를 해결해준다.
■ 표준 명세
● JPA는 인터페이스의 모음이다.
● JPA 2.1은 표준 명세를 구현한 3가지 구현체를 사용한다.
● 하이버네이트, EclipseLink, DataNucleus
● 하이버네이트를 주로 사용한다.
■JPA 사용 장점
□ 생산성
JPA를 사용하면 CURD를 위한 SQL을 작성하지 않아도 된다.
그저, JPA 제공해주는 메서드를 사용하면 기본적인 CURD 기능을 이용할 수 있다.
● 저장: jpa.persist(member)
● 조회: Member member = jpa.find(memberId)
● 수정: member.setName(“변경할이름”)
● 삭제: jpa.remove(member)
□ 유지보수
기존에는 필드를 변경하면 관련된 모든 SQL을 수정했어야 했다.
하지만 JPA를 사용하면 단순히 필드만 변경하면 된다. 관련된 SQL은 모두 JPA가 처리해준다.
□ 패러다임 불일치 해결
상속, 연관관계, 객체 그래프 탐색 등 기존에 객체와 DB 간의 구조적 차이로 발생하는 문제들을 자동으로 처리해준다.
예를 들어 특정 객체를 상속받은 객체를 DB에 저장하기 위해 추가적인 SQL을 작성하지 않아도 된다.
단순히 해당 객체를 저장하면 JPA에서 상속과 유사하게 RDB 테이블을 자동으로 구성해준다.
□ 신뢰할 수 있는 엔티티, 계층
특정 객체를 필드로 갖는 객체를 조회할 경우 이제 더 이상 엔티티가 참조할 수 있는 범위를 신경 쓰지 않아도 된다.
[엔티티 신뢰성 보장]
Team과 Order를 필드로 갖는 Member를 조회할 때, 조회한 Member를 통해 자유롭게 Team이나 Order를 조회할 수 있다.
□ 동일성 보장
JPA에서는 DB에서 조회한 엔티티(동일 트랜잭션 내)는 항상 같음을 보장해준다.
[JPA의 동일성 보장]
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.
■ JPA의 성능 최적화 기능
□ 1차 캐시와 동일성 보장
JPA는 같은 트랜잭션 안에서는 캐시를 사용해서 같은 엔티티를 반환한다.
[1차 캐시 사용]
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
□ 트랜잭션을 지원하는 쓰기 지연
JPA는 트랜잭션을 커밋할 때까지 SQL을 모아 두고, 커밋하는 순간에 모아둔 SQL을 보낸다.
[쓰기 지연 - INSERT]
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
□ 지연 로딩과 즉시 로딩
● 지연 로딩 : 객체가 실제 사용될 때 로딩
● 즉시 로딩 : JOIN SQL로 한 번에 연관된 객체까지 미리 조회
JPA는 지연 & 즉시 로딩을 통해 상황에 맞춰서 사용할 수 있도록 지원해 주기 때문에 이를 잘 이용하면 성능을 상승시킬 수 있다.
'Spring > JPA' 카테고리의 다른 글
#6 다양한 연관관계 매핑 (0) | 2021.08.28 |
---|---|
#5 연관관계 매핑 기본 (0) | 2021.08.27 |
#4 엔티티 매핑 (0) | 2021.08.27 |
#3 영속성 관리 (0) | 2021.08.25 |
#2 JPA 시작 (0) | 2021.08.25 |
댓글