인프런 커뮤니티 질문&답변

자프링님의 프로필 이미지

작성한 질문수

실전! 스프링 데이터 JPA

스프링 데이터 JPA delete 안되는 이유가 궁금합니다

작성

·

2K

0

안녕하세요! 스프링 데이터 JPA를 상속받은 리포지토리를 통해 crud 구현 연습을 하고 있었습니다. 게시판 유저와 유저가 작성한 글을 각각 User, Article 엔티티로 표현했습니다.

User의 경우 모든 기능이 정상적으로 작동됩니다.

그런데 Article은 삭제가 이루어지지 않는 현상이 발생하고 있습니다.

그렇다고 중간에 예외가 발생하는 것도 아니고, 삭제시에는 삭제한 엔티티의 정보를 반환하게끔 설계했는데 멀쩡히 반환이 잘되고 DB에서도 삭제되지 않았습니다.

package com.crud.controller.article;

import com.crud.constant.MessageConst;
import com.crud.dto.Result;
import com.crud.entity.User;
import com.crud.service.ArticleService;
import com.crud.service.UserService;
import com.crud.utils.ResultUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {

    private final ArticleService articleService;
    private final UserService userService;
    private final MessageConst messageConst;

    @GetMapping
    public ResponseEntity<Result<Page<ArticleDTO>>> articles(@ModelAttribute ArticleSearchCond articleSearchCond,
                                                             @PageableDefault Pageable pageable) {
        Page<ArticleDTO> page = articleService.findAll(articleSearchCond, pageable);
        return ResponseEntity.ok().body(ResultUtils.success(page, messageConst.successSearch()));
    }

    @GetMapping("/{id}")
    public ResponseEntity<Result<ArticleDTO>> article(@PathVariable Long id) {
        ArticleDTO foundArticle = new ArticleDTO(articleService.findById(id));
        return ResponseEntity.ok().body(ResultUtils.success(foundArticle, messageConst.successSearch()));
    }

    @PostMapping("/save")
    public ResponseEntity<Result<ArticleDTO>> save(@RequestBody @Validated ArticleSaveDTO saveDTO) {
        User user = userService.findById(saveDTO.getUserId());
        ArticleDTO articleDTO = articleService.save(saveDTO, user);
        return ResponseEntity.ok().body(ResultUtils.success(articleDTO, messageConst.successSave()));
    }

    @PatchMapping("/{id}")
    public ResponseEntity<Result<ArticleDTO>> update(@PathVariable Long id, @RequestBody ArticleUpdateDTO updateDTO) {
        ArticleDTO updatedArticle = articleService.update(id, updateDTO);
        return ResponseEntity.ok().body(ResultUtils.success(updatedArticle, messageConst.successUpdate()));
    }

    // 삭제 메서드 호출입니다!!!!!!
    @DeleteMapping("/{id}")
    public ResponseEntity<Result<ArticleDTO>> delete(@PathVariable Long id) {
        ArticleDTO deletedArticle = articleService.delete(id);
        return ResponseEntity.ok().body(ResultUtils.success(deletedArticle, messageConst.successDelete()));
    }

}
package com.crud.service;

import com.crud.controller.article.ArticleDTO;
import com.crud.controller.article.ArticleSaveDTO;
import com.crud.controller.article.ArticleSearchCond;
import com.crud.controller.article.ArticleUpdateDTO;
import com.crud.entity.Article;
import com.crud.entity.User;
import com.crud.exception.NoSuchDataException;
import com.crud.repository.article.ArticleRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class ArticleService {

    private final ArticleRepository articleRepository;

    @Transactional(readOnly = true)
    public Page<ArticleDTO> findAll(ArticleSearchCond articleSearchCond, Pageable pageable) {
        return articleRepository.findAll(articleSearchCond, pageable);
    }

    @Transactional(readOnly = true)
    public Article findById(Long id) {
        return articleRepository.findById(id).orElseThrow(NoSuchDataException::new);
    }

    public ArticleDTO save(ArticleSaveDTO saveDTO, User user) {
        Article article = Article.createArticle(saveDTO, user);
        Article savedArticle = articleRepository.save(article);
        return new ArticleDTO(savedArticle);
    }

    public ArticleDTO update(Long id, ArticleUpdateDTO updateDTO) {
        Article article = findById(id);
        article.update(updateDTO);
        return new ArticleDTO(article);
    }

    // 삭제 메서드입니다!!!!!
    public ArticleDTO delete(Long id) {
//        articleRepository.deleteById(id);
        Article article = findById(id);
        articleRepository.delete(article);
        return new ArticleDTO(article);
    }

}

컨트롤러가 userService, articleService를 둘다 주입받고 있어서 트랜잭션이 꼬였나하고 userService를 지워보았는데 여전히 삭제는 안되었습니다.

 

그런데 만약 articleService.delete()의 반환타입을 void로 하여 아무것도 반환안하고(컨트롤러가 반환하는 api의 내용물도 그냥 null로 설정했습니다) 주석처리한 deleteById(id)를 사용해서 id로 바로 지우면 DB에서의 삭제가 이루어졌습니다.

 

이유를 계속 생각해보고 이거저거 건드려보고있긴한데, 아직까지는 도저히 모르겠습니다.. 리포지토리는 동적검색메서드만 따로 추가되었지, 삭제 메서드는 스프링 데이터 JPA의 delete를 그대로 가져다 쓴것입니다. 혹시 추가적인 코드가 필요하다면 말씀해주세요!

답변 2

1

자프링님의 프로필 이미지
자프링
질문자

delete 쿼리는 flush 이후에 나가는데 article 객체를 가져올때 자동으로 user 객체도 가져오기때문에 영속성 컨텍스트에 유지되어서 delete 쿼리 자체가 안나가기 때문인 것 같습니다.

따라서 article.getUser().getArticles().remove(article) 처럼 연관관계를 삭제해줌으로써 delete쿼리가 정상적으로 나갑니다.

1

자프링님의 프로필 이미지
자프링
질문자

추가적인 질문이지만 컨트롤러가 서비스를 여러개 호출하는 설계는 괜찮은건가요?

뭔가 같은 엔티티의 컨트롤러와 서비스끼리만 묶이는 것이 이뻐보이는데..ㅠㅠ

안녕하세요. 자프링님, 공식 서포터즈 코즈위버입니다.

한 개의 컨트롤러에서 여러 서비스를 의존하는 것은 괜찮습니다. 지금 작서하신 코드에서 조금 더 정리한다면, 컨트롤러에서 MemerService, ArticleService 를 의존하는 것보단 ArticleService만 의존하도록 하고, Member 정보를 조회하는 부분이 ArticleService 내부에 있으면 될 것 같습니다.

이 때 Service에서 다른 Service를 호출하는 형태로 갈 수도 있지만, ArticleService가 ArticleRepository, MemberRepository를 의존하도록 하도록 작성할 수 있습니다. 그러면 DTO가 아닌 엔터티를 직접 다루어 처리하므로 코드가 간결해질 수 있습니다. 엔터티를 가공하여 DTO로 만들어야 할 필요가 없다면 Repository에서 엔터티를 바로 찾아 사용하는 것이 간결합니다.

감사합니다.