JPA 심화 공부(1) - 2024.5.13

동준·2024년 5월 12일
0

개인공부(스프링)

목록 보기
14/14

ORM

JPA의 존재 목적이자 의의

ORM의 한계

상속의 문제

  • 객체 : 객체간에 멤버변수나 상속관계를 맺을 수 있다.
  • RDB : 테이블들은 상속관계가 없고 모두 독립적으로 존재한다.
  • 해결방법 : 매핑정보에 상속정보를 넣어준다. (@OneToMany, @ManyToOne)

관계 문제

  • 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계도 있음)
  • RDB : 외래키(FK)를 설정하여 Join 으로 조회시에만 참조가 가능하다. (즉, 다대다는 매핑 테이블 필요)
  • 해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)

탐색 문제

  • 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 콜렉션도 순회한다.
  • RDB : 탐색시 참조하는 만큼 추가 쿼리나, Join 이 발생하여 비효율적이다.
  • 해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다.(@FetchType, fetchJoin())

밀도 문제

  • 객체 : 멤버 객체크기가 매우 클 수 있다.
  • RDB : 기본 데이터 타입만 존재한다.
  • 해결방법 : 크기가 큰 멤버 객체는 테이블을 분리하여 상속으로 처리한다. (@embedded)

식별성 문제

  • 객체 : 객체의 hashCode 또는 정의한 equals() 메소드를 통해 식별
  • RDB : PK 로만 식별
  • 해결방법 : PK 를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리 한다.(@Id,@GeneratedValue )

객체와 데이터의 완벽한 매핑 불가로 인해 최적화 방법을 찾아야만 했음

ORM 나름의 최적화 방법

캐싱

1차 캐싱(영속성 컨텍스트 내부에는 엔티티를 보관하는 저장소) + 2차 캐싱(애플리케이션 범위의 캐시로, 공유 캐시라고도 하며, 애플리케이션을 종료할 때 까지 캐시가 유지)

(1) Entity에 @Cacheable 적용 후 설정 추가

// Team.java

@Entity
@Cacheable
public class Team {
	@Id @GeneratedValue
	private Long id;
	...
}
# application.yml

spring.jpa.properties.hibernate.cache.use_second_level_cache: true
# 2차 캐시 활성화합니다.

spring.jpa.properties.hibernate.cache.region.factory_class: XXX
# 2차 캐시를 처리할 클래스를 지정합니다.

spring.jpa.properties.hibernate.generate_statistics: true
# 하이버네이트가 여러 통계정보를 출력하게 해주는데 캐시 적용 여부를 확인할 수 있습니다.

(2) sharedCache.mode 설정

# appplication.yml

spring.jpa.properties.javax.persistence.sharedCache.mode: ENABLE_SELECTIVE

영속성 컨텍스트(1차 캐시)

객체의 영속성 상태는 Entity Manager 의 메소드를 통해 전환된다.
Raw JPA 관점에서 순서대로 요약정리 해보자면

  1. persist(),merge() (영속성 컨텍스트에 저장된 상태, managed)
    persist는 save, merge는 update라고 보기
  2. flush() (DB에 쿼리가 전송된 상태)
    전송됐다고 다는 아니고...
  3. 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() 동작이 발생하기 전까지 최적화한다.

      // 최적화 예시
      update account set username = 'username' where id = 2;
      update account set password = 'password' where id = 2;
      
      // 이렇게 두 개 있지만 각각 날리지 않고 공통적으로 묶어 하나의 쿼리로 정리할 수 있는 것을...
      
      update account set username = 'username', password = 'password' where id = 2;
      // 엔티티 매니저가 이런 식으로 합쳐버림
      
    • flush() 동작으로 전송된 쿼리는 더이상 쿼리 최적화는 되지 않고, 이후 commit()으로 반영만 가능하다.

  • 쓰기 지연 효과
    • 여러개의 객체를 생성할 경우 모아서 한번에 쿼리를 전송한다.
    • 영속성 상태의 객체가 생성 및 수정이 여러번 일어나더라도 해당 트랜잭션 종료시 쿼리는 1번만 전송될 수 있다.
    • 영속성 상태에서 객체가 생성되었다 삭제되었다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다.
    • 즉, 여러가지 동작이 많이 발생하더라도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라가게된다.

쓰기 지연 플로우

Team teamA = new Team();
teamA.setName("TeamA");
em.persist(teamA); // 영속성 상태화

Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB); // 영속성 상태화

Member member_A = new Member();
member_A.setName("memberA");
member_A.setTeam(teamA);

em.persist(member_A); // 영속성 상태화

// 얘가 있냐 없냐에 따라서
em.flush();
// 결과 조회가 달라지게 된다

Member findMember = em.find(Member.class, member_A.getId());
Team findTeam= findMember.getTeam();

System.out.println(findTeam.getName());
// flush가 있는 경우
// 쓰기 지연: 트랜잭션을 커밋할 때 모아둔 쿼리를 반영하는 과정.
// flush: 이때 실제로 반영, 보내는 작업
// 쓰기 지연이 발생하지 않음

create member
create team
insert team      // flush 작동 전까진 쓰기지연
insert member    // flush 작동 전까진 쓰기지연
// flush()가 호출되기 전에는 영속성 컨텍스트의 변경 내용이 
// 데이터베이스에 반영되지 않아 쓰기 지연이 발생
print "TeamA" (memberA.getTeam())
// flush() 호출 후에는 데이터베이스에 쿼리가 바로 반영
// DB에서 조회한 팀명을 갖고오게 됨
// flush가 없는 경우
// 트랜잭션이 끝날 때까지 insert 쿼리가 발생하지 않음

create member
create team
print "TeamA" (memberA.getTeam()) 
// 쓰기 지연이 발생하더라도 영속성 컨텍스트(1차 캐시)에서 조회해옴
insert team      // 쓰기 지연이 발생한 부분
insert member    // 쓰기 지연이 발생한 부분
profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글