위에서 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 이기 때문에 a≥필드의 값이 null 로 변경된다.
그래서 필드의 업데이트를 진행시에는 merge 보다는 영속화 하여 더티체킹을 하는 편이 좋다.
어플리케이션에 따라 다르지만 Spring DATA JPA 를 사용할 때 , 엔티티 저장 시 save 메소드를 사용하는 것에 대해서 한번쯤은 고려해 봐야겠다.
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, 20244: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)publicclassCreateUpdateMemberServiceImplimplementsCreateUpdateMemberService{
privatefinal EntityManager em;
privatefinal BCryptPasswordEncoder passwordEncoder;
@Transactionalpublic 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);