CRUD쿼리, 자바 객체를 SQL로.., SQL을 자바 객체로 등의 의 무한 반복, 지루한 코드가 이어지는 등 SQL에 의존적인 개발을 피하기 어렵다.
반복적인 SQL매핑작업
객체를 영구 보관하기위해 관계형 데이터 베이스에 저장하게 되는데 새로운 컬럼이 추가되면 객체에 추가해줘야하는 등 개발자는 SQL매퍼가 아닌가?라는 생각이 들정도로 반복적인 매핑작업을 하게된다.
객체답게 모델링 할 수 없는 한계
엔티티 신뢰 문제 발생
class MemberService {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); //???
member.getOrder().getDelivery(); // ???
}
}
이와 같이 member.getTeam()와 member.getOrder().getDelivery()를 사용할 수 있는지는 직접 sql쿼리를 가서 분석해야만 알 수 있는 엔티티 신뢰 문제가 발생한다.
이처럼 객체답게 모델링 할수록 매핑 작업만 늘어나게 되 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까? 라는 생각에서 나오게 된게 JPA(Java Persistence API)이다.
Java Persistence API
자바 진영의 ORM 기술 표준
ORM?
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORM 프레임워크가 중간에서 매핑
- 대중적인 언어에는 대부분 ORM 기술이 존재 (ex: typescript, pythone등)
개발자가 직접 JDBC API를 쓰는게 아니라, JPA에게 명령을 하면 JPA가 JDBC API를 호출해 SQL를 생성하여 결과를 반환받는다.
아래에서 이로 인해 발생하는 문제를 구체적으로 설명하겠다.
1) 자바컬렉션에 보관한다면?
abstract class Item{
Long id;
String name;
int price;
}
class Album extends Item{
String artist;
}
class Movie extends Item{
String director;
String actor;
}
class Book estends Item{
String author;
String actor;
}
만약, 해당 객체들을 데이터베이스가 아닌 자바 컬렉션에 보관한다면 다음 같이 부모 자식이나 타입에 대한 고민 없이 해당 컬렉션을 아래와 같이 그냥 사용하면 된다.
list.add(album);
list.add(movie);
Album album = list.get(albumId);
2) 데이터 베이스에 저장한다면?
객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.
그나마 데이터 베이스 모델링에서 이야기하는 슈퍼타입 서브타입 관계를 사용하면 객체 상속과 가장 유사한 형태로 테이블을 설계할 수 있다.
위의 그림과 같이 ITEM테이블의 DTYPE 컬럼을 사용해서 어떤 자식 테이블과 관계가 있는지 정의하고
자식테이블에 ITEM_ID칼럼을 통해 부모테이블과의 관계를 정의했다.
이때, Album객체를 저장할려면 다음 두 SQL을 만들어야 한다.
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
이 때 JDBC API를 사용해서 이 코드를 완성하려면
1. 부모 객체(Item)에서 부모 데이터만 꺼내서 item용 insert sql을 작성
2. 자식객체에서 자식 데이터만 꺼내서 ALBUM 용 INSERT SQL을 작성해야한다.
3. 자식타입에 따라서 DTYPE도 저장해야 한다.
이렇게 작성해야할 코드량이 많만치 않다.
또한 조회할 때도 album을 조회한다면 item과 album테이블을 조인해서 조회한다음 그 결과로 album객체를 생성해야하는 등 조회하는 것도 쉬운 일이 아니다.
3) JPA와 상속
JPA는 상속과 관련되 패러다임의 불일치 문제를 개발자 대신 해결해준다.
개발자는 마치 자바컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.
1. Item을 상속한 Album객체 저장
개발자는 한줄의 코드만 날리면, 나머진 JPA가 처리해 알아서 2개의 insert query를 생성해 실행한다.
2. Album 객체를 조회.
1차 캐시와 동일성(identity) 보장.
1) 같은 트랜잭션 안에서는 같은 엔티티를 반환한다. - 약간의 조희 성능 향상
String memberId = "100";
Member memeber1 = jpa.find(Member.classs, memberId); //SQL
Member memeber2 = jpa.find(Member.classs, memberId); //캐쉬
member1 == member2 // true
- [JDBC API와 비교]
JDBC API를 사용해서 해당코드를 직접 작성했다면 회원을 조회할 때마다 SELECT SQL을 사용해서 데이터베이스와 두 번 통신했을 것이다.
2) DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read보장.
트랜잭션을 지원하는 쓰기 지연(transactional wirtie-behind)
1) 트랜잭션을 커밋할 때까지 INSERT SQL을 모음.
2) JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.begin(); // [트랜잭션] 시작
em.persist(member A);
em.persist(member B);
em.persist(member C);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
transaction.commiit()// [트랜잭션] 커밋
//커밋하는 순간 데이터베이스에 INSET SQL을 모아서 보낸다.
간단한 설정을 통해 실제 사용하는 시점에 지연 로딩(Lazy Loading)과 즉시로딩 인지를 정의할 수 있다.