Spring JPA - 영속성 컨텍스트

김재현·2022년 8월 8일
0

Programmers

목록 보기
14/28

2일차

  • 영속성 컨텍스트
    • EntityManagerFactory, EntityManager에 대해 학습.
    • 영속성 컨텍스트에 대해 학습.
    • 엔티티의 생명주기에 대해 학습.
    • 영속성 컨텍스트의 특징에 대해 학습.
    • 실습을 통한 영속성 컨텍스트에 대해 이해.

1일차 복습

  • JPA
    IDB의 테이블과 자바의 오브젝트를 맵핑시켜주는 ORM(Object Relation Mapper) 프레임워크.
  • Entity
    RDB의 테이블과 매핑되는 객체.
  • EntityManagerFactory
    • 비용이 비싼 Bean. 하나만 만든다.
    • Entity를 관리하는 EntityManager을 생산하는 공장.
    • Thread Safe하다. (하나의 빈에 여러 Thread가 접근해도 문제없이 동작한다.)
  • EntityManager
    • EntityManager는 Entity를 저장하고, 수정하고, 삭제하고, 조회하는 (CRUD) 등 Entity와 관련된 모든 일을 처리한다.
    • Thread Safe하지 않고, 따라서 여러 Thread에서 동시에 접근할 경우 동시성 이슈가 발생할 수 있다.

  • EntityManagerFactory가 EntityManager를 생성.
    EntityManager들이 DB Connection Pool을 관리하고 있는 것들에 접근해서 Connection을 획득한다.
  • DB에 commit이 일어날 때 Connection을 획득한다.

영속성 컨텍스트(Persistence Context)

  • JPA를 이용하는데 가장 중요한 요소.
  • 엔티티를 영구 저장하는 환경이라는 뜻이다.
  • 엔티티매니저는 엔티티를 영속성 컨텍스트에 보관하고 관리함.

  • EntityManager가 persist()메서드를 호출하게 되면 영속화 선언이 됨.
    영속화를 선언한다, 영속화를 하겠다 : entity객체를 영속성 컨텍스트 안에서 관리하겠다.

특징

  • 영속성 컨텍스트와 식별자 값
    • 영속성 컨텍스트 안에서 관리되는 엔티티는 식별자 값을 반드시 가져야한다.
      ex) id값을 설정해주지 않으면 오류가 난다.
    • key-value로 엔티티를 관리하기 때문.
      영속성 컨텍스트가 key-value 형태로 1차 캐시를 관리하는데, 이것을 관리하기 위해서는 PK값이 필수적이다.
  • 영속성 컨텍스트와 데이터 베이스 저장
    • JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 DB에 반영한다. (FLUSH)
    • 플러시(flush)는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업인데, 이때 등록, 수정, 삭제한 엔티티를 DB에 반영한다.
  • 영속성 컨텍스트가 엔티티를 관리함으로 얻는 이점
    • 1차 캐시
    • 동일성 보장(1차캐시 덕분에 일어남.)
    • 트랙잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

엔티티 생명주기

  • 비영속(new / trasient) : 영속성 컨텍스트와 전혀 관계가 없음
  • 영속(managed) : 영속성 컨텍스트에 저장됐음.
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가, 분리되었음.
  • 삭제(removed) : 삭제된 상태.
  • 사용된 메서드
    • persist() : 비영속 상태의 엔티티를 영속 상태로 전환.
      삭제했던 엔티티를 다시 영속상태로 전환.
    • flush() : DB에 연산을 실행.
    • detach() : 영속 상태의 엔티티를 준영속 상태로 전환.
    • clear() : 영속 상태의 모든 엔티티를 준영속 상태로 전환.
    • close() : 영속성 컨텍스트를 종료시킴. 영속 상태의 모든 엔티티를 준영속 상태로 만들고 영속성 컨텍스트를 종료시킴.
    • merge() : 준영속 상태의 엔티티를 다시 영속 상태로 만듦.
    • remove() : 영속화된 엔티티를 삭제.

비영속

