TIL - 22.12.12(Spring 강의 숙련 1. JPA 심화)

자라나는 ㅇㅅㅇ개발자·2022년 12월 12일
0

TIL

목록 보기
30/126


01. 영속성 컨텍스트


01. 영속성 컨텍스트란?

  • 엔티티를 영구 저장하는 환경
  • 어플리케이션(자바 코드)이 데이터베이스에서 꺼내욘 데이터 객체를 보관하는 역할
  • 엔티티 매니저를 통해 엔티티를 조회하거나 저장할 때 엔티티를 보관하고 관리
    엔티티 매니저마다 개별적으로 부여되는 논리적인 공간같은 개념

비영속(New)

  • 영속성 컨텍스트와 관계가 없는 새로운 상태
  • 해당 객체의 데이터가 변경되거나 말거나 실제 DB의 데이터와는 관계없이 Java 객체인 상태

영속(Managed)

  • 엔티티 매니저를 통해 엔티티가 영속성 컨텍스트에 저장되어 관리되고 있는 상태
  • 데이터의 생성, 변경 등을 JPA가 추적하면서 필요하다면 DB에 반영

준영속(Detached)

  • 영속성 컨텍스트에서 관리되다가 분리된 상태

삭제(Removed)

  • 영속성 컨텍스트에서 삭제된 상태


2. 영속성 컨텍스트 설계 이유

  • DB를 이용하는 작업은 상대적으로 부하와 비용이 많이 필요한데, 이를 줄여주기 위해 영속성 컨텍스트 내부에 1차 캐시를 둔다.
    1. find("memberB")와 같은 로직이 있을 때 먼저 1차 캐시를 조회.
    2. 있다면 해당 데이터를 반환
    3. 없다면 그 때 실제 DB로 "SELECT *FROM..."의 쿼리를 내보낸다.
    4. 반환하기 전에 1차 캐시에 저장하고 반환해준다.
    memberB를 find하는 요청이 다시 들어와도 굳이 DB를 다녀올 필요가 없어진다.

  • memberA, memeberB를 생성할 때마다 DB를 다녀오는 것은 비효율적이므로 내부에 '쓰기 지연 SQL 저장소'를 둔다.
    1. memberA, memberB를 영속화시킨다.
    2. entityManager.commit()메서드를 호출하면
    3. 내부적으로 쓰기 지연 SQL 저장소에서 Flush가 일어나
    4. "INSERT A","INSERT B"와 같은 쓰기 전용 쿼리들이 DB로 흘러들어간다.

  • DirtyChecking을 통해 데이터의 변경을 감지해서 자동으로 수정해준다.
    1. 1차 캐시에는 DB의 엔티티의 정보만 저장하는 것이 아니다.
    2. 해당 엔티티를 조회한 시점의 데이터의 정보를 같이 저장해두고
    3. 엔티티객체와 조회 시점의 데이터가 다르다면 변경이 발생했다는 것을 파악
    4. 해당 변경 부문을 반영 할 수 있는 UPDATE 쿼리를 작성해둔다.

  • 데이터의 어플리케이션 단의 동일성을 보장해준다.



2. 엔티티 매핑 심화


1. 기본 엔티티 매핑 관련

기본 엔티티 관련 어노테이션

  • @Entity
    기본 생성자는 필수이다.(기본적으로 생성자가 하나도 없다면 자바에서 자동으로 생성해주지만, 파라미터가 있는 생성자가 하나라도 있다면 기본생성자를 만들어주지 않으므로 주의)
    final 클래스, enum, interface 등에는 사용할 수 없다.
    저장할 필드라면 final을 사용하면 안된다.

  • @Table
    엔티티와 매핑할 테이블의 이름.

  • @Column
    객체 필드를 테이블 컬럼에 매핑하는데 사용
    생략이 가능하다.
    속성들은 자주 쓸 일이 없고, 특정 속성은 effect가 있으니 이름을 지정할 때가 아니라면 보통 생략하기도 한다.

  • @Enumerated
    Java Enum을 테이블에서 사용한다고 생각하면 이해하기에 도움이 된다.
    속성으로는 Ordinal, String이 있는데, String의 경우 해당 문자열 그대로 저장해서 비용은 많이 들지만 나중에 Enum이 변경되어도 위험할 일이 없기 때문에 일반적으로 String을 사용한다.


