들어가기에 앞서 예제는 Member와 MemberWhdwlHis(회원 탈퇴 내역)이 1:1로 Mapping되어 있으며,
회원 탈퇴 내역의 PK값은 Member의 key로 매핑되어 있다.
@Table(name = "ST_MBR_WHDWL_HIS")
public class MemberWhdwlHis{
@Id
@Column(name = "MBR_NO", nullable = false)
private String memberNo;
@OneToOne(fetch = FetchType.LAZY)
@MapsId // 외래키를 기본키로 사용할때 id를 매핑
@JoinColumn(name = "MBR_NO", referencedColumnName = "MBR_NO")
private Member member;
@Column(name = "MBR_ID")
private String mbrId;
...
/**
* 회원 탈퇴 내역 등록
*/
public static MemberWhdwlHis of( Member member, int reasonCode){
return MemberWhdwlHis.builder()
....
.build();
}
위에서 .of()는 탈퇴 내역 엔티티 객체를 생성하는 역할을한다.
먼저, 아래 예시를 보면
/**
* 회원 탈퇴
*
* @param mbr 요청 회원
* @param request 탈퇴 사유 객체
*/
@Transactional
public void secession(MemberImpl mbr,ManageRequest.Secession request){
// 멤버 객체 조회
Member member = memberRepository.findById(mbr.getMemberNo()).orElseThrow(() -> new ServerException(HttpStatus.BAD_REQUEST,"MBAC-FC0002"));
// 탈퇴 사유 객체 생성
MemberWhdwlHis memberWhdwlHis = MemberWhdwlHis.of(member,request.getReasonCode());
//탈퇴 내역 저장
memberWhdwlHisRepository.save(memberWhdwlHis);
}
멤버 객체를 조회하고, 탈퇴 사유 객체를 생성할때 앞에서 생성된 멤버 객체를 파라미터로 넘겨 탈퇴 사유 객체를 생성한후, 탈퇴 내역을 저장하는 간단한 로직이다.
그러나 위 코드를 실행시 아래와 같이 HHH000099 오류가 발생했다.
식별자가 NULL? 구글링을 해보니 JPA내부적인 버그라는데 정확한 이유를 알지못했다.
이유를 찾기 위해 실행된 쿼리를 쭉 따라가보았다.
그러던중 insert쿼리전에 select쿼리를 실행하는 사실을 알게됐다.
1.먼저 멤버 객체를 조회하는 쿼리가 실행됐다.
2. INSERT전에 .save()에 들어갈 객체가 조회됐다.
이말은 항상 save하기전에 무조건 select가 실행된다면, 만건의 데이터를 넣을때 만건의 select를 실행하기 때문에 2배의 시간이 걸린다는 뜻이다.
따라서 심각성을 인지하고 .save의 구현체인 SimpleJpaRepository를 살펴보았다.
위에서 엔티티의 식별자가 있을경우 else로 들어가 merge()하고, 식별자가 없을경우 persist()하고 있었다.
(식별자가 없는경우는 보통 키값에 @GeneratedValue를 설정하는 경우라고 생각하면 된다.)
여기서 다행이였던 점은 만건의 데이터를 넣을때 만건의 select가 실행되는건 아닌것을 깨달았다.
DB설계를 어떻게 하느냐에 다르겠지만 보통 @GeneratedValue를 key값으로 두는 경우가 많기때문에 즉,식별자가 없는 경우가 더 많기 때문이다.
그런데 이것을 그냥 방치하는것은 말도 안되기때문에 아래와 같이 해결하였다.
insert가 일어날 테이블에 Persistable<>을 implements하고 Persistable의 메소드들을 오버라이드하였다.
@Entity
public class MemberWhdwlHis implements Persistable<String>{
...
@Transient // 해당 데이터를 테이블의 컬럼과 매핑 시키지 않는다.
@Builder.Default
private boolean isNew = true;
@PrePersist // 영속 상태의 엔티티를 이용하여 데이터 업데이트를 수행하기 이전에 실행
@PostLoad // 엔티티를 로딩한 후에 호출함을 의미
void markNotNew() {
this.isNew = false;
}
@Override
public boolean isNew() {;
return this.isNew;
}
@Override
public String getId() {
return this.memberNo;
}
}
위에서 살펴본 .save() 내부에 있는 isNew를 true로 만들어 select쿼리를 실행하지 않도록 초기에 설정해놓는것이다.
위와 같이 설정한후 다시 실행해보았다.
1. Member 조회 쿼리 실행
2. 탈퇴 내역 INSERT 쿼리 실행
위와 같이 SELECT쿼리를 타지않고 바로 INSERT쿼리가 실행되는 모습을 확인할 수 있다.
위와 같이 변경함에 따라 자연스럽게 처음에 발생한 HHH000099 에러가 사라졌다.
'지식 저장소' 카테고리의 다른 글
등록 로직에서 dto를 Entity로 변환할때 변환하는 코드는 어디에? (0) | 2023.08.31 |
---|---|
MPA와 SPA란? (0) | 2023.05.11 |
같은 값 기준 정렬 후 페이징 시 목록 꼬임 문제 (0) | 2023.01.10 |
Replication(복제) (0) | 2022.09.08 |
Cannot add or update a child row 에러 (0) | 2022.01.19 |