// 객체가 영속성컨텍스트, 데이트베이스와 무관한 상태이다.
Customer customer = new Customer(); 
customer.setId(1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

영속

Customer customer = new Customer();
customer.setId(1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

// customer객체가 영속성 컨텍스트에서 관리된다.
em.persist(customer);

준영속

// 영속상태의 customer객체(엔티티)를 영속성컨텍스트에서 분리한다.
em.detach(customer);
// 영속상태의 모든 객체를 영속성컨텍스트에서 분리한다.
em.clear()
// 영속성컨텍스트를 종료한다.
em.close()

삭제

// customer 엔티티를 영속성컨텍스트에서 분리하고, DB에서도 삭제한다.
em.remove(customer)

영속성 컨텍스트 이해

  • 저장
EntityManager em = emf.createEntityManager(); // 1)엔티티 매니저 생성

EntityTransaction transaction = em.getTransaction(); // 2)트랜잭션 획득
transaction.begin(); // 3)트랙잰셕 begin

Customer customer = new Customer(); // 4-1)비영속
customer.setId(1L);
customer.setFirstName("honggu");
customer.setLastName("kang");

em.persist(customer); // 4-2)영속화

transaction.commit(); // 5)트랜잭션 commit
// 트랜잭션이 커밋이 되는 순간 쿼리가 수행된다. flush DB와 동기화가 된다.

  • 조회
    • DB에 쿼리가 가지 않고, 1차 캐시 선에서 처리한다.
@Test
void 조회_1차캐시_이용() {
	EntityManager em = emf.createEntityManager();
  EntityTransaction transaction = em.getTransaction();
  transaction.begin();

  Customer customer = new Customer();
  customer.setId(1L);
  customer.setFirstName("honggu");
  customer.setLastName("kang");

	em.persist(customer);
  transaction.commit();

  Customer entity = em.find(Customer.class, 1L); // 1차 캐시에서 조회한다.
  log.info("{} {}", entity.getFirstName(), entity.getLastName());
}

  • DB를 이용한 조회
    • clear()를 했기 때문에 1차 캐시에 고객정보가 없다. 따라서 DB에서 정보를 조회하고, 정보를 캐시에 저장한 후 반환한다.
@Test
void 조회() {
	EntityManager em = emf.createEntityManager();
	EntityTransaction transaction = em.getTransaction();
  transaction.begin();

	Customer customer = new Customer();
  customer.setId(2L);
  customer.setFirstName("guppy");
  customer.setLastName("hong");

  em.persist(customer);
  transaction.commit();

  em.clear(); //영속성 컨텍스트를 초기화 한다.

  Customer entity = em.find(Customer.class, 2L); // DB 에서 조회한다. SELECT ...
  log.info("{} {}", entity.getFirstName(), entity.getLastName());
  em.find(Customer.class, 2L); // SELECT Query 수행되지 않는다. 1차캐시 사용
}

  • 수정
    • 변경사항이 있다면 업데이트를 수행하고, 없다면 쿼리를 변경하지 않는다.
  • [변경감지 - dirty checking]
    JPA 는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해 두는데 이것을 스냅샷이라 한다. 그리고 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다. 만일 스냅샷과 비교하여 변경된 내용이 있을 경우 update Query를 수행한다. (변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용이 된다.)
@Test
void 수정() {
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();

    Customer customer = new Customer();
    customer.setId(1L);
    customer.setFirstName("honggu");
    customer.setLastName("kang");

    em.persist(customer);
    transaction.commit();
    // 엔티티를 영속화한후, 커밋을해서 flush()를 통해 DB에 저장.

    transaction.begin();

    Customer entity = em.find(Customer.class, 1L);
    entity.setFirstName("guppy");
    entity.setLastName("hong");

    // em.update(entity) ??!!
    transaction.commit(); // flush -> UPDATE ... 
}

  • 삭제
@Test
void 삭제() {
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();

    Customer customer = new Customer();
    customer.setId(1L);
    customer.setFirstName("honggu");
    customer.setLastName("kang");

    em.persist(customer);
    transaction.commit();

    transaction.begin();

    Customer entity = em.find(Customer.class, 1L);
    em.remove(entity);
    
    transaction.commit(); // flush -> DELETE ..
}

엔티티 매핑

목표

  • 엔티티 매핑
    • 엔티티 매핑 실습.
    • 엔티티 간의 연관관계 매핑을 실습 (OneToOne, OneToMany, ManyToOne)
    • 고급 매핑 전략에 대해 소개합니다.
  • 프록시와 연관관계
    • 프록시에 대해 학습.
    • 즉시 로딩(Eager fetch), 지연 로딩(Lazy fetch)에 대해 학습.
    • 영속성 전이에 대해 학습합니다. (CASCADE, 고아객체)

  • 회원-주문(1:다)
    주문-주문상품(1:다)
    주문상품-상품(다:1)
  • 주문 도메인을 구성하는 단일 엔티티를 만들고, 이들의 연관관계를 설정한다.

단일 엔티티 매핑

  • JPA는 엔티티 객체를 생성할 때, 기본생성자(Default Constructor)를 사용한다.

  • @Entity를 사용해서 영속성 컨텍스트에서 관리할 수 있는 객체라는 것을 선언한다.
  • @Table 엔티티 객체를 어떤 RDB 테이블과 맵핑시킬지 테이블 이름을 명시해준다.
@Entity                    // Entity 선언
@Table(name = "member")    // Table 선언으로 맵핑할 테이블 명시.
public class Member {
    @Id
    private Long id;
    private String name;
    private String nickName;
    private int age;
    private String address;
    private String description;

		// getter, setter
}
  • Auto-generate-ddl으로 테이블 자동으로 생성.
spring:
  h2:
    console:
      enabled: true
  jpa:
    generate-ddl: true
    database: H2
    show-sql: true
    open-in-view: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
        query.in_clause_parameter_padding: true
        hbm2ddl:
          auto: create-drop

  • create, create-drop, update는 개발, 혹은 테스트 환경에서 많이 사용한다.

  • DDL 옵션

@Entity
@Table(name = "member")
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false, length = 30)
    private String name;

    @Column(nullable = false, length = 30, unique = true)
    private String nickName; // nick_name

    private int age; // age

    @Column(name = "addres", nullable = false)
    private String address;
    
    @Column(name = "description", nullable = true)
    private String description;
}
  • @GenerateValue : PK의 생성전략.
  • @Column(name = "") : RDB "name"에 명시한 속성의 컬럼과 맵핑을 시키겠다는 의미로, 위의 경우 name이라는 컬럼을 만들겠다는 의미.
    name을 명시하지 않으면 필드명을 따른다.
  • nullable : 테이블에서 not null 제약조건이 추가된다.
    length는 String타입에서 사용.
    unique는 RDB의 제약조건, unique값 옵션을 추가해줌.
