Dev/JPA

JPA 에서 영속성의 의미와 사용하는 애노테이션 정리

린네의 2023. 12. 11. 18:26

 

 

jpa 를 강의를 들으면서 가장 기본적인 영속성의 개념에 대해 간단하게 정리해보려고 한다.

 

 

1. 영속성 컨텍스트란 무엇인가?

데이터 베이스와 객체 사이에 중간 단계가 있는 개념이라고 할 수 있다. 

객체를 데이터베이스로 옮기거나, 데이터베이스에 있는 데이터를 객체에 매핑할 때 중간에 존재하는 저장 공간이다.

 

간단하게 코드로 나타내면 EntityManager.persist(entity) 로 표현할 수 있다.

 

이 때 이 영속성 컨텍스트가 존재함으로써 일종의  버퍼링 기능을 수행하게 되는데 

persistence.xml 파일에

 

<property name="hibernate.jdbc.batch_size" value="10" />

 

를 추가하여 버퍼 사이즈를 조율할 수 있다.

 

버퍼링 기능을 사용하면 쿼리를 일일히 데이터베이스에 날리는게 아니라 지정한 버퍼 크기만큼 쌓아놨다가 한번에 데이터베이스에 요청하여 커밋할 수 있다.

 

JPA 실제 사용시 transaction commit 시점에 데이터베이스에 반영 되는 것을 염두에 두어야 한다. EntityManager.psersist(entity) 를 사용하여

영속성을 부여하더라도 데이터베이스에는 반영 되지 않는다.

 

위 내용을 기반으로 신규 엔티티를 생성하고 영속성을 부여하는 간단한 코드를 작성해보자.

 

 

public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // persistence.xml  에 있는  unitName 기재
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

            //TestMember 생성해보기
            TestMember tm = new TestMember();
            em.persist(tm);

    		//실제 db 에 반영 되는 시점
            tx.commit();

        } catch(Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }

 

여기서 생성한 Member 은 아래와 같다

 

@Entity
@Table(name="new_table")
public class TestMember {

    private LocalDate localDate;
    private LocalDateTime localDateTime;

    @Lob
    private String bigData;

    @Enumerated(EnumType.STRING)
    private TestRecord testRecord;

    @Transient
    private String temp;

    @Id
    private long realId;
  
  
  
    
  ... Getter/Setter ...
  }

 

 

 실행 결과 아래와 같은 쿼리 실행문이 실행 되면서 db 에 테이블이 생성되는 것을 확인 할 수 있다 

 

 create table new_table (
       realId bigint not null,
        bigData clob,
        localDate date,
        localDateTime timestamp,
        testRecord varchar(255),
        primary key (realId)
    )

 

 

2. 플러시의 개념 

  플러시는 쌓아놨던 데이터 변경 사항들 ( 쿼리 요청들 ) 을 데이터베이스에 요청하는 개념이다.  

  일반적으로 transaction commit 이 발생하면 자동으로 플러시가 된다. 즉, 데이터베이스에 데이터 변경사항들이 실제로 저장 된다.

  transaction commit 외에도 플러시를 발생시키는 방법은 

  

  1) jpql 요청

  2) EntityManager.flush() 함수를 통한 호출

 

  이 있다. EntityManager.flush() 는 일반적으로 사용하진 않는다.

  주의 해야할 점은 플러시가 되었다고해서 1차 캐시에 담긴 내용이 삭제되는 것은 아니라는 것이다. 

 

3.  준영속성이란?

   영속성 컨텍스트에 등록된 객체(Entity) 를 영속성 컨텍스트에서 제거하는 것을 의미한다. 즉, 영속성에서 detach 된 상태를 말한다.

객체가 detach 되었다면, commit 하더라도 데이터베이스에 그 객체의 변경사항은 반영 되지 않는다.

  EntityManager.detach() 를 통해 개별 객체의 영속성을 제거할 수 있다.  이 외에도 

 

 1) EntityManager.clear()  : EntityManager 를 통해 등록된 모든 개체에 대해 영속성 제거

 2) EntityManager.close() : EntityManager 자체를 닫아버리기 때문에 객체를 관리할 수 없음 

 

