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. 트랜잭션 커밋될때, 영속성컨텍스트 동작 과정

  1. flush() 호출
  2. Entity 와 스냅샷 비교
    1. 스냅샷: DB 에서 가져와서 1차 캐시한 엔티티
  3. 바뀐 값에 대해 Update query 쓰기지연SQL 저장소에 저장
  4. flush() : SQL query 디비에 전송
  5. Commit

 

e. flush()

  • flush() 가 하는일
    1. 변경감지 (더티체킹)
    2. 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
    3. 쓰기 지연소 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 조회 안함