// 에드센스

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.


연관관계 매핑 시 고려할 것 3가지

  • 다중성
  • 단/양방향
  • 연관관계의 주인

 

 

다중성

N:1 , 1:N , 1:1 , N:N의 4가지 다중성이 있다.

보통은 다대일과 일대다를 많이 사용하고 다대다는 실무에서 거의 사용하지 않는다고 한다.

 

단방향/양방향

테이블에서는 외래 키 하나로 조인을 통해서 테이블간 양방향 참조가 가능하기 때문에 사실상 방향성이 없다. 하지만 객체에서는 참조용 필드를 통해 단방향으로만 접근할 수 있다. 단방향으로 서로 참조하고있으면 이를 양방향이라고 부른다. 

 

연관관계의 주인

테이블에서는 외래 키 하나로 두 테이블의 연관관계를 맺는다. 즉, 테이블의 연관관계를 관리하는 포인트는 외래 키 하나이다. 하지만 객체의 양방향 관계는 A->B , B->A 처럼 2개의 참조가 필요하다. JPA는 두 객체 연관관계 중 하나를 정해서 데이터베이스의 연관관계를 관리하는데 이를 연관관계의 주인이라고 한다. 

외래 키를 가진 쪽이 연관관계의 주인이고 외래키는 다(N)쪽에 존재한다.

 

 

 


다대일(N:1)

다대일 단방향
다대일 양방향

가장 많이 사용하는 관계이다. 외래 키는 항상 다(N)쪽에 있다. 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다(N)쪽이다. 여기서는 Member의 team이 연관관계의 주인이다.

또한 양방향 연관관계에서는 항상 서로를 참조하고 있어야한다. 이를 위해서는 이전 포스팅의 순수객체 상태 관련 메소드를 사용하면 좋다.

 

 

 


일대다(1:N)

일대다 단방향

일대다 단방향은 일(1)이 연관관계의 주인이다.

객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조이다. 꼭 @JoinColunm을 사용해야한다. 사용하지 않으면 중간에 매핑테이블이 자동으로 생성돼버린다.

 

엔티티가 관리하는 외래 키가 다른 테이블에 있기에 추가로 update SQL이 실행된다. 일대다 단방향보다는 다대일 양방향을 이용하자.

 

 

일대다 양방향

이런 매핑은 공식적으로 존재하지 않는다.

@JoinColumn(insertable=false, updatable=false) 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법이다.

 

즉, 그냥 다대일 양방향을 사용하는 것이 좋다.

 

 

 


일대일 (1:1)

일대일 단방향

주 테이블이나 대상 테이블 중 어떤곳에 외래 키를 두어도 된다.

외래 키에 데이터베이스 유니크 제약조건 추가돼야 한다.

 

 

주 테이블에 외래 키

  • 주 객체가 대상 객체를 참조하는 형태로, 객체지향 개발자들이 선호한다.
  • 장점은 주 테이블만 확인해도 대상 테이블과의 연관관계를 알 수 있다.
  • 단점은 값이 없으면 외래 키에 null 허용

 

대상 테이블에 외래 키

  • 전통적인 데이터베이스 개발자들은 대상 테이블에 외래 키를 두는 것을 선호한다.
  • 장점은 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.
  • 단점은 프록시 기능의 한계로 지연로딩으로 설정해도 항상 즉시로딩이 된다.

 

 

 

일대일 양방향

양방향 매핑이므로 연관관계의 주인을 정해야한다. 이 경우 MEMBER 테이블이 외래 키를 가지고 있으므로 Member 엔티티의 locker가 연관관계의 주인이다.

따라서 반대 매핑인 Locker 엔티티의 member는 mappedBy를 통해 연관관계의 주인이 아니라고 명시해야 한다.

 

 

 

 


다대다 (N:N)

김영한 강사님이 쓰지 말라신다. 쓰지 말자.

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

 

예를 들어 회원들(N)은 상품들(N)을 주문하고, 상품들(N)도 회원들(N)에게 주문된다고 하면, 이 둘은 다대다 관계다.

고로 회원 테이블과 상품 테이블만으로 이 관계를 표현할 수 없고 아래처럼 중간 테이블을 통해 다대일, 일대다 관계로 풀어내야 한다. (@ManyToMany => @ManyToOne , @OneToMany)

 

 

 

