[JPA] 엔티티 삭제 시 casecade로 간편하게!

Jinseok Lee·2022년 1월 10일
1

사연

회사에서 프로젝트 도중 한 가지를 삭제하는 경우 하위의 여러가지 의존되어 있는 데이터들을 함께 삭제해줘야 하는 필요가 있었다. 하지만 기존 방식은 root가 되는 엔티티를 삭제하고 또 그 엔티티의 id를 가지고 여러가지 엔티티들을 배회하며 삭제해줘야 하는 번거로움이 존재했다.

@Transactional
public void delteBackOfficeById(Long backOfficeId) {
	backOfficeRepository.deleteById(backOfficeId);
        List<MenuEntity> menus = menuRepository.findByBackOfficeId(backOfficeId);
        menus.forEach(menuEntity -> {
            List<FunctionKey> functionKeys = functionKeyRepository.findByMenuId(menuEntity.getMenuId());
            functionKeyRepository.deleteAllByIds(functionKeys.stream().map(FunctionKey::getFunctionId).collect(Collectors.toList());
        });
        menuRepository.deleteAllById(menus.stream().map(Menu::getMenuId).collect(Collectors.toList());
        List<Role> roles = roleRepository.findByBackOfficeId(backOfficeId);
        roles.forEach(role -> {
            List<RoleFunctionKeyMap> roleFunctionKeyMapList = roleFunctionKeyRepository.findByRoleId(role.getRoleId());
            roleFunctionKeyMapRepository.deleteAllByIds(roleFunctionKeyMapList.stream().map(RoleFunctionKeyMap::getRoleFunctionKeyId).collect(Collectors.toList());
        });
        roleRepository.deleteAllByIds(roles.stream().map(Role::getRoleId().collect(Collectors.toList()));
        userGrantRepository.deleteAllByBackOfficeId(backOfficeId);
        groupGrantRepository.deleteAllByBackOfficeId(backOfficeId);
}

연구

jpa를 사이드 프로젝트에서 여러번 사용해 본 적이 있었는데 그 경험을 토대로 jpa 영속성을 사용해서 삭제하면 어떨까 하는 생각이 들었다. 그래서 기존 방식에서 변경하여 엔티티들의 연관관계를 우선 설정해주고 casecade를 ALL 혹은 DELETE로 걸어주고 엔티티를 삭제하면 될거 같았다.

public BackOfficeEntity {
	...    
	@OneToMany(mappedBy = 'backOffice', casecase = CasecadeType.ALL)
	private List<MenuEntity> menus = new ArrayList<>();
	....
}

하지만 영속성전이로 인한 삭제를 하게 되는경우 주의해야 하는 부분이 필요할 것으로 보였는데 만약에 backOffice라는 상위 엔티티가 존재하고 menu라는 하위 엔티티가 존재하는 경우에 backOffice를 삭제하게 되면 menu엔티티를 부모의 아이디로 한 번에 지우는 것이 아닌 해당 엔티디들의 아이디들의 값을 모두 불러와서 개별로 삭제를 진행을 하는 것인데, 만약에 하위의 엔티티가 몇만, 몇십만개 이상 넘어가는 경우 성능 이슈가 있을지도 모르겠다는 생각이 들었다.

하지만 내가 개발하고 있던 서비스의 경우 트래픽이 과하게 몰리는 서비스가 아니기에 성능보다는 jpa의 설계가 주는 데이터에 대한 신뢰도를 더 우선순위에 두기로 했다.

개선

BackOfficeEntity

  • userGrants
  • groupGrants
  • menus
  • roles
@Entity
public class BackOfficeEntity extends TimeBaseEntity {
	
    ... 
    
    @OneToMany(mappedBy = "backOffice", cascade = CascadeType.ALL)
    private List<UserGrantEntity> userGrants = new ArrayList<>();

    @OneToMany(mappedBy = "backOffice", cascade = CascadeType.ALL)
    private List<GroupGrantEntity> groupGrants = new ArrayList<>();

    @OneToMany(mappedBy = "backOffice", cascade = CascadeType.ALL)
    private List<MenuEntity> menus = new ArrayList<>();

    @OneToMany(mappedBy = "backOffice", cascade = CascadeType.ALL)
    private List<RoleEntity> roles = new ArrayList<>();
	
    ...

}

MenuEntity

  • functionKeys
@Entity
public class MenuEntity extends TimeBaseEntity {
	...
	@OneToMany(mappedBy = "menu", cascade = CascadeType.ALL, orphanRemoval = true)
	private Set<FunctionKeyEntity> functionKeys = new HashSet<>();
	...

}

RoleEntity

  • roleFunctionKeyMapList
@Entity
public class RoleEntity extends TimeBaseEntity {
	...
    @OneToMany(mappedBy = "role", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<RoleFunctionKeyMapEntity> roleFunctionKeyMapList = new ArrayList<>();
    ...
}

위와 같이 엔티티에 연관관계를 설정했을때 변경 된 소스코드는 아래와 같다.

@Transactional
public void deleteBackOfficeById(Long backOfficeId) {
	BackOfficeEntity backOfficeEntity = backOfficeRepository.findById(backOfficeId);
	backOfficeRepository.delete(backOfficeEntity);
}

삭제시에는 반드시 jpa 영속성에서 관리가 되는 상태(위의 코드에서는 findById로 불러와진 값)의 엔티티를 직접 삭제해줘야 한다. 이렇게 했을때 처음에 관련된 엔티티들을 삭제하는 것과 동일한 결과가 나오는 것을 확인할 수 있었다.

이로 인해 소스코드의 양을 줄이고 jpa에게 연관된 엔티티의 삭제를 위임해 에러를 줄일 수 있었다.

profile
전 위메프, 이직준비중

0개의 댓글