본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/2부(관심 주제와 지역 정보)

34. 관심주제 등록하기

by Backchus 2020. 4. 22.

태그등록 뷰

SQL 확인을 위한 application.properties설정

# 기존 코드 ..............

# 개발할 때에만 create-drop 또는 update를 사용하고 운영 환경에서는 validate를 사용합니다.
spring.jpa.hibernate.ddl-auto=create-drop

# 개발시 SQL 로깅을 하여 어떤 값으로 어떤 SQL이 실행되는지 확인합니다.
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

 

프론트엔드 라이브러리

 

Tagify - demo

#textarea In this example, the field is pre-ocupied with 3 tags, and last tag is not included in the whitelist, and will be removed because the enforceWhitelist option flag is set to true HTML The Matrix, Pulp Fiction, Mad Max JAVASCRIPT The Matrix, Pulp F

yaireo.github.io

 

관심 주제 화면을 요청하기위한 핸들러 작성

package me.weekbelt.studyolle.settings;

@RequiredArgsConstructor
@Controller
public class SettingsController {

    // 기존 코드 ............

    @GetMapping("/settings/tags")
    public String updateTags(@CurrentUser Account account, Model model) {
        model.addAttribute(account);
        return "settings/tags";
    }
}

 

fragments.html에 tagify추가

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head th:fragment="head">
    <meta charset="UTF-8">
    <title>StudyOlle</title>
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="/node_modules/font-awesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/node_modules/@yaireo/tagify/dist/tagify.css">     // 추가
    <script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <style>
        .container {
            max-width: 100%;
        }
        
        // 추가
        .tagify--outside{
            border: 0;
            padding: 0;
            margin: 0;
		}
    </style>
</head>

// 기존 코드 .........
</html>

 

settings/tags.html 생성

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--head-->
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<!--네비게이션 바-->
<div th:replace="fragments.html::main-nav"></div>

<div class="container">
    <div class="row mt-5 justify-content-center">
        <div class="col-2">
            <div th:replace="fragments.html :: settings-menu(currentMenu='tags')"></div>
        </div>
        <div class="col-8">
            <div class="row">
                <h2 class="col-sm-12">관심있는 스터디 주제</h2>
            </div>
            <div class="row">
                <div class="col-12">
                    <div class="alert alert-info" role="alert">
                        참여하고 싶은 스터디 주제를 입력해 주세요. 해당 주제의 스터디가 생기면 받을 수 있습니다.
                        태그를 입력하고 콤마(,) 또는 엔터를 입력하세요.
                    </div>
                    <input id="tags" type="text" name="tags" class="tagify-outside" aria-describedby="tagHelp"/>
                </div>
            </div>
        </div>
    </div>
    <!-- footer -->
    <div th:replace="fragments.html::footer"></div>
</div>
<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
</body>
</html>

 

타임리프 자바스크립트 템플릿

<script type="application/javascript" th:inline="javascript">
</script>

 

settings/tags.html에 tagify를 적용하기 위한 js코드 추가

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--head-->
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<!--네비게이션 바-->
<div th:replace="fragments.html::main-nav"></div>

// 기존 코드 .....

<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
<script type="application/javascript">
    $(function () {
        function tagRequest(url, tagTitle) {
            $.ajax({
                dataType: "json",
                autocomplete: {
                    enabled: true,
                    rightKey: true,
                },
                contentType: "application/json; charset=utf-8",
                method: "POST",
                url: "/settings/tags" + url,
                data: JSON.stringify({'tagTitle' : tagTitle})
            }).done(function (data, status) {
                console.log("${data} and status is ${status}");
            });
        }

        function onAdd(e) {
            tagRequest("/add", e.detail.data.value);
        }

        function onRemove(e) {
            tagRequest("/remove", e.detail.data.value);
        }

        const tagInput = document.querySelector("#tags");
        const tagify = new Tagify(tagInput, {
           pattern: /^.{0,20}$/,
           dropdown : {
               enabled: 1,      // suggest tags after a single character input
           }  // map tags
        });

        tagify.on("add", onAdd);
        tagify.on("remove", onRemove);

        // add a class to Tagify's input element
        tagify.DOM.input.classList.add('form-control');
        // re-place Tagify's input element outside of the element (tagify.DOM.scope), just before it
        tagify.DOM.scope.parentNode.insertBefore(tagify.DOM.input, tagify.DOM.scope);
    });
</script>
</body>
</html>

 

관심 주제 ajax요청을 전달할 DTO 객체 생성

package me.weekbelt.studyolle.settings.form;

@Data
public class TagForm {

    private String tagTitle;
}

 

TagRepository 생성

package me.weekbelt.studyolle.tag;

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

    Optional<Tag> findByTitle(String title);
}

 

관심 주제 등록 하는 핸들러 생성

package me.weekbelt.studyolle.settings;

@RequiredArgsConstructor
@Controller
public class SettingsController {

    // 기존 코드 .......
    private final TagRepository tagRepository;

    // 기존 코드 .......

    @ResponseBody
    @PostMapping("/settings/tags/add")
    public ResponseEntity<?> addTag(@CurrentUser Account account, @RequestBody TagForm tagForm) {
        String title = tagForm.getTagTitle();
        Tag tag = tagRepository.findByTitle(title).orElseGet(
                () -> tagRepository.save(Tag.builder().title(tagForm.getTagTitle())
                                .build()));

        accountService.addTag(account, tag);
        return ResponseEntity.ok().build();
    }
}

 

태그를 추가하는 addTag메소드 작성

package me.weekbelt.studyolle.account;

@Transactional
@RequiredArgsConstructor
@Service
public class AccountService implements UserDetailsService {

    // 기존 코드 ........

    public void addTag(Account account, Tag tag) {
        // account엔티티는 Detached 상태 이기때문에 account를 먼저 읽어 온다
        Optional<Account> byId = accountRepository.findById(account.getId());
        byId.ifPresent(a -> a.getTags().add(tag));
    }
}

 

Ajax 호출시 CSRF 토큰을 전달 하는 방법

모든 POST요청에는 CSRF토큰이 있어야 한다. AJAX요청도 마찬가지이다. 

따라서 AJAX요청에도 CSRF토큰을 전송해야 한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--head-->
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<!--네비게이션 바-->
<div th:replace="fragments.html::main-nav"></div>

// 기존 코드 .......

// 추가
<script type="application/javascript" th:inline="javascript">
    $(function () {
        const csrfToken = /*[[${_csrf.token}]]*/ null;
        const csrfHeader = /*[[${_csrf.headerName}]]*/ null;
        $(document).ajaxSend(function (e, xhr, options) {
            xhr.setRequestHeader(csrfHeader, csrfToken);
        })
    });
</script>

<script type="application/javascript">
   // 기존 코드 .......
</script>
</body>
</html>

이 로직을 추가하면 AJAX요청시 csrf토큰을 같이 포함해서 요청하게 된다.

 

 

참고 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 활용 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의 스

www.inflearn.com