참고:

더보기

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 연관관계 매핑  (0) 2021.07.21
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15
  1. 조영흠 2021.07.25 17:53

    글 잘 봤어요 승현씨

    • llshl 2021.11.13 16:24 신고

      감사합니당 ^_^ 많이 읽어주세요~~~~~~~

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.


연관관계가 필요한 이유

"객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다"

 

예를 들어보자.

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 N:1 관계다.
@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;
  
  @Column(name = "USERNAME")
  private String name;
  
  @Column(name = "TEAM_ID")
  private Long teamId;
}

@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;
  
  private String name;
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

라는 상황이 있을때, 특정 회원이 속한 팀을 찾으려면 어떻게 해야할까?

 

 

일반적인 경우는 다음과 같은 방법을 사용한다.

Member findMember = em.find(Member.class, member.getId());

Long findTeamId = findMember.getId();
Team findTeam = em.find(Team.class, findTeamId);

먼저 특정 회원을 조회하고, 그 회원의 팀 ID를 getter로 가져와서

팀 ID로 다시 조회한다. 

 

벌써 두번이나 쿼리가 발생했다. 이런것을 보고 객체지향스럽지 못하다고 한다.

즉, 객체를 테이블에 맞춰서 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래키를 사용한 조인을 통해 연관된 테이블을 찾는다.
  • 객체는 참조를 통해서 연관된 객체를 찾는다.

이것이 객체와 테이블이 가지는 가장 큰 차이점.

 

 

 


단방향 연관관계

객체지향 모델링

위에서 작성한 Member 엔티티를 조금 수정해보자.

@Entity
public class Member {

  @Id @GeneratedValue
  private Long id;

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

  // @Column(name = "TEAM_ID")
  // private Long teamId;

  //Team에 대한 참조를 넣음
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  
}

 

 

Team을 참조하는 Long형 teamId 대신 Team에 대한 참조값 team을 넣어서 매핑을 한다면 어떻게 될까?

이렇게 모델링을 한다면 위에서 예시로 들었던 특정 멤버의 팀을 조회하는 코드가 한결 간결해지고 쿼리도 줄어든다.

 

 

 

//조회
Member findMember = em.find(Member.class, member.getId());

//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

해당 회원의 팀 ID를 조회하고 그것으로 다시 팀을 조회하는것이 아닌,

한번에 해당 회원의 팀을 조회할 수 있다. team 참조가 있기 때문에.

 

 

 

하지만 Team에서는 어떤 Member가 있는지 조회할 수 없다. 단방향 연관관계이기 때문이다.

  • 객체는 team이라는 참조를 통해 단방향으로 연관관계를 가진다. Member에서는 team을 통해 팀에 대한 정보를 알 수 있지만 Team에서는 Member를 알 수 있는 방법이 없기에 단방향 연관관계다.
  • 테이블에서는 TEAM_ID라는 외래키를 통해 연관관계를 가진다. 이는 Team에서도 Member를 조회할 수 있기에 양방향 관계라고 할 수 있다.

 

즉, 정리하자면

  • 객체는 참조를 통해 연관관계 가진다.
  • 객체의 연관관계는 단방향이다.
  • 테이블은 외래키를 통해 연관관계 가진다.
  • 테이블의 연관관계는 양방향이다.

 

 

 


양방향 연관관계

 

팀 1개에 N명의 멤버가 있을 수 있으므로 Team 엔티티에 리스트형으로 member의 참조값을 넣었다.

@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @OneToMany(mappedBy = "team")
  List<Member> members = new ArrayList<Member>();

 

 

이를 통해 객체에서도 Team -> Member로의 조회가 가능하다.

//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회

 

 

 


연관관계의 주인

  • 양방향 연관관계에서는 주인이 필요하다.
  • 테이블의 연관관계는 외래키 하나로 양쪽을 다 조회할 수 있지만,
  • 객체에서는 양쪽에서 서로를 참조하는 값이 있어야 서로간의 조회가 가능하다. 즉 까보면 단방향 연관관계 두개로 인해 양방향처럼 보이는 것이다.
class A {
	B b;
}
class B {
	A a;
}

