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

김병곤님의 프로필 이미지

작성한 질문수

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기

[실습] Thymeleaf - 템플릿 수정(resume)

질문있습니다!

해결된 질문

24.08.16 12:30 작성

·

55

·

수정됨

0

image.png

여기서 experience를 모델에서 못찾아서??

experience.startYearMonth 등 인식이 안되는것 같습니다.

cannot resolve라고 뜹니다. 혹시 제가 컨트롤러라든지 DTO라든지 잘못적고 놓쳤나보다 하고 봤는데 왜 안되는지 못찾겠습니다. 한번 확인 가능할까요?

컨트롤러

image.png

서비스

image.png

DTO

image.pngimage.png

 

데이터 초기화

image.png

 

이렇게 타임리프 적용이 안될 떄는 어떻게 찾는게 좋은 방법일까요?

답변 2

1

정보근님의 프로필 이미지
정보근
지식공유자

2024. 08. 16. 13:01

안녕하세요 정보근입니다:)

 

접근 방법 위주로 설명드리겠습니다.

 

먼저 cannot resolve란 문구가 뜨는 걸로 봤을 때, 다음과 같은 문제를 의심해볼 수 있을 것 같습니다.

  1. 모델에 데이터가 안 넘어왔다.

  2. 모델에 잘못된 데이터가 넘어왔다.

  3. 뷰에서 적절하게 데이터를 핸들링하지 못했다.

1번의 경우 컨트롤러에서 resume를 넣어주고 있으니 데이터는 잘 넘어갔을 것 같고요.

그럼 2번, resume 데이터가 어떻게 들어갔는지 확인이 필요할 것 같습니다.

가장 간단한 방법은 컨트롤러에서 로그를 찍어보는 것입니다.

필드명에 오타가 없는지, 값은 null이 아닌지 등을 확인해보시면 될 것 같아요.

 

여기까지 문제가 없을 경우 서버의 문제는 아니고, 프론트의 문제일 가능성이 높습니다.

이 때 브라우저에서 F12(크롬 기준)를 누른 뒤,

개발자 도구의 console 탭에 들어가시면 에러 메시지가 있을텐데요.

여기서 힌트를 얻으실 수 있습니다.

 

제가 직접 볼 수 있는 게 아니라 추정만 해보자면,

fragement까지 experience 데이터가 안 넘어왔을 수 있을 것 같아요.

올려주신 fragment-card-experience.html은,

experiences 리스트의 각각의 experience에 대해서 풀어주는 화면입니다.

그보다 상위의 resume.html에서 아래 코드와 같이 experiences에 대해 반복을 돌며

각 experience를 fragment로 넣어주고 있는지 확인해보셔도 좋을 것 같아요.

<!-- Experience Cards -->
<th:block th:each="experience :{resume.experiences}">
    <div th:replace="~{presentation/fragments/fragment-card-experience :: card(${experience}) }"></div>
</th:block>

필요에 따라 자바스크립트로 experiences나 experience를 로그를 찍어보셔도 좋습니다.

 

이와 같이 접근하셨을 때 해결이 어려우시다면

깃허브 리포지토리를 공유해주시면 한 번 봐보겠습니다.

 

감사합니다.

김병곤님의 프로필 이미지
김병곤
질문자

2024. 08. 19. 14:47

안녕하세요 혼자 해결하려고 노력을 해봤는데 코드 하나하나 다봐봤는데 너무 안찾아지는데 혹시 도움을 주실수있을까요? https://github.com/kimauto/portfolio-kimauto

정보근님의 프로필 이미지
정보근
지식공유자

2024. 08. 19. 14:58

안녕하세요:)

프로젝트 클론해서 실행시켜 봤는데,

localhost:8080/resume로 접속할 경우 화면이 잘 나와서요.

아마 강의 따라하며 작업하시던 중에 resolve 관련 문제 있어서 질문 올리시고,

작업 중이시던 내용은 커밋 안 하신 것 같은데요.

별도로 브랜치 생성하거나 해서 리포지토리에 올려주시고 답글 주시면 다시 봐보겠습니다.

김병곤님의 프로필 이미지
김병곤
질문자

2024. 08. 19. 15:13

아 빠른 정답 감사합니다. 그 resume홈페이지는 잘나오는데 experience가 계속 cannot resolve가 떠서 현 상태 커밋은완료했습니다. 한번 처음부터 코드를 다시 다 작성 해볼까요?

정보근님의 프로필 이미지
정보근
지식공유자

