[SB] 12. JPA

최재원's avatar
Apr 13, 2025
[SB] 12. JPA
  1. createNativeQuery
      • 기본 SQL 쿼리를 실행할 때 사용
  1. createQuery
      • JPA가 제공하는 객체지향 쿼리를 실행할 때 사용
      • 예시: select b from Board b join fetch b.user u left join fetch b.replies r left join fetch r.user where b.id = :id order by r.id desc
        • board_tbBoard 객체
        • b → 별칭
        • ?:username
        • inner joinjoin fetch
        • onb.user
        • left outer joinleft join
  1. createNamedQuery
      • Query Method
      • 함수 이름으로 쿼리를 생성
      • 사용하지 않음
  1. createEntityGraph
      • 사용하지 않음
  1. createEntityGraph
      • 사용하지 않음
  1. persist
      • 객체를 영속화 시킴
      • id 가 없는 객체는 insert 함
  1. find
      • 영속화 된 객체를 가져옴
      • 없으면 select 해서 가져옴

1. Persistence Context

1. PC의 위치

notion image

2. PC의 기능

notion image
  1. User user = joinDTO.toEntity(); // 1. 비영속 객체
    1. notion image
  1. em.persist(user); // 2. user -> 영속 객체
  1. insert
    1. notion image
  1. System.out.println(user); // 3. user -> db와 동기화
    1. notion image
 

2. Lazy Loading

엔티티 내부 연관관계의 객체를 초기에 로딩(조회, select)하지 않고, 연관관계에 있는 객체의 필드값에 접근 할 때 로딩되는 것
public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; private Boolean isPublic; @ManyToOne(fetch = FetchType.LAZY) private User user; private Timestamp createdAt; }
  • Board 객체를 조회할 때 User 객체는 로딩되지 않는다
  • Board 객체에서 User 객체의 필드 값에 접근할 때 User 객체가 로딩된다

핵심 요약

  • 레이지 로딩 주체: Hibernate (JPA 구현체)
  • 방법: 프록시 객체 생성 → 실제 접근 시점에 쿼리 실행
  • 주의: 영속성 컨텍스트가 닫힌 후(@Transactional 바깥 등)에 접근하면 LazyInitializationException 발생

3. EAGER 과 LAZY

  • EAGER → 연관된 객체의 데이터를 바로 받고 싶을 때
  • LAZY → 받기 싫고 나중에 getter 를 호출할 때만 데이터를 받고 싶을 때
 

4. 양방향 맵핑 @ManyToOne@OneToMany

@ManyToOne@OneToMany 가 서로 걸려있는 경우를 양방향 맵핑이라 한다

@ManyToOne (N:1 관계)

  • 다수(N)의 엔티티가 하나(1)의 엔티티에 연결되어 있을 때 사용.
  • 보통 외래 키는 @ManyToOne 쪽에 존재.
  • 예: 여러 Reply 는 하나의 Board 에 속함.
public class Reply { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Board board; private String content; // 댓글 내용 @CreationTimestamp private Timestamp createdAt; }

@OneToMany (1:N 관계)

  • 하나(1)의 엔티티가 여러 개(N)의 엔티티를 가짐.
  • 외래 키는 반대편(@ManyToOne)에 있으므로, mappedBy 속성 필요.
  • 주 테이블에는 외래 키가 없고, 연관된 객체 컬렉션만 존재.
public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; private Boolean isPublic; @ManyToOne(fetch = FetchType.LAZY) // 연관관계 설정 -> ORM 하려고 EAGER -> fk에 들어간 오브젝트를 바로 연관관계 맵핑을 해서 select를 여러 번 한다 , LAZY -> 무조건 LAZY를 사용한다. 연관관계 맵핑을 하지 않는다 private User user; @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.ALL) // mappedBy -> fk 의 주인인 reply의 필드 이름을 적어야 한다 private List<Reply> replies = new ArrayList<Reply>(); @CreationTimestamp private Timestamp createdA }

양방향 관계 정리

// Board <--> Reply // Board: 1개 // Reply: 여러 개
  • Reply 에서는 @ManyToOne 으로 Board 를 참조
  • Board 에서는 @OneToMany(mappedBy = "board") 로 컬렉션으로 Reply 를 참조
    • mappedBy 로 지정하는 이름은 Reply 에서 @ManyToOne 로 지정한 변수 이름을 넣는 것이다

양방향 맵핑을 하면 좋은 점

  • 이렇게 쿼리를 작성하면
em.createQuery("select b from Board b join fetch b.user u left join fetch b.replies r where b.id = :id", Board.class);
  • 이렇게 자동으로 오브젝트 릴레이션 맵핑을 하기 때문
    • notion image

⚠️ 주의사항

1. 연관관계의 주인(owner)

  • @ManyToOne 쪽이 연관관계의 주인 (즉, DB에 외래 키를 갖고 있음)
  • @OneToManymappedBy로 주인을 지정할 뿐, 외래 키를 소유하지 않음

2. 양방향 관계 설정 시 무한 루프 주의 (toString, JSON 직렬화)

  • 예: @ToString.Exclude, @JsonManagedReference, @JsonBackReference 등 사용

5. Cascade

notion image

@oneToMany

위 어노테이션을 가지고 있는 List<Reply> replies 객체는 여기에 Reply 가 추가 되거나 삭제 되면 트랜잭션이 끝난 다음 insert or delete 쿼리가 작동한다
쿼리를 진짜 객체 지향적으로 사용하는 방법
 
Share article

jjack1