이런 윈리지만 우리는 이를 양방향 연관관계라고 부른다.

 

 

  • 엔티티를 양방향으로 연관관계를 가지게 하기 위해 참조가 2개 필요한데,
  • 데이터베이스에서는 외래키는 1개 필요하다.
  • 이 차이로인해 JPA에서는 객체 연관관계 중 하나를 주인으로 삼고 이 외래키를 보관하도록 한다.
  • 연관관계의 주인만이 데이터베이스의 연관관계와 매핑되고 외래키를 관리할 수 있다. 주인이 아닌쪽은 읽기만 간으하다.

 

 

 


누구를 주인으로?

외래키가 있는 곳을 주인으로.

외래키는 N이 있는 곳.

(이 예시에서는 Member쪽이다)

 

 

양방향 매핑 규칙

  • 연관관계의 주인만이 외래키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아닌쪽에서 mappedBy 속성을 통해 주인 지정

 

 

주의할 점

연관관계의 주인에 값을 입력하지 않음

Team team = new Team();
team.setName("TeamA");
em.persist(team);

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

//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);

 

 

순수한 객체 관계를 고려하면 연관관계 주인과 반대편 양쪽 다 값을 입력 해야한다.

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);

DB에 올라가지 않고 1차 캐시에서 엔티티를 다시 바로 가져오는 경우를 순수한 객체상태라고 하는데 이때 연관관계의 주인쪽에만 값을 입력해주면 다른 한쪽엔 반영이 안된다. 1차 캐시를 없애고 조회하는 순간부터 반대편에서도 참조가 가능한 문제가 생기기에 영속성 컨텍스트로 올릴때 부터 양쪽에 값을 넣어주자.

 

 

이때 한가지 팁이 있다면,

연관관계 주인(Member)쪽 엔티티에서 setter 메소드를

public void setTeam(Team team){
    this.team = team;
    team.getMembers.add(this);
}

이처럼 수정하여 주인쪽은 자신의 인스턴스를 한번에 넣어주도록 수정하면 된다.

이때 setter 메소드는 관례적인 setter와 다르므로 메소드 이름을 changeTeam과 같이 이례적으로 바꾸면 더 좋다.

 

 

 

 

 

참고:

더보기

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 연관관계 매핑  (0) 2021.07.21
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.


@Entity

  • @Entity 어노테이션이 붙은 클래스는 JPA가 관리한다. 이를 엔티티라고 한다.
  • 이 엔티티와 데이터베이스의 테이블이 매핑된다.
  • 엔티티 클래스에는 기본 생성자가 있어야한다.
  • final 클래스, enum, interface, inner 클래스는 안된다.
  • 저장할 필드에 final키워드는 안된다.
  • name 속성을 사용하여 엔티티 이름을 커스터마이징 할 수 있다. (중복되는 이름의 클래스가 없다면 기본값 권장)

 

 

 


데이터베이스 스키마 자동 생성

  • create: 기본 테이블 삭제 후 다시 생성(DROP + CREATE)
  • create-drop: create와 같으나 애플리케이션 종료 시점에 DROP
  • update: 변경사항만 반영(운영중인 DB에는 사용하면 안됨)
  • validate: 엔티티와 테이블이 정상적으로 매핑됐는지만 확인
  • none: 사용x

개발 초기에는 create나 update를,

테스트 서버에는 update나 validate를,

운영 서버에는 validate나 none을 권장한다.

 

 

 


필드와 컬럼 매핑

import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
@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;
}

 

  • @Colunm: 해당 필드와 명시한 컬럼 매핑
  • @Temporal: 날짜 타입 매핑
  • @Enumerated: enum타입 매핑
  • @Lob: 대용량 매핑, CLOB, BLOB이 있다.
  • @Transient: 해당 필드는 매핑하지 않겠다.

 

여기서 Enumerated 매핑 사용시 EnumType.ORDINAL 옵션은 사용하지 않도록하자

이는 enum 순서를 데이터베이스에 저장하는 것으로 의미 자체도 모호하고 나중에 찾기 힘든 버그를 야기한다.

 

EnumType.STRING으로 하자. 이는 enum의 이름 그 자체를 데이터베이스에 저장한다.

 

 

 


기본키 매핑

직접할당과 자동생성이 있다.

직접 할당시 @Id를 통해 지정할 수 있다.

@Id
private Long id;

 

 

