자바 ORM 표준 JPA 프로그래밍 - 기본편

송민규

Updated:

Intro

자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 내용을 정리 해보았습니다.


1. JPA 소개

  • JPA?
    • Java Persistence API
    • 자바 진영의 ORM 기술 표준
  • ORM?
    • Object-relational mapping(객체 관계 매핑)
    • 객체는 객체대로 설계
    • 관계형 데이터베이스는 관계형 데이터베이스대로 설계
    • ORM 프레임워크가 중간에서 매핑
  • JPA는 애플리케이션과 JDBC 사이에서 동작

그림

  • JPA에서 가장 중요한 2가지
    • 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
    • 영속성 컨텍스트

2. 영속성 컨텍스트

  • 영속성 컨텍스트란?
    • 영속성 컨텐스트는 엔티티를 영구 저장하는 환경이라는 뜻
    • 애플리케이션과 DB 사이에서 객체를 보관하는 가상의 DB 역할을 한다.
    • 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리하게 된다
  • 엔티티 생명주기
    • 비영속 (new/transient) - 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    • 영속 (managed) - 영속성 컨텍스트에 관리되는 상태
    • 준영속 (detached) - 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 삭제 (removed) - 삭제된 상태


그림

//객체를 생성한 상태(비영속) 
Member member = new Member(); 
member.setId("member1"); 
member.setUsername(회원1);

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);

//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태 
em.detach(member);

//객체를 삭제한 상태(삭제) 
em.remove(member);

영속성 컨텍스트의 이점

  • 1차 캐시 그림 그림
  • 동일성 (identity) 보장
    Member a = em.find(Member.class, "member1");
    Member b = em.find(Member.class, "member1");
    System.out.println(a == b); //동일성 비교 true
    
  • 트랜잭션을 지원하는 쓰기 지연 (transactional writer-behind)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

