인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

Chaerin Yu님의 프로필 이미지
Chaerin Yu

작성한 질문수

실전! Querydsl

수정, 삭제 벌크 연산

삭제 벌크 연산 flush() 호출

해결된 질문

작성

·

1.2K

2

영한님 안녕하세요.
 
벌크 연산 후, flush(), clear() 처리를 해줘야 DB와 영속성 컨텍스트 에 있는 엔티티 상태가 같게 유지 된다고 하셨는데요.
 
삭제 벌크 연산 같은 경우에는 flush(), clear() 처리를 별도로 해주지 않더라도 데이터 조회해보면 삭제되어서 조회가 안되더라구요..
 
그렇게 되면 삭제 연산 시에는 굳이 flush, clear 처리를 안해줘도 되는게 아닌가요??
 
강의 삭제 테스트 코드에서도 확인해보니 flush 호출을 안했는데도 조회 시 데이터가 삭제되어 있습니다. ㅠㅠ
@Test
    public void bulkDelete() {

        List<Member> result = queryFactory
                .selectFrom(member)
                .fetch(); // 104명

        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

        long count = queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();

        System.out.println("delete count="+count); // 84
        List<Member> result1 = queryFactory
                .selectFrom(member)
                .fetch(); // 20명

        for (Member member1 : result1) {
            System.out.println("member1 = " + member1);
        }
    }

답변 3

0

package study.querydsl;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import study.querydsl.entity.Member;
import study.querydsl.entity.QMember;
import study.querydsl.entity.Team;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import java.util.List;

import static study.querydsl.entity.QMember.member;

@Transactional
@SpringBootTest
public class QuerydslBasicTest {
	@PersistenceContext
	EntityManager em;

	JPAQueryFactory queryFactory;

	@BeforeEach
	public void before() {
		queryFactory = new JPAQueryFactory(em);
		Team teamA = new Team("teamA");
		Team teamB = new Team("teamB");
		em.persist(teamA);
		em.persist(teamB);

		Member member1 = new Member("member1", 10, teamA);
		Member member2 = new Member("member2", 20, teamA);
		Member member3 = new Member("member3", 30, teamA);
		Member member4 = new Member("member4", 40, teamA);

		em.persist(member1);
		em.persist(member2);
		em.persist(member3);
		em.persist(member4);
		em.flush();
		System.out.println("영속성 컨텍스트 초기화");
		System.out.println("여기서 안하면 before에서 이미 영속성 컨텍스트 안에 team과 member들이 들어가고 시작");
		em.clear();
	}

	@Test
	public void bulkUpdate() {

		//member1 = 10 -> 비회원
		//member2 = 20 -> 비회원
		//member3 = 30 -> 유지
		//member4 = 40 -> 유지
		System.out.println("여기서 영속성 컨텍스트 1차 캐시에 들어가게 됨.");
		List<Member> result1 = queryFactory.selectFrom(member).fetch();

		for (Member member1 :
				result1) {
			System.out.println("member1 = " + member1);
		}

		long count = queryFactory.update(member)
				.set(member.username, "비회원")
				.where(member.age.lt(28))
				.execute();

		//em.flush
		//em.clear
		System.out.println("영속성 컨텍스트 초기화X");
		System.out.println("JPQL은 데이터베이스에서 먼저 조회하고 영속성 컨텍스트에 해당 엔티티가 존재하는지 확인");
		System.out.println("id로 따지면 1차캐시에 모두 존재하기 때문에 영속성 컨텍스트 1차 캐시에 있는 엔티티 그대로 가져온다.");
		List<Member> result = queryFactory.selectFrom(member).fetch();
		for (Member member1 :
				result) {
			System.out.println("member1 = " + member1);
		}
	}

	@Test
	public void bulkDelete() {

		//member1 = 10 -> 비회원
		//member2 = 20 -> 비회원
		//member3 = 30 -> 유지
		//member4 = 40 -> 유지

		List<Member> result = queryFactory.selectFrom(member).fetch();

		for (Member member1 :
				result) {
			System.out.println("member1 = " + member1);
		}
		long updated = queryFactory.update(member)
				.set(member.username, "비회원")
				.where(member.age.lt(28))
				.execute();
		long count = queryFactory.delete(member)
				.where(member.age.lt(18))
				.execute();


		//em.flush
		//em.clear

		System.out.println("delete count="+count);
		System.out.println("영속성 컨텍스트 초기화X");
		System.out.println("JPQL은 데이터베이스에서 먼저 조회하고 영속성 컨텍스트에 해당 엔티티가 존재하는지 확인");
		System.out.println("데이터베이스에서 이미 삭제된 엔티티는 영속성 컨텍스트 1차 캐시에서 조회해오지 않습니다.");
		System.out.println("결론적으로 영속성 컨텍스트는 그대로 남아있고 벌크 연산 결과는 반영되지 않았습니다.");
		System.out.println("그 증거로 bulk update 결과는 반영되지 않습니다.");
		List<Member> result1 = queryFactory.selectFrom(member).fetch();
		for (Member member1 :
				result1) {
			System.out.println("member1 = " + member1);
		}
	}

}