2. 연관관계 관련 심화

단방향 연관관계

  • @ManyToOne : 이름 그대로 다대일(N:1) 관계라는 매핑 정보.(한명의 유저가 여러개의 주문을 할 경우)
    주요 속성으로는 optional, fetch, cascade가 있다.
    optional : false로 설정하면 항상 연관된 엔티티가 있어야만 생성할 수 있다.

  • @JoinColumn(name="food_id") : 외래키를 매핑할 때 사용.(실제 데이터베이스에서 객체 필드에는 해당 객체의 테이블의 외래키가 들어간다.)
    기본적으로 @Column이 가지고 있는 필드 매핑관련 옵션 설정들과 외래키 관련된 몇가지 옵신이 추가되어있는 옵션

단방향 연관관계 예제 코드

@Entity
@Getter
@Setter
public class Member {
	@Id
	@Column(name = "member_id")
	private String id;
	private String username;
	
	@ManyToOne // 하나의 팀에 여러명의 멤버가 들어갈 수 있다.
	@JoinColumn(name="team_id")
	private Team team;

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


@Entity
@Getter
@Setter
public class Team {
	@Id
	@Column (name = "TEAM_ID")
	private String id;
	
	private String name;
}

양방향 연관관계

  • 단방향 연관관계를 양쪽에서 사용한 것
  • 외래키는 연관관계가 있는 두 개의 테이블 중에 하나의 테이블에만 있으면 충분
    두 객에 연관관계 중 하나를 정해 테이블의 외래키를 관리해야 하는데, 이것을 연관관계의 주인 이라고 한다.
    연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래키를 관리한다.
    반대로 주인이 아닌 쪽은 읽기만 가능하다.
  • 연관관계의 주인을 정한다는 말은 외래키 관리자를 선택한다는 의미

양방향 연관관계 예제 코드

@Getter
@Entity
@NoArgsConstructor
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
		@Column(nullable = false)
    private String memberName;

    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Orders> orders = new ArrayList<>();

    public Member(String memberName) {
        this.memberName = memberName;
    }
}
@Getter
@Entity
@NoArgsConstructor
public class Orders {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    public Orders(Food food, Member member) {
        this.food = food;
        this.member = member;
    }
}
@Getter
@Entity
@NoArgsConstructor
public class Food {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String foodName;
    @Column(nullable = false)
    private int price;

    @OneToMany(mappedBy = "food",fetch = FetchType.EAGER)
    private List<Orders> orders = new ArrayList<>();

    public Food(String foodName, int price) {
        this.foodName = foodName;
        this.price = price;
    }
}

4. 프록시

  • JPA는 성능을 최적화시키기 위해 엔티티가 실제 사용될 때까지 지연 로딩으로 DB 조회를 지연시킨다.
    이 때 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대상에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요하며, 이것을 프록시 객체라고 한다.
  • 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회
    연관된 엔티티를 조인해서 다 긁어와버리는 것
    @ManyToOne(fetch = FetchType.EAGER)
  • 지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회
    가짜 객체를 이용하면 그 때 별도의 쿼리가 생성되어 날아간다.
    정확하게 이해하고 필요한 상황이 아니라면 가급적 지연 로딩을 걸어두는 것이 일반적이다.
    @ManyToOne(getch = FetchType.LAZY)
  • 즉시 로딩 : @ManyToOne, @OneToOne
  • 지연 로딩 : @OneToMany, @ManyToMany
    -> 따라서, 굳이 특수한 경우가 아니라면 @ManyToOne(FetchType.Lazy)를 사용

영속성 전이

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이기능(CASCADE)을 사용
    예를들어 유저테이블과 메모 테이블이 있는데 영속화한 유저객체가 있으면, 메모 테이블도 같이 영속화되어 같이 관리되는 것을 영속성 전이라고 한다.
  • 영속성 전이 설정 방법
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Address> addresses;

0개의 댓글