그림 그림

  • 변경 감지 (Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

//em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋
  • 지연 로딩 (Lazy Loading)
    • 지연로딩은 연관 관계 매핑되어 있는 엔티티를 조회 시 우선 프록시 객체를 반환하고, 실제로 필요할 때 쿼리를 날려 가져오는 기능이다.
      즉, 필요할 때 데이터를 가져오는 기능이다.

3. 엔티티 매핑

  • 엔티티 매핑 소개
    • 객체와 테이블 매핑: @Entity, @Table
    • 필드와 컬럼 매핑: @Column
    • 기본 키 매핑: @Id
    • 연관관계 매핑: @ManyToOne, @JoinColumn
package hellojpa; 

import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime; 
import java.util.Date;

@Entity 
public class Member {
  
 @Id @GeneratedValue
 private Long id;
 
 @Column(name = "name")
 private String username;
 
 private Integer age;
 
 @Enumerated(EnumType.STRING) //enum 타입 매핑
 private RoleType roleType;
 
 @Temporal(TemporalType.TIMESTAMP) //날짜 타입 매핑
 private Date createdDate;
 
 @Temporal(TemporalType.TIMESTAMP) 
 private Date lastModifiedDate;

 @Lob //BLOB, CLOB 매핑
 private String description;
 
 //Getter, Setter… 
} 

단방향 연관관계

그림

 @Entity
 public class Member {
  
   @Id @GeneratedValue
   private Long id;
   
   @Column(name = "USERNAME")
   private String name;
   private int age;

   @ManyToOne // 현재 클래스가 Many, 참조 대상이 One 이라고 설정해주는 역할을 함
   @JoinColumn(name = "TEAM_ID") //참조 대상인 Team 객체와 실제 테이블에 있는 FK 컬럼을 매핑해주는 역할을 함
   private Team team;
 }

  • 연관관계 저장
 //팀 저장
 Team team = new Team();
 team.setName("TeamA");
 em.persist(team);
 
 //회원 저장
 Member member = new Member();
 member.setName("member1");
 member.setTeam(team); //단방향 연관관계 설정, 참조 저장
 em.persist(member);
  • 참조로 연관관계 조회 - 객체 그래프 탐색
 //조회
 Member findMember = em.find(Member.class, member.getId()); 

 //참조를 사용해서 연관관계 조회
 Team findTeam = findMember.getTeam();
  • 연관관계 수정
 // 새로운 팀B
 Team teamB = new Team();
 teamB.setName("TeamB");
 em.persist(teamB);
 
 // 회원1에 새로운 팀B 설정
 member.setTeam(teamB);

양방향 연관관계

그림

  @Entity
  public class Member {
  
    @Id @GeneratedValue
    private Long id;
    
    @Column(name = "USERNAME")
    private String name;
    private int age;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

  @Entity
  public class Team {
  
    @Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "team") // 나를 참조하고 있는 엔티티에서 어떤 필드에 나를 매핑해두었는지 명시
    // Member 테이블에서 team 이라는 변수명으로 Team 객체를 참조하고 있으니 mappedBy의 값으로 team을 준다
    List<Member> members = new ArrayList<Member>();
  }

연관관계의 주인

  • 양방향 매핑 규칙
    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
    • 주인이 아닌쪽은 읽기만 가능
    • 주인은 mappedBy 속성 사용X
    • 주인이 아니면 mappedBy 속성으로 주인 지정
  • 누구를 주인으로?
    • 외래 키가 있는 곳을 주인으로 정해라 (1:N 에서 N쪽을 연관관계 주인으로)
    • 여기서는 Member.team이 연관관계의 주인 그림
  • 양방향 매핑시 가장 많이 하는 실수 (연관관계의 주인에 값을 입력하지 않음)
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

team.getMembers().add(member); 
//연관관계의 주인에 값 설정
member.setTeam(team); //**

em.persist(member);
  • 양방향 매핑 정리
    • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
    • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
    • JPQL에서 역방향으로 탐색할 일이 많음
    • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

4. 프록시

  • Member 엔티티를 조회할 때 Team도 함께 조회해야 할까?
    • 실제로 필요한 비즈니스 로직에 따라 다르다
    • 비즈니스 로직에서 필요하지 않을 때가 있는데, 항상 Team을 함께 가져와서(join) 사용할 필요는 없다
    • 낭비가 발생하게 된다
    • JPA는 이 낭비를 하지 않기 위해, 지연로딩과 프록시라는 개념으로 해결한다
  • em.find() vs em.getReference()
    • em.find() 는 DB를 통해서 실제 엔티티 객체를 조회하는 메서드
    • em.getReference() 는 DB의 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 메서드이다 그림 그림

프록시 특징

그림

  • 실제 클래스를 상속받아서 만들어진다 (하이버네이트가 내부적으로 상속받아서 만든다)
  • 실제 클래스와 겉 모양이 같다
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다 그림
  • 프록시 객체는 실제 객체의 참조를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
  • 프록시 객체는 처음 사용할 때 한 번만 초기화 된다

즉시 로딩과 지연 로딩

  • 프록시 학습 처음에 했던 질문 Member를 조회할 때 Team도 함께 조회 해야 할까?
    • 비즈니스 로직에서 단순히 멤버 로직만 사용하는데 함께 조회하면, 아무리 연관관계가 걸려있다고 해도 손해이다
  • JPA는 이 문제를 지연로딩 LAZY를 사용해서 프록시로 조회하는 방법으로 해결 한다
    @Entity
    public class Member {
      
    @Id
    @GeneratedValue
    private Long id;
      
    @Column(name = "USERNAME")
    private String name;
      
    @ManyToOne(fetch = FetchType.LAZY) //**
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    }
    

    그림

  • Member와 Team을 자주 함께 사용한다면? (즉시로딩)
@Entity
public class Member {

@Id
@GeneratedValue
private Long id;

@Column(name = "USERNAME")
private String name;

@ManyToOne(fetch = FetchType.EAGER) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
}

그림

  • 프록시와 즉시로딩 주의
    • @ManyToOne, @OneToOne은 기본이 즉시 로딩
    • @OneToMany, @ManyToMany는 기본이 지연 로딩
  • 지연 로딩 활용 - 실무
    • 모든 연관관계에 지연 로딩을 사용해라!
    • 실무에서 즉시 로딩을 사용하지 마라!
    • 즉시 로딩은 상상하지 못한 쿼리가 나간다

Tags:

Categories:

Updated:

Leave a comment