1. 쿼리 파일 만들기 (QueryMapper)
▶ MyBatis
- JDBC Template에 이어 SQL Mapper 두 번째 주자로 탄생
- JDBC 프로그래밍 RowMapper가 가지고 있는 단점인 반복되는 코드를 줄이고자 함
- SQL 쿼리들을 XML 파일에 작성해 코드와 SQL 분리
- jdbc로 처리하는 코드의 설정 부분을 줄이고 실제 sql문에 연결함으로서 빠른 개발
- map 인터페이스(혹은 클래스)와 SQL 쿼리와 ResultSet 매핑을 위한 XML 및 애너테이션을 사용
- 다른 방식에 비해 객체 자체보다 쿼리에 집중할 수 있음
2. 쿼리 코드 만들기 (JpaRepository)
1) QueryMapper의 DB의존성 및 중복 쿼리 문제로 ORM이 탄생
2) ORM은 DAO 혹은 Mapper를 통해 조작하는 것이 아니라 테이블을 아예 하나의 객체(Object)와 대응시켜 버림
3) ORM이 해결해야 하는 문제점과 해결책 (객체와 데이터베이스 테이블의 차이점에 대한 문제)
- 상속의 문제
- 객체 : 객체간에 멤버변수나 상속관계를 맺을 수 있다.
- RDB : 테이블들은 상속관계가 없고 모두 독립적으로 존재한다.
- 해결방법 : 매핑정보에 상속정보를 넣어준다 (@OneToMany, @ManyToOne)
RDB에 있는 테이블의 필드가 객체와 매핑이 되고, 어떤 필드가 다른 객체의 외래키를 사용하는데 그 외래키가 의미를 갖도록 하는 것
- 관계 문제
- 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계 포함)
- RDB : 외래키를 설정해 Join으로 조회 시에만 참조가 가능 (단, 다대다는 매핑 필요)
- 예시 : User 테이블에 있는 여러 명의 멤버가 여러 개의 동아리를 가입할 수 있고, 동아리는 여러 명의 멤버를 받을 수 있다고 가정 (다대다)
→ 그러면 가입자 명부를 이용해 중간에 매핑 정보를 가진 테이블을 가지면 다대다 관계를 설정해줄 수 있다.
- 해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)
- 탐색 문제
- 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 컬렉션이 순회한다.
- RDB : 탐색 시 참조하는만큼 추가 쿼리나 Join이 발생하며 비효율적이다.
- 해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다. (@FetchType, fetchJoin())
설정해놓은 관계정보(OneToMany...) 방향성정보(JoinColumn...)를 통해 참조탐색을 한 번에 한다는 뜻. 즉, 쿼리를 한 번에 연결해서
여러 개를 한꺼번에 조회하거나 Join 쿼리를 자동으로 생성해주는 애너테이션이나 메서드를 사용한다는 것
- 밀도 문제
- 객체 : 멤버 객체크기가 매우 클 수 있다.
- RDB : 기본 데이터타입만 존재한다.
- 해결방법 : 크기가 큰 멤버 객체는 테이블을 분리해 상속으로 처리한다. (@embedded)
- 식별성 문제
- 객체 : 객체의 hashCode 또는 정의한 equals() 메서드를 통해 식별 → 객체가 hashCode를 통해 equals() 메서드를 호출했을 때 같은 객체인지 확
인하는 것
- RDB : 고유식별자(PK)로만 식별
- 해결방법 : PK를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리한다. (@Id, @GeneratedValue)
>>> 그러니까, JPA에서 만든 이런 애너테이션들을 사용해 객체와 테이블간의 간극에서 오는 문제점을 해결할 수 있게된다!
3. 영속성 컨텍스트
◆ 영속성 컨텍스트(1차 캐시-임시저장소)를 활용한 쓰기 지연
> 영속성
- 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성
- 영속성을 갖지 않으면 데이터를 메모리에서만 존재하게 되고 프로그램이 종료되면 해당 데이터는 모두 사라지게 된다.
- 그래서 우리는 데이터를 파일이나 DB에 영구 저장함으로써 데이터에 영속성을 부여한다.
> 영속성의 4가지 상태
- 비영속 : 엔티티 객체가 만들어져 아직 저장되지 않은 상태로, 연속성 컨텍스트와 전혀 관계가 없는 상태
- 영속 : 엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태
- 준영속 : 엔티티가 영속성 컨텍스트에 저장되어 있다가 분리된 상태로, 영속성 컨텍스트가 더 이상 관리하지 않는 상태
- 삭제 : 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하겠다고 표시한 상태
> 객체의 영속성 상태는 Entity Manager의 메서드를 통해 전환된다.
> Raw JPA 관점에서 순서대로 요약정리 해보자면?
new => 비영속 상태 => persist(), merge() => 영속성 컨텍스트에 저장된 상태 => flush() => DB에 쿼리가 전송된 상태
=> commit() => DB에 쿼리가 반영된 상태
Item item = new Item(); // 1
item.setItemNm("테스트 상품");
EntityManager em = entityManagerFactory.createEntityManager(); // 2
EntityTransaction transaction = em.getTransaction(); // 3
transaction.begin();
em.persist(item); // 4-1
em.flush(item). // 4-2 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)
transaction.commit(); // 5
em.close(); // 6
1️⃣ 영속성 컨텍스트에 담을 상품 엔티티 생성
2️⃣ 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
3️⃣ 데이터 변경 시 무결성을 위해 트랜잭션 시작
4️⃣ 영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기 전
5️⃣ 트랜잭션을 DB에 반영, 이 때 실제로 INSERT SQL 커밋 수행
6️⃣ 엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환
◆ 쓰기 지연이 발생하는 시점
- flush() 동작이 발생하기 전까지 최적화 한다.
- flsuh() 동작으로 전송된 쿼리는 더이상 쿼리 최적화는 되지 않고, 이후 commit()으로 반영만 가능하다.
◆ 쓰기 지연 효과
- 여러 개의 객체를 생성할 경우 모아서 한 번에 쿼리를 전송한다.
- 영속성 상태의 객체가 생성 및 수정이 여러 번 일어나더라도 해당 트랜잭션 종료 시 쿼리를 한 번만 전송될 수 있다.
- 영속성 상태에서 객체가 생성되었다 삭제된다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다. → 삭제가 될거라면 DB에 보내지 않는 게 당연히 좋다! 그런 관점에서 쓰기 지연이 효과가 있다는 것.
- 즉, 여러 가지 동작이 많이 발생하더라도 쿼리는 트랜잭션 당 최적화되어 최소 쿼리만 날라가게 된다.
- 예시 : setName()을 해서 여러 개의 변수를 추가하는데 모두 UPDATE 쿼리를 날리는 것은 비효율적 → 그것들을 모두 영속성 컨텍스트에 set 해놓음 → 한 번에 flush()로 영속 객체에 대한 쿼리를 자동으로 Manager가 만들어줌 → 최소한의 쿼리만 날라가게 됨