본문 바로가기

Spring/JPA

JPA- 벌크연산

JpqlMain.class

package jpql;

public class JpqlMain {
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
			// 멤버 100명 persist
            for (int i = 0; i < 100; i++) {
                Member member = new Member("Member" + i, i);
                em.persist(member);
            }
            
			// 나이가 90이상인 회원의 type을 ADMIN으로 변경하는 쿼리
            String query = "update Member m set m.type = jpql.MemberType.ADMIN where m.age >= :age ";
            int i = em.createQuery(query).setParameter("age", 90).executeUpdate();
            System.out.println(i);  // int i는 update된 row의 수

			// type이 ADMIN인 member 조회
            List<Member> result = em.createQuery("select m from Member m where m.type = jpql.MemberType.ADMIN",Member.class)
                    .getResultList();
            for (Member member : result) {
                System.out.println("member = " + member);
            }

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            em.close(); // 종료
        }
        emf.close();
    }
}

결과

Hibernate: 
    /* select
        m 
    from
        Member m 
    where
        m.type = jpql.MemberType.ADMIN */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.TEAM_ID as team_id5_0_,
            member0_.type as type3_0_,
            member0_.USERNAME as username4_0_ 
        from
            Member member0_ 
        where
            member0_.type='ADMIN'
member = Member{id=91, username='Member90', age=90}
member = Member{id=92, username='Member91', age=91}
member = Member{id=93, username='Member92', age=92}
member = Member{id=94, username='Member93', age=93}
member = Member{id=95, username='Member94', age=94}
member = Member{id=96, username='Member95', age=95}
member = Member{id=97, username='Member96', age=96}
member = Member{id=98, username='Member97', age=97}
member = Member{id=99, username='Member98', age=98}
member = Member{id=100, username='Member99', age=99}

 벌크 연산을 하게되면 update되는 row의 수가 여러개라도 하나의 update 쿼리문만이 나가서 효율적이다. 위의 예시에서는 100개의 멤버 중 10개의 멤버가 업데이트 되는데 그 과정에서 한개의 update 쿼리만이 나간다.

 

 문제는 벌크 연산은 영속성 컨텍스트는 건들지 않고 DB에 직접 접근하기 때문에 영속성 컨텍스트의 값을 조회하게 되면 원하는 값이 나오지 않을 수 있다.

package jpql;

public class JpqlMain {
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Member member1 = new Member("회원 1", 20);
            Member member2 = new Member("회원 2", 40);
            Member member3 = new Member("회원 3", 60);
            em.persist(member1);
            em.persist(member2);
            em.persist(member3);

            // 모든 member의 age를 100으로 update
            String query = "update Member m set m.age = 100";
            int i = em.createQuery(query).executeUpdate(); //executeUpdate()로 벌크 연산, i는 update된 row의 수
            
            Member findMember = em.find(Member.class, member1.getId());
            System.out.println("findMember = " + findMember);  // 영속성 컨텍스트의 내용이 DB와 다른 값이 출력
            // 영속성 컨텍스트 비우기
            em.clear();

            // 영속성 컨텍스트가 비워져 있으니 DB에 먼저 접근 후 영속성 컨텍스트 초기화
            Member findMember2 = em.find(Member.class, member1.getId());
            System.out.println("findMember = " + findMember2); // DB와 같은 값이 출력
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            em.close(); // 종료
        }
        emf.close();
    }
}

출력>>>
Hibernate: 
    /* update
        Member m 
    set
        m.age = 100 */ update
            Member 
        set
            age=100
findMember = 20
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.TEAM_ID as team_id5_0_0_,
        member0_.type as type3_0_0_,
        member0_.USERNAME as username4_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
findMember = 100

 지금 예시에서는 처음 em.find()로 조회하는 경우 기존 영속성 컨텍스트의 나이가 20인 멤버들이 조회가 되어, DB와는 다른 데이터가 조회된다. 그렇기에 벌크 연산을 사용한 뒤 em.clear()로 영속성 컨텍스트를 비워준다음 다시 DB에 접근해서 내용을 DB에서 영속성 컨텍스트로 가져와야 올바른 값을 조회할 수 있다.

'Spring > JPA' 카테고리의 다른 글

JPA- Open Session In View  (0) 2023.07.16
JPA- DISTINCT  (0) 2023.07.10
JPA-Join Fetch  (0) 2023.06.18
JPA- 경로 표현식  (0) 2023.06.18
JPA- 조건식의 사용  (0) 2023.06.18