spring
객체 연관관계, 영속성 컨텍스트
짱쭈니어
2025. 2. 10. 14:37
a. 객체 설계 단계에서는 단방향 연관관계로 끝내야 한다.
- 단방향으로만으로도 관계 매핑이 완료된거임 → 양뱡향으로 해버리면 객체 입장에서 고려할것이 많아짐
- 양방향은 사실 단방향의 2개일 뿐
- 테이블 설계하면서 객체 설계 같이 진행할 것 → 이미 테이블간의 관계는 이미 정해짐
- 무엇이 1:1 관계이고, 1:N 관계인지 N:M 관계인지는 테이블 설계하면서 나올것임 이때 → 외래키를 갖고 있는 객체가 주인으로 단방향 연관관계 맺으면 됌
- 다중성이 헷갈릴때에는 반대로 생각해보기
- 다중성 : @ManyToMany 인지, @OneToMany인지..
- 실무에서 다대다는 지양해야함!
- 양방향은 언제 ?
- 설계 단계에선 하지 않음, 실무하다가 정말 필요하다 생각되면 그 때 추가하면 된다.
- 테이블 수정할 필요도 없고! (테이블 영향도 없음) mappedBy 관계 맺으면 끝
- 단, 양방향하게 되면 무한호출 조심
- 무한호출을 야기하는 toString()이나 JSON 변환을 하지 않기
- 또한 양방향 관계시, 연관관계 편의 메서드 필요하면 생성
- 두개의 테이블에서 공통된 컬럼 값 업데이트를 일일히 하지 않고, 메서드 생성하여 두개의 컬럼 모두 업데이트!
- Order 1 : OrderItem N 관계에서 order.getOrderItem 필요한 경우에 양방향 하면 되는데
- orderItem.findByOrder() 이렇게 find 한번 하는게 나을 수 있음
- 최대한 양방향 피할수있으면 피하기 (고려해야할게 많아지기 때문에)
- 설계 단계에선 하지 않음, 실무하다가 정말 필요하다 생각되면 그 때 추가하면 된다.
b. 양방향 매핑 규칙
- 2관계중 외래키가 있는곳으로 주인으로 하여, 관계중 주인을 정해야 한다.
- 주인이 아니면 읽기만 가능, mappedBy 속성으로 주인을 지정해줘야함
// order : orderItem 1:N 관계
@Entity
@Table (name ="`order`")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Order{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long order_id;
// OrderItem 엔티티에 Order 엔티티 부르는 변수명을 mappedBy 해줘야함
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItem = new ArrayList<>();
}
@Entity
@Table (name ="order_item")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class OrderItem{
...
@ManyToOne
@JoinColumn (name = "order_id") //외래키 컬럼
private Order order;
}
c. 영속성 컨텍스트
- 영속성 컨텍스트를 이해해야 실제 JPA가 어떻게 작동하는지 알 수 있다.
- API 요청에 따라 EntityManagerFactory 는 EntityManager 생성
- EntityManager.persist(entity);
- DB 저장이 아닌, 영속성 컨텍스트에 저장을 하겠다.
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근!
- 영속상태
- em.persist() 한 상태
- 객체를 저장하여서 , 영속성 컨텍스트가 관리하는 상태 ⇒ But 아직 DB 저장 전
- 비영속상태
- 객체 생성만 한 상태
- 트랜잭션 커밋 했을때 영속성 컨텍스트안에 있는 쿼리 날림
- JPA 는 영속성컨텍스트 덕분에 조금이나마 효율! → SQL 을 모아서 보내기 때문에
- 마이바티스는 DB 로 바로 쿼리 보냄
- 효율이 있긴 하나, 크진 않음 → 영속성 컨텍스트가 트랜잭션 커밋을 할때 DB에 보내고 비워지기에, 하나의 트랜잭션안의 쿼리만 같이 DB 에 전송하는것이기 때문이다 (많은 처리를 하는 API 의 경우는 도움됌)
Member member = new Member(); // 비영속상태
member.setName("haha");
em.persist(member); // 영속상태 -> 이때 save 쿼리 안나감 / 커밋될때 나감
String whatName = member.getName(); // 영컨에서 값 가져오는것
c-1. 트랜잭션 커밋될때, 영속성컨텍스트 동작 과정
- flush() 호출
- Entity 와 스냅샷 비교
- 스냅샷: DB 에서 가져와서 1차 캐시한 엔티티
- 바뀐 값에 대해 Update query 쓰기지연SQL 저장소에 저장
- flush() : SQL query 디비에 전송
- Commit
e. flush()
- flush() 가 하는일
- 변경감지 (더티체킹)
- 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연소 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
- flush() 가 호출되는 순간 : 영속성 컨텍스트의 변경 내용 즉시 DB 반영
- em.flush() : 직접호출하는 방법 (사용할 일 없음/ 테스트할때 사용)
- transaction commit : flush 자동호출
- JPQL 쿼리 실행 : flush 자동호출
- JPQL 쿼리 실행되기전에 flush 한 후에 JPQL 쿼리 실행
- flush() 되어도 영속성 컨텍스트가 비워져도 1차 캐시는 값 유효하다.
Member firstMemeber = new Member();
firstMember.setName("ha");
memberRepository.save(firstMemeber);
em.flush(); //flush()를 해도 영속성 컨텍스트에 1차 캐시 값은 남아있다.
System.out.println(firstMember.getName) //영속성 컨텍스트에서 캐시 값 가져오는거 -> DB 조회 안함