JPA

CHEESE·2023년 1월 29일
post-thumbnail

💡 Intro

Spring Data JDBC

도메인 주도 설계의 영향을 받은 프레임워크

객체 지향 패러다임

시스템을 구성하는 객체들에게 적절한 책임을 할당하는 것

상속과 연관 관계

객체의 연관 관계에는 방향성이 있다
테이블의 연관 관계에는 방향성이 없다
객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다

SQL을 직접 다룰 때 발생하는 문제점

JDBCTemplate, Mybatis => SQL Mapper

1. 반복 작업

새로운 필드가 추가되면 SQL을 다 수정해야 한다.
99개 바꿨는데 1개를 못 바꿨다면 ? 장애 발생 !

2. 신뢰성

SQL Mapper로 만들어진 코드를 까봐야지만 믿고 사용할 수 있다.
남이 만든 DAO의 메소드를 사용했더니 일부 데이터가 없는 경험 !

💡 JPA

ORM

객체 지향 패러다임과 RDBMS 패러다임을 중간에서 번역해주는 것
자바 진영의 ORM 표준은 JPA => 즉 인터페이스, API
자바 퍼시스턴스 api -> 자카르타 퍼시스턴스 api
hibernate가 유명한 이유는 스프링 JPA의 구현체이기 때문입니당
따지자면 JPA == hibernate라고 봐도 무방

특징

1. 데이터베이스 스키마를 자동으로 생성하는 기능 지원

spring.jpa.hibernate.ddl-auto = create
create, create-drop, update, validate, none
프로덕션 환경에서는 create, create-drop을 사용하면 ...안된다.
실제로 validate, none을 사용하는 것이 맞음

어떻게 생성하나?

어노테이션 기반으로 생성한다. @Entity, @Table, @Id ...

2. 영속성 컨텍스트

엔티티를 영구 저장하는 환경
엔티티 매니저가 영속성 컨텍스트에서 엔티티를 관리함

1) 1차 캐시

영속성 컨텍스트는 hashmap과 비슷하게 생겼다.
key는 엔티티의 @Id값, value는 엔티티가 들어있다.
1차 캐시에 데이터가 있으면 1차 캐시에서 조회하고,
없으면 DB에서 조회해서 1차 캐시에 저장 후 반환한다.
따라서 영속성 컨텍스트에서는 키값이 같은 엔티티가 2개 이상 존재할 수 없다.

언제까지 유지가 되나?

트랜잭션이 열리고, 트랜잭션이 닫힐 때까지
영속성 컨텍스트는 트랜잭션과 운명 공동체다 !

2) 동일성 보장

Station station1 = station.save(new Station("잠실역"));
Station station2 = station.findByName("잠실역");
station1 == station2 // true

3) 쓰기 지연

한 번에 쿼리가 나간다.
모아서 flush되는 순간 방출
save() 할 때 insert 쿼리는 모아두고 엔티티를 1차 캐시에 저장
commit되는 시점에 모아둔 쿼리들을 flush

쓰기 지연돼야 하는데도 insert 쿼리가 나갔다면?

@GeneratedValue를 통해 ID를 자동 생성하는 경우 ID 값을 얻어오기 위해 데이터베이스에 insert 쿼리를 날린다.

save()에서 select 쿼리가 나갔다면?

save메소드에서는 isNew일 경우에 persist, 아니면 merge를 실행하는데 isNew의 판별 기준이 id == null 이다.
persist가 실행되면 인자로 받아온 엔티티를 그대로 리턴한다.
merge가 실행되면 인자로 받아온 엔티티가 리턴되지 않을 수도 있다.
그럼 실제 엔티티의 주소값이 달라진다.
따라서 save를 실행한다면 그 리턴값을 엔티티에 저장하는 것이 안전하다.
Id를 지정한 상태에서 select 쿼리를 안 나가게 하려면 엔티티를 Persistable 인터페이스 구현체로 만들고 isNew를 오버라이드하면 된다.

4) 변경 감지

영속성 컨텍스트에는 스냅샷 기능이 있어서 commit하기 전 달라진 점이 있으면 update 쿼리를 실행한다.

final Station station1 = stations.save(new Station("잠실역"));
station1.changeName("신도림역");
final Station station2 = stations.findByName("신도림역");
assertThat(station2).isNotNull();	// pass

findByName 처럼 id 기반 조회가 아닌 쿼리를 JPQL이라고 한다.
영속성 컨텍스트를 거치지 않고 바로 데이터베이스를 조회한다.
JPQL이 작동하는 순간에는 무조건 flush를 날린다.

5) 지연 로딩

@ManyToOne의 기본 fetchType은 EAGER
@OneToMany의 기본 fetchType은 LAZY
fetchType이 LAZY인 경우 실제 항목을 사용할 때 select 쿼리가 나간다.
디버거 동작시에도 쿼리가 실행될 수 있으니 참고
엔티티 사용 방식에 따라 fetchType을 LAZY로 변경하면 성능 이점을 얻을수도

💡 엔티티

생명주기

비영속
영속
준영속
삭제

엔티티 연관관계

단방향, 양방향
엄밀히 말하면 객체에는 양방향 연관관계가 없다. 서로 다른 단방향 연관관계 2개가 양방향인 것처럼 보이게 할 뿐이다.
연관 관계의 주인은 비즈니스 중요도로 판별하는 것이 아니다.
게시글과 댓글은 비즈니스 중요도로 게시글이 주인인 것 같지만 댓글이 연관 관계의 주인이다. 주인만이 연관 관계와 매핑되고 외래 키를 등록, 수정, 삭제 할 수 있다.
주인이 아닌 쪽은 읽기만 가능함. 주인이 아닌 곳에서 등록/수정/삭제 해도 엔티티의 연관 관계에 영향을 미치지 않는다.
다대일 일대다 관계에서는 다쪽이 외래키를 갖는다.

0개의 댓글