2024. 08. 19. 15:27

2시 46분에 커밋하신 타임리프-템플릿수정(index)가 최신 맞을까요?

제가 해당 소스에서 resume.html만 아래와 같이 수정하면 잘 나오거든요.

experience가 resolve 안 된다는 게 resume 화면 조회할 때 얘기가 아닐까요?

fragment-card-experience까지 잘 동작하는걸로 보여서요.

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">

<div th:replace="~{presentation/fragments/fragment-head :: head}"></div>

<body class="d-flex flex-column h-100 bg-light">
<main class="flex-shrink-0" id="content">

    <!-- Page Content-->
    <div class="container px-5 my-5">
        <div class="text-center mb-5">
            <h1 class="display-5 fw-bolder mb-0"><span class="text-gradient d-inline">Resume</span></h1>
        </div>
        <div class="row gx-5 justify-content-center">
            <div class="col-lg-11 col-xl-9 col-xxl-8">
                <!-- Experience Section-->
                <section>
                    <div class="d-flex align-items-center justify-content-between mb-4">
                        <h2 class="text-primary fw-bolder mb-0">Experience</h2>
                    </div>
                    <!-- Experience Cards -->
                    <th:block th:each="experience : ${resume.experiences}">
                        <div th:replace="~{presentation/fragments/fragment-card-experience :: card(${experience}) }"></div>
                    </th:block>

                </section>
                <!-- Achievement Section-->
<!--                <section>-->
<!--                    <h2 class="text-secondary fw-bolder mb-4">Achievement</h2>-->
<!--                    &lt;!&ndash; Achievement Cards &ndash;&gt;-->
<!--                    <th:block th:each="achievement : ${resume.achievements}">-->
<!--                        <div th:replace="~{presentation/fragments/fragment-card-achievement :: card(${achievement}) }"></div>-->
<!--                    </th:block>-->

<!--                </section>-->
                <!-- Divider-->
                <div class="pb-5"></div>
                <!-- Skills Section-->
<!--                <section>-->
<!--                    &lt;!&ndash; Skillset Card&ndash;&gt;-->
<!--                    <div class="card shadow border-0 rounded-4 mb-5">-->
<!--                        <div class="card-body p-5">-->
<!--                            &lt;!&ndash; Skills list&ndash;&gt;-->
<!--                            <th:block th:each="skillType: ${skillTypes}">-->
<!--                                <div th:replace="~{presentation/fragments/fragment-list-skill :: list(${resume.skills}, ${skillType.name()}) }"></div>-->
<!--                            </th:block>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </section>-->
            </div>
        </div>
    </div>
</main>

</body>

</html>

 

조금 더 상황을 자세히 설명해주시거나,

전체 에러 로그를 주셔도 좋을 것 같습니다.

아니면 https://github.com/infomuscle/portfolio-yongback 에서 소스 받으셔서

현재 작업 중이신 강의의 커밋으로 리셋하신 후 비교해보셔도 도움될 것 같습니다.

 

김병곤님의 프로필 이미지
김병곤
질문자

2024. 08. 19. 17:14

혹시 다시 한번 해봐 주실수 있나요? 방금 커밋했구

https://github.com/kimauto/portfolio-kimauto

 

Exception evaluating SpringEL expression: "experience.startYearMonth" (template: "presentation/fragments/fragment-card-experience" - line 9, col 57)] with root cause 이런한 오류로 resume 페이지가 안열립니다. 죄송합니다. ㅠ

 

정보근님의 프로필 이미지
정보근
지식공유자

2024. 08. 19. 17:51

최종 버전에 fragement-card-experience.html이 삭제되어 있어서,

복구하고 실행시켜보니 Exception evaluating SpringEL expression: "experience.startYearMonth"와 같은 로그가 뜨네요.

 

이 상황에서 제 접근법을 설명드려보겠습니다.

일단 에러 로그를 보면 experience.startYearMonth를 찾지 못해서 에러가 발생하는 것으로 추정됩니다.

startYearMonth 필드의 문제인지, experience 자체의 문제인지 확인하기 위해 해당 라인을 주석 처리 해봤어요.

그랬더니 experience.description에 대해 똑같은 에러가 떠서 experience 데이터가 템플릿으로 제대로 전달되지 못하고 있다고 생각했습니다.

 

그럼 해당 fragment는 th:fragment="card(experience)"와 같이 선언되어있고,

card(experience)를 통해서 experience 데이터를 받죠?

