jpa 데이터 저장시 의도치 않은 update 쿼리문 문제

Choco·2024년 1월 8일
post-thumbnail

프로젝트 성능 향상을 위한 리팩토링 중에 다대다 관계에서 데이터 저장시 불 필요한 update쿼리가 추가로 실행되는 문제를 찾았다.

이를 해결 해보자.

ERD 구성

프로젝트에 ERD는 위에 그림 처럼 구성 되어있고, 현재 문제점에 중요한 부분은
wharf,person,worker,permission이다.

Permission 엔티티는 Person,Wharf 엔티티와 @ManyToOne 관계를 유지하고 있다.

@Entity
@Getter
@NoArgsConstructor
public class Permission {
   
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long PermissionId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "personId")
    private Person person;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "wharfId")
    private Wharf wharf;

    public Permission(Person person, Wharf wharf) {
        this.person = person;
        this.wharf = wharf;
    }
}

Worker는 Person을 상속받고 있다.

@Entitiy
public class Worker extends Person{}

문제가 되는 로직

dto를 기준으로 Worker 엔티티 생성하고
worker랑 연관된 Permission 엔티티 생성하고 저장
Worker 엔티티 저장 순으로 로직이 진행된다.

public void registerWorker(ReqWorkerDto reqWorkerDto){
  
  	//reqWorkerDto에서 wharfId를 이용하여 Wharf를 반환
  	List<Wharf> wharfList = wharfRepository.findAllById(reqWorkerDto.getWharfs());
	
    //dto to entitiy(다른 엔티티와 관계가 없는 col만)
  	Worker worker = reqWorkerDto.toEntity();
	
    //worker와 wharf객체 리스트를 이용하여 permission 객체를 생성
  	permissionService.permit(worker,wharfList);
  	
    //worker 저장
    workerRepository.save(worker);

  }

//permit 메서드 로직
public void permit(Person person,List<Wharf> wharfList) {
  	
    List<Permission> permissionList = new ArrayList<>();
  	
    for(Wharf wharf: wharfList){
    	Permission permission = new Permission(person,wharf);
  		permissionList.add(permission);
  	}
  	
    permissionRepository.saveAll(permissionList);
 
  }

실행결과

실행 결과 발생하는 쿼리를 살펴보자

   select
        w1_0.wharf_id,
        w1_0.name 
    from
        wharf w1_0 
    where
        w1_0.wharf_id in (?)
2024-01-08T15:52:59.520+09:00 DEBUG 18928 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        permission
        (person_id,wharf_id) 
    values
        (?,?)
2024-01-08T15:52:59.534+09:00 DEBUG 18928 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        person
        (birth,is_worker,name,nationality,phone,sex,type) 
    values
        (?,?,?,?,?,?,'worker')
2024-01-08T15:52:59.537+09:00 DEBUG 18928 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        worker
        (account_id,face_url,position,person_id) 
    values
        (?,?,?,?)
2024-01-08T15:52:59.550+09:00 DEBUG 18928 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    update
        permission 
    set
        person_id=?,
        wharf_id=? 
    where
        permission_id=?
  1. dto에서 받는 WharfId를 통해 Whraf 객체로 바꾸는 쿼리
  2. Permission 엔티티 저장
  3. Person 엔티티 저장
  4. Worker 엔티티 저장
  5. Permission update(?)

마지막에 Permission을 update하는 쿼리가 발생한다.
보통 JPA에서 update쿼리는 db에 있는 데이터를 가져와서(findBy..)이를 수정하고 더티체킹을 통해 실행된다.
하지만 로직에서 이러한 부분은 없다.

의심지점

update쿼리는 worker 엔티티 저장 이후에 실행 되었다.
다시 서비스 로직을 살펴보면

public void registerWorker(ReqWorkerDto reqWorkerDto){
  
  //reqWorkerDto에서 wharfId를 이용하여 Wharf를 반환
  List<Wharf> wharfList = wharfRepository.findAllById(reqWorkerDto.getWharfs());
	
   //dto to entitiy(다른 엔티티와 관계가 없는 col만)
  Worker worker = reqWorkerDto.toEntity();
	
   //worker와 wharf객체 리스트를 이용하여 permission 객체를 생성
   permissionService.permit(worker,wharfList);
  	
   //worker 저장
   workerRepository.save(worker);

 }

worker가 엔티티가 저장되는 부분은 제일 마지막

//worker 저장
workerRepository.save(worker);

해당 부분이다.

이 코드에서 추가적으로 update쿼리가 나가는것으로 추측할 수 있는데..
해당 부분의 코드는 기본으로 제공하는 save()메서드 이므로 바꿀 방법이 없다.

그러면 코드의 순서를 바꿔 보자

public void registerWorker(ReqWorkerDto reqWorkerDto){
  
   //reqWorkerDto에서 wharfId를 이용하여 Wharf를 반환
   List<Wharf> wharfList = wharfRepository.findAllById(reqWorkerDto.getWharfs());
	
   //dto to entitiy(다른 엔티티와 관계가 없는 col만)
   Worker worker = reqWorkerDto.toEntity();
  
   //worker 저장
   workerRepository.save(worker);
	
   //worker와 wharf객체 리스트를 이용하여 permission 객체를 생성
   permissionService.permit(worker,wharfList);

}

Permission을 저장하는 부분과 worker를 저장하는 부분의 순서를 바꿨다.

수정 후 실행결과

    select
        w1_0.wharf_id,
        w1_0.name 
    from
        wharf w1_0 
    where
        w1_0.wharf_id in (?)
2024-01-08T16:13:10.063+09:00 DEBUG 13708 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        person
        (birth,is_worker,name,nationality,phone,sex,type) 
    values
        (?,?,?,?,?,?,'worker')
2024-01-08T16:13:10.070+09:00 DEBUG 13708 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        worker
        (account_id,face_url,position,person_id) 
    values
        (?,?,?,?)
2024-01-08T16:13:10.073+09:00 DEBUG 13708 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        permission
        (person_id,wharf_id) 
    values
        (?,?)
  1. dto에서 받는 WharfId를 통해 Whraf 객체로 바꾸는 쿼리
  2. Person 엔티티 저장
  3. Worker 엔티티 저장
  4. Permission 엔티티 저장

update 부분이 사라졌다!

해결

문제는 해결 했는데 그에 대한 이유를 생각해보자.

JPA는 일대다 관계에서 일이 부모 엔티티,다가 자식 엔티티 라고 했을때 주로 자식 엔티티가 보통 주인 관계를 갖는다.

JPA에서 주인이란 관계를 수정할 수 있는 엔티티를 뜻한다. 주로 FK를 가지고 있는쪽을 주인으로 설정하고, mappedBy가 없는 쪽이 주인이다.

문제가 생긴 로직은 주인인 Permission을 먼저 생성하고 Worker를 생성하였다. DB 관점에선 Permission을 먼저 insert하면 그와 연관된 workerId를 지정할 수 없고, 주인이 아닌 Worker엔티티가 저장 될때 주인인 Permission에서 update 쿼리가 추가로 나가는것이다.

profile
주니어 백엔드 개발자 입니다:)

0개의 댓글