스프링과 JPA 기반 웹 애플리케이션 개발 #41 관심 주제 등록 기능 구현

Jake Seo·2021년 6월 9일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #41 관심 주제 등록 기능 구현

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


TagRepository 생성

@Transactional(readOnly = true)
public interface TagRepository extends JpaRepository<Tag, Long> {

    Optional<Tag> findByTitle(String title);
}

AccountTagRepository 생성

public interface AccountTagRepository extends JpaRepository<AccountTag, Long> {
}

TagForm 생성 (Form Backing Object)

@Data
public class TagForm {
    @Pattern(regexp = "^.{0,20}$")
    private String tagTitle;
}

자바스크립트에서 제약조건으로 걸었던 정규식을 여기에도 넣어주어서 2번 검증하게 했다.

fragments에 ajax csrf 문제 해결을 위한 부분 작성

<script type="application/javascript" th:fragment="ajax-with-csrf" th:inline="javascript">
  $(function() {
    let csrfToken = /*[[${_csrf.token}]]*/ null;
    let csrfHeader = /*[[${_csrf.headerName}]]*/ null;
    // 리액트 등을 이용할 때는 프론트 서버에서 API 서버로 fetch 해서 CSRF TOKEN 등 가져오는 부분 만들어야 할듯
    // 그리고 fetch 라이브러리에서 항상 헤더에 CSRF TOKEN, CSRF HEADER NAME 같이 보내도록 설정하고...
    $(document).ajaxSend(function(e, xhr, options) {
      xhr.setRequestHeader(csrfHeader, csrfToken);
    });
  });
</script>

th:inline이라는 기능으로 서버 메모리에 있는 변수와 같은 정보를 뷰단으로 끌고올 수 있다. ajaxSend()에 위와 같이 세팅해주면, 매 요청마다 csrf 헤더와 csrf 토큰을 함께 보내준다.

리액트 등으로 프론트를 바꾸었을 때도 csrf 를 항상 서버에 요청해서 해당 뷰에 대한 csrf를 발급 받아야 할 것 같다.

AccountService 작성

addTag() 메소드 작성

    public void addTag(Account account, Tag tag) {
        AccountTag accountTag = new AccountTag();
        accountTag.setAccount(account);
        accountTag.setTag(tag);

        if(!account.getAccountTags().contains(accountTag)) {
            accountTagRepository.save(accountTag);
        }
        // .getOne() 이라고 리포지토리에서 Lazy Loading 을 지원하는 게 있었는데 Deprecated 됐다.
        // 만일 Lazy Loading 하더라도 지금 이 순간에는 전혀 쓸모 없다.
    }

AccountTag를 받아서 AccountTag 도메인에 추가해주는 내용이다. 만일, 해당 AccountTag를 이미 갖고 있다면, AccountTag에 추가하지 않는다.

AccountTag 도메인 일부 수정

@Getter @Setter
@EqualsAndHashCode(of = {"account", "tag"})
@Builder @AllArgsConstructor @NoArgsConstructor
public class AccountTag {
...

위와 같이 애노테이션을 달았다. AccountTag의 동등을 비교할 때는 account 필드와 tag 필드를 이용한다.

SettingsController

addTag() 메소드 추가

    @PostMapping(ADD_TAGS_MAPPING_PATH)
    @ResponseBody
    public ResponseEntity addTag(@LoginAccount Account loginAccount, @Valid @RequestBody TagForm tagForm) {
        String title = tagForm.getTagTitle();
        // Optional 의 장점? NPE 방지, Stream 형태로 자연스럽게 연결
        // ifPresent() 는 값이 존재할 때만 특정 로직을 수행
        // orElseGet() 은 값이 없다면 특정 로직을 수행하여 값을 만듦
        Tag tag = tagRepository.findByTitle(title).orElseGet(() ->
                tagRepository.save(
                        Tag.builder().title(tagForm.getTagTitle()).build()
                )
        );

        accountService.addTag(loginAccount, tag);

//        Optional 안쓰는 버전
//        Tag tag = tagRepository.findByTitle(title);
//        if(tag == null) {
//            tagRepository.save(Tag.builder().title(tagForm.getTagTitle()).build());
//        }

        return ResponseEntity.ok().build();

Optional 객체를 써보았다. Optional 객체는 결과 값을 Stream 형태로 받아서 케이스별로 처리를 명확하게 하는데 도움이 되는 것 같다. 그런데 대부분은 익숙하지 않아서 코드의 가독성은 그냥 if문을 쓰는 것이 일반 개발자들에게는 훨씬 좋은 것 같다.

  • Optional 메소드들
    • ifPresent(): 값이 존재한다면 람다 로직 수행
    • orElseGet(): 값이 존재하지 않는다면 람다 로직 수행 후 결과값 반환
profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글