비영속성을 확인하는 간단한 샘플코드를 작성해 보자.

 

Member member = new Member();
       member.setId(100L);
       member.setName("HelloJPA");

       //영속
       em.persist(member);
            
       //비영속
       em.detach(member);
            
       tx.commit();

 

 

위 코드에서 id 가 100 이고 이름이 HelloJPA 인 객체는 db 에 반영 되었을까? 답은 아니오다. 비영속상태이기 때문이다.

 

 

4.  엔티티 매핑하기 

  엔티티 매핑은 총 세가지 분류로 나눌 수 있다. 객체와 테이블 , 필드와 컬럼, 연관 관계 이다. 

 

  객체와 테이블은 말그대로 객체(Entity) 와 테이블을 매핑하는 것이다. 샘플 코드를 작성해보자 

@Entity
@Table(name="new_table")
public class TestMember {
  
   ...
   
  }

 

 @Entity 애노테이션을 통해 엔티티임을 명시하고, @Table 애노테이션을 사용하여 실제 테이블명을 지정할 수 있다. 

 

 필드와 컬럼은 클래스 내의 필드에 데이터베이스 컬럼을 매핑하는 것을 의미한다. 샘플 코드를 작성해보자

@Entity
@Table(name="new_table")
public class TestMember {

    private LocalDate localDate;
    private LocalDateTime localDateTime;

    @Lob
    private String bigData;

    @Enumerated(EnumType.STRING)
    private TestRecord testRecord;

    @Transient
    private String temp;

    @Id
    @Column(name = "realId")
    private long realId;
    
    ... Getter/Setter ...
    
    }

 

@Table 애노테이션과 동일하게 @Columm 을 통해 실제 컬럼명을 지정할 수 있다. ( 동일하다면 따로 지정하지 않아도 상관 없다.)

 이 외에도 @Enumerated , @Temporal, @Lob, @Transient 애노테이션이 있는데 각각의 의미는 아래와 같다.

 

     - @Enumerated :

         enum 타입의 변수를 사용할 때 사용 한다. 주의 해야할 점은 enumType 에 ordinal 쓰면 안 된다는 것이다.
             ordinal 은  enum 타입의 데이터를 순서대로 저장하게 되는데  나중에 데이터가 변경 되었을 경우 데이터 자체의 값이 아니라 순서번호로 지정되게 되면 동일한 데이터에 대해 과거와 현재의 순서번호만 변경되어 큰 문제가 생길 수 있기 때문이다. 


            예시 )  'GUEST, USER, ADMIN' 라는 entype 이 정의 되어  있다고 가정 하자.
                      이때  GUEST 는 0, USER 은 1, ADMIN 은 2 에 해당 한다.   

                      시간이 지나 'TEST, GUEST, USER, ADMIN'  으로 데이터가 바뀌었을 경우   'GUEST 는 0, USER 은 1, ADMIN 은 2'                          에서 GUEST 는 1, USER 은 2, ADMIN 은 3 으로 바뀌게 된다. 처음에 들어온 TEST 가 0 이 되기 때문이다.

            따라서 enumType 은 무조건 string 으로 지정해서 써야한다.  사용 방법은 아래와 같다.


            @Enumerated(EnumType.String)



     - @Temporal :

         날짜 타입 지정시 사용 한다.  요즘엔 LocalDate, LocateDateTime 타입이 JAVA 에서 제공 되기 때문에 잘 쓰지 않는다.

        닐짜 데이터 타입 변수에 @Temporal 를 지정하는 대신 LocalDate, LocalDateTime  타입으로 변수를 생성만하면 알아서 매핑 되기 때문이다. 


     - @Lob : 

         대량 데이터 삽입시 사용 ( Blob, Clob) 한다. 

         문자면 CLob,   다른 것은 Blob 으로 매핑 ( byte 같은 것들 ) 된다.

 

     - @Transient :  db 에 반영하고 싶지 않은 변수에 대해 지정  한다. 이 애노테이션을 사용하면 데이터베이스와 연관 되지 않는다 ( ddl 자동 생성시에도 생성 되지 않음 )