자라선

[자바 ORM 표쥰 JPA 프로그래밍] 7일차 - flush() & 준영속 본문

Develop/JPA

[자바 ORM 표쥰 JPA 프로그래밍] 7일차 - flush() & 준영속

자라선 2021. 8. 12. 00:26

1. flush() 메소드

flush() 메소드는 영속성 컨텍스트의 엔티티들을 DB에 반영(동기화)해주는 기능을 담당한다.

 

이러한 flush() 메소드는 실행 시 변경 감지하여 스냅샷와 영속성 컨텍스트의 모든 엔티티를 비교하여 수정된 엔티티는 수정 쿼리를 쓰기 지연 SQL 저장소에 등록하고 DB에 전송한다.

 

flush() 가 작동하는 시점은 3가지 인데.

  • 개발자가 flush()를 직접 호출하는 경우 - 웬만해서는 사용되지는 않으며 타 프레임워크나 JPA와 혼용시 사용된다고 한다.
  • 트랜잭션 commit() 호출 경우 - DB와 영속성 컨텍스트는 아예 별개이므로 영속성 컨텍스트의 엔티티가 DB에 적용되지 않고 그대로 commit 되어버리면 아무런 작업이 일어나지 않는다. 그래서 commit 시 자동으로 flush()를 호출하도록 한다.
  •  JPQL 쿼리 실행 경우 - JPQL은 개발자가 SQL을 비즈니스 로직단에 작성하여 호출하는 경우인데 이떄 SQL는 영속성 컨텍스트와는 관련이 없으며 JPA는 SQL을 해석하지도 않는다. 그 때문에 무조건 DB로부터 SQL를 호출하게 되는데....
                Member member = new Member();   // 엔티티 인스턴스 생성
                member.setId("ID1");
                member.setUsername("USERNAME1");
    
                em.persist(member); // 영속성 컨텍스트에 등록
    
                // JPQL 요청
                TypedQuery<Member> query = em.createQuery("SELECT M FROM Member M", Member.class);
                List<Member> resultList = query.getResultList();​

    코드에서 보면 영속성 컨텍스트에 등록하였지만 동일한 트랙잭션 안에서 JPQL로 SQL 호출한다면??
    flush()를 사용하지 않으면 영속성 컨텍스트에 등록된 "ID1"식별자 엔티티는 불러오지 못할 것이다.

    이러한 오류 때문에 JPQL를 호출할떄 flush()가 자동으로 실행되게 되는것이다.

 

2. 준영속 상태

비영속, 영속 상태를 는 말 그대로 엔티티가 영속이냐 아니냐를 구분하는 것 이였지만 

준영속은 영속상태였던 엔티티가 분리되었을때의 상태를 말한다.

 

즉 더 이상 엔티티 매니저와는 관련이 없어진 것이다.

            Member member = new Member();   // 엔티티 인스턴스 생성
            member.setId("ID1");
            member.setUsername("USERNAME1");

            em.persist(member); // 영속성 컨텍스트에 등록
            em.detach(member);  // 엔티티를 분리하게 됨(비영속)

            tx.commit();//  commit 하여 변경된 값을 감지하여 DB에 SQL로 전달한다.

대표적인 메소드는 detach() 메소드가 있으나 개발자가 직접 명시하여 잘 사용되진 않으며

 

clear()나 close() 상태일때 준영속 상태가 많이 나타난다.

 

엔티티를 재사용 하기 위해서 이러한 준영속 상태가 발생하게 되는데 아래 코드로 살펴보자

 

    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

    public static void main(String[] args) {
        Member member = new Member();   // 엔티티 인스턴스 생성
        member.setId("ID1");
        member.setUsername("USERNAME1");

        // 엔티티를 영속성 엔티티에 등록하였으며 동시에 DB와 동기화 하여 INSERT 됨
        insert(member);

        // 비영속 상태일때 username 을 수정 하려함
        update(member);

        emf.close(); //엔티티 매니저 팩토리 종료
    }

    public static void insert(Member member) {
        //엔티티 매니저 팩토리 생성
        EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
        EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
        try {
            tx.begin(); //트랜잭션 시작

            em.persist(member); // 영속성 컨텍스트에 등록

            tx.commit();//  commit 하여 변경된 값을 감지하여 DB에 SQL로 전달한다.
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티 매니저 종료
        }
    }

    public static void update(Member member) {

        EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
        EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
        try {
            tx.begin(); //트랜잭션 시작

            member = em.merge(member);    //merge 를 사용해야지만 식별자를 가져와서 준영속 -> 영속 상태로 변경할 수 있다.
            member.setUsername("수정했다.");

            tx.commit();//  commit 하여 변경된 값을 감지하여 DB에 SQL로 전달한다.
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티 매니저 종료
        }

    }

1. member 엔티티를 영속성 컨텍스트로 등록 후 DB와 동기화 하였다.

2. 그 후 트랜잭션이 종료 되어 엔티티 매니저가 종료되며 동시에 영속성 컨텍스트또한 close 되었다.

3. member 엔티티는 영속 상태에서 준영속 상태가 되었다.

4. member 엔티티를 다시 컬럼을 update 하기 위해 엔티티 매니저의 트랜잭션 작업 내에 접속했지만 사용할 수가 없다.

5. 준영속상태의 member 엔티티를 영속 상태로 변경하기 위해 merge() 를  사용하여 다시 영속상태로 변경하였다.

6. username 엔티티 컬럼의 값으 변경 후 commit 하니 DB에 정상적으로 적용되었다.

 

4, 5번이 중요한 부분인데 이미 사용해버린 엔티티를 재사용 하기위해서는 merge() 를 사용하여 영속 상태로 가져온 것이다. 이때 준영속 상태 뿐만 아닌 비영속 상태의 엔티티 또한 사용 가능하다.

 

어떻게 보면 그냥 find로 하여 DB에서 영속성 컨텍스트로 가져와서 사용하면 되지 않겠냐고 할수 있겠지만

DB의 커넥션을 줄여 성능 최적화에 도움될 수 있는 기능이다.

Comments