drop table if exists member CASCADE 
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start with 1 increment by 1
create table member (id bigint not null, address varchar(255) not null, age integer not null, description varchar(255), name varchar(30) not null, nickName varchar(30) not null, primary key (id))
alter table member add constraint UK_1m3ighjll05v7njjxeopp823j unique (nickName)
  • DDL 속성을 명시해놓으면 코드를 직접 작성한 사람이 아니더라도 Entity만 보고도 RDB테이블을 추측할 수 있다는 장점이 있다.
    RDB테이블과 Entity가 동일하도록 노력하자.

기본키 맵핑전략

  • @Id : JPA에서 사용할 Entity이름을 지정.
    Entity는 항상 기본키를 가져야한다. RDB의 PK값이 되고, 영속성 컨텍스트에서 1차 캐시에서 관리되는 유니크한 키 값이 되는 속성.

직접 할당

  • 영속화 전 애플리케이션에서 직접 값을 할당한다.
    PK값을 개발자가 임의로 지정할 수 있다.
@Id
@Column(name = "id")
private Long id;
  • @GeneratedValue의 strategy 전략에 4가지가 있다.

SQUENCE

  • 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속화한다.
  • ORACLE, H2
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

Table

  • 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속화.

IDENTITY

  • MySQL의 AUTO_INCREMENT전략.
  • 데이터베이스 엔티티를 저장해서 식별자 값을 획득 후 영속화.
  • 엔티티가 영속화되려면 식별자 값이 반드시 필요하기 때문에, em.persist() 시점에 INSERT쿼리가 수행된다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

AUTO

  • 데이터베이스 방언(dialect)에 따라서 자동으로 전략을 선택.
    엔진에 맞는 쿼리를 자동으로 생성한다.
  • 자주 사용.
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Id
@GeneratedValue
private Long id;

기타 컬럼 매핑

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;
}

@Enumerated, @Lob

0개의 댓글