JPA | flush, clear, persist , emerge, find, createQuery, bulk 연산

2024. 1. 25. 16:46

내가 JPA 강의를 보고 작성한 VELOG 시리즈

영속성 컨텍스트를 통해 데이터를 관리하기 위해서는 
엔티티 객체의 영속화가 필요하며, 엔티티의 생명주기를 관리해야한다.
이를 위해서 entityManager 에 있는 메소드 들에 대해서 알아보자

 

flush

flush : 영속성 컨텍스트의 내용들을 데이터베이스화 동기화 하기 위해 사용하는 메소드
쓰기 지연 큐에 있는 쿼리들을  적용하여 동기화를 도와주는 메소드이다.
commit 메소드 호출 시 자동으로 호출 된다.

 

clear

clear : 영속성 컨텍스트를 초기화 하는 메소드

 

persist

https://docs.oracle.com/javaee%2F7%2Fapi%2F%2F/javax/persistence/EntityManager.html#persist-java.lang.Object-

 

persist : 비영속 상태의 엔티티를 영속화 하는 메소드
일반적으로 새로 생성한 엔티티 객체를 영속화 할 때 사용하는 메소드 이다.
Spring DATA JPA 에서 Repository 설정이 완료되면 
save 메소드를 볼 수 있는데, save 메소드 안에 포함 되어 있다.

- Spring Data JPA 의 save 메소드
JpaRepository 인터페이스를 구현한 SImpleJpaRepository
	@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null");

		if (entityInformation.isNew(entity)) {
			entityManager.persist(entity);
			return entity;
		} else {
			return entityManager.merge(entity);
		}
	}

 

 

merge

https://docs.oracle.com/javaee%2F7%2Fapi%2F%2F/javax/persistence/EntityManager.html#persist-java.lang.Object-

merge : 영속화 된 엔티티를 병합하는 메소드
위에서 Spring DATA JPA 의 save 메소드에 persist 와 같이 보이던 메소드 이다.
준영속 상태의 객체를 영속화 시킬 때 사용한다. 

데이터 베이스에서 식별자로 엔티티를 찾고
영속성 컨텍스트의 엔티티에 병합 하며 , 만약 존재하지 않는 경우 새로 생성해서 추가한다.

merge 를 사용하면 엔티티의 모든 속성을 변경하기 때문에
비어있는 값이 없는지 확인해야 한다.
            Member member = new Member();
            member.setUsername("member1");
            member.setAge(20);
            em.persist(member);

            em.flush();

            em.detach(member);
            Member member1  = new Member();
            member1.setId(member.getId());
            member1.setUsername("멤버1");
            em.merge(member1);

            em.flush();
            em.clear();

            Member findMember = em.find(Member.class,member.getId());
            System.out.println(" MERGE 이후 멤버의 AGE 값 "+findMember.getAge());
            System.out.println(" MERGE 이후 멤버의 Username 값 "+findMember.getUsername());
            --------
             MERGE 이후 멤버의 AGE 값 0
             MERGE 이후 멤버의 Username 값 멤버1​

member 객체를 준영속화 시키고 member의 @Id 필드를 사용해서 Member 인스턴스를 생성 후 
업데이트 할 값을 넣어준다. ( 위의 경우에는 'username' 필드만 변경 ) 
이후 merge 를 진행하면 
1. 해당 객체의 id 값을 사용하여 데이터 베이스에서 객체를 찾고 
2. member1 의 필드 값들을 데이터 베이스에 넣어준다. 
이 때, 'age'에 대한 값은 설정하지 않아 null 이기 때문에 `age`필드의 값이 null 로 변경된다.

그래서 필드의 업데이트를 진행시에는 
merge 보다는 영속화 하여 더티체킹을 하는 편이 좋다.

어플리케이션에 따라 다르지만 Spring DATA JPA 를 사용할 때 , 엔티티 저장 시 
save 메소드를 사용하는 것에 대해서 한번쯤은 고려해 봐야겠다.

 

find 

https://docs.oracle.com/javaee%2F7%2Fapi%2F%2F/javax/persistence/EntityManager.html#persist-java.lang.Object-

find : @Id 로 정의된 엔티티의 식별자를 사용하여 Database 에서 엔티티를 조회하는 메소드 
주로 엔티티 타입 과 엔티티 식별자 ( @Id 로 정의된 필드 ) 를 사용하여 데이터 베이스에서 엔티티를 조회하여 
영속성 컨텍스트로 영속화 하는 메소드이다.

메소드를 commit 시가 아닌 즉시 실행된다.
            Member member = new Member();
            member.setUsername("member1");
            member.setAge(20);
            em.persist(member);

            em.flush();
            em.clear();

            Member findMember = em.find(Member.class, member.getId());
            System.out.println(findMember.getUsername());
            System.out.println(findMember.getAge());

            System.out.println("COMMIT 이전 ");

            tx.commit();

            System.out.println("COMMIT 이후 ");
            ---
            member1
            20
            COMMIT 이전 
            COMMIT 이후​

 

createQuery

createQuery : JPQL 을 작성하기 위한 메소드
JPQL을 문자열로 직접 생성하기 위한 메소드이다. find 메소드를 사용하는 것 보다.
다양한 쿼리를 작성할 수 있다. JPQL 에 대해서는 추후에 더 작성할 예정이다. 

 

Bulk 연산 

