JPA는 데이터 액세스 계층의 상단에 위치한다.
데이터 저장/조회 등은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM를 통해서 일어난다. Hibernate ORM은 JDBC API를 이용해서 데이터베이스에 접근한다.
1차 캐시
와 쓰기 지연 SQL 저장소
가 있다.EntityManager
클래스에 의해 관리된다.EntityManager
객체를 통해서 JPA의 API를 사용할 수 있다.Transaction
객체를 통해서 데이터베이스의 테이블에 데이터를 저장한다.
EntityManager
객체 -> em
Transaction
객체 -> tx
em.persist(객체)
1차 캐시
에 객체를 저장한다.쓰기 지연 SQL 저장소
에 INSERT 쿼리가 저장된다.tx.commit()
: 쓰기 지연 SQL 저장소
에 있던 쿼리를 실행한다.쓰기 지연 SQL 저장소
에서 제거된다.em.flush()
가 쿼리를 실행해주는 것!em.flush()
: 영속성 컨텍스트의 변경 사항을 테이블에 반영해주는 API쓰기 지연 SQL 저장소
에 있던 쿼리들을 데이터베이스에 전달해준다.em.find(클래스 타입, 식별자)
1차 캐시
에 해당 객체가 있는지 조회한다.1차 캐시
에서 찾고자 하는 객체가 존재하지 않으면, 테이블에 직접 SELECT 쿼리를 전송해서 조회한다.em.remove(객체)
쓰기 지연 SQL 저장소
에 DELETE 쿼리를 저장한다.JPA에는 따로 UPDATE API는 없고 tx.commit()
를 사용한다.
setter 메서드로 값을 변경하고 tx.commit()
를 실행하면 UPDATE 쿼리가 실행된다.
💡
tx.commit()
를 사용하는데 어떻게 INSERT 쿼리가 아닌 UPDATE 쿼리가 실행되는가?
영속성 컨텍스트에 엔티티 객체가 저장되는 경우에, 저장되는 시점의 상태를 그대로 가지고 있는스냅샷
을 생성한다.
해당 엔티티의 값을 setter 메서드로 변경한 후,tx.commit()
을 하면 변경된 엔티티와 이 전의 스냅샷을 비교한 후, 변경된 값이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행한다.
데이터베이스 테이블과 엔티티 클래스 간에 매핑 과정은 크게 다음과 같이 나눌 수 있다.
@Entity
애너테이션을 붙여 Entity 클래스로 인식하게 만들어준다.@Id
애너테이션을 붙여, 식별자로 만들어준다.@Table
은 필수가 아니다.@Entity
애너테이션을 붙이면 해당 클래스는 JPA 관리 대상 엔티티가 된다.✔️ 과정
1) em.persist()
호출
tx.commit()
를 하지 않아도 INSERT 문이 실행된다.2) 테이블에 INSERT 쿼리가 실행될 때, DB에서 식별자가 AUTO_INCREMENT로 생성된다.
3) DB에서 생성된 식별자를 1차 캐시로 가져와서 채워준다.
✅ IDENTITY 전략은 commit 호출 없이 데이터를 저장해주고, DB에 저장된 식별자를 가져와서 1차 캐시에 넣어준다.
1) sequence 생성
2) sequence가 식별자를 생성
3) em.persist()
호출
4) IDENTITY 전략과 달리, tx.commit()
를 해야 INSERT 문이 실행된다.
@Column
애너테이션을 통해 필드와 컬럼을 매핑한다.@Column
애너테이션이 없어도, JPA는 기본적으로 모든 필드를 컬럼으로 매핑한다.@Transient
애너테이션 : 테이블 컬럼과 매핑하지 않겠다는 의미@Temporal
애너테이션 : java.util.Date
, java.util.Calendar
타입으로 매핑할 때 사용@Enumerated
애너테이션 : enum 타입과 매핑할 때 사용EnumType.ORDINAL
과 EnumType.STRING
두 가지 타입이 있다.EnumType.STRING
을 사용하는 것을 권장함❗️ 주의
필드가 원시 타입이라면, 기본적으로nullable = false
이다.
오히려@Column
애너테이션만 추가한다면nullable = true
로 변경되기 때문에 (1) 애너테이션을 사용하지 않거나, (2) @Column(nullable=false)을 작성해주자.
외래키
를 통해서 연관 관계가 생성되는데, JPA에서는 객체 참조
를 통해 관계를 맺는다.방향성
기준으로 생각했을 때,참조할 수 있는 객체의 수
를 기준으로 생각했을 때,List<객체>
가 포함된다.다
에 해당하는 테이블이 일
의 기본키를 외래키로 가진다.일대다 단방향으로 객체를 참조하는 경우에, 일반적인 테이블 간의 관계를 표현하지 못한다.
=> 따라서 일다대 단방향은 잘 사용하지 않음!
다
에 해당하는 클래스의 외래키 필드
에 @ManyToOne
, @JoinColumn
애너테이션을 추가한다.@JoinColumn(name = 외래키 칼럼명)
일
에 해당하는 테이블)의 기본키의 컬럼명과 동일하다.@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member; // 외래키 필드
@ManyToOne
+ @JoinColumn(name = 외래키 칼럼명)
: 다(N)쪽에 작성@OneToMany(mappedBy = 외래키 필드명)
: 일(1) 쪽에 작성// 다(N)에 해당하는 클래스 - Order
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member; // 외래키 필드
// 일(1)에 해당하는 클래스 - Member
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
private void mappingManyToOneBiDirection() {
tx.begin();
Member member = new Member("hgd@gmail.com", "Hong Gil Dong", "010-1111-1111");
Order order = new Order();
member.addOrder(order); // (1)
order.addMember(member); // (2)
em.persist(member);
em.persist(order);
tx.commit();
Member findMember = em.find(Member.class, 1L); // (5)
findMember
.getOrders()
.stream()
.forEach(findOrder -> {
System.out.println("findOrder: " +
findOrder.getOrderId() + ", "+ findOrder.getOrderStatus());
});
}
(1) member 객체에 order를 추가해주지 않아도, 연관 관계가 맺어져 있기 때문에 테이블에는 정상적으로 저장된다.
-> 하지만 order를 추가해주지 않으면, 1차 캐시에는 저장되지 않기 때문에 조회를 위해서는 추가해야 한다.
(2) order에 member 객체를 필수로 추가해야 한다.
-> member는 외래키 역할을 하기 때문이다.
@ManyToMany
애너테이션을 사용할 수 있다.@ManyToMany
는 외래키들로만 테이블을 구성하므로 추가적인 속성을 저장할 수 없다는 단점이 있다.@OneToOne
애너테이션 사용
일대일 단방향의 경우, @OneToOne
+ @JoinColumn
일대일 양방향인 경우, @OneToOne
+ @JoinColumn
과 @OneToMany(mappedBy)
외래키를 어느 엔티티에 두든 상관없다.
-> 아래 예시에서 설명!
다음 그림과 같이, 회원 애그리거트에 회원 정보(Member)와 Stamp가 있다.
회원 정보가 회원 애그리거트의 애그리거트 루트가 된다.
이때 어떤 관점으로 해당 애그리거트를 바라보느냐에 따라 연관 관계 매핑이 달라진다.
DDD 관점
MEMBER_ID
이고 Stamp에서 Member 객체를 참조한다.// Stamp 클래스
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
이때, 회원 정보가 애그리거트 루트이기 때문에 회원 정보를 통해서 Stamp에 접근해야 한다.
회원 정보가 Stamp에 접근할 수 있도록 객체 참조가 있어야 한다.
=> 따라서 회원 정보와 Stamp는 서로를 참조하는 양방향 연관 관계를 맺어야 한다.
외래키
를 중심으로 생각하자!
1:N 관계에서는 N이 외래키를 가지고 있다.
그러므로 N이 1에 접근할 수 있지만, 1은 N에 접근할 수 없다.
하지만 1으로 N을 접근해야 한다면, 1에서 접근할 수 있도록 양방향으로 만들어줘야 한다.
💡 JPA랑 Spring Data JPA는 다른 개념 인가요?
다른 개념이다!
Spring Data JPA는 순수 JPA를 더 편리하게 사용할 수 있도록 Spring이 제공하는 기능이다.
오늘 배운 것들은 Spring Data JPA에 대한 것이 아니라 순수 JPA에 대한 것이다!
위에서 em.persist()
나 tx.commit
과 같은 기능을 매번 직접 호출해서 사용했는데, Spring이 이를 쉽게 사용하도록 도와주는 것이 Spring data JPA이다.