프로젝트 성능 향상을 위한 리팩토링 중에 다대다 관계에서 데이터 저장시 불 필요한 update쿼리가 추가로 실행되는 문제를 찾았다.
이를 해결 해보자.
프로젝트에 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=?
마지막에 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
(?,?)
update 부분이 사라졌다!
문제는 해결 했는데 그에 대한 이유를 생각해보자.
JPA는 일대다 관계에서 일이 부모 엔티티,다가 자식 엔티티 라고 했을때 주로 자식 엔티티가 보통 주인 관계를 갖는다.
JPA에서 주인이란 관계를 수정할 수 있는 엔티티를 뜻한다. 주로 FK를 가지고 있는쪽을 주인으로 설정하고, mappedBy가 없는 쪽이 주인이다.
문제가 생긴 로직은 주인인 Permission을 먼저 생성하고 Worker를 생성하였다. DB 관점에선 Permission을 먼저 insert하면 그와 연관된 workerId를 지정할 수 없고, 주인이 아닌 Worker엔티티가 저장 될때 주인인 Permission에서 update 쿼리가 추가로 나가는것이다.