자바 ORM 표준 JPA

wook2·2023년 2월 28일
0

spring

목록 보기
2/8

해당 강의를 바탕으로 작성하였습니다.
https://www.inflearn.com/course/ORM-JPA-Basic

JPA

JPA란 Java Persistent API의 약자로 자바 진영의 ORM 기술 표준이다.
객체와 관계형 데이터베이스 사이에 연결다리를 해주는 역할이라고 할 수 있다.

JPA는 표준 인터페이스이고, 구현체로는 Hibernate를 주로 사용한다.

JPA를 사용하면 얻는 이점

  1. 객체 중심으로 개발할 수 있다.
  2. 생산성 증가 : entityManger를 통해 persist(), find(), remove() 메서드로 CRUD를 작성할 수 있다.
  3. 1차캐시와 동일성 보장 : 같은 트랜잭션 안에서는 같은 엔티티를 반환한다.
  4. 쓰기 지연 : 트랜잭션을 커밋할 때까지 insert sql을 모으고 jdbc batch sql을 통해 한번에 sql을 전송한다.
  5. 지연로딩 : 객체가 실제 사용될 때 로딩
  6. 즉시로딩 : join 구문에 연결된 객체까지 한번에 미리 조회

JPA 원리

애플리케이션은 하나의 EntityManagerFactory를 생성해 EntityManager를 생성한다.
EntityManager는 쓰레드간에 자원공유를 하지 않는다. 또한 JPA의 모든 변경은 트랜잭션 내에서 이루어 진다.

JPQL

JPQL은 엔티티를 대상으로 쿼리를 날린다. SQL을 추상화하여 특정 DB에 의존적이지 않고, 객체를 대상으로 쿼리를 실행할 수 있다.

영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경이다.
  • 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있다.

엔티티의 생명주기

  • 비영속 : 영속성 컨텍스트와 관련이 없는 새로운 상태
  • 영속 : 영속성 컨텍스트에게 관리되고 있는 상태
  • 준영속 : 영속성 컨텍스트에서 분리된 상태
  • 삭제 : 영속성 컨텍스트에서 삭제된 상태
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//엔티티를 영속
em.persist(member);

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

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

영속성 컨텍스트 1차 캐시

영속성 컨텍스트에 저장되면 1차 캐시에서 엔티티를 찾아온다. 1차 캐시에 없다면 데이터베이스로 쿼리가 날라간다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

엔티티 등록

쓰기 지연 SQL 저장소에 SQL을 저장하고, 1차 캐시에 엔티티를 저장한다.
트랜잭션이 커밋되면 쓰기 지연 저장소에 저장된 내용을 DB로 내려준다.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

엔티티 수정 (변경 감지)

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋

flush : 영속성 컨텍스트의 내용을 DB에 반영하는 과정

엔티티 수정시에는 변경 감지가 일어나는데,
flush 하는 시점에 영속성 컨텍스트에 있는 스냅샷과 비교해 UPDATE 쿼리를 같이 flush 시켜준다.

플러시를 하는 방법은 다음과 같다.

  • em.flush() 를 직접 호출한다.
  • transaction을 commit한다.
  • JPQL 쿼리를 실행한다 : JPQL 쿼리 실행 시 SQL을 데이터베이스에 보내기 위해서는 영속 컨텍스트와 데이터베이스가 동기화가 되어있어야 하기 때문이다.

엔티티 매핑

@Entity: @Entity가 붙은 클래스는 JPA가 관리한다.

객체 - 테이블 매핑 : @Entity , @Table
필드 - 컬럼 매핑 : @Column
기본키 매핑 : @Id
연관관계 매핑 : @ManyToOne

엔티티를 바탕으로 데이터베이스 스키마 자동 생성

DDL을 애플리케이션 실행 시 자동 생성해주는 기능이 있다.

옵션은 5가지 종류가 있다.

  • create : 기존테이블 삭제 후 다시 생성
  • create-drop : create와 같으나 종료시점에 테이블 DROP
  • update : 변경분만 반영
  • validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
  • none : 사용하지 않음