createQuery 의 executeUpdate 메소드를 사용하여 Update 쿼리를 작성하는 연산을 말한다.
반환 값으로 영향 받은 레코드들의 개수를 반환하며 다수의 레코드를 변경할 때 사용할 때 유리하다.
더티 체킹이나 merge 메소드를 사용하는 것처럼 flush 메소드 실행 시 적용되는 것이 아닌
즉시 테이블 레코드에 대한 업데이트가 발생한다.

따라서 BULK 연산을 사용할 때는 
연산 후 영속성 컨텍스트를 초기화 하고 업데이트 한 레코드들을 
데이터베이스에서 조회하여 영속화 시키는 작업이 필요하다.

            Member member = new Member();
            member.setUsername("member1");
            member.setAge(20);
            em.persist(member);

            Member member1 = new Member();
            member.setUsername("member13");
            member.setAge(30);
            em.persist(member1);

            System.out.println("벌크 이전");
            System.out.println("영향받은 레코드 수 : "+em.createQuery("update Member m set m.age = 10").executeUpdate());
            System.out.println("벌크 이후");
            System.out.println("영속성 컨텍스트를 초기화 하지 않았을 때 member의 나이 : "+member.getAge());
            System.out.println("영속성 컨텍스트를 초기화 하지 않았을 때 member1의 나이 : "+member1.getAge());

            System.out.println("영속성 컨텍스트 초기화");
            em.clear();

            Member findMember = em.find(Member.class,member.getId());
            Member findMember1 = em.find(Member.class,member1.getId());

            System.out.println("영속성 컨텍스트를 초기화 이후 member의 나이 : "+findMember.getAge());
            System.out.println("영속성 컨텍스트를 초기화 이후 member1의 나이 : "+findMember1.getAge());

            System.out.println("COMMIT 이전 ");
            tx.commit();
            System.out.println("COMMIT 이후 ");
            
--------------------------------------------------
벌크 이전
1월 25, 2024 4:32:57 오후 org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Hibernate: 
    /* insert org.example.entity.Member
        */ insert 
        into
            Member
            (age, TEAM_ID, username, id) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* insert org.example.entity.Member
        */ insert 
        into
            Member
            (age, TEAM_ID, username, id) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* update
        org.example.entity.Member */ update
            Member 
        set
            age=?,
            TEAM_ID=?,
            username=? 
        where
            id=?
Hibernate: 
    /* update
        Member m 
    set
        m.age = 10 */ update
            Member 
        set
            age=10
영향받은 레코드 수 : 2
벌크 이후
영속성 컨텍스트를 초기화 하지 않았을 때 member의 나이 : 30
영속성 컨텍스트를 초기화 하지 않았을 때 member1의 나이 : 0
영속성 컨텍스트 초기화
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.TEAM_ID as TEAM_ID4_0_0_,
        member0_.username as username3_0_0_,
        team1_.id as id1_3_1_,
        team1_.name as name2_3_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.id 
    where
        member0_.id=?
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.TEAM_ID as TEAM_ID4_0_0_,
        member0_.username as username3_0_0_,
        team1_.id as id1_3_1_,
        team1_.name as name2_3_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.id 
    where
        member0_.id=?
영속성 컨텍스트를 초기화 이후 member의 나이 : 10
영속성 컨텍스트를 초기화 이후 member1의 나이 : 10
COMMIT 이전 
COMMIT 이후

 


벌크 연산이 진행 되면 쓰기 지연 큐에 존재하는 쿼리들이 모두 적용되는 것을 볼 수 있으며,
이후 영속성 컨텍스트를 초기화 하지 않으면 변경되기 이전의 값을 반환 받는다.
( find 메소드를 사용하더라도 영속화 되어 있는 객체가 존재하면
영속성 컨텍스트의 엔티티를 리턴한다. 즉 변경되지 않은 엔티티를 반환 ) 

이후 clear 메소드를 통해 영속성 컨텍스트를 초기화를 진행한 이후 조회 시
변경된 엔티티 값을 반환 받을 수 있다. 

작성을 마치며 

entityManager 에서 주로 사용하는 메소드들에 대해서 알아보았다. 
merge 나 bulk 연산에 대한 주의 점을 한번더 상기시키는 시간이였다. 
Spring DATA JPA 를 사용하면 entityManger 를 직접 사용하는 일이 잘 없기 때문에, merge 메소드 사용에 대한 
고민을 많이 하지 않았었다. 최근에 배운 것들을 정리 하기 위한 어플리케이션을 가지고 놀때 , SpringBoot 에서 
entityManger 를 직접 호출하여 save 메소드 대신 더티체킹을 적용하여 직접 영속화 하는 메소드를 정의하였다. 

@Service
@RequiredArgsConstructor
@Transactional(readOnly = false)
public class CreateUpdateMemberServiceImpl implements CreateUpdateMemberService {
    private final EntityManager em;
    private final BCryptPasswordEncoder passwordEncoder;

    @Transactional
    public Long createMember(MemberInfoDto memberInfoDto)
    {


        Member member = Member.builder()
                .age(memberInfoDto.getAge())
                .userName(memberInfoDto.getUserName())
                .phone(memberInfoDto.getPhone())
                .userEmail(memberInfoDto.getUserEmail())
                .userNick(memberInfoDto.getUserNick())
                .address(new Address(memberInfoDto.getCity(), memberInfoDto.getStreet(), memberInfoDto.getZipcode()))
                .build();
        em.persist(member);​

 

이런 것들을 고민하면서 조금 더 나은 사람이 되도록 노력해야겠다

BELATED ARTICLES

more