Spring/JPA기본

JPQL의 Update, delete 사용, 벌크연산

Per ardua ad astra ! 2021. 8. 4. 13:48

벌크연산

 

update

 

예시)
재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면

SQL
UPDATE Product as p SET p.price = (p.price * 1.1)
WHERE p.count < 10;

 

SQL은 위와같은 쿼리를 날려 조건을 만족하느 모든 엔티티의 값을 변경할 수 있다. 

JPQL은 이것이 가능할까?? 멤버의 이름을 모두 "붕붕이"로 바꾸는 쿼리를 날려 실행해보자

 

예시 코드 (실패사례)

            // 멤버
            Member member1 = new Member();
            member1.setUsername("정석우");
            em.persist(member1);

            // 멤버
            Member member2 = new Member();
            member2.setUsername("정동생");
            em.persist(member2);

            // 멤버
            Member member3 = new Member();
            member3.setUsername("동동생");
            em.persist(member3);

            em.createQuery("UPDATE Member m SET m.username = :username").setParameter("username", "붕붕이");
            em.clear();

            List<Member> resultList = em.createQuery("SELECT m FROM Member m", Member.class).getResultList();
            for (Member member : resultList) {
                System.out.println("member.Name = " + member.getUsername());
            }

"UPDATE Member m SET m.username = :username" 쿼리를 날려서 이름을 바꿨음에도 불구하고 의도치않은 오류로 제대로 실행이 되지 않는다. 


안되는 이유 
JPA가 변경 감지 기능으로 실행하려면 너~무 많은 SQL을 실행해야한다. 
1. 모든 멤버 엔티티를 조회하고
2. 멤버 엔티티의 이름을 모두 붕붕이로 바꾸려고 설정하면
3. 트랜잭션 커밋 시점에 변경감지가 동작하여 각 엔티티에 해당하는 UPDATE SQL을 각각 다 날려주는데, 변경된 데이터가 100건이라면 100번의 UPDATE SQL을 각각 실행시켜주는 것이다. 

=> SQL의 UPDATE문처럼 쿼리 한번으로 한번에 많은 데이터를 변경또는 삭제하고 싶다면 (벌크연산)
executeUpdate()를 뒤에 붙여서 일괄 실행하면 된다. (executeUpdate()의 연산 결과는 변경된 엔티티 수를 반환한다.)  
(update, delete는 지원되고 insert는 하이버네이트에서 지원된다) 

 

다시 올바른 코드로 변경해보자!

 

코드

            // 멤버
            Member member1 = new Member();
            member1.setUsername("정석우");
            em.persist(member1);

            // 멤버
            Member member2 = new Member();
            member2.setUsername("정동생");
            em.persist(member2);

            // 멤버
            Member member3 = new Member();
            member3.setUsername("동동생");
            em.persist(member3);

            int resultCount = em.createQuery("UPDATE Member m SET m.username = :username")
                                .setParameter("username", "붕붕이")
                                .executeUpdate();
            System.out.println("resultCount = " + resultCount);
            em.clear();

            List<Member> resultList = em.createQuery("SELECT m FROM Member m", Member.class).getResultList();
            for (Member member : resultList) {
                System.out.println("member.Name = " + member.getUsername());
            }

executeUpdate()를 실행했고, 변경된 엔티티 수는 resultCount에 저장될 것이다.

 

출력 결과

 

주의할점 !!

=> 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 날리는 것이다.  !! ****
즉, 벌크 연산을 수행하면 DB의 데이터들이 변경되고, 벌크 연산 후 다시 조회를 했을 때 영속성 컨텍스트에 이미 값이 있다면 변경된 DB의 값이 조회되는 것이 아니라, 영속성 컨텍스트의 낡은 값이 조회될 수 있는 것이다. 

 

위의 예시로 만약 update이후 em.clear()를 실행하지 않고 조회를 한다면 member의 이름은 변경전의 이름으로 그대로 출력될 것이다. 

 

출력결과 (em.clear()를 없앴을 때)


따라서, 벌크연산을 실행하고 나면 영속성 컨텍스트를 초기화(clear)해서 다시 DB에서 데이터를 가져오는 것이 올바르다.

 

 

 

이 게시물은 '자바 ORM 표준 JPA 프로그래밍' 강의를 수강하고 정리한 내용임을 밝힙니다. 
출처: https://www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com