자동할당(@GenerateValue)는 4가지 종류가 있다.

  • IDENTITY: 데이터베이스에 위임, MYSQL
  • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
  • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
  • AUTO: 방언에 따라 자동 지정, 기본값
@GeneratedValue(strategy = GenerationType.[여기에전략])
private Long id;

 

 

IDENTITY 전략

  • 기본키 생성을 DB에 위임 (ex. MySQL의 AUTO_INCREMENT)

 

영속성 컨텍스트에 persist할때, 우리는 Id와 Value를 알고있어야 한다.

 

지난 포스팅의 사진

하지만 DB에서 알아서 기본키를 생성하는데 코드를 짜는 우리가 이를 알 수는 없다. 그럼 어떻게 하느냐?

IDENTITY전략을 사용하면 트랜잭션 커밋 시점이 아닌 persist시점에 INSERT SQL이 실행된다.

그렇게해서 DB에서 식별자를 바로 조회할 수 있다.

@Entity
public class Member {

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

 

 

 

SEQUENCE 전략

  • 데이터베이스의 시퀀스 오브젝트 사용
  • 이는 유한한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.
  • 오라클, PostgreSQL, DB2, H2에서 사용

이 전략 또한 DB에서 기본키 값을 부여해 주는 방식이므로 우리는 다음 기본키로 어떤 값이 올지 모른다.

즉, 이 전략도 영속성 컨텍스트에 영속화 시키기 전에 Id값을 미리 조회하는 추가적인 절차가 필요한 것이다.

 

SEQUENCE 전략에서는 persist하는 순간

이와 같이 DB로부터 다음 기본키 값을 받아온다.

 

 

 

 

그런데, 이렇게 INSERT 쿼리를 날리기 위해 추가적인 DB와의 통신이 늘어날수록 성능이 떨어지게 된다. 당연히도.

그렇다면 어떻게 개선할 수 있을까?

 

 

 

SeqenceGeneratorallocationSize 옵션을 통해 개선이 가능하다.

이 옵션은 DB와 한번 통신 할때마다 시퀀스를 한번에 가져와놓을 수량을 지정할 수 있다. 예를들어 50이라고 해두면 50번에 한번씩만 시퀀스를 호출하는 작업을 해주면 된다.

 

@Entity
@SequenceGenerator(
    name = “MEMBER_SEQ_GENERATOR",
    sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 50)	//한번에 가져올 시퀀스 값 수량
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
	private Long id;

 

 

 

 

TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략.
  • 장점은 모든 데이터베이스에 적용 가능하다는 점.
  • 단점은 성능이 다소 떨어진다는 점.

 

create table MY_SEQUENCES (
  sequence_name varchar(255) not null,
  next_val bigint,
  primary key ( sequence_name )
)
@Entity
@SequenceGenerator(
    name = “MEMBER_SEQ_GENERATOR",
    sequenceName = “MY_SEQUENCE", //직접 만든 시퀀스 테이블을 매핑
    pkColunmValue = "MEMBER_SEQ",
    allocationSize = 50)	//한번에 가져올 시퀀스 값 수량
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
	private Long id;

 

 

 

 


권장하는 식별자 전략

  • 기본키 제약조건: null이 아니며 변하면 안된다.
  • 이 조건을 미래까지 쭉 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
  • 권장 형태: Long형 + 대체키 + 키 생성전략 사용

*대체키란,

후보키가 두개 이상일 경우 그 중에서 어느 하나를 기본키로 지정하고 남은 후보키들을 대체키라한다.

 

*후보키란,

 테이블에서 각 행을 유일하게 식별할 수 있는 최소한의 속성들의 집합다.

 

 

 

 

 

 

참고:

더보기

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 연관관계 매핑  (0) 2021.07.21
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.

 


영속성 컨텍스트란?

"엔티티를 영구 저장하는 환경"

 

  • 엄밀히 말하자면 DB에 저장하는 것이 아니라 엔티티를 영속성 컨텍스트라는 곳에 저장한다는 의미
  • 눈에 보이지 않는 논리적인 개념이다.
  • 엔티티 매니저를 통해 접근한다.
  • 엔티티 매니저와 영속성 컨텍스트가 1대 1로 매핑된다

 


엔티티의 생명주기

