JsonProcessingException는 IOException Exception을 상속하는 Checked Exception이다. 그러기 때문에 throws로 상위 메서드로 넘기든 자신이 try catch 해서 throw를 던지든 해야 한다. 이것은 문법적인 강제 선택이다. 그에 반해 Unchecked Exception은 명시적인 예외 처리를 하지 않아도 된다.
총 쿼리가 2개가 발생한다. 태그를 삭제하면 스터디를 조회하는 쿼리가 발생한다. 왜? 스터디를 조회해야 한다. 가져와야 태그를 제거할 수 있다. 가져오면서 스터디가 있는지 없는(checkIfExistingStudy) 확인하고, 권한(checkIfManager)도 확인한다.
findByPath를 가져올 때, 즉 tag를 수정하기 위해 study 정보를 가져오려는 경우 데이터를 이전과 똑같이 전부다 가져와야 할까? 이전에 사용한 form 을 조회할 때는 모든 데이터를 다 가져왔다. 하지만 tag를 넣고 뺄때도 전부다 가져와야 할까? 멤버, 매니저, 태그 목록, 지역 목록 등등..-> 아니다!! 😋😋
우선적으로 수정할 수 있는 권한(매니저인지 아닌지 필요)이 있는지 알아야 한다. 그리고 태그 정보를 수정해야 하므로 연관관계를 가져와야 한다. 그렇게 가져올 수 있도록 만들었다.
StudySettingsController.java
@PostMapping("/tags/add")
@ResponseBody
public ResponseEntity addTag(@CurrentAccount Account account, @PathVariable String path,
@RequestBody TagForm tagForm) {
Study study = studyService.getStudyToUpdateTag(account, path);
Tag tag = tagService.findOrCreateNew(tagForm.getTagTitle()); //findOrCreateNew 중복코드가 생길 수 있기 때문에 코드를 빼준다.
studyService.addTag(study, tag);
return ResponseEntity.ok().build();
}
@PostMapping("/tags/remove")
@ResponseBody
public ResponseEntity removeTag(@CurrentAccount Account account, @PathVariable String path,
@RequestBody TagForm tagForm) {
Study study = studyService.getStudyToUpdateTag(account, path);
Tag tag = tagRepository.findByTitle(tagForm.getTagTitle());
if (tag == null) {
return ResponseEntity.badRequest().build(); //없는 경우에는 bad request로 응답 보내기
}
studyService.removeTag(study, tag);
return ResponseEntity.ok().build();
}
StudyService.java
// 모든 연관관계를 다 가져온다.
public Study getStudyToUpdateTag(Account account, String path) {
Study study = repository.findAccountWithTagsByPath(path);
checkIfExistingStudy(path, study);
checkIfManager(account, study);
return study;
}
getStudyToUpdateTag 라는 새로운 service method를 만들었다. 여기서는 findAccountWithTagsByPath 라는 리파지토리, 메서드를 만들어서 사용했다.
StudyRepository.java
package com.yuri.studyolle.study;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import com.yuri.studyolle.domain.Study;
@Transactional(readOnly = true)
public interface StudyRepository extends JpaRepository<Study, Long> {
boolean existsByPath(String path);
// 모든 연관관계를 다 가져온다.
@EntityGraph(value = "Study.withAll", type = EntityGraph.EntityGraphType.LOAD)
Study findByPath(String path);
@EntityGraph(value = "Study.withTagsAndManagers", type = EntityGraph.EntityGraphType.FETCH)
Study findAccountWithTagsByPath(String path);
@EntityGraph(value = "Study.withZonesAndManagers", type = EntityGraph.EntityGraphType.FETCH)
Study findAccountWithZonesByPath(String path);
}
사실 여기서 findAccountWithTagsByPath, WithTags는 무시된다. JPA가 처리하는 키워드가 아니다. 결국에는 findByPath 와 같은 query가 발생한다.
같은 쿼리가 발생하지만 다른 엔티티를 사용하기 위해 이름을 다른 메소드로 만든 것이다.
SettingsController.java : 프로파일 정보를 수정하는 컨트롤러
@PostMapping(TAGS + "/add")
@ResponseBody
public ResponseEntity addTag(@CurrentAccount Account account, @RequestBody TagForm tagForm) {
Tag tag = tagService.findOrCreateNew(tagForm.getTagTitle());
accountService.addTag(account, tag);
return ResponseEntity.ok().build();
}
여기서 나오는 account 객체는 detached 상태이다. persistent 상태 객체가 아니라!
AccountService.java
public void addTag(Account account, Tag tag) {
Optional<Account> byId = accountRepository.findById(account.getId());
byId.ifPresent(a -> a.getTags().add(tag)); // id가 있으면 account에 tag 추가하기
}
detached 상태인 객체를 데이터를 변경하고, 인식하게 하기 위해서는 JPA를 통해서 db로부터 가져와야 persistent 상태가 된다. (트랜잭션 안에서 가져와야..)
SettingsSettings Controller.java
@PostMapping("/tags/add")
@ResponseBody
public ResponseEntity addTag(@CurrentAccount Account account, @PathVariable String path,
@RequestBody TagForm tagForm) {
Study study = studyService.getStudyToUpdateTag(account, path);
Tag tag = tagService.findOrCreateNew(tagForm.getTagTitle()); //findOrCreateNew 중복코드가 생길 수 있기 때문에 코드를 빼준다.
studyService.addTag(study, tag);
return ResponseEntity.ok().build();
}
그럼 여기서의 study 객체는 무슨 상태일까? persistent 상태이다. 이미 영속성 컨텍스트는 열려있다. 열려있는 상태에서 repository를 통해서 가져왔다.
그래서 Service 안에서 Study를 repository 통해서 조회할 필요가 없다.
중복된 코드를 fragments.html 에 넣어 치환해주기.
조회할 때는 어떤 쿼리가 발생하는지, 언제 왜 발생하는지 이해해야 한다 . 그 쿼리가 정말 필요한 만큼의 데이터를 가져왔는지를 판단할 수 있어야 한다!
출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발
https://cheese10yun.github.io/checked-exception/