JPA (Java Persistence API)
JPA는 JAVA의 객체지향과 관계형 DB를 매핑해주는 ORM(Object Relational Mapping) 기술이다.
장점으로는
1. 특정 데이터베이스에 종속되지 않는다.
2. 객체지향적이다.
단점으로는
1. 복잡한 쿼리는 처리하기 힘들다.
2. 자동으로 생성되는 쿼리로 인해 개발자가 의도하지 않은 쿼리를 날리면서 성능저하가 있을 수 있다.
3. 쓰긴 쉬운데 잘쓰긴 어렵다.
++ 특정 데이터베이스에 종속되지 않는다.
#Oracle의 현재 시간
SYSDATE
#MySQL의 현재 시간
NOW()
위처럼 DB마다 쿼리가 조금씩 다르다. 하지만 JPA는 추상화된 데이터 접근 계층을 제공하고 이를통해 같은 함수라면 같은 결과를 기대할 수 있다.
++ 객체지향적이다.
@Entity
@Table(name = "A")
public class A{
. . .
@NToM(~~)
@JoinColumn(name="b_id")
private B b;
}
테이블 하나와 매핑되는 클래스가 뚝딱 만들어지는데 이는 몹시 객체지향적이다. 내부에서 다양한 annotation을 활용하여 연관관계(N:M, 1:N, N:1, 1:1)를 매핑할 수 있다.
Entity
Entity
entity란 DB의 테이블에 대응하는 클래스이다.
@Entity annotation을 통해 Entity Manager가 관리한다.
Persistence Context (영속성 컨텍스트)
entity를 영구 저장하도록 지원하는 환경으로 Entity manager를 통해 접근한다.
Application과 DB 중간사이의 계층의 영속성 컨텍스트는 버퍼링과 캐싱 등에서 이점을 갖는다.
Entity Manager
Persistence Context 에 접근하여 DB작업을 제공하는 객체이다.
따라서 Entity Manager를 통해 DB Connection을 이용하고 DB에 접근한다.
Transaction단위를 수행할때마다 생성된다. 즉 사용자의 요청이 올때마다 생성되고 끝나면 닫는다.
Transaction 수행후에는 반드시 Entity Manager를 닫으면 내부적으로 DB Connection을 반환한다.
thread간 공유하지 않는다.
Entity Manager Factory
entity manager instance를 관리하는 주체이다.
Application 실행 시 한개의 Entity Manager Factory가 생성된다.
사용자로부터 요청이 오면 Entity manager를 생성한다.
Entity Lifecycle
new : 비영속 상태로 new 키워드를 통해 생성된 상태이다. 아직 영속성 컨텍스트에 저장되지 않았다.
Managed : 영속 상태로 엔티티가 영속성 컨텍스트에 저장되어 관리되는 상태이다. Application과 DB사이 계층에 존재하여 DB의 값과 차이가 있을 수 있다. 트랜잭션 commit 후 DB와 값을 일치시킨다.
Detached : 준영속 상태로 엔티티가 저장되었다가 분리된 상태이다. commit 전에 detached됐다면 엔티티가 변경되어도 쿼리가 날아가지 않는다.
Removed : 삭제 상태로 영속성 컨텍스트에서 삭제되고 flush()혹은 commit()이 실행되면 DB에서도 삭제된다.
* commit()과 flush()
flush는 jpa의 영속성 컨텍스트와 DB를 동기화하는 작업을 말한다. flush()가 실행되면 DB에 쿼리를 날린다.
이 때 변경감지(Dirty Checking)를 통해 수정된 entity를 update하는 쿼리를 날린다. 또한 *쓰기 지연된 쿼리들을 한 번에 보낸다.
commit은 트랜잭션을 begin()으로 실행한 뒤 commit()하여 트랜잭션을 종료한다. 이때 commit()은 내부적으로 flush()를 호출하며 트랜잭션을 끝내는 역할을 한다.
가장 큰 차이는 flush를 통해 전송된 쿼리는 rollback될 수 있지만 commit은 트랜잭션을 종료하므로 rollback될 수 없다.
Persistence Context 이점
Persistence Context는 Application과 DB사이의 계층에서 여러가지 작업을 수행할 수 있다.
1차 캐시
영속성 컨텍스트 내에서 저장되는 캐시로 Map<Key, Value>로 저장된다.
entityManager.find() 메소드 호출 시 1차 캐시에서 조회하고, 없으면 DB에서 조회 후 1차 캐시에 저장하고 반환한다.
즉, 처음 find 호출 시 1차 캐시에 존재하지 않는다면 SELECT query가 날아가고, 다음 같은 key값을 find할 경우 SELECT query는 날아가지 않는다.
이를 통해 DB에 접근하는 횟수를 줄이며 성능을 향상시킬 수 있다.
동일성 보장
하나의 트랜잭션에서 같은 Key값은 같은 Entity 조회를 보장받을 수 있다. 즉, == 연산에서 결과값이 true이다.
반대로 MyBatis는 조회 결과를 다시 인스턴스화하여 return하기때문에 == 연산에서 결과값이 false이다.
변경감지 (Dirty Checking)
1차 캐시에 DB에서 처음 불러온 entity의 스냅샷을 저장한다.
이후 스냅샷과 비교하여 다른점이 있다면 UPDATE쿼리를 사용한다.
find 메소드를 호출하여 entity를 불러오고, 값을 변경한 뒤 save메소드를 호출하면 insert가 아닌 update 쿼리가 날아가도록 해준다.
반대로 entity의 ID와 1차 캐시에 같은 ID를 찾을 수 없다면 insert 쿼리가 날아가도록 해준다.
*쓰기 지연
영속성 컨텍스트는 트랜잭션 처리를 도와주는 쓰기지연을 지원한다.
한 트랜잭션안에서 이뤄지는 UPDATE나 SAVE의 쿼리는 쓰기지연 저장소에 저장되었다가 트랜잭션이 commit(내부적으로는 flush)되는 순간 DB에 날린다. 이를통해 DB Connection 시간을 줄일 수 있고 트랜잭션이 테이블에 접근하는 시간을 줄여 교착상태 등을 방지할 수 있다.
** 하지만 특정 INSERT 쿼리는 즉시 날아간다.
Entity가 영속(Managed)상태가 되려면 식별자(PK)가 필요하다. 이때 Entity의 ID 생성전략을 IDENTITY로 사용한다면, 이는 데이터베이스에 실제로 저장한 뒤 다른 식별자들과 구분한다. 따라서 Insert쿼리는 즉시 날아가고 이런 경우에는 쓰기지연을 통한 성능 최적화를 얻기 힘들다.
생성전략을 Sequence로 사용한다면, entityManager가 entity를 영속화하기전에 DB Sequence를 먼저 조회하고 조회한 식별자를 통해 Entity를 생성한뒤 영속화한다. 그 후 flush를 통해 Entity를 DB에 저장한다.
이전에 JPA를 사용해보기전에도 공부를 목적으로 포스팅을 한 번 했었다. 지금 그 글을 보니 너무 뜬구름 잡는 내용이고 개념도 정확하게 작성되지 않은 게시글을 포스팅했다. 지금은 삭제했다. 수정하는거보다 이렇게 새로 적는게 빠를 정도였으니까 :-)