이 fragment를 호출하는 곳에서 제대로 데이터를 넣어주는지 확인하기 위해 resume.html을 봤습니다.

 

그랬더니 아래와 같이 선언되어 있었어요.

<th:block th:each="experience : ${resume.experiences}">
    <div th:replace="~{presentation/fragments/fragment-card-experience :: card(experience)}"></div>
</th:block>

 

여기서 card(experience) 부분만 card(${experience})로 바꿔주니

resume 페이지가 정상 조회됩니다.

 

이제 설명을 드리면,

th:block 태그에서 th:each 속성을 활용해서 resume.experiences 리스트의 각각 요소를 반복하는데,

이 요소를 experience라고 저희가 선언했습니다.

그런데 th:block 태그 안에서 해당 요소를 선택하려면 ${}로 감싸줘야 합니다.

이건 타임리프 문법적인 부분이고요.

그런데 단순히 experience를 넣어줬으니 리스트 안의 experience 데이터가 아니고

experience라는 문자열이 들어가게 된겁니다.

문자열은 startYearMonth 같은 필드가 없으니 오류가 발생했고요.

 

이 부분을 코틀린 코드로 표현하자면 다음 코드 중 아래와 같은 상황이었다고 보시면 이해가 되실 것 같아요.

// 정상적으로 experience 데이터를 넣는 경우
for (experience in resume.experiences) {
    println(experience)
}

// experiences 리스트에 대해 반복을 돌면서 "experience" 문자열을 넣는 경우
for (experience in resume.experiences) {
    println("experience")
}

 

 

아니면 card(${experience})가 아니고 card(experience)라고 선언한 상태에서,

fragement-card-experience.html을 다음과 같이 바꿔보세요.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="card shadow border-0 rounded-4 mb-5" th:fragment="card(experience)">
    <div class="card-body p-5">
        <div class="row align-items-center gx-5">
            <div th:text="${experience}"></div>
        </div>
    </div>
</div>
</body>
</html>

 

fragment에 들어온 데이터가 experience 객체가 아니고 "experience"라는 문자열이었단 걸 확인하실 수 있습니다.

김병곤님의 프로필 이미지
김병곤
질문자

2024. 08. 19. 19:40

해결했습니다. 정말 친절하게 답변해주셔서 감사합니다. 앞으로는 좀 더 정확하게 해결해보겠습니다. 덕분에 공부에 지친 마음에 힘이됩니다. 아직 제가 전체적인 지식이 많이 부족한 것같습니다. 혼자 해결하려는 습관을 기르도록하겠습니다! 감사합니다!!

0

인프런 AI 인턴님의 프로필 이미지

2024. 08. 16. 12:36

안녕하세요, 인프런 AI 인턴입니다.

김병곤님의 질문을 바탕으로 하여, Thymeleaf 템플릿에서 모델이 제대로 인식되지 않는 문제에 대해 유사한 해결 방안을 제시했던 링크를 찾았습니다. 문제 해결에 도움이 될 수 있을 것 같아 추천드립니다.


  • 섹션3 컨트롤러테스트 오류에서는 Thymeleaf 사용시 데이터 바인딩과 관련된 문제에 대해 대화형으로 문제를 해결하는 과정을 볼 수 있습니다. 특히 Thymeleaf에서 JSON 데이터를 인식하고 처리하는 부분에 문제가 생겼을 때, 데이터 객체의 필드명 확인이라는 기본적이면서도 중요한 해결 방안을 제시합니다. 이런 방법은 Thymeleaf에서 객체를 올바르게 인식하지 못하는 경우 적용해볼 수 있는 일반적인 해결책이 될 수 있습니다.

제시된 사례와 유사하게, 김병곤님의 문제도 Thymeleaf 템플릿에서 experience 객체의 startYearMonth, endYearMonth 등의 프로퍼티를 인식하지 못하는 것으로 보입니다. 이 경우, 컨트롤러에서 Model 객체에 experience 객체 또는 이를 포함하는 resume 객체를 정확하게 전달하고 있는지, DTO에서 해당 프로퍼티를 정의하였는지 등을 확인할 필요가 있습니다.

또한, Thymeleaf 템플릿에서 객체를 정상적으로 참조하기 위해서는 컨트롤러, 서비스, DTO 간의 데이터 전달이 올바르게 이루어지고 있는지 점검하는 것이 중요합니다. 특히 Model 객체에 데이터를 추가할 때 사용하는 키 값들이 뷰(Template)에서 사용하는 이름과 일치하는지 확인해보세요.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.