운영시에는 create, create-drop, update를 사용해서는 안되고, validate 또는 none을 사용하여야 한다.

필드와 컬럼 매핑

@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
}

매핑 어노테이션

@Column : 컬럼 매핑
@Temporal : 날짜 타입 매핑
@Enumerated : enum 타입 매핑
@Lob : BLOB, CLOB 매핑
@Transient : 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

기본 키 매핑

@Id @GeneratedValue(strategy = GenerationType.AUTO) 
private Long id;

@GeneratedValue : 자동생성
전략들

  • IDENTITY: 데이터베이스에 위임, MYSQL
  • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE(@SequenceGenerator 필요)
  • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용(@TableGenerator 필요)
  • AUTO: 방언에 따라 자동 지정, 기본값

연관관계 매핑

단방향 연관관계

단방향 연관관계에서는 아래의 그림과 같이 한쪽으로만 연결 된 경우이다.
Member 객체에만 Team 객체가 있고, Team 객체에는 Member에 관련된 내용이 없다.


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

양방향 연관관계

@Entity
public class Member {

    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")
    List<Member> members = new ArrayList<Member>();
}

객체와 테이블의 차이

객체의 양방향 연관관계 매핑은 두개의 단방향 매핑과 같다.

  • 객체

    회원 -> 팀 ( 단방향 )
    팀 -> 회원 ( 단방향 )

  • 테이블
    테이블은 외래키로 두 테이블의 연관관계를 관리하기 연관관계 1개로 양방향을 관리할 수 있다.

객체에서는 양방향 연관관계 매핑의 경우 연관관계의 주인이 필요하다.

연관관계의 주인

  • 연관관계의 주인만 외래키를 관리
  • 두 객체 중 하나만 연관관계의 주인이 된다
  • 주인이 아닌쪽은 읽기만 가능
  • 주인이 아니면 mappedBy 속성으로 주인을 지정해야 한다
  • 외래키가 있는 쪽을 주인으로 정하기

양방향 연관관계시 주의점

  • 양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.
    (순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다.)

  • 연관관계 편의 메소드를 생성하자
    양방향 매핑시에 무한 루프를 조심하자(toString(), lombok, JSON 생성 라이브러리)

양방향 매핑 정리

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

연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래키의 위치를 기준으로 정해야함
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);

다양한 연관관계 매핑

ManyToOne
다대일 관계
가장 많이 사용되는 연관관계
외래키가 있는 쪽이 연관관계의 주인이다.

OneToMany


일대다 관계에서는 항상 Many 쪽에 외래키가 있다.

  • 테이블 일대다 관계는 항상 다(N)쪽에 외래키가 있음
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래키를 관리하는 특이한 구조
  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)

일대다 단방향 매핑의 단점

  • 엔티티가 관리하는 외래키가 다른 테이블에 있음
  • 연관관계 관리를 위해 추가로 외래키와 관련하여 UPDATE SQL 실행
  • 일대다 양방향의 경우 공식적인 표준스펙은 아니며 권장하지 않는다.

= > 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자

OneToOne

  • 일대일 관계는 그 반대도 일대일
  • 주테이블이나 대상테이블 중에 외래키 선택 가능
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

주테이블에 외래키가 있는 경우

  • 주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래키를 두고 대상 테이블을 찾음
  • 객체지향 개발자 선호
  • JPA 매핑 편리(주테이블이므로 단방향 연관관계만 설정)
    장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    단점: 값이 없으면 외래 키에 null 허용

대상테이블에 외래키가 있는 경우

  • 대상 테이블에 외래 키가 존재
  • 전통적인 데이터베이스 개발자 선호
    장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
    단점: 대상테이블에 외래키가 있으므로 양방향 연관관계 설정 필요

ManyToMany

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
  • 연결 테이블을 통해서 일대다, 다대일 관계로 풀어내야함

ManyToMany를 일대다 혹은 다대일로 풀어내어 새로운 엔티티를 생성하는 것이 바람직함.

profile
꾸준히 공부하자

0개의 댓글