  • 비영속: 영속성 컨텍스트와 관련 없는 새로운 상태
  • 영속: 영속성 컨텍스트에 관리되는 상태
  • 준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제: 삭제된 상태

 

 

비영속 상태

영속성 컨텍스트와 엔티티가 아무런 관련이 없음. 그저 엔티티가 새로 생성되어있는 상태일 뿐.

Member member = new Member();
member.setId("member1");
member.setUsername("Lee");

그냥 member객체를 생성했을 뿐 아무런 다른 과정이 없다.

 

 

영속 상태

생성한 엔티티를 영속성 컨텍스트에 넣음. 

Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);

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

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

 

 

준영속, 삭제 상태

준영속 상태는 영속성 컨텍스트에 있다가 detach된 상태

삭제는 삭제 상태

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

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

 

 

 


영속성 컨텍스트의 장점

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

 

1차 캐시

  • find나 persist시 1차 캐시에 저장된다.
  • 영속성 컨텍스트 안에 1차 캐시 공간이 있다. PK가 Key가 되고 엔티티 그 자체가 Value가 된다.
  • JPA를 통한 조회 작업시 바로 DB에 SELECT 쿼리를 날리는 것이 아닌 1차 캐시부터 조회한다. 만약 1차 캐시가 있다면 (찾고자 하는 엔티티가 영속화 돼있다면) 쿼리문을 날리지 않고도 조회가 가능하고 캐시에 없으면 DB를 조회한다.
  • 만약 DB에서 조회를 한다면 그 엔티티는 1차 캐시에 저장된다. 
  • 요청 시작부터 트랜잭션 종료 시까지만 1차 캐시가 사용된다. 매우 짧은 순간이기에 정교한 구조가 아니면 성능상의 큰 이점을 얻기는 힘들다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//1차 캐시에 저장됨
em.persist(member);

//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
  • member1은 DB에서 조회한 것이 아닌 영속성 컨텍스트의 1차캐시에서 조회한 엔티티이다. 
  • 사실 persist만으로 DB에 저장이 되진 않는다. persist가 완료되면 영속성 컨텍스트에 저장되는 것이지 트랜잭션이 수행되어야 DB에 저장된다.

 

 

만약 1차 캐시에 없는 엔티티를 조회하려고 하면 DB에서 조회 후 1차 캐시에 영속화 한다.

 

 

 

동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); //동일성 비교 true

같은 트랜잭션 안에서의 같은 엔티티를 조회하면 동일한 엔티티임을 보장할 수 있다.

 

 

 

 

트랜잭션을 지원하는 쓰기 지연

em.persist(memberA);
em.persist(memberB);  //여기까지는 DB에 보내지 않고

transaction.commit()  //이 순간에 한번에 SQL이 전송됨

  • memberA와 memberB에 대한 영속화 요청이 들어오면 JPA는 이 엔티티를 분석하여 적절한 SQL문을 생성한다.
  • 단, 바로 DB에 SQL을 전송하는 것이 아닌 영속성 컨텍스트 내부의 쓰기 지연 SQL 저장소 라는 곳에 차곡차곡 모아둔다.
  • 그리고 트랜잭션 Commit의 순간 쌓여있던 SQL들이 한번에 요청된다.

 

 

변경 감지(더티체크)

find로 가져와서 setter로 그냥 객체 수정 그냥 하면 JPA가 수정을 감지하고 자동 반영해준다. 즉, 다시 persist 안해도 됨.

 

  • Commit 시점에 수정된 엔티티와 그것의 스냅샷(초기 상태를 가지고 있는 일종의 복사본)을 비교하여 바뀐부분이 있는지 확인하고, 이에대한 SQL문을 SQL저장소에 저장한다. 그리고 update 쿼리를 생성하여 전송한다.
  • 엔티티의 삭제도 이런 원리로 작동한다.

 

 

 


플러시란?

  • 영속성 컨텍스트의 변경내용을 DB에 반영하는 것
  • 모아놓은 SQL들을 한번에 실행(트랜잭션과 다름)
  • 영속성 컨텍스트를 비우지 않음
  • 즉, 영속성 컨텍스트와 DB와의 동기화
  • 플러시를 해도 1차캐시는 유지됨

 

