Create API Operation(5) - Delete

GEONNY·2024년 8월 4일
0

Building-API

목록 보기
16/28
post-thumbnail

CRUD Operation의 마지막, Delete 입니다.

📌Controller

Controller 의 deleteMemer method를 변경합니다. memberService 의 deleteMember method 의 결과를 item 에 담아 ItemResponse 를 리턴하게 합니다.

    @DeleteMapping(value = "/member/{memberId}"
    		, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<ItemResponse<Long>> deleteMember(
    		@PathVariable("memberId") String memberId) {
        return ResponseEntity.ok()
                .body(ItemResponse.<Long>builder()
                        .status(messageConfig.getCode(NormalCode.MODIFY_SUCCESS))
                        .message(messageConfig.getMessage(NormalCode.MODIFY_SUCCESS))
                        .item(memberService.deleteMember(memberId))
                        .build());
    }

📌Service implementation

@Override
public Long deleteMember(String memberId) {
    Member memberEntity = memberRepository.findById(memberId)
            .orElseThrow(() -> new EntityNotFoundException(
                    "삭제할 회원 ID 가 존재하지 않습니다. -> " + parameter.memberId())
            );
    memberRepository.delete(memberEntity);
    return 1L;
}

삭제할 Id 체크 후 memberRepository 의 delete method 를 호출합니다. @Transactional 은 interface에 있으니 생략하고 delete 요청을 합니다.

정상적으로 응답이 왔고, 데이터도 삭제되었습니다.

📚참고

📕SimpleJpaRepository delete method

SimpleJpaRepository 의 delete method 코드를 보겠습니다.

@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {

	Assert.notNull(entity, "Entity must not be null");

	if (entityInformation.isNew(entity)) {
		return;
	}

	Class<?> type = ProxyUtils.getUserClass(entity);

	T existing = (T) entityManager.find(type, entityInformation.getId(entity));

	// if the entity to be deleted doesn't exist, delete is a NOOP
	if (existing == null) {
		return;
	}

	entityManager.remove(entityManager.contains(entity) ? entity : entityManager.merge(entity));
}

null 체크를 하고,

Assert.notNull(entity, "Entity must not be null");

isNew 메소드를 호출하여 신규 entity인 경우 method를 중지합니다. isNew method는 이전에 설명 했으니 참고 하세요.

Class<?> type = ProxyUtils.getUserClass(entity);

proxy 객체가 사용됐을 수 도 있기 때문에 ProxyUtils 의 getUserClass 메소드를 호출하여 실제 entity class type 을 가져옵니다.

T existing = (T) entityManager.find(type, entityInformation.getId(entity));

추출한 entity 의 class type 과 id 로 영속성 컨텍스트에서 객체를 조회합니다. 만약 영속성 컨텍스트에 entity 가 없는 경우 Database 에서 조회하여 있는 경우 영속성 컨텍스트에 추가하고 그 entity 를 반환합니다.

if (existing == null) {
	return;
}

영속성 컨텍스트와 Database에 존재하지 않는 경우 method 를 중지합니다.

entityManager.remove(entityManager.contains(entity) 
	? entity 
    : entityManager.merge(entity)
);

contains 를 통해 entity 가 영속성 컨텍스트에 포함되었는지 확인하고, 있다면 entity 를, 없다면 merge method 를 실행하여 entity 를 영속 상태로 만든 후에 remove method로 전달합니다.

❓entityManager.find() 로 entity 를 영속 상태로 만드는데, 왜 contains 로 또 영속성 컨텍스트에 포함되어 있는지 확인하는 걸까요?
예를들어 아래와 같은 두가지 경우가 있을 수 있습니다. 위의 삭제 코드와 같이 findById method를 통해 영속성 엔티티에서 Entity를 조회하여 전달한 경우, 아래와 같이 Entity를 생성하여 전달하는 경우. 예시를 위해 BaseEntity 에 @Setter 를 추가하고 isNew method 결과가 true 가 되도록 createDate 를 set 해줍니다.

 Member memberEntity = new Member(memberId, null, null, null, null);
 memberEntity.setCreateDate(LocalDateTime.now());
 memberRepository.delete(memberEntity);

이 경우 delete로 전달한 memberEntity 는 비영속 상태 입니다. 하지만 같은 키의 데이터는 있기 때문에 entityManager.find 의 결과는 null 이 아닙니다. 그래서 remove method 까지 호출되지만 entity 가 비영속 상태이기 때문에 entityManager.contains 의 결과는 false 가 되고, 결국 merge method 가 실행되어 영속화 후에 삭제되게 되는 것이죠. entityManager.find() 는 키가 있는지 체크하고, contains 로는 영속화 객체인지 체크 하는 것 입니다.

📙delete vs deleteById

@Override
@Transactional
public void deleteById(ID id) {

	Assert.notNull(id, ID_MUST_NOT_BE_NULL);

	findById(id).ifPresent(this::delete);
}

findById 코드를 보면 알 수 있듯이 결국 delete method 를 호출합니다. null 처리도 해주고, findById 도 해주니 deleteById method 를 쓰면 코드가 좀 더 깔끔해 질순 있지만 findById method 에서 throw 하는 Exception 과 message 를 그래도 써야 하죠. 처음에 작성한 코드와 같이 Exception 과 message 를 원하는 데로 커스텀 할 수 없습니다. 팀의 룰에 맞게, 로직에 맞게 활용하시면 됩니다.😉

profile
Back-end developer

0개의 댓글