0

죄송합니다. Chaerin Yu님께서는 bulkUpdate 후에 영속성 컨텍스트 초기화를 하지 않고 select 쿼리 후 조회했을 때는 바뀐게 반영되지 않는데 bulkDelete에서는 바뀐게 반영된다. 이게 왜 그런지 모르겠다고 질문해주셨습니다. 

 

해당 답변에 안일하게 답변을 달았다가 Chaerin Yu님께서 다시 질문을 주셔서 보게 되었습니다.

그래서 직접 테스트 코드를 짜본 결과 제가 이해한 바로는 두 벌크 연산 테스트 결과의 차이는 JPQL이 먼저 DB에 조회하고 그걸 바탕으로 영속성 컨텍스트의 1차 캐시에 해당 엔티티가 있는지 조회한 후 해당 엔티티가 존재한다면 그걸 그대로 반환하는 데서 오는 것이라고 봤습니다.

 

bulkUpdate 후에는 해당 엔티티들의 개수와 id는 변하지 않았기 때문에 DB에서 조회한 id 값들도 같을 것이고 1차 캐시에서 해당 엔티티들도 그대로 있을테니 1차 캐시에 있는 엔티티들을 불러온 것이고, bulkDelete 후에는 DB에서 조회한 id 들에 한해서만 영속성 컨텍스트 1차 캐시에서 값을 조회해오는 것이라 DB에서 삭제된 1차 캐시의 해당 엔티티는 불러오지 않았습니다.

 

결론을 말하자면 bulkDelete도 벌크 연산 후 영속성 컨텍스트에 바로 반영되지는 않는다고 말씀드릴 수 있을 것 같습니다.

제가 테스트한다고 짜보았던 코드를 밑에 첨부드리겠습니다.

 

감사합니다.

Chaerin Yu님의 프로필 이미지
Chaerin Yu
질문자

감사합니다.

0

안녕하세요. Chaerin Yu님, 공식 서포터즈 y2gcoder입니다.

벌크 연산은 말씀하신 것처럼 DB에 직접적으로 쿼리를 날리기 때문에 영속성 컨텍스트에 남아있는 값과 달라 데이터 무결성이 깨질 수 있습니다. 

하지만 지금 보여주시는 예시에서는 벌크연산 후 다시 조회하시면서 영속성 컨텍스트에 다시 member를 가져오고 계시기 때문에 문제가 없습니다!

감사합니다.

Chaerin Yu님의 프로필 이미지
Chaerin Yu
질문자

수정 벌크 연산 예제에서 전체 Member 조회 후 bulk update 실행한 다음 flush, clear를 거치지 않고 출력하게 되는 경우 upate 처리되기 전과 동일하게 나오고 있습니다. 

flush, clear 처리를 해줘야만 28살 미만의 사용자 이름이 '비회원'으로 업데이트가 됩니다.

bulkUpdate 테스트와 bulkDelete 테스트에서 update와 delete를 제외하고는 동일하게 실행했는데 2번째 반복문에서 조회할 때  update에서는 영속성 컨텍스트에서 데이터를 가져오고 delete에서는 delete 직후에 flush 발생한 후, DB에서 다시 가져오는 거로 보여서 질문드렸습니다.

delete 쿼리를 날리면 초기화(flush, clear) 없이도 이미 DB와 영속성 컨텍스트 데이터가 동일합니다.

 

@Test
    @Commit
    public void bulkUpdate() {

        List<Member> result1 = queryFactory
                .selectFrom(member)
                .fetch();

        for (Member member1 : result1) {
            System.out.println("member1 = " + member1);
        }

        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28))
                .execute();

        // em.flush(); // flush 해줘야만 아래에서 조회 시, 28살 미만 회원들 이름이 '비회원'으로 보임
        // em.clear();

        List<Member> result = queryFactory
                .selectFrom(member)
                .fetch();

        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }
    }

 

 

 

Chaerin Yu님의 프로필 이미지
Chaerin Yu

작성한 질문수

질문하기