영속성 컨텍스트를 플러시 하는 방법

  1. em.flush() - 직접호출
  2. 트랜잭션 커밋 - 자동호출
  3. JPQL 쿼리 실행 - 자동호출

 

JPQL 쿼리 실행시 플러시가 자동으로 실행된다.

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();

왜 자동으로 플러시가 실행되지?

 

persist만으로는 DB까지 반영이 되지 않고 영속성 컨텍스트라는 논리적 공간에만 저장되기에 Commit이 되기 전에 JPQL 쿼리가 요청되면 이에 대한 응답을 해야하기 때문에 현재 영속성 컨텍스트에 있는 것들을 일단 전부 플러시 하는 것. 그래야만 JPQL 쿼리에 대한 응답이 보장된다.

 

 

 


준영속 상태

  • 영속상태였던 엔티티가 영속성 컨텍스트에서 분리된 상태(detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

 

준영속 상태로 만드는 법

  1. em.detach(entity) - 특정 엔티티만 준영속 상태로 전환
  2. em.clear() - 영속성 컨텍스트를 완전히 초기화
  3. em.close() - 영속성 컨텍스트를 종료

 

 

참고:

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 연관관계 매핑  (0) 2021.07.21
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15

이 카테고리는 인프런 김영한님의 JPA 강의를 보고 정리하는 공간입니다.


JPA란?

정의

  • Java Persistence API의 약자로 자바진영의 ORM 기술 표준이다
  • 따지고 보면 JPA는 여러 인터페이스의 모음이다. 그 인터페이스들을 구현한 구현체로 여러가지가 있는데 그 중 가장 대표적인 것이 Hibernate(자바 환경에서 객체-관계 모델 매핑 솔루션)이다. 

 

 

ORM?

  • Object Relational Mapping의 약자로 기존까지의 객체 vs 관계형 데이터베이스 사이의 패러다임을 해결해준다.
  • 객체는 객체대로 설계, RDB는 RDB대로 설계를 하고 이 둘 사이의 매핑을 도와주는 프레임워크라고 할 수 있겠다.

 

동작

JPA는 애플리케이션과 JDBC사이에서 동작한다.

  • 우리가 JPA에게 명령을 내리면 JPA는 적합한 SQL문을 생성하고 JDBC를 사용하여 DB와 상호작용한다. 즉 개발자가 직접 쿼리를 작성하지 않아도 된다! 

 

저장

  • JPA에게 객체를 넘기면 JPA가 객체를 분석하고 INSERT 쿼리를 생성한다.

 

 

조회

  • 조회도 마찬가지다.

 

 

 


왜 JPA를 써야하는가?

  1. SQL 중심 개발이 아닌 객체 중심 개발
  2. 생산성 향상
  3. 유지보수
  4. 패러다임 불일치 해결
  5. 성능
  6. 데이터 접근 추상화와 벤더 독립성
  7. 표준

이라는 장점이 있다.

 

 

 

생산성

JPA는 CRUD가 이미 다 정의돼있다. 너무 간편.

  • 저장: jpa.persist(member)
  • 조회: Member member = jpa.find(memberId)
  • 수정: member.setName(“변경할 이름”)
  • 삭제: jpa.remove(member)

특시 수정연산의 경우 자바 컬렉션의 데이터를 다루듯 그냥 set으로 값만 바꿔주면 마법처럼 수정이 이루어진다. 정확한 원리는 추후의 포스팅에서 다루겠다.

 

 

 

유지보수

기존에는 필드에 변경사항이 있을 경우 모든 SQL을 수정해야 했다.

하지만 JPA에선 필드만 수정하면 된다. SQL은 JPA가 알아서 처리해주기때문.

 

 

 

성능

JPA는 애플리케이션과 JDBC 사이에 존재한다. 즉 중간에서 여러 기능(캐싱, 쓰기지연, 즉시로딩, 지연로딩 등)을 수행해 줄 수있기에 성능 최적화에 특화돼있다. 이것들에 대한 자세한 내용 역시 이후의 포스팅에서 다루겠다.

 

 

 

참고:

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계  (2) 2021.07.24
[JPA] 연관관계 매핑  (0) 2021.07.21
[JPA] 엔티티 매핑  (0) 2021.07.19
[JPA] 영속성 관리  (0) 2021.07.17
[JPA] JPA란?  (0) 2021.07.15

+ Recent posts