JPA | flush, clear, persist , emerge, find, createQuery, bulk 연산
2024. 1. 25. 16:46
영속성 컨텍스트를 통해 데이터를 관리하기 위해서는
엔티티 객체의 영속화가 필요하며, 엔티티의 생명주기를 관리해야한다.
이를 위해서 entityManager 에 있는 메소드 들에 대해서 알아보자
flush
flush : 영속성 컨텍스트의 내용들을 데이터베이스화 동기화 하기 위해 사용하는 메소드
쓰기 지연 큐에 있는 쿼리들을 적용하여 동기화를 도와주는 메소드이다.
commit 메소드 호출 시 자동으로 호출 된다.
clear
clear : 영속성 컨텍스트를 초기화 하는 메소드
persist
persist : 비영속 상태의 엔티티를 영속화 하는 메소드
일반적으로 새로 생성한 엔티티 객체를 영속화 할 때 사용하는 메소드 이다.
Spring DATA JPA 에서 Repository 설정이 완료되면
save 메소드를 볼 수 있는데, save 메소드 안에 포함 되어 있다.
- Spring Data JPA 의 save 메소드@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
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
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);
이런 것들을 고민하면서 조금 더 나은 사람이 되도록 노력해야겠다
'SpringBoot > JPA' 카테고리의 다른 글
JPA | 영속성 컨텍스트 구조 | 쓰기 지연 큐 ( SQL 쿼리 저장소 ), 1차 Cache (2) | 2024.01.23 |
---|---|
Entity Life Cycle | Transient, Managed, Detached, Removed (1) | 2024.01.21 |
JPA | EntityManager, EntityManagerFactory, Entity, PersistenceUnit (0) | 2024.01.17 |
JPA 란 (0) | 2024.01.15 |