<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hwang-dev</title>
    <link>https://hwang-dev.tistory.com/</link>
    <description>더 나은 개발자가 되기 위해 
꾸준히 공부하고 기록합니다.</description>
    <language>ko</language>
    <pubDate>Sat, 6 Jun 2026 13:58:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hwang-dev</managingEditor>
    <image>
      <title>hwang-dev</title>
      <url>https://tistory1.daumcdn.net/tistory/8485369/attach/b782f9d89b024dc3af198f42593cb7c6</url>
      <link>https://hwang-dev.tistory.com</link>
    </image>
    <item>
      <title>[Spring Boot] Thymeleaf 자주 사용하는 기본 문법 정리</title>
      <link>https://hwang-dev.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 Thymeleaf가 무엇인지, 서버 사이드 템플릿 엔진이 어떤 방식으로 동작하는지 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Thymeleaf를 사용할 때 자주 마주치는 기본 문법을 정리해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf는 HTML 태그에 th:* 형태의 속성을 추가해서 사용한다.&lt;br /&gt;처음 보면 낯설 수 있지만, 결국 핵심은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Controller에서 Model에 데이터 저장
        &amp;darr;
Thymeleaf HTML에서 Model 데이터 사용
        &amp;darr;
서버에서 HTML 렌더링
        &amp;darr;
브라우저에 완성된 HTML 응답
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 오류신고 관리 시스템 예시를 기준으로 다음 문법들을 정리한다.&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;- th:text
- th:if
- th:unless
- th:each
- th:href
- th:action
- th:value
- th:object
- th:field
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제에서 사용할 데이터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 예제에서 사용할 상황을 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류신고 목록 화면을 만든다고 가정한다.&lt;br /&gt;Controller에서는 오류신고 목록 데이터를 조회해서 Model에 담는다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports&quot;)
public String reportList(Model model) {
    List&amp;lt;Report&amp;gt; reports = reportService.findAll();

    model.addAttribute(&quot;reports&quot;, reports);

    return &quot;reports/list&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 의미는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;/reports 요청을 받는다.
        &amp;darr;
오류신고 목록을 조회한다.
        &amp;darr;
reports라는 이름으로 Model에 담는다.
        &amp;darr;
templates/reports/list.html 화면을 반환한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Controller에서 Thymeleaf로 데이터 전달.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdbKrB/dJMcaffL7JF/y53KsA4XU3Gj3LGcKoF5Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdbKrB/dJMcaffL7JF/y53KsA4XU3Gj3LGcKoF5Wk/img.png&quot; data-alt=&quot;Controller에서 Model에 담은 데이터는 Thymeleaf 템플릿에서 ${...} 표현식으로 사용할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdbKrB/dJMcaffL7JF/y53KsA4XU3Gj3LGcKoF5Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdbKrB%2FdJMcaffL7JF%2Fy53KsA4XU3Gj3LGcKoF5Wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;802&quot; data-filename=&quot;Controller에서 Thymeleaf로 데이터 전달.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Controller에서 Model에 담은 데이터는 Thymeleaf 템플릿에서 ${...} 표현식으로 사용할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 reports/list.html에서는 ${reports}라는 이름으로 데이터를 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thymeleaf 표현식 기본&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf에서는 서버에서 전달받은 데이터를 ${...} 형태로 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:text=&quot;${username}&quot;&amp;gt;사용자명&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 ${username}은 Controller에서 Model에 담은 username 값을 의미한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;model.addAttribute(&quot;username&quot;, &quot;길동&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 값을 전달하면 Thymeleaf는 HTML을 렌더링할 때 ${username} 부분을 &quot;길동&quot;으로 바꿔준다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;길동&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;표현식&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;${username}&lt;/td&gt;
&lt;td&gt;Model에 담긴 username 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;${report.title}&lt;/td&gt;
&lt;td&gt;report 객체의 title 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;${reports}&lt;/td&gt;
&lt;td&gt;Model에 담긴 reports 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@{/reports}&lt;/td&gt;
&lt;td&gt;URL 경로 표현식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 가장 자주 사용하는 ${...}와 @{...}를 중심으로 살펴본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:text - 텍스트 출력하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:text는 값을 화면에 출력할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 HTML에서는 값을 직접 작성한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;사용자명&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf에서는 다음과 같이 작성한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:text=&quot;${username}&quot;&amp;gt;사용자명&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 다음과 같이 데이터를 전달했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;model.addAttribute(&quot;username&quot;, &quot;길동&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 서버에서 렌더링된 최종 HTML은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;길동&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, th:text는 태그 내부의 기본 텍스트를 서버에서 전달받은 값으로 대체한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류신고 제목 출력 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류신고 상세 화면에서는 제목, 내용, 처리상태 등을 출력할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;h2 th:text=&quot;${report.title}&quot;&amp;gt;오류신고 제목&amp;lt;/h2&amp;gt;
&amp;lt;p th:text=&quot;${report.content}&quot;&amp;gt;오류신고 내용&amp;lt;/p&amp;gt;
&amp;lt;span th:text=&quot;${report.status}&quot;&amp;gt;처리상태&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서는 다음과 같이 report 데이터를 넘겨준다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports/{id}&quot;)
public String reportDetail(@PathVariable Long id, Model model) {
    Report report = reportService.findById(id);

    model.addAttribute(&quot;report&quot;, report);

    return &quot;reports/detail&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 다음과 같다면,&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;title = &quot;로그인 오류&quot;
content = &quot;로그인 시 오류가 발생합니다.&quot;
status = &quot;접수&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 HTML은 다음과 비슷하게 렌더링된다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;h2&amp;gt;로그인 오류&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;로그인 시 오류가 발생합니다.&amp;lt;/p&amp;gt;
&amp;lt;span&amp;gt;접수&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:if - 조건이 참일 때만 출력하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:if는 조건이 참일 때만 해당 태그를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인한 사용자에게만 로그아웃 버튼을 보여주고 싶다면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;a th:if=&quot;${loginUser != null}&quot; href=&quot;/logout&quot;&amp;gt;로그아웃&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loginUser가 존재하면 로그아웃 링크가 출력되고,&lt;br /&gt;loginUser가 없으면 해당 태그는 출력되지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관리자 메뉴 출력 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자에게만 관리자 페이지 링크를 보여주고 싶다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;a th:if=&quot;${user.role == 'ADMIN'}&quot; href=&quot;/admin/reports&quot;&amp;gt;
    관리자 페이지
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user.role 값이 ADMIN이면 관리자 페이지 링크가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 일반 사용자라면 해당 링크는 HTML 결과에 포함되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;user.role == 'ADMIN'
        &amp;darr; true
관리자 페이지 링크 출력

user.role == 'USER'
        &amp;darr; false
관리자 페이지 링크 출력 안 됨
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;처리상태에 따른 버튼 출력 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류신고가 아직 처리되지 않은 상태일 때만 답변 등록 버튼을 보여줄 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;button th:if=&quot;${report.status == '접수'}&quot;&amp;gt;
    답변 등록
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 관리자 화면에서 처리 완료된 건은 수정 버튼만 보여줄 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;button th:if=&quot;${report.status == '완료'}&quot;&amp;gt;
    답변 수정
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:if는 로그인 여부, 권한, 처리상태처럼 조건에 따라 화면을 다르게 보여줄 때 자주 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:unless - 조건이 거짓일 때 출력하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:unless는 th:if의 반대라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 거짓일 때 해당 태그를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 오류신고 목록이 비어 있을 때 안내 문구를 보여주고 싶다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:unless=&quot;${#lists.isEmpty(reports)}&quot;&amp;gt;
    오류신고가 존재합니다.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 위 코드는 목록이 비어 있지 않을 때 출력된다.&lt;br /&gt;목록이 비어 있을 때 문구를 보여주려면 다음처럼 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:if=&quot;${#lists.isEmpty(reports)}&quot;&amp;gt;
    등록된 오류신고가 없습니다.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 th:unless를 사용하면 다음과 같이 쓸 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:unless=&quot;${!#lists.isEmpty(reports)}&quot;&amp;gt;
    등록된 오류신고가 없습니다.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개인적으로는 초반에는 th:unless보다 th:if를 명확하게 쓰는 편이 읽기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:if=&quot;${#lists.isEmpty(reports)}&quot;&amp;gt;
    등록된 오류신고가 없습니다.
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무 코드에서도 조건이 복잡해지면 unless보다 if로 명확하게 표현하는 것이 더 읽기 쉬울 때가 많다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:each - 목록 반복 출력하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:each는 목록 데이터를 반복 출력할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류신고 목록 화면에서는 여러 건의 오류신고를 테이블 형태로 출력해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 HTML에서는 다음과 같이 직접 여러 줄을 작성해야 한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;1&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;로그인 오류&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;접수&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;2&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;지도 화면 오류&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;완료&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 데이터는 DB에서 조회되기 때문에 몇 건이 나올지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 th:each를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;tr th:each=&quot;report : ${reports}&quot;&amp;gt;
    &amp;lt;td th:text=&quot;${report.id}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
    &amp;lt;td th:text=&quot;${report.title}&quot;&amp;gt;제목&amp;lt;/td&amp;gt;
    &amp;lt;td th:text=&quot;${report.status}&quot;&amp;gt;처리상태&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 이 부분이다.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;th:each=&quot;report : ${reports}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 향상된 for문과 비슷하게 보면 된다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;for (Report report : reports) {
    // report 사용
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, ${reports} 목록에서 데이터를 하나씩 꺼내 report라는 이름으로 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;th_each 반복구조.drawio.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3ZyP4/dJMcaipZtBq/IMbkmrjEgyjcqDA4qnzpD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3ZyP4/dJMcaipZtBq/IMbkmrjEgyjcqDA4qnzpD0/img.png&quot; data-alt=&quot;th:each는 목록 데이터를 하나씩 꺼내 반복 영역을 생성한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3ZyP4/dJMcaipZtBq/IMbkmrjEgyjcqDA4qnzpD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3ZyP4%2FdJMcaipZtBq%2FIMbkmrjEgyjcqDA4qnzpD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;802&quot; data-filename=&quot;th_each 반복구조.drawio.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;th:each는 목록 데이터를 하나씩 꺼내 반복 영역을 생성한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;반복 상태값 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:each에서는 반복 상태값도 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;tr th:each=&quot;report, stat : ${reports}&quot;&amp;gt;
    &amp;lt;td th:text=&quot;${stat.count}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
    &amp;lt;td th:text=&quot;${report.title}&quot;&amp;gt;제목&amp;lt;/td&amp;gt;
    &amp;lt;td th:text=&quot;${report.status}&quot;&amp;gt;처리상태&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 stat은 반복 상태 정보를 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;속성&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stat.count&lt;/td&gt;
&lt;td&gt;1부터 시작하는 순번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stat.first&lt;/td&gt;
&lt;td&gt;첫 번째 요소인지 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stat.last&lt;/td&gt;
&lt;td&gt;마지막 요소인지 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stat.even&lt;/td&gt;
&lt;td&gt;짝수 번째 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stat.odd&lt;/td&gt;
&lt;td&gt;홀수 번째 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록 화면에서 번호를 출력할 때는 보통 stat.count를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;td th:text=&quot;${stat.count}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 DB의 id 값과 별개로 화면에 1, 2, 3, 4 순번을 출력할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:href - 동적 링크 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:href는 링크 URL을 동적으로 만들 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 HTML에서는 다음과 같이 고정된 링크를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;/reports/1&quot;&amp;gt;상세보기&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 목록 화면에서는 각 오류신고의 id가 다르다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;/reports/1
/reports/2
/reports/3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 th:href를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;a th:href=&quot;@{/reports/{id}(id=${report.id})}&quot;&amp;gt;
    상세보기
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 report.id가 10이라면 최종 HTML은 다음과 같이 만들어진다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;/reports/10&quot;&amp;gt;
    상세보기
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류신고 제목을 클릭하면 상세 화면으로 이동하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록 화면에서는 제목을 클릭했을 때 상세 화면으로 이동하도록 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;tr th:each=&quot;report : ${reports}&quot;&amp;gt;
    &amp;lt;td th:text=&quot;${report.id}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
        &amp;lt;a th:href=&quot;@{/reports/{id}(id=${report.id})}&quot;
           th:text=&quot;${report.title}&quot;&amp;gt;
            제목
        &amp;lt;/a&amp;gt;
    &amp;lt;/td&amp;gt;
    &amp;lt;td th:text=&quot;${report.status}&quot;&amp;gt;처리상태&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 th:href는 상세 페이지 URL을 만들고,&lt;br /&gt;th:text는 링크에 표시할 제목을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;report.id = 10
report.title = &quot;로그인 오류&quot;

        &amp;darr; 렌더링

&amp;lt;a href=&quot;/reports/10&quot;&amp;gt;로그인 오류&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:action - form 요청 URL 설정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:action은 form 요청을 보낼 URL을 설정할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 오류신고 등록 화면이 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;form th:action=&quot;@{/reports}&quot; method=&quot;post&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;title&quot; placeholder=&quot;제목&quot;&amp;gt;
    &amp;lt;textarea name=&quot;content&quot; placeholder=&quot;내용&quot;&amp;gt;&amp;lt;/textarea&amp;gt;

    &amp;lt;button type=&quot;submit&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 사용자가 입력한 데이터를 POST /reports로 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서는 다음과 같이 요청을 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@PostMapping(&quot;/reports&quot;)
public String createReport(ReportCreateRequest request) {
    reportService.create(request);

    return &quot;redirect:/reports&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;GET /reports/new
오류신고 등록 화면 조회

        &amp;darr; 사용자가 입력 후 등록 버튼 클릭

POST /reports
오류신고 등록 처리

        &amp;darr;

redirect:/reports
목록 화면으로 이동
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;등록 화면과 등록 처리 흐름.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHAYz/dJMcaffMmHL/6MlDg7Jt9DuvrLCkNW7YZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHAYz/dJMcaffMmHL/6MlDg7Jt9DuvrLCkNW7YZ0/img.png&quot; data-alt=&quot;th:action은 form 전송 URL을 지정하고, 등록 처리 후에는 목록 화면으로 redirect할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHAYz/dJMcaffMmHL/6MlDg7Jt9DuvrLCkNW7YZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHAYz%2FdJMcaffMmHL%2F6MlDg7Jt9DuvrLCkNW7YZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;802&quot; data-filename=&quot;등록 화면과 등록 처리 흐름.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;th:action은 form 전송 URL을 지정하고, 등록 처리 후에는 목록 화면으로 redirect할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:action은 회원가입, 로그인, 게시글 등록, 관리자 답변 등록처럼 form 전송이 필요한 화면에서 자주 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:value - input 값 설정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:value는 input 태그의 값을 설정할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 오류신고 수정 화면에서 기존 제목을 input에 보여주고 싶다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; name=&quot;title&quot; th:value=&quot;${report.title}&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 report.title 값이 &quot;로그인 오류&quot;라면 최종 HTML은 다음과 같이 렌더링된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; name=&quot;title&quot; value=&quot;로그인 오류&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 화면에서는 기존 데이터를 input에 미리 채워 넣어야 하기 때문에 th:value를 자주 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;form th:action=&quot;@{/reports/{id}(id=${report.id})}&quot; method=&quot;post&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;title&quot; th:value=&quot;${report.title}&quot;&amp;gt;
    &amp;lt;textarea name=&quot;content&quot; th:text=&quot;${report.content}&quot;&amp;gt;&amp;lt;/textarea&amp;gt;

    &amp;lt;button type=&quot;submit&quot;&amp;gt;수정&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 form 객체 바인딩을 사용하면 th:value보다 th:field를 더 많이 사용하게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:object - form 객체 연결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:object는 form에서 사용할 객체를 지정할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 오류신고 등록 폼에서 reportForm 객체를 사용한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 다음과 같이 빈 form 객체를 전달한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports/new&quot;)
public String reportForm(Model model) {
    model.addAttribute(&quot;reportForm&quot;, new ReportForm());

    return &quot;reports/new&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML에서는 th:object로 form 객체를 연결한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;form th:action=&quot;@{/reports}&quot; th:object=&quot;${reportForm}&quot; method=&quot;post&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; th:field=&quot;*{title}&quot; placeholder=&quot;제목&quot;&amp;gt;
    &amp;lt;textarea th:field=&quot;*{content}&quot; placeholder=&quot;내용&quot;&amp;gt;&amp;lt;/textarea&amp;gt;

    &amp;lt;button type=&quot;submit&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 th:object=&quot;${reportForm}&quot;은 이 form에서 사용할 기준 객체가 reportForm이라는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내부에서는 *{title}, *{content}처럼 객체의 필드를 간단하게 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;th:object=&quot;${reportForm}&quot;
        &amp;darr;
*{title}   &amp;rarr; reportForm.title
*{content} &amp;rarr; reportForm.content
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;th:field - form 필드 바인딩하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:field는 form 객체의 필드와 input, textarea 같은 입력 요소를 연결할 때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; th:field=&quot;*{title}&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 reportForm 객체의 title 필드와 input을 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:field를 사용하면 Thymeleaf가 name, id, value 같은 속성을 자동으로 처리해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;form th:object=&quot;${reportForm}&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; th:field=&quot;*{title}&quot;&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 결과는 다음과 비슷하게 만들어진다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; id=&quot;title&quot; name=&quot;title&quot; value=&quot;&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 화면에서 reportForm.title 값이 존재한다면 value에도 기존 값이 들어간다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; id=&quot;title&quot; name=&quot;title&quot; value=&quot;로그인 오류&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, th:field는 form 입력값을 객체의 필드와 연결할 때 사용하는 문법이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;th_object와 th_field 관계.drawio.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J5btp/dJMcacQO4zG/LttXXkeqh3bU3vJTuRNYw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J5btp/dJMcacQO4zG/LttXXkeqh3bU3vJTuRNYw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J5btp/dJMcacQO4zG/LttXXkeqh3bU3vJTuRNYw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ5btp%2FdJMcacQO4zG%2FLttXXkeqh3bU3vJTuRNYw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;802&quot; data-filename=&quot;th_object와 th_field 관계.drawio.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류신고 등록 폼 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 본 문법을 사용해서 오류신고 등록 폼을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Controller에서 form 객체를 전달한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports/new&quot;)
public String reportForm(Model model) {
    model.addAttribute(&quot;reportForm&quot;, new ReportForm());

    return &quot;reports/new&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록 요청을 처리하는 Controller는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@PostMapping(&quot;/reports&quot;)
public String createReport(ReportForm reportForm) {
    reportService.create(reportForm);

    return &quot;redirect:/reports&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Thymeleaf HTML은 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;form th:action=&quot;@{/reports}&quot; th:object=&quot;${reportForm}&quot; method=&quot;post&quot;&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;title&quot;&amp;gt;제목&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; th:field=&quot;*{title}&quot; placeholder=&quot;제목을 입력하세요&quot;&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;content&quot;&amp;gt;내용&amp;lt;/label&amp;gt;
        &amp;lt;textarea th:field=&quot;*{content}&quot; placeholder=&quot;내용을 입력하세요&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;button type=&quot;submit&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 사용한 Thymeleaf 문법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문법&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:action&lt;/td&gt;
&lt;td&gt;form 요청 URL 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:object&lt;/td&gt;
&lt;td&gt;form 객체 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:field&lt;/td&gt;
&lt;td&gt;form 객체의 필드와 입력 요소 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류신고 등록 화면처럼 입력 폼이 필요한 경우에는 th:action, th:object, th:field를 함께 사용하는 경우가 많다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류신고 목록 화면 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 오류신고 목록 화면 예시를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서는 목록 데이터를 Model에 담는다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports&quot;)
public String reportList(Model model) {
    List&amp;lt;Report&amp;gt; reports = reportService.findAll();

    model.addAttribute(&quot;reports&quot;, reports);

    return &quot;reports/list&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML에서는 th:each로 목록을 반복 출력한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;table&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;번호&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;제목&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;처리상태&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;상세&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;

    &amp;lt;tbody&amp;gt;
        &amp;lt;tr th:each=&quot;report, stat : ${reports}&quot;&amp;gt;
            &amp;lt;td th:text=&quot;${stat.count}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
            &amp;lt;td th:text=&quot;${report.title}&quot;&amp;gt;제목&amp;lt;/td&amp;gt;
            &amp;lt;td th:text=&quot;${report.status}&quot;&amp;gt;접수&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;
                &amp;lt;a th:href=&quot;@{/reports/{id}(id=${report.id})}&quot;&amp;gt;
                    상세보기
                &amp;lt;/a&amp;gt;
            &amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 사용한 문법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문법&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:each&lt;/td&gt;
&lt;td&gt;오류신고 목록 반복&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:text&lt;/td&gt;
&lt;td&gt;번호, 제목, 상태 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:href&lt;/td&gt;
&lt;td&gt;상세 페이지 링크 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록 화면에서는 th:each, th:text, th:href 조합을 자주 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자주 사용하는 문법 한 번에 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 살펴본 문법을 한 번에 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문법&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:text&lt;/td&gt;
&lt;td&gt;텍스트 출력&lt;/td&gt;
&lt;td&gt;&amp;lt;p th:text=&quot;${report.title}&quot;&amp;gt;제목&amp;lt;/p&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:if&lt;/td&gt;
&lt;td&gt;조건이 참일 때 출력&lt;/td&gt;
&lt;td&gt;&amp;lt;button th:if=&quot;${report.status == '접수'}&quot;&amp;gt;답변 등록&amp;lt;/button&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:unless&lt;/td&gt;
&lt;td&gt;조건이 거짓일 때 출력&lt;/td&gt;
&lt;td&gt;&amp;lt;p th:unless=&quot;${loginUser != null}&quot;&amp;gt;로그인이 필요합니다.&amp;lt;/p&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:each&lt;/td&gt;
&lt;td&gt;반복 출력&lt;/td&gt;
&lt;td&gt;&amp;lt;tr th:each=&quot;report : ${reports}&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:href&lt;/td&gt;
&lt;td&gt;링크 URL 설정&lt;/td&gt;
&lt;td&gt;&amp;lt;a th:href=&quot;@{/reports/{id}(id=${report.id})}&quot;&amp;gt;상세&amp;lt;/a&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:action&lt;/td&gt;
&lt;td&gt;form 요청 URL 설정&lt;/td&gt;
&lt;td&gt;&amp;lt;form th:action=&quot;@{/reports}&quot; method=&quot;post&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:value&lt;/td&gt;
&lt;td&gt;input 값 설정&lt;/td&gt;
&lt;td&gt;&amp;lt;input th:value=&quot;${report.title}&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:object&lt;/td&gt;
&lt;td&gt;form 객체 연결&lt;/td&gt;
&lt;td&gt;&amp;lt;form th:object=&quot;${reportForm}&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:field&lt;/td&gt;
&lt;td&gt;form 필드 바인딩&lt;/td&gt;
&lt;td&gt;&amp;lt;input th:field=&quot;*{title}&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf는 HTML 태그에 th:* 속성을 추가해서 서버 데이터를 화면에 출력하거나, 조건 처리, 반복 처리, URL 생성, form 바인딩 등을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 정리한 핵심 문법은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;값 출력       &amp;rarr; th:text
조건 출력     &amp;rarr; th:if, th:unless
반복 출력     &amp;rarr; th:each
링크 생성     &amp;rarr; th:href
form 요청     &amp;rarr; th:action
input 값 설정 &amp;rarr; th:value
form 바인딩   &amp;rarr; th:object, th:field
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 문법이 많아 보이지만, 실제로 CRUD 화면을 만들 때 자주 쓰는 조합은 어느 정도 정해져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록 화면에서는 주로 다음 조합을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;th:each + th:text + th:href
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록/수정 화면에서는 주로 다음 조합을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;th:action + th:object + th:field
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Thymeleaf를 처음 공부할 때는 모든 문법을 한 번에 외우려고 하기보다,&lt;br /&gt;목록 화면과 등록 화면을 직접 만들면서 필요한 문법을 익히는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 다시 오류신고 관리 시스템 프로젝트로 돌아가서, 실제 화면 흐름을 기준으로 사용자 URL과 관리자 URL을 나누어 API/URL 설계서를 정리해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thymeleaf.org/&quot;&gt;Thymeleaf 공식 사이트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thymeleaf.org/documentation.html&quot;&gt;Thymeleaf 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html&quot;&gt;Thymeleaf 공식 튜토리얼 - Using Thymeleaf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-thymeleaf.html&quot;&gt;Spring Framework 공식 문서 - Thymeleaf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc-view.html&quot;&gt;Spring Framework 공식 문서 - View Technologies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/Spring</category>
      <category>Spring Boot</category>
      <category>th:each</category>
      <category>th:field</category>
      <category>th:text</category>
      <category>thymeleaf</category>
      <category>Thymeleaf 문법</category>
      <category>템플릿엔진</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/18</guid>
      <comments>https://hwang-dev.tistory.com/18#entry18comment</comments>
      <pubDate>Mon, 1 Jun 2026 17:06:56 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Thymeleaf란? 서버 사이드 템플릿 엔진 개념 정리</title>
      <link>https://hwang-dev.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Spring Boot로 웹 프로젝트를 만들다 보면 화면을 어떤 방식으로 구성할지 고민하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTML만 사용할 수도 있고, JSP를 사용할 수도 있으며, React나 Vue 같은 프론트엔드 프레임워크를 사용할 수도 있다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;그리고 Spring Boot에서 자주 사용되는 선택지 중 하나가 바로 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Thymeleaf&lt;/span&gt;&lt;/b&gt;&lt;span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 글에서는 Thymeleaf가 무엇인지, 어떤 방식으로 동작하는지, Controller와 어떻게 연결되는지 정리해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Thymeleaf란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf는 Java 기반의 &lt;/span&gt;&lt;b&gt;&lt;span&gt;서버 사이드 템플릿 엔진&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 템플릿 엔진이란, 서버에서 준비한 데이터를 HTML에 합쳐서 최종 화면을 만들어주는 도구를 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 사용자 이름을 화면에 출력한다고 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반 HTML은 다음과 같이 고정된 값만 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;사용자명&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 실제 서비스에서는 로그인한 사용자에 따라 이름이 달라져야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;아구몬&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;파피몬&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;파닥몬&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이처럼 서버에서 조회한 데이터를 HTML에 넣어 화면에 보여주기 위해 사용하는 것이 템플릿 엔진이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf를 사용하면 HTML 안에서 다음과 같이 서버 데이터를 출력할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:text=&quot;${username}&quot;&amp;gt;사용자명&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 &lt;/span&gt;&lt;span&gt;th:text&lt;/span&gt;&lt;span&gt;는 Thymeleaf 문법이다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Controller에서 &lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;이라는 이름으로 데이터를 넘기면, Thymeleaf가 해당 값을 HTML에 넣어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 서버에서 &lt;/span&gt;&lt;span&gt;username = &quot;아구몬&quot;&lt;/span&gt;&lt;span&gt;이라는 값이 전달되면 최종 HTML은 다음과 같이 만들어진다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;아구몬&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, Thymeleaf는 HTML 파일에 서버 데이터를 채워 넣어 최종 화면을 만들어주는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Thymeleaf는 브라우저가 실행하는 문법이 아니다&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf를 처음 보면 HTML에 &lt;/span&gt;&lt;span&gt;th:text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;th:each&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;th:if&lt;/span&gt;&lt;span&gt; 같은 속성이 붙어 있어서 브라우저가 이 문법을 해석한다고 생각할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 실제로는 그렇지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;브라우저는 Thymeleaf 문법을 모른다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;th:text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;th:each&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;th:if&lt;/span&gt;&lt;span&gt; 같은 문법은 서버에서 Thymeleaf가 먼저 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 다음과 같은 Thymeleaf 템플릿이 있다고 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:text=&quot;${username}&quot;&amp;gt;사용자명&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서버에서 &lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt; 값이 &lt;/span&gt;&lt;span&gt;&quot;아구몬&quot;&lt;/span&gt;&lt;span&gt;으로 전달되면, Thymeleaf는 이 템플릿을 다음과 같은 HTML로 변환한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;아구몬&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 브라우저는 변환이 끝난 HTML만 받는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정리하면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Thymeleaf 템플릿
&amp;lt;p th:text=&quot;${username}&quot;&amp;gt;사용자명&amp;lt;/p&amp;gt;

        &amp;darr; 서버에서 렌더링

브라우저가 받는 HTML
&amp;lt;p&amp;gt;아구몬&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, Thymeleaf는 브라우저에서 동작하는 기술이 아니라 &lt;/span&gt;&lt;b&gt;&lt;span&gt;서버에서 HTML을 만들어주는 기술&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Thymeleaf 동작 흐름&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf의 동작 흐름을 간단히 보면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;사용자 요청
    &amp;darr;
Controller
    &amp;darr;
Service / Repository
    &amp;darr;
DB 조회
    &amp;darr;
Model에 데이터 저장
    &amp;darr;
Thymeleaf HTML 렌더링
    &amp;darr;
브라우저에 HTML 응답&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Thymeleaf 동작 흐름.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ld1qE/dJMcajh5mCx/21wbGb7vkvVqg3eqmGRGdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ld1qE/dJMcajh5mCx/21wbGb7vkvVqg3eqmGRGdk/img.png&quot; data-alt=&quot;Thymeleaf는 Controller에서 전달한 Model 데이터를 HTML 템플릿에 적용 후, 완성된 HTML을 브라우저에 응답한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ld1qE/dJMcajh5mCx/21wbGb7vkvVqg3eqmGRGdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fld1qE%2FdJMcajh5mCx%2F21wbGb7vkvVqg3eqmGRGdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;621&quot; data-filename=&quot;Thymeleaf 동작 흐름.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Thymeleaf는 Controller에서 전달한 Model 데이터를 HTML 템플릿에 적용 후, 완성된 HTML을 브라우저에 응답한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자가 URL로 접근하면 Controller가 요청을 받는다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Controller는 필요한 데이터를 조회하고, 그 데이터를 &lt;/span&gt;&lt;span&gt;Model&lt;/span&gt;&lt;span&gt;에 담는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후 Thymeleaf는 &lt;/span&gt;&lt;span&gt;Model&lt;/span&gt;&lt;span&gt;에 담긴 데이터를 HTML 템플릿에 채워 넣고, 최종적으로 완성된 HTML을 브라우저에 응답한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 핵심은 브라우저가 받는 것이 Thymeleaf 파일 자체가 아니라는 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span&gt;브라우저는 서버에서 렌더링이 끝난 &lt;/span&gt;&lt;b&gt;&lt;span&gt;완성된 HTML&lt;/span&gt;&lt;/b&gt;&lt;span&gt;을 받는다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Spring Boot에서 Thymeleaf 파일 위치&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Spring Boot에서 Thymeleaf HTML 파일은 일반적으로 다음 경로에 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;src/main/resources/templates&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 프로젝트 구조는 다음과 같이 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;src
 └─ main
    └─ resources
       └─ templates
          ├─ index.html
          ├─ login.html
          └─ reports
             ├─ list.html
             └─ detail.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Controller에서 문자열을 반환하면 Spring Boot는 &lt;/span&gt;&lt;span&gt;templates&lt;/span&gt;&lt;span&gt; 폴더 아래에서 해당 HTML 파일을 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 다음 Controller를 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/login&quot;)
public String loginForm() {
    return &quot;login&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드에서 &lt;/span&gt;&lt;span&gt;return &quot;login&quot;;&lt;/span&gt;&lt;span&gt;은 다음 파일을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;src/main/resources/templates/login.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번에는 하위 폴더에 있는 파일을 반환해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports&quot;)
public String reportList() {
    return &quot;reports/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드에서 &lt;/span&gt;&lt;span&gt;return &quot;reports/list&quot;;&lt;/span&gt;&lt;span&gt;는 다음 파일을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;src/main/resources/templates/reports/list.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, Controller의 반환 문자열은 Thymeleaf HTML 파일 경로와 연결된다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Controller와 Thymeleaf 연결 방식&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf를 이해하려면 Controller, Model, HTML 파일의 관계를 같이 봐야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 오류신고 목록 화면을 만든다고 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/reports&quot;)
public String reportList(Model model) {
    List&amp;lt;Report&amp;gt; reports = reportService.findAll();
    model.addAttribute(&quot;reports&quot;, reports);
    return &quot;reports/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드는 다음과 같은 흐름으로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;1. /reports 요청을 받는다.
2. reportService.findAll()로 오류신고 목록을 조회한다.
3. 조회 결과를 reports라는 이름으로 Model에 담는다.
4. reports/list.html 화면을 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Controller와 Thymeleaf 연결 구조.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6L5BI/dJMcahYSCbQ/LXv1bbXCUmuvjqiWHjvngK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6L5BI/dJMcahYSCbQ/LXv1bbXCUmuvjqiWHjvngK/img.png&quot; data-alt=&quot;Controller는 Model에 데이터를 담고, 반환 문자열로 사용할 Thymeleaf 템플릿 경로를 지정한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6L5BI/dJMcahYSCbQ/LXv1bbXCUmuvjqiWHjvngK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6L5BI%2FdJMcahYSCbQ%2FLXv1bbXCUmuvjqiWHjvngK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;591&quot; data-filename=&quot;Controller와 Thymeleaf 연결 구조.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Controller는 Model에 데이터를 담고, 반환 문자열로 사용할 Thymeleaf 템플릿 경로를 지정한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제 &lt;/span&gt;&lt;span&gt;reports/list.html&lt;/span&gt;&lt;span&gt;에서는 Controller에서 넘긴 &lt;/span&gt;&lt;span&gt;reports&lt;/span&gt;&lt;span&gt; 데이터를 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;table&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;번호&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;제목&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;처리상태&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;

    &amp;lt;tr th:each=&quot;report : ${reports}&quot;&amp;gt;
        &amp;lt;td th:text=&quot;${report.id}&quot;&amp;gt;1&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&quot;${report.title}&quot;&amp;gt;제목&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&quot;${report.status}&quot;&amp;gt;처리상태&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 중요한 부분은 다음 두 줄이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;model.addAttribute(&quot;reports&quot;, reports);
return &quot;reports/list&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;model.addAttribute(&quot;reports&quot;, reports);&lt;/span&gt;&lt;span&gt;는 HTML에서 사용할 데이터를 담는 부분이고,&lt;/span&gt;&lt;br /&gt;&lt;span&gt;return &quot;reports/list&quot;;&lt;/span&gt;&lt;span&gt;는 어떤 HTML 화면을 보여줄지 결정하는 부분이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;JSP, React와는 뭐가 다를까?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf를 이해할 때 JSP, React와 비교하면 조금 더 쉽게 감이 온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;먼저 JSP와 Thymeleaf는 둘 다 서버에서 HTML을 만들어 브라우저에 응답한다는 점에서 비슷하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다만 JSP는 &lt;/span&gt;&lt;span&gt;.jsp&lt;/span&gt;&lt;span&gt; 파일을 사용하고, Thymeleaf는 &lt;/span&gt;&lt;span&gt;.html&lt;/span&gt;&lt;span&gt; 파일에 &lt;/span&gt;&lt;span&gt;th:*&lt;/span&gt;&lt;span&gt; 속성을 추가해서 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;JSP&lt;/td&gt;
&lt;td&gt;Thymeleaf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;파일 확장자&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;.jsp&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;.html&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;렌더링 위치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;서버&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;서버&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;작성 방식&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;JSP 태그, JSTL 사용&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;HTML 태그에 &lt;/span&gt;&lt;span&gt;th:*&lt;/span&gt;&lt;span&gt; 속성 사용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;주 사용 환경&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;기존 Spring MVC, eGovFrame&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Spring Boot 프로젝트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React와는 방식이 더 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf는 서버에서 HTML을 완성해서 브라우저에 전달한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;반면 React는 브라우저에서 JavaScript를 실행하고, API로 데이터를 받아 화면을 구성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Thymeleaf vs React 렌더링 방식 비교.png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKONYT/dJMcabLcZjZ/pjeh939CT1g8xhbGzTW0w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKONYT/dJMcabLcZjZ/pjeh939CT1g8xhbGzTW0w0/img.png&quot; data-alt=&quot;Thymeleaf는 서버에서 HTML을 완성해 브라우저에 응답하고, React는 브라우저에서 JavaScript를 실행해 화면을 구성한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKONYT/dJMcabLcZjZ/pjeh939CT1g8xhbGzTW0w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKONYT%2FdJMcabLcZjZ%2Fpjeh939CT1g8xhbGzTW0w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;821&quot; height=&quot;481&quot; data-filename=&quot;Thymeleaf vs React 렌더링 방식 비교.png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Thymeleaf는 서버에서 HTML을 완성해 브라우저에 응답하고, React는 브라우저에서 JavaScript를 실행해 화면을 구성한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;표로 정리하면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;구분&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Thymeleaf&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;React&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;화면 생성 위치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;서버&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;브라우저&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;데이터 전달 방식&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Model&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;API JSON&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;프로젝트 구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;백엔드와 화면이 한 프로젝트에 가까움&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;프론트엔드와 백엔드 분리 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;적합한 화면&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;CRUD, 게시판, 관리자 페이지&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;동적 UI가 많은 웹 애플리케이션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, Thymeleaf는 서버에서 HTML을 만들어주는 방식이고, React는 브라우저에서 화면을 구성하는 방식이라고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Thymeleaf를 사용하면 좋은 경우&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf는 다음과 같은 경우에 잘 어울린다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- Spring Boot로 웹 프로젝트를 처음 만들어볼 때
- 게시판, 관리자 페이지, CRUD 중심 화면을 만들 때
- 프론트엔드 분리보다 백엔드 흐름 학습이 우선일 때
- JSP 대신 Spring Boot에 어울리는 템플릿 엔진을 사용하고 싶을 때
- 로그인, 회원가입, 목록, 상세, 등록, 수정 화면을 빠르게 구현하고 싶을 때&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 오류신고 관리 시스템을 만든다고 하면 다음 화면들은 Thymeleaf로 충분히 구현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;사용자 화면
- 회원가입
- 로그인
- 오류신고 등록
- 내 오류신고 목록
- 오류신고 상세

관리자 화면
- 전체 오류신고 목록
- 오류신고 상세
- 처리상태 변경
- 답변 등록 / 수정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이런 화면들은 대부분 서버에서 데이터를 조회한 뒤 HTML로 보여주는 구조다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;따라서 Thymeleaf와 잘 어울린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반대로 화면 인터랙션이 복잡하거나, 프론트엔드와 백엔드를 완전히 분리해야 하거나, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;모바일 앱에서도 같은 API를 사용해야 한다면 React 같은 프론트엔드 프레임워크를 고려할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결국 중요한 것은 Thymeleaf가 더 좋은지 React가 더 좋은지를 단순 비교하는 것이 아니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;프로젝트의 목적과 화면의 성격에 맞게 선택하는 것이 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;정리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Thymeleaf는 Spring Boot에서 서버 사이드 화면을 구성할 때 자주 사용하는 템플릿 엔진이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Controller에서 데이터를 &lt;/span&gt;&lt;span&gt;Model&lt;/span&gt;&lt;span&gt;에 담아 전달하면, Thymeleaf는 해당 데이터를 HTML에 출력해서 최종 화면을 만들어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;핵심 흐름은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Controller에서 데이터 준비
    &amp;darr;
Model에 데이터 저장
    &amp;darr;
Thymeleaf HTML로 전달
    &amp;darr;
서버에서 HTML 렌더링
    &amp;darr;
브라우저에 응답&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>java</category>
      <category>Spring Boot</category>
      <category>thymeleaf</category>
      <category>Thymeleaf란</category>
      <category>서버사이드렌더링</category>
      <category>서버사이드템플릿엔진</category>
      <category>템플릿엔진</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/17</guid>
      <comments>https://hwang-dev.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 29 May 2026 16:51:53 +0900</pubDate>
    </item>
    <item>
      <title>[오류신고 관리 시스템] DB 설계서 작성하기</title>
      <link>https://hwang-dev.tistory.com/16</link>
      <description>&lt;p data-end=&quot;456&quot; data-start=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서는 오류신고 관리 시스템의 요구사항 정의서를 작성했다.&lt;/p&gt;
&lt;p data-end=&quot;565&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;요구사항 정의서를 통해 회원 기능, 사용자 오류신고 기능, 관리자 오류신고 기능, 공통 요구사항을 정리했다면,&lt;/p&gt;
&lt;p data-end=&quot;565&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 이 요구사항을 바탕으로 어떤 데이터를 저장해야 하는지 정리해보려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;565&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;567&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 db-schema.md 파일에 작성한 DB 설계서를 기준으로,&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;567&quot; data-ke-size=&quot;size16&quot;&gt;오류신고 관리 시스템의 테이블을 어떻게 나누었는지 정리해본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;670&quot; data-start=&quot;653&quot; data-section-id=&quot;o5zh1n&quot; data-ke-size=&quot;size26&quot;&gt;DB 설계서를 작성한 이유&lt;/h2&gt;
&lt;p data-end=&quot;700&quot; data-start=&quot;672&quot; data-ke-size=&quot;size16&quot;&gt;기능을 구현하기 전에 먼저 생각해야 할 것이 있다.&lt;/p&gt;
&lt;p data-end=&quot;727&quot; data-start=&quot;702&quot; data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;어떤 데이터를 저장할 것인가&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;727&quot; data-start=&quot;702&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;776&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;오류신고 관리 시스템에서는 단순히 오류신고 제목과 내용만 저장하면 되는 것이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;778&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같은 정보도 함께 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;778&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;필요 정보&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가&amp;nbsp;오류신고를&amp;nbsp;작성했는가? &lt;br /&gt;오류신고의&amp;nbsp;현재&amp;nbsp;처리상태는&amp;nbsp;무엇인가? &lt;br /&gt;관리자가&amp;nbsp;답변을&amp;nbsp;등록했는가? &lt;br /&gt;누가&amp;nbsp;답변을&amp;nbsp;등록했는가? &lt;br /&gt;언제&amp;nbsp;등록되고&amp;nbsp;언제&amp;nbsp;수정되었는가?&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;962&quot; data-start=&quot;896&quot; data-ke-size=&quot;size16&quot;&gt;이런 기준을 미리 정리하지 않으면, 기능을 구현하다가 컬럼을 계속 추가하거나 테이블 구조를 다시 수정해야 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1001&quot; data-start=&quot;964&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 프로젝트에서는 구현 전에 DB 설계서를 먼저 작성했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1024&quot; data-start=&quot;1008&quot; data-section-id=&quot;g9kyvs&quot; data-ke-size=&quot;size26&quot;&gt;1차 개발 범위의 테이블&lt;/h2&gt;
&lt;p data-end=&quot;1062&quot; data-start=&quot;1026&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 1차 개발에서는 테이블을 크게 두 개로 나누었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;테이블명&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;users&lt;/td&gt;
&lt;td&gt;사용자 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error_reports&lt;/td&gt;
&lt;td&gt;오류신고 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1186&quot; data-start=&quot;1136&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 너무 많은 테이블을 만들기보다, 1차 개발에 필요한 핵심 테이블만 먼저 설계했다.&lt;/p&gt;
&lt;p data-end=&quot;1227&quot; data-start=&quot;1188&quot; data-ke-size=&quot;size16&quot;&gt;users 테이블은 회원가입, 로그인, 권한 구분을 위해 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;1293&quot; data-start=&quot;1229&quot; data-ke-size=&quot;size16&quot;&gt;error_reports 테이블은 사용자가 등록한 오류신고와 관리자의 답변, 처리상태를 저장하기 위해 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;1293&quot; data-start=&quot;1229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;테이블구성도.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKxWv0/dJMcacXymqL/AJP7KJrutWjILjI5TzCT0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKxWv0/dJMcacXymqL/AJP7KJrutWjILjI5TzCT0K/img.png&quot; data-alt=&quot;테이블 구성도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKxWv0/dJMcacXymqL/AJP7KJrutWjILjI5TzCT0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKxWv0%2FdJMcacXymqL%2FAJP7KJrutWjILjI5TzCT0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;401&quot; data-filename=&quot;테이블구성도.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테이블 구성도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1677&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size16&quot;&gt;이번 DB 설계서에서는 1차 개발 범위의 테이블을 users, error_reports 두 개로 정리했다. users는 사용자 정보를 저장하고, error_reports는 사용자가 등록한 오류신고 정보를 저장하는 테이블로 설계했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1699&quot; data-start=&quot;1684&quot; data-section-id=&quot;s1m4h7&quot; data-ke-size=&quot;size26&quot;&gt;users 테이블 설계&lt;/h2&gt;
&lt;p data-end=&quot;1733&quot; data-start=&quot;1701&quot; data-ke-size=&quot;size16&quot;&gt;users 테이블은 사용자 정보를 저장하는 테이블이다.&lt;/p&gt;
&lt;p data-end=&quot;1749&quot; data-start=&quot;1735&quot; data-ke-size=&quot;size16&quot;&gt;주요 컬럼은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1895&quot; data-start=&quot;1751&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;컬럼명&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1789&quot; data-start=&quot;1774&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1779&quot; data-start=&quot;1774&quot;&gt;id&lt;/td&gt;
&lt;td data-end=&quot;1789&quot; data-start=&quot;1779&quot; data-col-size=&quot;sm&quot;&gt;사용자 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1812&quot; data-start=&quot;1790&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1801&quot; data-start=&quot;1790&quot;&gt;login_id&lt;/td&gt;
&lt;td data-end=&quot;1812&quot; data-start=&quot;1801&quot; data-col-size=&quot;sm&quot;&gt;로그인 아이디&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1837&quot; data-start=&quot;1813&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1824&quot; data-start=&quot;1813&quot;&gt;password&lt;/td&gt;
&lt;td data-end=&quot;1837&quot; data-start=&quot;1824&quot; data-col-size=&quot;sm&quot;&gt;암호화된 비밀번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1855&quot; data-start=&quot;1838&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1845&quot; data-start=&quot;1838&quot;&gt;name&lt;/td&gt;
&lt;td data-end=&quot;1855&quot; data-start=&quot;1845&quot; data-col-size=&quot;sm&quot;&gt;사용자 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1873&quot; data-start=&quot;1856&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1863&quot; data-start=&quot;1856&quot;&gt;role&lt;/td&gt;
&lt;td data-end=&quot;1873&quot; data-start=&quot;1863&quot; data-col-size=&quot;sm&quot;&gt;사용자 권한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1895&quot; data-start=&quot;1874&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1887&quot; data-start=&quot;1874&quot;&gt;created_at&lt;/td&gt;
&lt;td data-end=&quot;1895&quot; data-start=&quot;1887&quot; data-col-size=&quot;sm&quot;&gt;가입일시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1897&quot; data-ke-size=&quot;size16&quot;&gt;여기서 가장 중요한 컬럼은 login_id, password, role이다.&lt;/p&gt;
&lt;p data-end=&quot;1945&quot; data-start=&quot;1897&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1981&quot; data-start=&quot;1947&quot; data-ke-size=&quot;size16&quot;&gt;login_id는 사용자가 로그인할 때 입력하는 아이디다.&lt;/p&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;1983&quot; data-ke-size=&quot;size16&quot;&gt;로그인 아이디는 중복되면 안 되기 때문에 UNIQUE 제약조건을 두었다.&lt;/p&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;1983&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2055&quot; data-start=&quot;2027&quot; data-ke-size=&quot;size16&quot;&gt;password는 비밀번호를 저장하는 컬럼이다.&lt;/p&gt;
&lt;p data-end=&quot;2107&quot; data-start=&quot;2057&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호는 평문으로 저장하면 안 되기 때문에 암호화된 값을 저장하는 것을 기준으로 잡았다.&lt;/p&gt;
&lt;p data-end=&quot;2107&quot; data-start=&quot;2057&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2135&quot; data-start=&quot;2109&quot; data-ke-size=&quot;size16&quot;&gt;role은 사용자 권한을 저장하는 컬럼이다.&lt;/p&gt;
&lt;p data-end=&quot;2196&quot; data-start=&quot;2137&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 일반 사용자와 관리자를 구분해야 하므로, 권한 정보를 사용자 테이블에 저장하도록 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2226&quot; data-start=&quot;2203&quot; data-section-id=&quot;pxfcfn&quot; data-ke-size=&quot;size26&quot;&gt;error_reports 테이블 설계&lt;/h2&gt;
&lt;p data-end=&quot;2278&quot; data-start=&quot;2228&quot; data-ke-size=&quot;size16&quot;&gt;error_reports 테이블은 사용자가 등록한 오류신고 정보를 저장하는 테이블이다.&lt;/p&gt;
&lt;p data-end=&quot;2294&quot; data-start=&quot;2280&quot; data-ke-size=&quot;size16&quot;&gt;주요 컬럼은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2519&quot; data-start=&quot;2296&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;컬럼명&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2335&quot; data-start=&quot;2319&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2324&quot; data-start=&quot;2319&quot;&gt;id&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2335&quot; data-start=&quot;2324&quot;&gt;오류신고 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2356&quot; data-start=&quot;2336&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2346&quot; data-start=&quot;2336&quot;&gt;user_id&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2356&quot; data-start=&quot;2346&quot;&gt;작성자 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2371&quot; data-start=&quot;2357&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2365&quot; data-start=&quot;2357&quot;&gt;title&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2371&quot; data-start=&quot;2365&quot;&gt;제목&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2388&quot; data-start=&quot;2372&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2382&quot; data-start=&quot;2372&quot;&gt;content&lt;/td&gt;
&lt;td data-end=&quot;2388&quot; data-start=&quot;2382&quot; data-col-size=&quot;sm&quot;&gt;내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2406&quot; data-start=&quot;2389&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2398&quot; data-start=&quot;2389&quot;&gt;status&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2406&quot; data-start=&quot;2398&quot;&gt;처리상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2426&quot; data-start=&quot;2407&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2416&quot; data-start=&quot;2407&quot;&gt;answer&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2426&quot; data-start=&quot;2416&quot;&gt;관리자 답변&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2452&quot; data-start=&quot;2427&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2438&quot; data-start=&quot;2427&quot;&gt;admin_id&lt;/td&gt;
&lt;td data-end=&quot;2452&quot; data-start=&quot;2438&quot; data-col-size=&quot;sm&quot;&gt;답변한 관리자 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2475&quot; data-start=&quot;2453&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2467&quot; data-start=&quot;2453&quot;&gt;answered_at&lt;/td&gt;
&lt;td data-end=&quot;2475&quot; data-start=&quot;2467&quot; data-col-size=&quot;sm&quot;&gt;답변일시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2497&quot; data-start=&quot;2476&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2489&quot; data-start=&quot;2476&quot;&gt;created_at&lt;/td&gt;
&lt;td data-end=&quot;2497&quot; data-start=&quot;2489&quot; data-col-size=&quot;sm&quot;&gt;등록일시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2519&quot; data-start=&quot;2498&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2511&quot; data-start=&quot;2498&quot;&gt;updated_at&lt;/td&gt;
&lt;td data-end=&quot;2519&quot; data-start=&quot;2511&quot; data-col-size=&quot;sm&quot;&gt;수정일시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2578&quot; data-start=&quot;2521&quot; data-ke-size=&quot;size16&quot;&gt;이 테이블에서는 user_id, status, answer, admin_id가 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;2578&quot; data-start=&quot;2521&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2611&quot; data-start=&quot;2580&quot; data-ke-size=&quot;size16&quot;&gt;user_id는 오류신고를 작성한 사용자를 나타낸다.&lt;/p&gt;
&lt;p data-end=&quot;2672&quot; data-start=&quot;2613&quot; data-ke-size=&quot;size16&quot;&gt;일반 사용자는 본인이 작성한 오류신고만 조회할 수 있어야 하므로, 오류신고와 작성자 정보를 연결해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2672&quot; data-start=&quot;2613&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2701&quot; data-start=&quot;2674&quot; data-ke-size=&quot;size16&quot;&gt;status는 오류신고의 처리상태를 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;2752&quot; data-start=&quot;2703&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 처리상태를 접수, 확인중, 처리완료, 반려로 나누었다.&lt;/p&gt;
&lt;p data-end=&quot;2752&quot; data-start=&quot;2703&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2782&quot; data-start=&quot;2754&quot; data-ke-size=&quot;size16&quot;&gt;answer는 관리자가 작성한 답변을 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;2782&quot; data-start=&quot;2754&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2814&quot; data-start=&quot;2784&quot; data-ke-size=&quot;size16&quot;&gt;admin_id는 답변을 등록한 관리자를 나타낸다.&lt;/p&gt;
&lt;p data-end=&quot;2814&quot; data-start=&quot;2784&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2855&quot; data-start=&quot;2816&quot; data-ke-size=&quot;size16&quot;&gt;즉, 하나의 오류신고에는 작성자와 답변 관리자가 함께 연결될 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2889&quot; data-start=&quot;2862&quot; data-section-id=&quot;qaet41&quot; data-ke-size=&quot;size26&quot;&gt;users와 error_reports의 관계&lt;/h2&gt;
&lt;p data-end=&quot;2946&quot; data-start=&quot;2891&quot; data-ke-size=&quot;size16&quot;&gt;이번 DB 설계에서 가장 중요하게 본 부분은 users와 error_reports의 관계다.&lt;/p&gt;
&lt;p data-end=&quot;2978&quot; data-start=&quot;2948&quot; data-ke-size=&quot;size16&quot;&gt;사용자 한 명은 여러 개의 오류신고를 작성할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3021&quot; data-start=&quot;2980&quot; data-ke-size=&quot;size16&quot;&gt;따라서 users와 error_reports는 다음 관계를 가진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;users 1 : N error_reports&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3108&quot; data-start=&quot;3062&quot; data-ke-size=&quot;size16&quot;&gt;그리고 error_reports.user_id는 users.id를 참조한다.&lt;/p&gt;
&lt;p data-end=&quot;3133&quot; data-start=&quot;3110&quot; data-ke-size=&quot;size16&quot;&gt;그런데 여기서 하나 더 고려할 점이 있다.&lt;/p&gt;
&lt;p data-end=&quot;3145&quot; data-start=&quot;3135&quot; data-ke-size=&quot;size16&quot;&gt;관리자도 사용자다.&lt;/p&gt;
&lt;p data-end=&quot;3145&quot; data-start=&quot;3135&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3193&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;관리자가 오류신고에 답변을 등록하면, 해당 답변을 누가 등록했는지도 저장해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;3240&quot; data-start=&quot;3195&quot; data-ke-size=&quot;size16&quot;&gt;그래서 error_reports 테이블에는 admin_id 컬럼도 두었다.&lt;/p&gt;
&lt;p data-end=&quot;3240&quot; data-start=&quot;3195&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3273&quot; data-start=&quot;3242&quot; data-ke-size=&quot;size16&quot;&gt;admin_id 역시 users.id를 참조한다.&lt;/p&gt;
&lt;p data-end=&quot;3287&quot; data-start=&quot;3275&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;users.id &amp;rarr; error_reports.user_id
users.id &amp;rarr; error_reports.admin_id&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3422&quot; data-start=&quot;3369&quot; data-ke-size=&quot;size16&quot;&gt;user_id는 오류신고 작성자를 의미하고, admin_id는 답변한 관리자를 의미한다.&lt;/p&gt;
&lt;p data-end=&quot;3458&quot; data-start=&quot;3424&quot; data-ke-size=&quot;size16&quot;&gt;같은 users 테이블을 참조하지만, 역할이 서로 다르다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DB 관계도.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utlLx/dJMcafzZ2JV/dixo1NYTNK5fGNKW8cBUJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utlLx/dJMcafzZ2JV/dixo1NYTNK5fGNKW8cBUJK/img.png&quot; data-alt=&quot;error_reports 테이블은 작성자를 나타내는 user_id와 답변 관리자를 나타내는 admin_id를 통해 users 테이블을 두 번 참조한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utlLx/dJMcafzZ2JV/dixo1NYTNK5fGNKW8cBUJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FutlLx%2FdJMcafzZ2JV%2Fdixo1NYTNK5fGNKW8cBUJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;491&quot; data-filename=&quot;DB 관계도.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;error_reports 테이블은 작성자를 나타내는 user_id와 답변 관리자를 나타내는 admin_id를 통해 users 테이블을 두 번 참조한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3798&quot; data-start=&quot;3770&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3798&quot; data-start=&quot;3770&quot; data-ke-size=&quot;size16&quot;&gt;조금 더 간단하게 표현하면 이렇게 만들 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;users.id ── 작성자 ──&amp;gt; error_reports.user_id

users.id ── 답변 관리자 ──&amp;gt; error_reports.admin_id&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3954&quot; data-start=&quot;3902&quot; data-ke-size=&quot;size16&quot;&gt;이 관계도에서 핵심은 error_reports가 users를 두 번 참조한다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;4007&quot; data-start=&quot;3956&quot; data-ke-size=&quot;size16&quot;&gt;하나는 작성자를 나타내기 위한 참조이고, 다른 하나는 답변한 관리자를 나타내기 위한 참조다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4030&quot; data-start=&quot;4014&quot; data-section-id=&quot;oselco&quot; data-ke-size=&quot;size26&quot;&gt;처리상태 컬럼을 둔 이유&lt;/h2&gt;
&lt;p data-end=&quot;4058&quot; data-start=&quot;4032&quot; data-ke-size=&quot;size16&quot;&gt;오류신고는 단순히 등록만 되는 데이터가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;4092&quot; data-start=&quot;4060&quot; data-ke-size=&quot;size16&quot;&gt;등록 이후 관리자가 확인하고, 처리하거나 반려할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4136&quot; data-start=&quot;4094&quot; data-ke-size=&quot;size16&quot;&gt;그래서 status 컬럼을 두어 오류신고의 현재 상태를 저장하도록 했다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;4268&quot; data-start=&quot;4138&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4189&quot; data-start=&quot;4160&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4165&quot; data-start=&quot;4160&quot;&gt;접수&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4189&quot; data-start=&quot;4165&quot;&gt;사용자가 오류신고를 등록한 초기 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4217&quot; data-start=&quot;4190&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4196&quot; data-start=&quot;4190&quot;&gt;확인중&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4217&quot; data-start=&quot;4196&quot;&gt;관리자가 내용을 확인 중인 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4239&quot; data-start=&quot;4218&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4225&quot; data-start=&quot;4218&quot;&gt;처리완료&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4239&quot; data-start=&quot;4225&quot;&gt;처리가 완료된 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4268&quot; data-start=&quot;4240&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4245&quot; data-start=&quot;4240&quot;&gt;반려&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4268&quot; data-start=&quot;4245&quot;&gt;처리 대상이 아니거나 부적절한 신고&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4313&quot; data-start=&quot;4270&quot; data-ke-size=&quot;size16&quot;&gt;처리상태를 컬럼으로 관리하면 목록 화면에서 상태별로 구분해서 보여줄 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4385&quot; data-start=&quot;4315&quot; data-ke-size=&quot;size16&quot;&gt;또한 관리자 화면에서 아직 처리되지 않은 오류신고만 확인하거나, 처리완료된 오류신고를 따로 확인하는 기능으로 확장할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4406&quot; data-start=&quot;4392&quot; data-section-id=&quot;1e3yhq0&quot; data-ke-size=&quot;size26&quot;&gt;인덱스를 고려한 이유&lt;/h2&gt;
&lt;p data-end=&quot;4435&quot; data-start=&quot;4408&quot; data-ke-size=&quot;size16&quot;&gt;DB 설계서에는 인덱스 고려사항도 함께 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;4490&quot; data-start=&quot;4437&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 데이터가 많지 않겠지만, 오류신고가 계속 쌓인다고 가정하면 조회 성능을 고려해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;4518&quot; data-start=&quot;4492&quot; data-ke-size=&quot;size16&quot;&gt;특히 다음 컬럼들은 자주 조회될 가능성이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;4761&quot; data-start=&quot;4520&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;테이블&lt;/td&gt;
&lt;td&gt;컬럼&lt;/td&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4589&quot; data-start=&quot;4552&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4560&quot; data-start=&quot;4552&quot;&gt;users&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4571&quot; data-start=&quot;4560&quot;&gt;login_id&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4589&quot; data-start=&quot;4571&quot;&gt;로그인, 아이디 중복 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4635&quot; data-start=&quot;4590&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4606&quot; data-start=&quot;4590&quot;&gt;error_reports&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4616&quot; data-start=&quot;4606&quot;&gt;user_id&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4635&quot; data-start=&quot;4616&quot;&gt;사용자별 오류신고 목록 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4680&quot; data-start=&quot;4636&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4652&quot; data-start=&quot;4636&quot;&gt;error_reports&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4663&quot; data-start=&quot;4652&quot;&gt;admin_id&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4680&quot; data-start=&quot;4663&quot;&gt;관리자별 답변 이력 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4718&quot; data-start=&quot;4681&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4697&quot; data-start=&quot;4681&quot;&gt;error_reports&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4706&quot; data-start=&quot;4697&quot;&gt;status&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4718&quot; data-start=&quot;4706&quot;&gt;처리상태별 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4761&quot; data-start=&quot;4719&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4735&quot; data-start=&quot;4719&quot;&gt;error_reports&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4748&quot; data-start=&quot;4735&quot;&gt;created_at&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4761&quot; data-start=&quot;4748&quot;&gt;최신순 목록 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4815&quot; data-start=&quot;4763&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 내 오류신고 목록을 조회할 때는 user_id 기준으로 조회하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;4871&quot; data-start=&quot;4817&quot; data-ke-size=&quot;size16&quot;&gt;관리자 화면에서는 status나 created_at 기준으로 목록을 조회할 가능성이 높다.&lt;/p&gt;
&lt;p data-end=&quot;4907&quot; data-start=&quot;4873&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이런 컬럼들은 인덱스 적용을 검토할 대상으로 정리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4929&quot; data-start=&quot;4914&quot; data-section-id=&quot;fnqohg&quot; data-ke-size=&quot;size26&quot;&gt;추후 확장 예정 테이블&lt;/h2&gt;
&lt;p data-end=&quot;4980&quot; data-start=&quot;4931&quot; data-ke-size=&quot;size16&quot;&gt;1차 개발에서는 users, error_reports 두 개의 테이블만 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;5012&quot; data-start=&quot;4982&quot; data-ke-size=&quot;size16&quot;&gt;하지만 기능이 확장되면 추가 테이블이 필요할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5027&quot; data-start=&quot;5014&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;5112&quot; data-start=&quot;5029&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style11&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;테이블명&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;5078&quot; data-start=&quot;5053&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5067&quot; data-start=&quot;5053&quot;&gt;attachments&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5078&quot; data-start=&quot;5067&quot;&gt;첨부파일 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;5112&quot; data-start=&quot;5079&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5098&quot; data-start=&quot;5079&quot;&gt;report_histories&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5112&quot; data-start=&quot;5098&quot;&gt;처리상태 변경 이력&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5155&quot; data-start=&quot;5114&quot; data-ke-size=&quot;size16&quot;&gt;첨부파일 기능을 추가하려면 오류신고와 연결되는 첨부파일 테이블이 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;5198&quot; data-start=&quot;5157&quot; data-ke-size=&quot;size16&quot;&gt;또한 처리상태 변경 이력을 남기려면 별도의 이력 테이블이 필요할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5198&quot; data-start=&quot;5157&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5281&quot; data-start=&quot;5200&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 모든 테이블을 만들기보다, 1차 개발에서는 핵심 기능에 필요한 테이블만 만들고&lt;/p&gt;
&lt;p data-end=&quot;5281&quot; data-start=&quot;5200&quot; data-ke-size=&quot;size16&quot;&gt;이후 기능 확장에 따라 테이블을 추가하는 방향으로 잡았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;5293&quot; data-start=&quot;5288&quot; data-section-id=&quot;1melx8&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-end=&quot;5337&quot; data-start=&quot;5295&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 오류신고 관리 시스템의 DB 설계서를 작성한 내용을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;5395&quot; data-start=&quot;5339&quot; data-ke-size=&quot;size16&quot;&gt;1차 개발 범위에서는 users, error_reports 두 개의 테이블을 사용하기로 했다.&lt;/p&gt;
&lt;p data-end=&quot;5395&quot; data-start=&quot;5339&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5480&quot; data-start=&quot;5397&quot; data-ke-size=&quot;size16&quot;&gt;users 테이블은 사용자 정보와 권한 정보를 저장하고, error_reports 테이블은 오류신고 내용, 처리상태, 관리자 답변을 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;5559&quot; data-start=&quot;5482&quot; data-ke-size=&quot;size16&quot;&gt;특히 error_reports 테이블에서는 user_id와 admin_id를 통해 작성자와 답변한 관리자를 구분하도록 설계했다.&lt;/p&gt;
&lt;p data-end=&quot;5559&quot; data-start=&quot;5482&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5638&quot; data-start=&quot;5561&quot; data-ke-size=&quot;size16&quot;&gt;이번 DB 설계서를 작성하면서 단순히 컬럼을 나열하는 것보다,&lt;/p&gt;
&lt;p data-end=&quot;5638&quot; data-start=&quot;5561&quot; data-ke-size=&quot;size16&quot;&gt;각 컬럼이 어떤 기능을 위해 필요한지 함께 생각하는 것이 중요하다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;5638&quot; data-start=&quot;5561&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;5700&quot; data-start=&quot;5640&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이 DB 설계와 요구사항을 바탕으로 URL/API 설계를 어떻게 정리했는지 작성해보려고 한다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;5700&quot; data-start=&quot;5640&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;5700&quot; data-start=&quot;5640&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트&amp;nbsp;진행&amp;nbsp;과정과&amp;nbsp;관련&amp;nbsp;문서는&amp;nbsp;아래&amp;nbsp;GitHub&amp;nbsp;저장소에서&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt; &amp;nbsp;&lt;a href=&quot;https://github.com/hwangjihwan-dev/error-report-system&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/hwangjihwan-dev/error-report-system&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/프로젝트 기록</category>
      <category>DB설계</category>
      <category>DB설계서</category>
      <category>ERD</category>
      <category>springboot</category>
      <category>개인프로젝트</category>
      <category>데이터베이스설계</category>
      <category>백엔드프로젝트</category>
      <category>오류신고관리시스템</category>
      <category>테이블설계</category>
      <category>포트폴리오</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/16</guid>
      <comments>https://hwang-dev.tistory.com/16#entry16comment</comments>
      <pubDate>Wed, 27 May 2026 18:07:49 +0900</pubDate>
    </item>
    <item>
      <title>[오류신고 관리 시스템] 개인 프로젝트 요구사항 정의서 작성하기</title>
      <link>https://hwang-dev.tistory.com/15</link>
      <description>&lt;p data-end=&quot;167&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서는 오류신고 관리 시스템 프로젝트의 README를 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;167&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;251&quot; data-start=&quot;169&quot; data-ke-size=&quot;size16&quot;&gt;README가 프로젝트의 전체 방향을 설명하는 문서라면,&lt;/p&gt;
&lt;p data-end=&quot;251&quot; data-start=&quot;169&quot; data-ke-size=&quot;size16&quot;&gt;요구사항 정의서는 &lt;b&gt;실제로 어떤 기능을 만들 것인지 구체화하는 문서&lt;/b&gt;라고 볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;251&quot; data-start=&quot;169&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;253&quot; data-ke-size=&quot;size16&quot;&gt;개인 프로젝트라고 해서 무조건 요구사항 정의서가 필요한 것은 아니지만,&lt;/p&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;253&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 회원 기능, 사용자 기능, 관리자 기능이 나뉘어 있기 때문에 기준을 먼저 정리해두는 것이 필요하다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;423&quot; data-start=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;그래서 구현에 들어가기 전에 requirements.md 파일을 만들고, 요구사항을 먼저 정리해보았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;451&quot; data-start=&quot;430&quot; data-section-id=&quot;v9cek2&quot; data-ke-size=&quot;size26&quot;&gt;왜 요구사항 정의서를 작성했을까?&lt;/h2&gt;
&lt;p data-end=&quot;497&quot; data-start=&quot;453&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 &amp;ldquo;오류신고를 등록하고 관리자가 답변하는 기능&amp;rdquo; 정도로 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;하지만 막상 기능을 떠올려보니 생각보다 정리해야 할 기준이 많았다.&lt;/p&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;552&quot; data-start=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 것들이다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;예시&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;누가 오류신고를 등록할 수 있는가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;로그인하지 않은 사용자는 어떻게 처리할 것인가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;사용자는 본인이 작성한 오류신고만 볼 수 있는가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;관리자는 모든 오류신고를 볼 수 있는가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;관리자 답변은 언제 등록할 수 있는가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;처리상태는 어떤 값으로 관리할 것인가?&lt;/span&gt;&lt;br /&gt;&lt;span&gt;입력값이 비어 있으면 어떻게 처리할 것인가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1078&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size16&quot;&gt;이런 기준을 미리 정리해두지 않으면, 구현 도중에 계속 판단이 흔들릴 수 있다고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;1078&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;799&quot; data-start=&quot;717&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 프로젝트는 단순 게시판이 아니라,&lt;/p&gt;
&lt;p data-end=&quot;799&quot; data-start=&quot;717&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 오류를 신고하고 관리자가 이를 확인한 뒤 처리상태를 변경하거나 답변을 등록하는 흐름을 가진다.&lt;/p&gt;
&lt;p data-end=&quot;799&quot; data-start=&quot;717&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;838&quot; data-start=&quot;801&quot; data-ke-size=&quot;size16&quot;&gt;그래서 기능을 바로 구현하기보다 먼저 요구사항을 정리해보기로 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;863&quot; data-start=&quot;845&quot; data-section-id=&quot;ndxnig&quot; data-ke-size=&quot;size26&quot;&gt;요구사항을 어떻게 나누었나?&lt;/h2&gt;
&lt;p data-end=&quot;901&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;이번 오류신고 관리 시스템의 요구사항은 크게 네 가지로 나누었다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;요구사항&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 회원 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자 오류신고 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 관리자 오류신고 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 공통 요구사항&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;980&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;회원 기능에는 회원가입, 로그인, 로그아웃을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;1049&quot; data-start=&quot;982&quot; data-ke-size=&quot;size16&quot;&gt;사용자 오류신고 기능에는 오류신고 등록, 내 오류신고 목록 조회, 내 오류신고 상세 조회, 관리자 답변 확인을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;1128&quot; data-start=&quot;1051&quot; data-ke-size=&quot;size16&quot;&gt;관리자 오류신고 기능에는 전체 오류신고 목록 조회, 오류신고 상세 조회, 처리상태 변경, 관리자 답변 등록, 관리자 답변 수정을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;1168&quot; data-start=&quot;1130&quot; data-ke-size=&quot;size16&quot;&gt;공통 요구사항에는 권한 체크, 입력값 검증, 페이징을 따로 분리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-end=&quot;1551&quot; data-start=&quot;1479&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;요구사항.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC2X81/dJMcaftbWG7/tN0cphdByvlvksSK2a6jp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC2X81/dJMcaftbWG7/tN0cphdByvlvksSK2a6jp0/img.png&quot; data-alt=&quot;요구사항 전체 구조도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC2X81/dJMcaftbWG7/tN0cphdByvlvksSK2a6jp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC2X81%2FdJMcaftbWG7%2FtN0cphdByvlvksSK2a6jp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;601&quot; data-filename=&quot;요구사항.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;요구사항 전체 구조도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1517&quot; data-start=&quot;1453&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1517&quot; data-start=&quot;1453&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 기능을 나눈 이유는 &lt;b&gt;누가 사용하는 기능인지&lt;/b&gt;를 기준으로 정리하는 편이 더 명확하다고 생각했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;1517&quot; data-start=&quot;1453&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1571&quot; data-start=&quot;1519&quot; data-ke-size=&quot;size16&quot;&gt;기능을 단순히 나열하면 나중에 구현할 때 사용자 화면과 관리자 화면의 기준이 흐려질 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1571&quot; data-start=&quot;1519&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1629&quot; data-start=&quot;1573&quot; data-ke-size=&quot;size16&quot;&gt;반면 역할과 목적을 기준으로 나누면, 이후 URL 설계나 권한 설정을 할 때도 기준이 더 분명해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-end=&quot;1648&quot; data-start=&quot;1636&quot; data-section-id=&quot;o8aqzk&quot; data-ke-size=&quot;size26&quot;&gt;역할별 기능 정리&lt;/h2&gt;
&lt;p data-end=&quot;1676&quot; data-start=&quot;1650&quot; data-ke-size=&quot;size16&quot;&gt;요구사항을 역할 기준으로 정리하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;주요 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비회원&lt;/td&gt;
&lt;td&gt;회원가입, 로그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반 사용자&lt;/td&gt;
&lt;td&gt;오류신고 등록, 내 오류신고 목록 조회, 상세 조회, 답변 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관리자&lt;/td&gt;
&lt;td&gt;전체 오류신고 조회, 상세 조회, 처리상태 변경, 답변 등록/수정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1875&quot; data-start=&quot;1820&quot; data-ke-size=&quot;size16&quot;&gt;오류신고 관리 시스템에서 가장 중요한 부분은 &lt;b&gt;일반 사용자와 관리자의 권한이 다르다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;1875&quot; data-start=&quot;1820&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1912&quot; data-start=&quot;1877&quot; data-ke-size=&quot;size16&quot;&gt;일반 사용자는 본인이 작성한 오류신고만 조회할 수 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1966&quot; data-start=&quot;1914&quot; data-ke-size=&quot;size16&quot;&gt;반면 관리자는 전체 오류신고를 조회하고, 처리상태를 변경하거나 답변을 등록할 수 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1966&quot; data-start=&quot;1914&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2040&quot; data-start=&quot;1968&quot; data-ke-size=&quot;size16&quot;&gt;이 기준은 나중에 URL 설계, Spring Security 설정, Service 로직을 구현할 때도 중요한 기준이 될 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;역할별 기능 구조도.png&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X7OGw/dJMcagyRfPq/fpFZEbH6WiKmnDwztmkPq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X7OGw/dJMcagyRfPq/fpFZEbH6WiKmnDwztmkPq1/img.png&quot; data-alt=&quot;역할별 기능 구조도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X7OGw/dJMcagyRfPq/fpFZEbH6WiKmnDwztmkPq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX7OGw%2FdJMcagyRfPq%2FfpFZEbH6WiKmnDwztmkPq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1121&quot; height=&quot;481&quot; data-filename=&quot;역할별 기능 구조도.png&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;역할별 기능 구조도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2352&quot; data-start=&quot;2305&quot; data-ke-size=&quot;size16&quot;&gt;표와 그림으로 정리해두면, 긴 설명 없이도 구조를 한눈에 볼 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-end=&quot;2415&quot; data-start=&quot;2354&quot; data-ke-size=&quot;size16&quot;&gt;또한 역할별 기능을 먼저 정리해두면 &amp;ldquo;이 기능은 누가 사용할 수 있어야 하는가?&amp;rdquo;라는 질문에 답하기 쉬워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1910&quot; data-start=&quot;1898&quot; data-section-id=&quot;1fd0lw0&quot; data-ke-size=&quot;size26&quot;&gt;오류신고 처리상태&lt;/h2&gt;
&lt;p data-end=&quot;1955&quot; data-start=&quot;1912&quot; data-ke-size=&quot;size16&quot;&gt;오류신고는 단순히 등록만 되는 데이터가 아니라, 처리 흐름을 가지는 데이터다.&lt;/p&gt;
&lt;p data-end=&quot;1988&quot; data-start=&quot;1957&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 처리상태를 아래 네 가지로 정리했다.&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2137&quot; data-start=&quot;1990&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2041&quot; data-start=&quot;2012&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2017&quot; data-start=&quot;2012&quot;&gt;접수&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2041&quot; data-start=&quot;2017&quot;&gt;사용자가 오류신고를 등록한 초기 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2072&quot; data-start=&quot;2042&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2048&quot; data-start=&quot;2042&quot;&gt;확인중&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2072&quot; data-start=&quot;2048&quot;&gt;관리자가 오류 내용을 확인 중인 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2108&quot; data-start=&quot;2073&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2080&quot; data-start=&quot;2073&quot;&gt;처리완료&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2108&quot; data-start=&quot;2080&quot;&gt;관리자가 답변을 등록하고 처리를 완료한 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2137&quot; data-start=&quot;2109&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2114&quot; data-start=&quot;2109&quot;&gt;반려&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2137&quot; data-start=&quot;2114&quot;&gt;처리 대상이 아니거나 부적절한 신고&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2186&quot; data-start=&quot;2139&quot; data-ke-size=&quot;size16&quot;&gt;처리상태를 미리 정리해두면 관리자가 어떤 흐름으로 오류신고를 처리할지 기준이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;2767&quot; data-start=&quot;2712&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 요구사항에서는 &lt;b&gt;관리자 답변 등록 시 처리상태를 처리완료로 변경&lt;/b&gt;하도록 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;2767&quot; data-start=&quot;2712&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2853&quot; data-start=&quot;2769&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 해두면 사용자는 본인이 등록한 오류신고가 현재 어떤 상태인지 확인할 수 있고,&lt;/p&gt;
&lt;p data-end=&quot;2853&quot; data-start=&quot;2769&quot; data-ke-size=&quot;size16&quot;&gt;관리자는 처리해야 할 신고와 이미 처리한 신고를 구분할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byOMGv/dJMcaaenMuq/iXnwvctOWHXUnjWVvaoQyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byOMGv/dJMcaaenMuq/iXnwvctOWHXUnjWVvaoQyK/img.png&quot; data-alt=&quot;오류신고는 등록 이후 접수, 확인중 단계를 거쳐 처리완료 또는 반려 상태로 구분되도록 했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byOMGv/dJMcaaenMuq/iXnwvctOWHXUnjWVvaoQyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyOMGv%2FdJMcaaenMuq%2FiXnwvctOWHXUnjWVvaoQyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;822&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;오류신고는 등록 이후 접수, 확인중 단계를 거쳐 처리완료 또는 반려 상태로 구분되도록 했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-end=&quot;3105&quot; data-start=&quot;3055&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름을 그림으로 넣으면, 단순히 상태값만 나열하는 것보다 훨씬 이해하기 쉬울 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;3172&quot; data-start=&quot;3107&quot; data-ke-size=&quot;size16&quot;&gt;또한 오류신고 관리 시스템이 단순 게시판이 아니라, 상태가 변하는 데이터를 다루는 시스템이라는 점도 더 잘 드러난다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-end=&quot;3196&quot; data-start=&quot;3179&quot; data-section-id=&quot;1yna7b0&quot; data-ke-size=&quot;size26&quot;&gt;예외 상황도 함께 정리했다&lt;/h2&gt;
&lt;p data-end=&quot;3235&quot; data-start=&quot;3198&quot; data-ke-size=&quot;size16&quot;&gt;요구사항을 정리하면서 가장 중요하다고 느낀 부분은 예외 상황이었다.&lt;/p&gt;
&lt;p data-end=&quot;3293&quot; data-start=&quot;3237&quot; data-ke-size=&quot;size16&quot;&gt;기능이 정상적으로 동작하는 경우만 생각하면 실제 구현 단계에서 빠지는 부분이 생길 수 있기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;3310&quot; data-start=&quot;3295&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 경우들이다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;예시&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인하지&amp;nbsp;않은&amp;nbsp;사용자가&amp;nbsp;오류신고를&amp;nbsp;등록하려는&amp;nbsp;경우 &lt;br /&gt;일반&amp;nbsp;사용자가&amp;nbsp;관리자&amp;nbsp;기능에&amp;nbsp;접근하려는&amp;nbsp;경우 &lt;br /&gt;다른&amp;nbsp;사용자의&amp;nbsp;오류신고를&amp;nbsp;조회하려는&amp;nbsp;경우 &lt;br /&gt;제목이나&amp;nbsp;내용이&amp;nbsp;비어&amp;nbsp;있는&amp;nbsp;경우 &lt;br /&gt;정의되지&amp;nbsp;않은&amp;nbsp;상태값으로&amp;nbsp;변경하려는&amp;nbsp;경우&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3518&quot; data-start=&quot;3441&quot; data-ke-size=&quot;size16&quot;&gt;이런 기준을 먼저 정리해두면, 나중에 Validation, Security, Service 로직에서 무엇을 막아야 하는지 훨씬 명확해진다.&lt;/p&gt;
&lt;p data-end=&quot;3591&quot; data-start=&quot;3520&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;본인이 작성하지 않은 오류신고는 조회할 수 없다&amp;rdquo;는 요구사항은 단순히 화면에서 버튼을 숨기는 문제로 끝나지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3666&quot; data-start=&quot;3593&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 URL을 직접 입력해서 다른 사람의 오류신고에 접근할 수도 있기 때문에, Service 로직에서도 작성자 검증이 필요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-end=&quot;3695&quot; data-start=&quot;3673&quot; data-section-id=&quot;1ay0w3d&quot; data-ke-size=&quot;size26&quot;&gt;요구사항 정의서를 작성하며 느낀 점&lt;/h2&gt;
&lt;p data-end=&quot;3757&quot; data-start=&quot;3697&quot; data-ke-size=&quot;size16&quot;&gt;요구사항 정의서를 작성하면서 단순히 기능 목록을 적는 것과 요구사항을 정리하는 것은 다르다는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;3784&quot; data-start=&quot;3759&quot; data-ke-size=&quot;size16&quot;&gt;기능 목록은 &amp;ldquo;무엇을 만들 것인가&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;3815&quot; data-start=&quot;3786&quot; data-ke-size=&quot;size16&quot;&gt;반면 요구사항 정의서는 다음 기준까지 함께 정리한다.&lt;/p&gt;
&lt;p data-end=&quot;3815&quot; data-start=&quot;3786&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;기준&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는가? &lt;br /&gt;어떤&amp;nbsp;값을&amp;nbsp;입력받는가? &lt;br /&gt;어떤&amp;nbsp;조건을&amp;nbsp;만족해야&amp;nbsp;하는가? &lt;br /&gt;어떤&amp;nbsp;예외&amp;nbsp;상황을&amp;nbsp;막아야&amp;nbsp;하는가?&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3928&quot; data-start=&quot;3889&quot; data-ke-size=&quot;size16&quot;&gt;이 기준을 미리 정리해두면 구현 단계에서 고민해야 할 부분이 줄어든다.&lt;/p&gt;
&lt;p data-end=&quot;4010&quot; data-start=&quot;3930&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 프로젝트에서는 &lt;b&gt;역할 분리&lt;/b&gt;, &lt;b&gt;처리상태&lt;/b&gt;, &lt;b&gt;예외 상황&lt;/b&gt;을 요구사항 정의서에서 먼저 정리한 것이 도움이 될 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;4010&quot; data-start=&quot;3930&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4022&quot; data-start=&quot;4017&quot; data-section-id=&quot;1melx8&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-end=&quot;4075&quot; data-start=&quot;4024&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 오류신고 관리 시스템의 요구사항 정의서를 작성한 이유와 전체 구조를 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;4112&quot; data-start=&quot;4077&quot; data-ke-size=&quot;size16&quot;&gt;이번 요구사항 정의서에서 가장 중요하게 본 부분은 세 가지였다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;중요&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할에&amp;nbsp;따라&amp;nbsp;기능을&amp;nbsp;나누는&amp;nbsp;것 &lt;br /&gt;오류신고의&amp;nbsp;처리상태를&amp;nbsp;정의하는&amp;nbsp;것 &lt;br /&gt;예외&amp;nbsp;상황을&amp;nbsp;미리&amp;nbsp;정리하는&amp;nbsp;것&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4229&quot; data-start=&quot;4174&quot; data-ke-size=&quot;size16&quot;&gt;개인 프로젝트라고 해도 구현 전에 이런 기준을 먼저 정리해두면 개발 흐름이 훨씬 명확해질 것 같다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;4302&quot; data-start=&quot;4231&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이 요구사항을 바탕으로 users, error_reports 테이블을 어떻게 설계했는지 정리해보려고 한다.&lt;/p&gt;</description>
      <category>개발/프로젝트 기록</category>
      <category>springboot</category>
      <category>springboot프로젝트</category>
      <category>개발문서</category>
      <category>개인프로젝트</category>
      <category>기능정의</category>
      <category>백엔드프로젝트</category>
      <category>오류신고관리시스템</category>
      <category>요구사항정의서</category>
      <category>포트폴리오</category>
      <category>프로젝트문서화</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/15</guid>
      <comments>https://hwang-dev.tistory.com/15#entry15comment</comments>
      <pubDate>Tue, 26 May 2026 17:59:46 +0900</pubDate>
    </item>
    <item>
      <title>정보처리기사 2026년 1회차 필기 합격 후기｜비전공 직장인 개발자의 공부 과정</title>
      <link>https://hwang-dev.tistory.com/14</link>
      <description>&lt;p data-end=&quot;339&quot; data-start=&quot;311&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 2026년 1회차 필기시험에 합격했다.&lt;/p&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;341&quot; data-ke-size=&quot;size16&quot;&gt;공부 기간은 1월 7일부터 시험 전날인 2월 28일까지였다.&lt;/p&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;341&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;흥달쌤 강의와 교재로 전체 범위를 1회독한 뒤, 시험 1주 전부터는 맞추다 웹사이트를 이용해서 기출문제를 매일 풀었다.&lt;/p&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;사실 정보처리기사 필기 합격이 엄청 대단한 결과라고 말하기에는 애매할 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 직장을 다니면서 시간을 쪼개 공부했고,&lt;/p&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;그 과정에서 부족했던 기초 지식을 다시 정리할 수 있었다는 점에서 나에게는 꽤 의미 있는 경험이었다.&lt;/p&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;611&quot; data-start=&quot;576&quot; data-ke-size=&quot;size16&quot;&gt;나는 현재 개발자로 일하고 있지만, 컴퓨터공학 전공자는 아니다.&lt;/p&gt;
&lt;p data-end=&quot;688&quot; data-start=&quot;613&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 Java, Spring, DB, 운영 유지보수 업무를 하고 있지만,&lt;/p&gt;
&lt;p data-end=&quot;688&quot; data-start=&quot;613&quot; data-ke-size=&quot;size16&quot;&gt;항상 CS 기초 지식이 조금 부족하다는 아쉬움이 있었다.&lt;/p&gt;
&lt;p data-end=&quot;688&quot; data-start=&quot;613&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;771&quot; data-start=&quot;690&quot; data-ke-size=&quot;size16&quot;&gt;물론 일을 하면서 필요한 지식은 그때그때 찾아가며 익혀왔다.&lt;br /&gt;또 이전에 SQLD를 취득하면서 데이터베이스 기초는 한 번 정리한 경험이 있었다.&lt;/p&gt;
&lt;p data-end=&quot;771&quot; data-start=&quot;690&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;839&quot; data-start=&quot;773&quot; data-ke-size=&quot;size16&quot;&gt;하지만 운영체제, 네트워크, 소프트웨어 공학 같은 CS 전반의 내용을 처음부터 체계적으로 정리해본 경험은 많지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;922&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 정보처리기사 필기 준비는 단순히 자격증을 따기 위한 공부라기보다,&lt;br /&gt;개발자로서 부족했던 CS 기초를 다시 정리하는 과정에 가까웠다.&lt;/p&gt;
&lt;p data-end=&quot;922&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1020&quot; data-start=&quot;924&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 정보처리기사 2026년 1회차 필기시험을 준비하면서 느낀 점, 공부 방법, 직장인 기준 공부 루틴, 그리고 실기시험까지 응시한 현재 상황을 정리해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;751&quot; data-start=&quot;737&quot; data-section-id=&quot;1wq2dfp&quot; data-ke-size=&quot;size26&quot;&gt;정보처리기사 취득방법&lt;/h2&gt;
&lt;p data-end=&quot;789&quot; data-start=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사는 한국산업인력공단에서 시행하는 국가기술자격 시험이다.&lt;/p&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;791&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 자격증을 취득하기 위해서는 필기시험과 실기시험에 모두 합격해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;791&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;876&quot; data-start=&quot;838&quot; data-ke-size=&quot;size16&quot;&gt;관련학과는 모든 학과가 응시 가능하며, 시험은 필기와 실기로 나뉜다.&lt;/p&gt;
&lt;p data-end=&quot;894&quot; data-start=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;필기시험 과목은 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;894&quot; data-start=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EA9St/dJMcahYC8Q1/c4OXUBTtKvtgd0im4kbvk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EA9St/dJMcahYC8Q1/c4OXUBTtKvtgd0im4kbvk0/img.png&quot; data-alt=&quot;정보처리기사 취득방법 필기 실기 과목 합격기준&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EA9St/dJMcahYC8Q1/c4OXUBTtKvtgd0im4kbvk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEA9St%2FdJMcahYC8Q1%2Fc4OXUBTtKvtgd0im4kbvk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;정보처리기사 취득방법 필기 실기 과목 합격기준&quot; loading=&quot;lazy&quot; width=&quot;1025&quot; height=&quot;557&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정보처리기사 취득방법 필기 실기 과목 합격기준&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;894&quot; data-start=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;필기 시험 과목&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;소프트웨어&amp;nbsp;설계 &lt;br /&gt;2.&amp;nbsp;소프트웨어&amp;nbsp;개발 &lt;br /&gt;3.&amp;nbsp;데이터베이스&amp;nbsp;구축 &lt;br /&gt;4.&amp;nbsp;프로그래밍&amp;nbsp;언어&amp;nbsp;활용 &lt;br /&gt;5. 정보시스템 구축관리&lt;/p&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1018&quot; data-start=&quot;958&quot; data-ke-size=&quot;size16&quot;&gt;필기시험은 객관식 4지 택일형으로 진행되며, 과목당 20문항씩 출제된다.&lt;/p&gt;
&lt;p data-end=&quot;1018&quot; data-start=&quot;958&quot; data-ke-size=&quot;size16&quot;&gt;시험 시간은 과목당 30분이다.&lt;/p&gt;
&lt;p data-end=&quot;1018&quot; data-start=&quot;958&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1072&quot; data-start=&quot;1020&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필기 합격 기준&lt;/b&gt;은 100점 만점 기준으로 과목당 40점 이상, &lt;b&gt;전 과목 평균 60점 이상&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;1185&quot; data-start=&quot;1074&quot; data-ke-size=&quot;size16&quot;&gt;즉, 평균 점수가 60점 이상이어도 &lt;b&gt;한 과목이라도 40점 미만&lt;/b&gt;이면 &lt;b&gt;과락으로 불합격&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1185&quot; data-start=&quot;1074&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 특정 과목만 집중하기보다는 모든 과목에서 최소 점수를 넘길 수 있도록 준비하는 것이 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;1257&quot; data-start=&quot;1187&quot; data-ke-size=&quot;size16&quot;&gt;실기시험은 정보처리 실무 과목으로 진행된다.&lt;/p&gt;
&lt;p data-end=&quot;1257&quot; data-start=&quot;1187&quot; data-ke-size=&quot;size16&quot;&gt;시험 시간은 2시간 30분이며, 100점 만점 기준 60점 이상이면 합격이다.&lt;/p&gt;
&lt;p data-end=&quot;1257&quot; data-start=&quot;1187&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1361&quot; data-start=&quot;1259&quot; data-ke-size=&quot;size16&quot;&gt;나는 이번 글에서 필기시험 합격 후기를 중심으로 작성하지만, 현재는 실기시험까지 응시한 상태다.&lt;br /&gt;실기 결과가 나오고 좋은 결과가 있다면, 실기 합격 후기도 따로 정리해볼 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1436&quot; data-start=&quot;1419&quot; data-section-id=&quot;1gkpk58&quot; data-ke-size=&quot;size26&quot;&gt;정보처리기사를 준비한 이유&lt;/h2&gt;
&lt;p data-end=&quot;1488&quot; data-start=&quot;1438&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사를 준비한 가장 큰 이유는 개발자로서 부족한 기본기를 정리하고 싶었기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;1556&quot; data-start=&quot;1490&quot; data-ke-size=&quot;size16&quot;&gt;나는 컴퓨터공학 전공자가 아니기 때문에 처음 개발 공부를 시작했을 때부터 CS 기초 지식이 충분하다고 느끼지는 못했다.&lt;/p&gt;
&lt;p data-end=&quot;1556&quot; data-start=&quot;1490&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1640&quot; data-start=&quot;1558&quot; data-ke-size=&quot;size16&quot;&gt;물론 실무를 하면서 필요한 지식은 그때그때 찾아가며 익혀왔다.&lt;br /&gt;또 이전에 SQLD를 취득하면서 데이터베이스 기초는 한 번 정리한 경험이 있었다.&lt;/p&gt;
&lt;p data-end=&quot;1640&quot; data-start=&quot;1558&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1695&quot; data-start=&quot;1642&quot; data-ke-size=&quot;size16&quot;&gt;하지만 운영체제, 네트워크, 소프트웨어 공학 같은 CS 전반의 지식은 여전히 부족하다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;1797&quot; data-start=&quot;1697&quot; data-ke-size=&quot;size16&quot;&gt;실무를 하다 보면 당장 눈앞의 오류를 해결하거나, 기능을 수정하거나, 운영 이슈를 처리하는 일이 많다.&lt;br /&gt;그러다 보니 필요한 부분을 그때그때 찾아서 해결하는 방식으로 성장해왔다.&lt;/p&gt;
&lt;p data-end=&quot;1797&quot; data-start=&quot;1697&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 시간이 지날수록 단순히 문제를 해결하는 것뿐만 아니라,&lt;/p&gt;
&lt;p data-end=&quot;1797&quot; data-start=&quot;1697&quot; data-ke-size=&quot;size16&quot;&gt;내가 사용하는 기술의 기반을 조금 더 체계적으로 정리해야겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;1797&quot; data-start=&quot;1697&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1997&quot; data-start=&quot;1903&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 쪽은 이전에 SQLD를 취득하면서 한 번 정리한 경험이 있었다.&lt;br /&gt;그래서 정규화, 트랜잭션, SQL 같은 내용은 다른 과목에 비해 비교적 익숙한 편이었다.&lt;/p&gt;
&lt;p data-end=&quot;1997&quot; data-start=&quot;1903&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2087&quot; data-start=&quot;1999&quot; data-ke-size=&quot;size16&quot;&gt;반면 운영체제, 네트워크, 소프트웨어 공학, 보안 같은 CS 전반의 내용은 실무를 하면서 한 번쯤 들어본 적은 있어도&lt;/p&gt;
&lt;p data-end=&quot;2087&quot; data-start=&quot;1999&quot; data-ke-size=&quot;size16&quot;&gt;체계적으로 공부했다고 말하기는 어려웠다.&lt;/p&gt;
&lt;p data-end=&quot;2104&quot; data-start=&quot;2089&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 내용들이다.&lt;/p&gt;
&lt;p data-end=&quot;2104&quot; data-start=&quot;2089&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;예시&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;2106&quot; data-end=&quot;2185&quot;&gt;
&lt;li data-section-id=&quot;q2o4xl&quot; data-start=&quot;2106&quot; data-end=&quot;2123&quot;&gt;운영체제의 프로세스와 스레드&lt;/li&gt;
&lt;li data-section-id=&quot;n763ph&quot; data-start=&quot;2124&quot; data-end=&quot;2133&quot;&gt;네트워크 계층&lt;/li&gt;
&lt;li data-section-id=&quot;ir006w&quot; data-start=&quot;2134&quot; data-end=&quot;2148&quot;&gt;소프트웨어 개발 방법론&lt;/li&gt;
&lt;li data-section-id=&quot;kjbc38&quot; data-start=&quot;2149&quot; data-end=&quot;2162&quot;&gt;요구사항 분석과 설계&lt;/li&gt;
&lt;li data-section-id=&quot;1ra0w71&quot; data-start=&quot;2163&quot; data-end=&quot;2171&quot;&gt;테스트 기법&lt;/li&gt;
&lt;li data-section-id=&quot;ek1es8&quot; data-start=&quot;2172&quot; data-end=&quot;2185&quot;&gt;보안 관련 기본 개념&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2234&quot; data-start=&quot;2187&quot; data-ke-size=&quot;size16&quot;&gt;익숙한 용어처럼 느껴지지만,&lt;br /&gt;막상 정확히 설명하라고 하면 애매한 내용들이 많았다.&lt;/p&gt;
&lt;p data-end=&quot;2234&quot; data-start=&quot;2187&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2280&quot; data-start=&quot;2236&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사는 이런 개발 기본 지식을 넓게 훑어볼 수 있는 시험이라고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;2356&quot; data-start=&quot;2282&quot; data-ke-size=&quot;size16&quot;&gt;자격증 자체가 개발 실력을 증명해주는 것은 아니지만,&lt;br /&gt;준비 과정에서 &lt;b&gt;부족했던 CS 기초를 정리&lt;/b&gt;할 수 있다는 점에서 의미가 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1355&quot; data-start=&quot;1340&quot; data-section-id=&quot;2chsdt&quot; data-ke-size=&quot;size26&quot;&gt;공부 기간과 공부 루틴&lt;/h2&gt;
&lt;p data-end=&quot;146&quot; data-start=&quot;80&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 필기 공부 기간은 &lt;b&gt;1월 7일부터 시험 전날인 3월 1일까지&lt;/b&gt;였다.&lt;br /&gt;대략 8주 정도 준비한 셈이다.&lt;/p&gt;
&lt;p data-end=&quot;146&quot; data-start=&quot;80&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;207&quot; data-start=&quot;148&quot; data-ke-size=&quot;size16&quot;&gt;직장인이다 보니 하루 종일 공부할 수 있는 환경은 아니었고, 출근 전과 퇴근 후 시간을 활용해서 공부했다.&lt;/p&gt;
&lt;p data-end=&quot;305&quot; data-start=&quot;209&quot; data-ke-size=&quot;size16&quot;&gt;필기 준비는 &lt;b&gt;흥달쌤 강의와 교재&lt;/b&gt;로 진행했다.&lt;/p&gt;
&lt;p data-end=&quot;305&quot; data-start=&quot;209&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eez4JN/dJMcahqRIqK/WPRgWEPWBLDkFpN0ng5ngK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eez4JN/dJMcahqRIqK/WPRgWEPWBLDkFpN0ng5ngK/img.png&quot; data-alt=&quot;1억뷰 N잡 흥달쌤 강의와 교재&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eez4JN/dJMcahqRIqK/WPRgWEPWBLDkFpN0ng5ngK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feez4JN%2FdJMcahqRIqK%2FWPRgWEPWBLDkFpN0ng5ngK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;1억뷰 N잡 흥달쌤 강의와 교재&quot; loading=&quot;lazy&quot; width=&quot;931&quot; height=&quot;565&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1억뷰 N잡 흥달쌤 강의와 교재&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;305&quot; data-start=&quot;209&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;교재를 함께 보면서 강의를 1회독했고, 처음부터 모든 내용을 완벽하게 외우기보다는 전체적인 흐름을 잡는 데 집중했다.&lt;/p&gt;
&lt;p data-end=&quot;389&quot; data-start=&quot;307&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 활용한 시간은 출근 전이었다.&lt;/p&gt;
&lt;p data-end=&quot;389&quot; data-start=&quot;307&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;평소보다 한 시간 정도 일찍 출근해서 업무 시작 전까지 흥달쌤 강의를 2배속으로 2~3개 정도 들었다.&lt;/p&gt;
&lt;p data-end=&quot;481&quot; data-start=&quot;391&quot; data-ke-size=&quot;size16&quot;&gt;퇴근길 지하철에서는 강의 1개 정도를 추가로 들었고,&lt;br /&gt;퇴근 후에는 1~2시간 정도 산책하면서 그날 들었던 강의 중 이해가 잘 되지 않았던 부분을 다시 들었다.&lt;/p&gt;
&lt;p data-end=&quot;525&quot; data-start=&quot;483&quot; data-ke-size=&quot;size16&quot;&gt;어느 정도 이해가 됐다고 느껴지면 다음 강의를 이어서 보는 식으로 공부했다.&lt;/p&gt;
&lt;p data-end=&quot;525&quot; data-start=&quot;483&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;634&quot; data-start=&quot;527&quot; data-ke-size=&quot;size16&quot;&gt;시험 1주 전부터는 &lt;b&gt;맞추다&lt;/b&gt; 웹사이트를 이용해서 기출문제를 풀었다.&lt;br /&gt;이때부터는 새로운 내용을 공부하기보다는, 기출을 풀면서 자주 틀리는 개념과 약한 과목을 확인하는 데 집중했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GZvJq/dJMcac37daA/uY8pAnGk96OWl6Ve3DeUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GZvJq/dJMcac37daA/uY8pAnGk96OWl6Ve3DeUkk/img.png&quot; data-alt=&quot;맞추다 기출문제 시험&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GZvJq/dJMcac37daA/uY8pAnGk96OWl6Ve3DeUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGZvJq%2FdJMcac37daA%2FuY8pAnGk96OWl6Ve3DeUkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;맞추다 기출문제 시험&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;791&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;맞추다 기출문제 시험&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;634&quot; data-start=&quot;527&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;743&quot; data-start=&quot;636&quot; data-ke-size=&quot;size16&quot;&gt;이번 공부를 하면서 느낀 것은, 직장인 공부는 하루 공부량보다 &lt;b&gt;루틴을 유지하는 것&lt;/b&gt;이 더 중요하다는 점이었다.&lt;br /&gt;매일 조금씩이라도 흐름을 끊지 않고 가져가는 것이 가장 큰 도움이 됐다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1815&quot; data-start=&quot;1807&quot; data-section-id=&quot;1atrqdo&quot; data-ke-size=&quot;size26&quot;&gt;공부 방법&lt;/h2&gt;
&lt;p data-end=&quot;1850&quot; data-start=&quot;1817&quot; data-ke-size=&quot;size16&quot;&gt;내가 공부하면서 가장 중요하게 생각한 것은 반복이었다.&lt;/p&gt;
&lt;p data-end=&quot;1968&quot; data-start=&quot;1852&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 모든 개념을 완벽하게 이해하려고 하면 시간이 너무 오래 걸린다.&lt;/p&gt;
&lt;p data-end=&quot;1968&quot; data-start=&quot;1852&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특히 정보처리기사 필기는 범위가 넓기 때문에,&lt;/p&gt;
&lt;p data-end=&quot;1968&quot; data-start=&quot;1852&quot; data-ke-size=&quot;size16&quot;&gt;모든 내용을 깊게 파고들기보다는 전체적인 흐름을 먼저 잡는 것이 중요하다고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;1989&quot; data-start=&quot;1970&quot; data-ke-size=&quot;size16&quot;&gt;공부 흐름은 대략 이렇게 가져갔다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2086&quot; data-start=&quot;1991&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2018&quot; data-start=&quot;1991&quot; data-section-id=&quot;xajonn&quot;&gt;흥달쌤 강의 1회독&lt;/li&gt;
&lt;li data-end=&quot;2030&quot; data-start=&quot;2019&quot; data-section-id=&quot;18nrf1d&quot;&gt;교재 문제 풀기 (이것도 알고보니 전부 기출문제였음)&lt;/li&gt;
&lt;li data-end=&quot;2045&quot; data-start=&quot;2031&quot; data-section-id=&quot;8yp12x&quot;&gt;틀린 문제 개념 정리 및 강의 시청&lt;/li&gt;
&lt;li data-end=&quot;2045&quot; data-start=&quot;2031&quot; data-section-id=&quot;8yp12x&quot;&gt;'맞추다'에서 기출문제 시험&lt;/li&gt;
&lt;li data-end=&quot;2065&quot; data-start=&quot;2046&quot; data-section-id=&quot;wn4vt2&quot;&gt;자주 나오는 키워드 반복 암기&lt;/li&gt;
&lt;li data-end=&quot;2086&quot; data-start=&quot;2066&quot; data-section-id=&quot;nsqi6m&quot;&gt;시험 직전에는 오답 위주로 복습&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2117&quot; data-start=&quot;2088&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 문제를 많이 틀려도 크게 신경 쓰지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;2185&quot; data-start=&quot;2119&quot; data-ke-size=&quot;size16&quot;&gt;기출을 반복하다 보면 비슷한 개념이 계속 등장한다.&lt;br /&gt;그래서 틀린 문제를 통해 개념을 익히는 방식이 꽤 효과적이었다.&lt;/p&gt;
&lt;p data-end=&quot;2185&quot; data-start=&quot;2119&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2209&quot; data-start=&quot;2187&quot; data-ke-size=&quot;size16&quot;&gt;다만 답만 외우는 방식은 피하려고 했다.&lt;/p&gt;
&lt;p data-end=&quot;2327&quot; data-start=&quot;2211&quot; data-ke-size=&quot;size16&quot;&gt;기사시험 자체를 처음 응시했기 때문에, 무조건 기출과 동일하게 나올거라고 믿을 수 없었다.&lt;br /&gt;그래서 최소한 왜 이 답이 맞는지, 다른 보기는 왜 아닌지 정도는 이해하고 넘어가려고 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4289&quot; data-start=&quot;4270&quot; data-section-id=&quot;1q3hih9&quot; data-ke-size=&quot;size26&quot;&gt;직장인 기준으로 느낀 공부 팁&lt;/h2&gt;
&lt;p data-end=&quot;4341&quot; data-start=&quot;4291&quot; data-ke-size=&quot;size16&quot;&gt;직장인이 정보처리기사 필기를 준비할 때 가장 어려운 점은 시간보다도 꾸준함이라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;4400&quot; data-start=&quot;4343&quot; data-ke-size=&quot;size16&quot;&gt;하루에 3~4시간씩 공부하기는 쉽지 않다.&lt;br /&gt;퇴근 후에는 피곤하고, 주말에는 쉬고 싶은 마음도 크다.&lt;/p&gt;
&lt;p data-end=&quot;4425&quot; data-start=&quot;4402&quot; data-ke-size=&quot;size16&quot;&gt;그래서 짧게라도 매일 보는 것이 중요했다.&lt;/p&gt;
&lt;p data-end=&quot;4448&quot; data-start=&quot;4427&quot; data-ke-size=&quot;size16&quot;&gt;내가 느낀 현실적인 팁은 다음과 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4472&quot; data-start=&quot;4455&quot; data-section-id=&quot;1bzvwgn&quot; data-ke-size=&quot;size26&quot;&gt;1. 아침 시간을 활용하기&lt;/h2&gt;
&lt;p data-end=&quot;4505&quot; data-start=&quot;4474&quot; data-ke-size=&quot;size16&quot;&gt;개인적으로는 퇴근 후보다 출근 전 공부가 더 잘 맞았다.&lt;/p&gt;
&lt;p data-end=&quot;4576&quot; data-start=&quot;4507&quot; data-ke-size=&quot;size16&quot;&gt;퇴근 후에는 생각보다 집중력이 많이 떨어졌다.&lt;br /&gt;그래서 중요한 개념 정리나 문제 풀이는 가능하면 아침에 하는 것이 좋았다.&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4578&quot; data-ke-size=&quot;size16&quot;&gt;아침에 1시간 정도만 공부해도, 하루를 시작하기 전에 어느 정도 공부량을 확보할 수 있다는 점이 좋았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4670&quot; data-start=&quot;4643&quot; data-section-id=&quot;elprg7&quot; data-ke-size=&quot;size26&quot;&gt;2. 강의는 이동 시간이나 산책 시간에 듣기&lt;/h2&gt;
&lt;p data-end=&quot;4698&quot; data-start=&quot;4672&quot; data-ke-size=&quot;size16&quot;&gt;책상에 앉아서만 공부하려고 하면 부담이 커진다.&lt;/p&gt;
&lt;p data-end=&quot;4774&quot; data-start=&quot;4700&quot; data-ke-size=&quot;size16&quot;&gt;퇴근 후에는 책을 펼치는 것 자체가 귀찮게 느껴질 때도 있었다.&lt;br /&gt;그래서 강의는 이동 시간이나 산책 시간에 듣는 방식으로 활용했다.&lt;/p&gt;
&lt;p data-end=&quot;4821&quot; data-start=&quot;4776&quot; data-ke-size=&quot;size16&quot;&gt;완전히 집중해서 듣지는 못하더라도, 전체 흐름을 반복해서 익히는 데 도움이 됐다.&lt;/p&gt;
&lt;p data-end=&quot;4879&quot; data-start=&quot;4823&quot; data-ke-size=&quot;size16&quot;&gt;특히 용어나 개념이 낯설 때는 한 번에 이해하려고 하기보다 여러 번 들으면서 익숙해지는 것이 좋았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4900&quot; data-start=&quot;4886&quot; data-section-id=&quot;auetwd&quot; data-ke-size=&quot;size26&quot;&gt;3. 완벽주의 버리기&lt;/h2&gt;
&lt;p data-end=&quot;4920&quot; data-start=&quot;4902&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 필기는 범위가 넓다.&lt;/p&gt;
&lt;p data-end=&quot;4964&quot; data-start=&quot;4922&quot; data-ke-size=&quot;size16&quot;&gt;모든 내용을 완벽하게 이해하고 시험장에 들어가려고 하면 오히려 지치기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;5042&quot; data-start=&quot;4966&quot; data-ke-size=&quot;size16&quot;&gt;물론 이해가 중요하지 않다는 뜻은 아니다.&lt;br /&gt;하지만 시험 준비 관점에서는 자주 나오는 개념을 우선적으로 정리하는 것이 더 효율적이었다.&lt;/p&gt;
&lt;p data-end=&quot;5106&quot; data-start=&quot;5044&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 모든 내용을 깊게 파고들기보다는, 기출을 풀면서 자주 등장하는 개념을 중심으로 정리하는 방식이 좋았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;5134&quot; data-start=&quot;5113&quot; data-section-id=&quot;dliy47&quot; data-ke-size=&quot;size26&quot;&gt;4. 실무 경험을 너무 믿지 않기&lt;/h2&gt;
&lt;p data-end=&quot;5178&quot; data-start=&quot;5136&quot; data-ke-size=&quot;size16&quot;&gt;개발자로 일하고 있다고 해서 정보처리기사 필기가 무조건 쉬운 것은 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;5227&quot; data-start=&quot;5180&quot; data-ke-size=&quot;size16&quot;&gt;실무에서 자주 쓰는 내용은 도움이 되지만, 시험은 시험만의 표현과 출제 방식이 있다.&lt;/p&gt;
&lt;p data-end=&quot;5318&quot; data-start=&quot;5229&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 DB를 실무에서 다뤄봤더라도 정규화 단계나 관계 대수 문제는 따로 공부해야 한다.&lt;br /&gt;Java를 사용하고 있더라도 C언어 코드 문제는 낯설 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5357&quot; data-start=&quot;5320&quot; data-ke-size=&quot;size16&quot;&gt;그래서 실무 경험이 있는 사람도 기출 정리는 꼭 필요하다고 느꼈다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;5377&quot; data-start=&quot;5364&quot; data-section-id=&quot;1e91ai0&quot; data-ke-size=&quot;size26&quot;&gt;시험장에서 느낀 점&lt;/h2&gt;
&lt;p data-end=&quot;5404&quot; data-start=&quot;5379&quot; data-ke-size=&quot;size16&quot;&gt;시험장에서는 생각보다 헷갈리는 문제가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;5503&quot; data-start=&quot;5406&quot; data-ke-size=&quot;size16&quot;&gt;기출에서 봤던 개념도 표현이 조금 다르게 나오면 순간적으로 고민하게 된다.&lt;br /&gt;그래서 단순히 답만 외우는 방식보다는, 왜 그 답이 되는지 정도는 이해하고 넘어가는 것이 좋다.&lt;/p&gt;
&lt;p data-end=&quot;5503&quot; data-start=&quot;5406&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5532&quot; data-start=&quot;5505&quot; data-ke-size=&quot;size16&quot;&gt;특히 용어 문제는 비슷한 개념끼리 헷갈리기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;5593&quot; data-start=&quot;5534&quot; data-ke-size=&quot;size16&quot;&gt;보안 용어, 네트워크 용어, 개발 방법론 관련 용어는 시험 직전에 한 번 더 정리해두는 것이 도움이 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fv4KV/dJMcaakWtf9/eqN65mekdmUBssvJnPNn8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fv4KV/dJMcaakWtf9/eqN65mekdmUBssvJnPNn8K/img.png&quot; data-alt=&quot;노션에 틀렸던 용어들 정리하면서 시험 직전에 봤어요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fv4KV/dJMcaakWtf9/eqN65mekdmUBssvJnPNn8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFv4KV%2FdJMcaakWtf9%2FeqN65mekdmUBssvJnPNn8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;노션에 틀렸던 용어들 정리하면서 시험 직전에 봤어요&quot; loading=&quot;lazy&quot; width=&quot;1554&quot; height=&quot;847&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션에 틀렸던 용어들 정리하면서 시험 직전에 봤어요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;5617&quot; data-start=&quot;5595&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5617&quot; data-start=&quot;5595&quot; data-ke-size=&quot;size16&quot;&gt;코드 문제도 급하게 풀면 실수하기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;5707&quot; data-start=&quot;5619&quot; data-ke-size=&quot;size16&quot;&gt;반복문, 조건문, 변수 값 변화를 차분하게 따라가야 한다.&lt;br /&gt;눈으로만 대충 보면 틀릴 수 있기 때문에, 시간이 허락한다면 간단히 메모하면서 푸는 것이 좋다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;5726&quot; data-start=&quot;5714&quot; data-section-id=&quot;1lnu9tt&quot; data-ke-size=&quot;size26&quot;&gt;합격 후 느낀 점&lt;/h2&gt;
&lt;p data-end=&quot;5764&quot; data-start=&quot;5728&quot; data-ke-size=&quot;size16&quot;&gt;필기에 합격했다고 해서 개발 실력이 갑자기 좋아지는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;5812&quot; data-start=&quot;5766&quot; data-ke-size=&quot;size16&quot;&gt;하지만 공부를 하면서 그동안 애매하게 알고 있던 개념들을 한 번 정리할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;5812&quot; data-start=&quot;5766&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5895&quot; data-start=&quot;5814&quot; data-ke-size=&quot;size16&quot;&gt;특히 운영체제, 네트워크, 데이터베이스, 소프트웨어 공학 같은 내용을 다시 보면서 내가 부족하다고 느꼈던 CS 기초를 조금이나마 채울 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;5940&quot; data-start=&quot;5897&quot; data-ke-size=&quot;size16&quot;&gt;실무에서 사용하는 기술도 결국 기본 개념 위에 쌓이는 것이라는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;5940&quot; data-start=&quot;5897&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6044&quot; data-start=&quot;5942&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 DB를 다룰 때 트랜잭션이나 정규화 개념을 이해하고 있으면 문제를 바라보는 관점이 달라질 수 있다.&lt;br /&gt;네트워크나 보안 개념도 운영 이슈를 이해하는 데 도움이 될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;6093&quot; data-start=&quot;6046&quot; data-ke-size=&quot;size16&quot;&gt;자격증은 결국 결과물이고, 진짜 중요한 것은 준비 과정에서 얻는 기본기라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;6143&quot; data-start=&quot;6095&quot; data-ke-size=&quot;size16&quot;&gt;이번 필기 합격은 끝이 아니라, 앞으로 개발자로 성장하기 위한 중간 과정이라고 느꼈다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OOJhr/dJMcagrVDRr/svXJqcjIrx8Xr1bMqvR90k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OOJhr/dJMcagrVDRr/svXJqcjIrx8Xr1bMqvR90k/img.png&quot; data-alt=&quot;정보처리기사 2026년 1회차 필기 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OOJhr/dJMcagrVDRr/svXJqcjIrx8Xr1bMqvR90k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOOJhr%2FdJMcagrVDRr%2FsvXJqcjIrx8Xr1bMqvR90k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;정보처리기사 2026년 1회차 필기 합격&quot; loading=&quot;lazy&quot; width=&quot;1475&quot; height=&quot;380&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정보처리기사 2026년 1회차 필기 합격&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;7919&quot; data-start=&quot;7903&quot; data-section-id=&quot;17ky7o5&quot; data-ke-size=&quot;size26&quot;&gt;실기시험까지 응시한 상태&lt;/h2&gt;
&lt;p data-end=&quot;7946&quot; data-start=&quot;7921&quot; data-ke-size=&quot;size16&quot;&gt;필기 합격 이후에는 바로 실기시험도 준비했다.&lt;/p&gt;
&lt;p data-end=&quot;8043&quot; data-start=&quot;7948&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 실기는 필기와는 또 다른 느낌이었다.&lt;/p&gt;
&lt;p data-end=&quot;8043&quot; data-start=&quot;7948&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8231&quot; data-start=&quot;8198&quot; data-ke-size=&quot;size16&quot;&gt;현재는 실기시험까지 응시한 상태이고, 결과를 기다리고 있다.&lt;/p&gt;
&lt;p data-end=&quot;8324&quot; data-start=&quot;8233&quot; data-ke-size=&quot;size16&quot;&gt;아직 합격 여부가 나온 것은 아니기 때문에 실기 후기를 자세히 쓰기는 이르지만,&lt;br /&gt;합격하게 된다면 실기 준비 과정과 시험장에서 느낀 점도 따로 정리해볼 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;8324&quot; data-start=&quot;8233&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8400&quot; data-start=&quot;8326&quot; data-ke-size=&quot;size16&quot;&gt;필기 합격 후기가 정보처리기사 준비의 첫 번째 기록이라면,&lt;br /&gt;실기 합격 후기는 실제로 자격증 취득까지 마무리한 기록이 될 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;8413&quot; data-start=&quot;8407&quot; data-section-id=&quot;1h9nj85&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;8481&quot; data-start=&quot;8415&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사 2026년 1회차 필기시험을 준비하면서 직장인으로 공부를 병행하는 것이 쉽지만은 않다는 것을 다시 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;8537&quot; data-start=&quot;8483&quot; data-ke-size=&quot;size16&quot;&gt;하지만 하루에 많은 시간을 쓰지 않더라도, 조금씩 꾸준히 공부하면 충분히 결과를 만들 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;8537&quot; data-start=&quot;8483&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8614&quot; data-start=&quot;8539&quot; data-ke-size=&quot;size16&quot;&gt;이번 정보처리기사 필기 준비는 단순히 자격증을 따기 위한 공부라기보다,&lt;/p&gt;
&lt;p data-end=&quot;8614&quot; data-start=&quot;8539&quot; data-ke-size=&quot;size16&quot;&gt;개발자로서 부족했던 CS 기초를 다시 정리하는 과정에 가까웠다.&lt;/p&gt;
&lt;p data-end=&quot;8614&quot; data-start=&quot;8539&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8762&quot; data-start=&quot;8616&quot; data-ke-size=&quot;size16&quot;&gt;이미 SQLD를 취득한 상태였기 때문에 데이터베이스 과목은 비교적 수월하게 공부할 수 있었다.&lt;br /&gt;하지만 운영체제, 네트워크, 소프트웨어 공학, 보안 같은 내용은 여전히 부족하다고 느끼는 부분이 많았고,&lt;/p&gt;
&lt;p data-end=&quot;8762&quot; data-start=&quot;8616&quot; data-ke-size=&quot;size16&quot;&gt;이번 시험을 준비하면서 전체적인 흐름을 정리할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;8762&quot; data-start=&quot;8616&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8846&quot; data-start=&quot;8764&quot; data-ke-size=&quot;size16&quot;&gt;나처럼 컴퓨터공학 전공자가 아니거나, 실무를 하면서도 CS 기초가 부족하다고 느끼는 사람에게는 정보처리기사가 좋은 출발점이 될 수 있다고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;8846&quot; data-start=&quot;8764&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8938&quot; data-start=&quot;8848&quot; data-ke-size=&quot;size16&quot;&gt;운영체제, 네트워크, 데이터베이스, 소프트웨어 공학 같은 내용은 당장 실무에서 매일 깊게 사용하지 않더라도, 개발자로 성장하기 위해 계속 쌓아가야 하는 기반이다.&lt;/p&gt;
&lt;p data-end=&quot;8938&quot; data-start=&quot;8848&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8973&quot; data-start=&quot;8940&quot; data-ke-size=&quot;size16&quot;&gt;현재는 실기시험까지 응시한 상태이고, 결과를 기다리고 있다.&lt;/p&gt;
&lt;p data-end=&quot;9010&quot; data-start=&quot;8975&quot; data-ke-size=&quot;size16&quot;&gt;좋은 결과가 나온다면 실기 합격 후기도 따로 작성해볼 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;9010&quot; data-start=&quot;8975&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;9096&quot; data-start=&quot;9012&quot; data-ke-size=&quot;size16&quot;&gt;이번 필기 합격 후기는 정보처리기사 준비 과정의 첫 번째 기록이자,&lt;br /&gt;비전공 직장인 개발자로서 부족했던 CS 기초를 정리해본 경험으로 남을 것 같다.&lt;/p&gt;</description>
      <category>자격증</category>
      <category>맞추다</category>
      <category>비전공 개발자</category>
      <category>정보처리기사</category>
      <category>정보처리기사 2026</category>
      <category>정보처리기사 시험과목</category>
      <category>정보처리기사 취득방법</category>
      <category>정보처리기사 필기</category>
      <category>정보처리기사 합격기준</category>
      <category>정보처리기사 합격후기</category>
      <category>흥달쌤</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/14</guid>
      <comments>https://hwang-dev.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 11 May 2026 18:09:39 +0900</pubDate>
    </item>
    <item>
      <title>[오류신고 관리 시스템] Spring Boot 개인 프로젝트 README 작성하기</title>
      <link>https://hwang-dev.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;현재 업무에서는 이미 운영 중인 시스템을 유지보수하면서 기존 코드를 수정하거나 기능을 추가하는 일을 주로 하고 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;기존 구조를 파악하고 오류 원인을 찾는 경험은 많이 쌓을 수 있었지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;백엔드 프로젝트를 처음부터 설계하고 구현하는 경험은 상대적으로 부족하다고 느꼈다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;그래서 업무에서 자주 접했던 &lt;b&gt;오류신고 처리 과정&lt;/b&gt;을 주제로 개인 프로젝트를 시작해보기로 했다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;이번 프로젝트는 사용자가 오류를 신고하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;관리자가 해당 내용을 확인해 처리상태를 변경하거나 답변을 등록하는 &lt;b&gt;오류신고 관리 시스템&lt;/b&gt;이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;익숙한 흐름을 바탕으로 직접 요구사항을 정리하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존에 접했던 시스템의 아쉬운 점도 일부 반영하면서 백엔드 기본기를 다시 정리해보는 것이 목표다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;795&quot; data-start=&quot;775&quot; data-section-id=&quot;n8433r&quot; data-ke-size=&quot;size26&quot;&gt;README를 먼저 작성한 이유&lt;/h2&gt;
&lt;p data-end=&quot;856&quot; data-start=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개인 프로젝트를 시작할 때는 처음부터 내 손으로 만든다는 기대감 때문에 바로 코드를 작성하고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;856&quot; data-start=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span&gt;하지만 이왕 하는 거 제대로 해보자는 마음으로, 무작정 개발을 시작하기보다는&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;856&quot; data-start=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; 프로젝트의 목적과 기능 범위를 정리하기 위해 먼저 README.md를 작성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;856&quot; data-start=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;900&quot; data-start=&quot;858&quot; data-ke-size=&quot;size16&quot;&gt;README를 먼저 작성하면 프로젝트의 목적과 기능 범위를 정리할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;900&quot; data-start=&quot;858&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;940&quot; data-start=&quot;902&quot; data-ke-size=&quot;size16&quot;&gt;특히 개인 프로젝트는 개발하다 보면 기능 범위가 계속 넓어지기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;940&quot; data-start=&quot;902&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1039&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 회원가입, 로그인, 오류신고 등록 정도만 생각하더라도,&lt;/p&gt;
&lt;p data-end=&quot;1039&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;개발을 진행하다 보면 검색, 페이징, 파일 첨부, 권한 처리, 예외 처리 같은 기능을 계속 추가하고 싶어진다.&lt;/p&gt;
&lt;p data-end=&quot;1039&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1041&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 프로젝트는 README에 1차 개발 범위를 먼저 정리해두고, 핵심 기능을 먼저 구현한 뒤 필요한 기능을 하나씩 추가하는 방식으로 진행하려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;1129&quot; data-start=&quot;1041&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1184&quot; data-start=&quot;1174&quot; data-section-id=&quot;n2sgqw&quot; data-ke-size=&quot;size26&quot;&gt;프로젝트 소개&lt;/h2&gt;
&lt;p data-end=&quot;1213&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size16&quot;&gt;README에는 프로젝트를 다음과 같이 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;1213&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;README&lt;/b&gt;&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#&amp;nbsp;오류신고&amp;nbsp;관리&amp;nbsp;시스템 &lt;br /&gt;&lt;br /&gt;##&amp;nbsp;프로젝트&amp;nbsp;소개 &lt;br /&gt;&lt;br /&gt;오류신고&amp;nbsp;관리&amp;nbsp;시스템은&amp;nbsp;사용자가&amp;nbsp;서비스&amp;nbsp;이용&amp;nbsp;중&amp;nbsp;발생한&amp;nbsp;오류를&amp;nbsp;신고하고, &lt;br /&gt;관리자가 해당 오류를 확인하여 처리상태 변경 및 답변을 등록할 수 있는 웹 기반 관리 시스템입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1053&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;오류신고는 단순히 글을 등록하는 것으로 끝나지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;1118&quot; data-start=&quot;1055&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 오류를 신고하면 관리자는 해당 내용을 확인하고, 처리상태를 변경하고, 필요한 경우 답변을 등록해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1180&quot; data-start=&quot;1120&quot; data-ke-size=&quot;size16&quot;&gt;즉, 일반적인 게시판 CRUD에 사용자와 관리자의 역할 구분, 처리상태 관리, 답변 흐름이 추가되는 구조다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;오류신고 흐름&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자 오류신고 등록&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 관리자 확인&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 처리상태 변경&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 관리자 답변 등록&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 사용자 답변 확인&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1582&quot; data-start=&quot;1508&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1258&quot; data-start=&quot;1250&quot; data-section-id=&quot;17o75js&quot; data-ke-size=&quot;size26&quot;&gt;주요 기능&lt;/h2&gt;
&lt;p data-end=&quot;1295&quot; data-start=&quot;1260&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 기능은 사용자 기능과 관리자 기능으로 나누었다.&lt;/p&gt;
&lt;p data-end=&quot;1675&quot; data-start=&quot;1665&quot; data-section-id=&quot;fqf06v&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1675&quot; data-start=&quot;1665&quot; data-section-id=&quot;fqf06v&quot; data-ke-size=&quot;size20&quot;&gt;일반 사용자&lt;/h4&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;일반 사용자&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 회원가입 및 로그인&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 오류신고 등록&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 본인이 작성한 오류신고 목록 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 본인이 작성한 오류신고 상세 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 관리자 답변 확인&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1871&quot; data-start=&quot;1819&quot; data-ke-size=&quot;size16&quot;&gt;일반 사용자는 전체 오류신고 목록이 아니라, 본인이 작성한 오류신고만 조회할 수 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1950&quot; data-start=&quot;1873&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 로그인한 사용자 정보를 기준으로 조회 조건을 처리해야 하기 때문에, 권한 처리와 사용자 식별 로직을 함께 고민해야 할 것 같다.&lt;/p&gt;
&lt;h4 data-end=&quot;1959&quot; data-start=&quot;1952&quot; data-section-id=&quot;1xiuz0a&quot; data-ke-size=&quot;size20&quot;&gt;관리자&lt;/h4&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;관리자&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 전체 오류신고 목록 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 오류신고 상세 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 처리상태 변경&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 관리자 답변 등록 및 수정&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1658&quot; data-start=&quot;1613&quot; data-ke-size=&quot;size16&quot;&gt;관리자는 전체 오류신고를 확인하고, 처리상태를 변경하며, 답변을 등록할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1777&quot; data-start=&quot;1660&quot; data-ke-size=&quot;size16&quot;&gt;관리자 기능에서는 단순 조회보다 처리 흐름이 중요하다.&lt;br /&gt;예를 들어 사용자가 오류신고를 등록하면 처음에는 접수 상태가 되고,&lt;/p&gt;
&lt;p data-end=&quot;1777&quot; data-start=&quot;1660&quot; data-ke-size=&quot;size16&quot;&gt;관리자가 확인하면 처리중, 답변을 완료하면 완료 상태로 변경할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2242&quot; data-start=&quot;2199&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2260&quot; data-start=&quot;2249&quot; data-section-id=&quot;mafnik&quot; data-ke-size=&quot;size26&quot;&gt;개발 범위&lt;/h2&gt;
&lt;p data-end=&quot;1827&quot; data-start=&quot;1789&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 모든 기능을 완성하려고 하면 프로젝트가 너무 커질 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1882&quot; data-start=&quot;1829&quot; data-ke-size=&quot;size16&quot;&gt;그래서 1차 개발에서는 핵심 흐름을 먼저 구현하고, 2차 개발에서 부가 기능을 추가할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;2330&quot; data-start=&quot;2302&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;1차 개발 범위&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 회원가입&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 로그인 / 로그아웃&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 오류신고 등록&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 오류신고 목록 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 오류신고 상세 조회&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 처리상태 변경&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 관리자 답변 등록 / 수정&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 권한 체크&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 입력값 검증&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;2차 개발 범위&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 오류신고 수정 / 삭제&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 페이징&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 첨부파일 업로드 / 다운로드&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 검색 기능&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 상태별 필터링&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 처리 이력 관리&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 이메일 알림&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 엑셀 다운로드&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 통계 대시보드&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 회원가입과 로그인, 오류신고 등록/조회, 관리자 답변 등록까지 이어지는 핵심 흐름을 먼저 완성하는 것이 목표다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2219&quot; data-start=&quot;2211&quot; data-section-id=&quot;1yur4zw&quot; data-ke-size=&quot;size26&quot;&gt;기술 스택&lt;/h2&gt;
&lt;p data-end=&quot;2243&quot; data-start=&quot;2221&quot; data-ke-size=&quot;size16&quot;&gt;기술 스택은 다음과 같이 생각하고 있다.&lt;/p&gt;
&lt;div class=&quot;note-box&quot;&gt;&lt;b&gt;기술 스택&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;### Backend&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- Java 17&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- Spring Boot 3.x&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- Spring Security&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- Spring Data JPA&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- PostgreSQL&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- Gradle&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;### Frontend&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 1차: Thymeleaf&lt;/span&gt;&lt;br /&gt;&lt;span&gt;- 2차: React 전환 예정&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2435&quot; data-start=&quot;2408&quot; data-ke-size=&quot;size16&quot;&gt;Java 버전은 Java 17을 사용할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;2497&quot; data-start=&quot;2437&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.x 기반으로 프로젝트를 구성한다면 Java 17은 기본 선택지에 가깝다고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;2608&quot; data-start=&quot;2499&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2608&quot; data-start=&quot;2499&quot; data-ke-size=&quot;size16&quot;&gt;기존 업무에서는 SQL을 직접 작성하거나 MyBatis 기반 코드를 접하는 경우가 많았기 때문에,&lt;/p&gt;
&lt;p data-end=&quot;2608&quot; data-start=&quot;2499&quot; data-ke-size=&quot;size16&quot;&gt;이번 개인 프로젝트에서는 JPA를 사용해 객체 중심으로 데이터를 다루는 방식도 연습해보고 싶었다.&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드는 우선 Thymeleaf로 시작할 예정이다.&lt;br /&gt;처음부터 React까지 함께 적용하면 범위가 커질 수 있어서,&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size16&quot;&gt;1차 개발에서는 Thymeleaf로 전체 흐름을 먼저 완성하고 이후 React로 전환하는 방향을 생각하고 있다.&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size14&quot;&gt;(사실 React도 한번도&amp;nbsp; 다뤄보지 못해서 연습을 위해 2차로 추가했다..)&lt;/p&gt;
&lt;p data-end=&quot;2740&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2748&quot; data-start=&quot;2742&quot; data-section-id=&quot;1h9nj85&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;2808&quot; data-start=&quot;2750&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 개인 프로젝트로 진행할 &lt;b&gt;오류신고 관리 시스템&lt;/b&gt;의 README 작성 내용을 정리해봤다.&lt;/p&gt;
&lt;p data-end=&quot;2858&quot; data-start=&quot;2810&quot; data-ke-size=&quot;size16&quot;&gt;README를 먼저 작성해보니 프로젝트의 목적과 기능 범위를 정리하는 데 도움이 됐다.&lt;/p&gt;
&lt;p data-end=&quot;2941&quot; data-start=&quot;2860&quot; data-ke-size=&quot;size16&quot;&gt;아직 실제 개발은 시작 단계지만, 회원/권한, 오류신고, 관리자 처리, 상태 관리 등을 직접 구현해보면서 백엔드 기본기를 다시 정리해보려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;3008&quot; data-start=&quot;2943&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이 프로젝트에서 &lt;b&gt;왜 Java 17과 Spring Boot를 선택했는지&lt;/b&gt;에 대해 정리해볼 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;3008&quot; data-start=&quot;2943&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/ShMhT/dJMcaiiQncI/eAPHbYBYHc41lmkrXKBF9K/README.md?attach=1&amp;amp;knm=tfile.md&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;README.md&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/프로젝트 기록</category>
      <category>java17</category>
      <category>JPA</category>
      <category>PostgreSQL</category>
      <category>readme</category>
      <category>Spring Boot</category>
      <category>SpringSecurity</category>
      <category>개발공부</category>
      <category>개인프로젝트</category>
      <category>백엔드</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/13</guid>
      <comments>https://hwang-dev.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 30 Apr 2026 09:03:17 +0900</pubDate>
    </item>
    <item>
      <title>[Nginx] location 우선순위 정리</title>
      <link>https://hwang-dev.tistory.com/12</link>
      <description>&lt;h2 data-end=&quot;150&quot; data-start=&quot;125&quot; data-section-id=&quot;18zvd8v&quot; data-ke-size=&quot;size26&quot;&gt;Nginx location 우선순위 요약&lt;/h2&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;152&quot; data-ke-size=&quot;size16&quot;&gt;Nginx의 location은 설정 파일에 작성된 순서대로 단순히 위에서 아래로 적용되는 것이 아니라, 정해진 매칭 규칙에 따라 선택된다.&lt;/p&gt;
&lt;p data-end=&quot;253&quot; data-start=&quot;233&quot; data-ke-size=&quot;size16&quot;&gt;먼저 결론부터 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1777246597768&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 정확히 일치하는 URI가 있으면 즉시 선택
location = /path {
    # /path 요청만 정확히 매칭
}

# 2. prefix 매칭 후 정규식 location 검사를 생략
location ^~ /path/ {
    # /path/ 로 시작하는 요청 매칭
    # 매칭되면 ~, ~* 정규식 location은 검사하지 않음
}

# 3. 대소문자를 구분하는 정규식 매칭
location ~ \.php$ {
    # .php 로 끝나는 요청 매칭
}

# 4. 대소문자를 구분하지 않는 정규식 매칭
location ~* \.(png|jpg|jpeg)$ {
    # .png, .jpg, .jpeg 확장자 요청 매칭
}

# 5. 일반 prefix 매칭
location /path/ {
    # /path/ 로 시작하는 요청 매칭
    # prefix 중 가장 긴 경로가 후보가 됨
}

# 6. 최후의 기본 location
location / {
    # 위 location에 매칭되지 않은 요청 처리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 위 순서가 설정 파일에 반드시 이렇게 작성해야 한다는 의미는 아니라는 것이다.&lt;br /&gt;Nginx가 요청 URI를 보고 어떤 location을 최종 선택하는지 이해하기 위한 우선순위로 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 정확한 실제 매칭 흐름은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;tip-box&quot;&gt;&lt;b&gt;실제 매칭 흐름&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 정확히 일치하는 location = 이 있는지 확인한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;2. prefix location 중 가장 긴 경로를 찾는다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. 가장 긴 prefix location이 ^~ 이면 정규식 검사를 생략하고 해당 location을 사용한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;4. ^~가 아니면 정규식 location을 설정 파일에 작성된 순서대로 검사한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;5. 정규식 location이 매칭되면 해당 location을 사용한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;6. 정규식이 하나도 매칭되지 않으면 처음에 찾았던 가장 긴 prefix location을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_core_module.html#location&quot; data-end=&quot;997&quot; data-start=&quot;906&quot;&gt;Nginx 공식 문서 - location&lt;span aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/a&gt;을 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 이것이다.&lt;/p&gt;
&lt;div class=&quot;result-box&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 prefix location보다 정규식 location이 최종적으로 우선될 수 있다.&lt;br /&gt;특정 경로를 정규식 location보다 우선 처리하고 싶다면 ^~를 사용해야 한다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1440&quot; data-start=&quot;1426&quot; data-section-id=&quot;i6qzjn&quot; data-ke-size=&quot;size26&quot;&gt;location이란?&lt;/h2&gt;
&lt;p data-end=&quot;1502&quot; data-start=&quot;1442&quot; data-ke-size=&quot;size16&quot;&gt;location은 클라이언트가 요청한 URI에 따라 어떤 처리를 할지 결정하는 Nginx 설정 블록이다.&lt;/p&gt;
&lt;p data-end=&quot;1548&quot; data-start=&quot;1504&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 설정은 /api/로 시작하는 요청을 백엔드 서버로 전달한다.&lt;/p&gt;
&lt;p data-end=&quot;1548&quot; data-start=&quot;1504&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777246796527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location /api/ {
    proxy_pass http://backend;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 아래 설정은 .png, .jpg, .jpeg로 끝나는 이미지 요청을 Nginx가 직접 처리하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777246812239&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location ~* \.(png|jpg|jpeg)$ {
    root /usr/share/nginx/html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1762&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 location을 이용하면 요청 경로에 따라 백엔드로 프록시할 수도 있고,&lt;/p&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1762&quot; data-ke-size=&quot;size16&quot;&gt;Nginx가 직접 정적 파일을 내려줄 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;1894&quot; data-start=&quot;1839&quot; data-ke-size=&quot;size16&quot;&gt;문제는 location 설정이 많아질수록 요청이 어느 블록에 매칭되는지 헷갈리기 쉽다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;1894&quot; data-start=&quot;1839&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;1jae43s&quot; data-end=&quot;1918&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size26&quot;&gt;location 문법 종류&lt;/h2&gt;
&lt;p data-end=&quot;1958&quot; data-start=&quot;1920&quot; data-ke-size=&quot;size16&quot;&gt;Nginx에서 자주 사용하는 location 문법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1777246871263&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location = /exact {
    # 정확히 /exact 요청만 매칭
}

location /images/ {
    # /images/ 로 시작하는 요청 매칭
}

location ^~ /static/ {
    # /static/ 로 시작하면 정규식 location 검사 생략
}

location ~ \.php$ {
    # 대소문자 구분 정규식 매칭
}

location ~* \.(png|jpg|jpeg)$ {
    # 대소문자 구분 없는 정규식 매칭
}

location / {
    # 최후의 기본 location
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 문법을 표로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 70.5814%; height: 132px;&quot; border=&quot;1&quot; data-end=&quot;2623&quot; data-start=&quot;2302&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody data-end=&quot;2623&quot; data-start=&quot;2324&quot;&gt;
&lt;tr&gt;
&lt;td&gt;문법&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2370&quot; data-start=&quot;2324&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2345&quot; data-start=&quot;2324&quot;&gt;location = /path&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2370&quot; data-start=&quot;2345&quot;&gt;요청 URI가 정확히 일치할 때만 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2412&quot; data-start=&quot;2371&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2391&quot; data-start=&quot;2371&quot;&gt;location /path/&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2412&quot; data-start=&quot;2391&quot;&gt;해당 경로로 시작하는 요청 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2474&quot; data-start=&quot;2413&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2436&quot; data-start=&quot;2413&quot;&gt;location ^~ /path/&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2474&quot; data-start=&quot;2436&quot;&gt;해당 prefix가 매칭되면 정규식 location 검사 생략&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2517&quot; data-start=&quot;2475&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2496&quot; data-start=&quot;2475&quot;&gt;location ~ regex&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2517&quot; data-start=&quot;2496&quot;&gt;대소문자를 구분하는 정규식 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2564&quot; data-start=&quot;2518&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2540&quot; data-start=&quot;2518&quot;&gt;location ~* regex&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2564&quot; data-start=&quot;2540&quot;&gt;대소문자를 구분하지 않는 정규식 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot; data-end=&quot;2623&quot; data-start=&quot;2565&quot;&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2580&quot; data-start=&quot;2565&quot;&gt;location /&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2623&quot; data-start=&quot;2580&quot;&gt;어떤 location에도 걸리지 않을 때 사용하는 기본 location&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;okbnd&quot; data-end=&quot;2665&quot; data-start=&quot;2630&quot; data-ke-size=&quot;size26&quot;&gt;일반 prefix location과 정규식 location&lt;/h2&gt;
&lt;p data-end=&quot;2717&quot; data-start=&quot;2667&quot; data-ke-size=&quot;size16&quot;&gt;가장 헷갈리는 부분은 일반 prefix location과 정규식 location의 관계다.&lt;/p&gt;
&lt;p data-end=&quot;2729&quot; data-start=&quot;2719&quot; data-ke-size=&quot;size16&quot;&gt;아래 설정을 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1777246919023&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    location /resource/data/ {
        proxy_pass http://backend;
    }

    location ~* \.(png|jpg|jpeg)$ {
        root /usr/share/nginx/html;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 아래 요청이 들어온다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777246943590&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/resource/data/sample.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 보면 /resource/data/로 시작하기 때문에 아래 location에 매칭될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777246958830&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location /resource/data/ {
    proxy_pass http://backend;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요청 URI가 .jpg로 끝나기 때문에 아래 정규식 location에도 매칭된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777246971151&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location ~* \.(png|jpg|jpeg)$ {
    root /usr/share/nginx/html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3378&quot; data-start=&quot;3240&quot; data-ke-size=&quot;size16&quot;&gt;Nginx는 먼저 prefix location 중 가장 긴 /resource/data/를 후보로 기억한다.&lt;br /&gt;그다음 정규식 location을 검사한다.&lt;br /&gt;그리고 정규식 location이 매칭되면 최종적으로 정규식 location을 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;3449&quot; data-start=&quot;3380&quot; data-ke-size=&quot;size16&quot;&gt;즉, 위 요청은 /resource/data/ location이 아니라 이미지 정규식 location으로 처리될 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;10dqdrp&quot; data-end=&quot;3510&quot; data-start=&quot;3491&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;^~를 사용해야 하는 경우&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3554&quot; data-start=&quot;3512&quot; data-ke-size=&quot;size16&quot;&gt;특정 경로는 정규식 location보다 무조건 먼저 처리하고 싶을 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3634&quot; data-start=&quot;3556&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 /resource/data/로 시작하는 요청은 파일 확장자가 .jpg든 .png든 무조건 백엔드로 보내고 싶다고 하자.&lt;/p&gt;
&lt;p data-end=&quot;3656&quot; data-start=&quot;3636&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때는 ^~를 사용하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;3656&quot; data-start=&quot;3636&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777247015062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location ^~ /resource/data/ {
    proxy_pass http://backend;
}

location ~* \.(png|jpg|jpeg)$ {
    root /usr/share/nginx/html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정하면 아래 요청은 .jpg로 끝나더라도 정규식 location 검사를 하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1777247257733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/resource/data/sample.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최종적으로 아래 location에 매칭된다.&lt;/p&gt;
&lt;pre id=&quot;code_1777247270333&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location ^~ /resource/data/ {
    proxy_pass http://backend;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;^~는 단순히 prefix 매칭을 의미하는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 prefix가 매칭되면 정규식 location 검사를 건너뛰게 만드는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 특정 경로를 정규식보다 우선 처리하고 싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 prefix location이 아니라 ^~ prefix location을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;2gvhcl&quot; data-end=&quot;4202&quot; data-start=&quot;4178&quot; data-ke-size=&quot;size26&quot;&gt;예시&lt;/h2&gt;
&lt;p data-end=&quot;4225&quot; data-start=&quot;4204&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같은 설정이 있다고 가정해보자.&lt;/p&gt;
&lt;p data-end=&quot;4225&quot; data-start=&quot;4204&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777247312821&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    location = /health {
        return 200 &quot;ok&quot;;
    }

    location /api/ {
        proxy_pass http://api-backend;
    }

    location ^~ /static/ {
        root /usr/share/nginx/html;
    }

    location ~* \.(png|jpg|jpeg)$ {
        root /usr/share/nginx/html/images;
    }

    location / {
        proxy_pass http://web-backend;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 요청은 다음과 같이 매칭된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;4989&quot; data-start=&quot;4614&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;요청 URI&lt;/td&gt;
&lt;td&gt;매칭되는 location&lt;/td&gt;
&lt;td&gt;이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4705&quot; data-start=&quot;4660&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4672&quot; data-start=&quot;4660&quot;&gt;/health&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4695&quot; data-start=&quot;4672&quot;&gt;location = /health&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4705&quot; data-start=&quot;4695&quot;&gt;정확히 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4772&quot; data-start=&quot;4706&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4721&quot; data-start=&quot;4706&quot;&gt;/api/users&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4740&quot; data-start=&quot;4721&quot;&gt;location /api/&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4772&quot; data-start=&quot;4740&quot;&gt;/api/ prefix 매칭, 정규식 매칭 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4842&quot; data-start=&quot;4773&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4794&quot; data-start=&quot;4773&quot;&gt;/static/logo.png&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4819&quot; data-start=&quot;4794&quot;&gt;location ^~ /static/&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4842&quot; data-start=&quot;4819&quot;&gt;^~ 매칭으로 정규식 검사 생략&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4918&quot; data-start=&quot;4843&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4864&quot; data-start=&quot;4843&quot;&gt;/upload/test.jpg&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4900&quot; data-start=&quot;4864&quot;&gt;location ~* \.(png|jpg|jpeg)$&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4918&quot; data-start=&quot;4900&quot;&gt;이미지 확장자 정규식 매칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4989&quot; data-start=&quot;4919&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4935&quot; data-start=&quot;4919&quot;&gt;/board/list&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4950&quot; data-start=&quot;4935&quot;&gt;location /&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4989&quot; data-start=&quot;4950&quot;&gt;다른 location에 매칭되지 않아 기본 location 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;5023&quot; data-start=&quot;4991&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5023&quot; data-start=&quot;4991&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 예시는 /static/logo.png다.&lt;/p&gt;
&lt;p data-end=&quot;5075&quot; data-start=&quot;5025&quot; data-ke-size=&quot;size16&quot;&gt;이 요청은 .png로 끝나기 때문에 이미지 정규식 location에도 매칭될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5075&quot; data-start=&quot;5025&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5159&quot; data-start=&quot;5077&quot; data-ke-size=&quot;size16&quot;&gt;하지만 /static/ location에 ^~가 붙어 있으므로&lt;/p&gt;
&lt;p data-end=&quot;5159&quot; data-start=&quot;5077&quot; data-ke-size=&quot;size16&quot;&gt;정규식 검사를 하지 않고 바로 /static/ location이 선택된다.&lt;/p&gt;
&lt;p data-end=&quot;5159&quot; data-start=&quot;5077&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;322&quot; data-start=&quot;316&quot; data-section-id=&quot;1h9nj85&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;Nginx의 location 우선순위는 단순히 설정 파일에 작성된 순서대로 결정되지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;451&quot; data-start=&quot;379&quot; data-ke-size=&quot;size16&quot;&gt;특히 일반 prefix location과 정규식 location이 함께 있을 때는 예상과 다른 location이 선택될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;453&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;464&quot; data-start=&quot;453&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;tip-box&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 정확히 일치하는 location = 이 있는지 확인한다.&lt;br /&gt;2. prefix location 중 가장 긴 경로를 찾는다.&lt;br /&gt;3. 가장 긴 prefix location이 ^~ 이면 정규식 검사를 생략하고 해당 location을 사용한다.&lt;br /&gt;4. ^~가 아니면 정규식 location을 설정 파일에 작성된 순서대로 검사한다.&lt;br /&gt;5. 정규식 location이 매칭되면 해당 location을 사용한다.&lt;br /&gt;6. 정규식이 하나도 매칭되지 않으면 처음에 찾았던 가장 긴 prefix location을 사용한다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>nginx</category>
      <category>Nginx</category>
      <category>nginx location</category>
      <category>nginx location 우선순위</category>
      <category>nginx proxy_pass</category>
      <category>nginx root</category>
      <category>nginx 문법</category>
      <category>nginx 설정</category>
      <category>nginx 정규식</category>
      <category>서버 설정</category>
      <category>웹서버</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/12</guid>
      <comments>https://hwang-dev.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 27 Apr 2026 08:27:46 +0900</pubDate>
    </item>
    <item>
      <title>운영서버 jpg 이미지 안보임 원인 - Nginx location 우선순위</title>
      <link>https://hwang-dev.tistory.com/11</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 4월 26일 오후 12_12_20.png&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdPnrD/dJMcafsP3df/jYFaLlwFZrxJ8KKFqntSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdPnrD/dJMcafsP3df/jYFaLlwFZrxJ8KKFqntSk0/img.png&quot; data-alt=&quot;Nginx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdPnrD/dJMcafsP3df/jYFaLlwFZrxJ8KKFqntSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdPnrD%2FdJMcafsP3df%2FjYFaLlwFZrxJ8KKFqntSk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-filename=&quot;ChatGPT Image 2026년 4월 26일 오후 12_12_20.png&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Nginx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;운영 환경에서 Nginx를 통해 서비스하던 jpg 이미지가 보이지 않는 문제를 발견했다.&lt;br /&gt;처음에는 파일 누락이나 경로 오류를 의심했지만, 실제 원인은 &lt;b&gt;Nginx location 우선순위와 try_files 설정&lt;/b&gt; 때문이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글에서는 운영에서 특정 jpg/png 이미지가 보이지 않았던 문제를 어떻게 추적했고,&lt;br /&gt;어떤 설정이 실제로 요청을 가로채고 있었는지,&lt;br /&gt;그리고 어떻게 해결했는지를 정리해보려고 한다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 화면에서 특정 이미지가 보이지 않았다.&lt;br /&gt;화면에서는 아래와 비슷한 형태의 경로로 이미지가 호출되고 있었다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;/test/test/sample.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;처음에는 아래 같은 가능성을 먼저 의심했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB에 저장된 파일명과 실제 파일명이 다른 경우&lt;/li&gt;
&lt;li&gt;요청 경로가 잘못 조합된 경우&lt;/li&gt;
&lt;li&gt;운영 서버 경로에 실제 이미지 파일이 없는 경우&lt;/li&gt;
&lt;li&gt;웹 서버나 애플리케이션 설정에서 이미지 요청을 다르게 처리하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 이슈를 볼 때는 보통 &lt;b&gt;파일 문제 &amp;rarr; 경로 문제 &amp;rarr; 설정 문제&lt;/b&gt; 순으로 좁혀가게 되는데,&lt;br /&gt;이번에도 같은 방식으로 접근했다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;처음에는 파일 누락이나 경로 문제를 의심했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 실제 운영 서버의 해당 경로를 확인했다.&lt;br /&gt;그 결과, 대상 이미지 파일은 서버에 정상적으로 존재하고 있었다.&lt;br /&gt;즉 이 시점에서 최소한 &lt;b&gt;&amp;ldquo;파일이 없어서 안 보이는 문제&amp;rdquo;는 아닐 가능성&lt;/b&gt;이 높아졌다.&lt;br /&gt;그래서 다음으로는 애플리케이션 코드와 웹 서버 설정을 보면서,&lt;br /&gt;요청이 실제로 어떻게 처리되는지 확인하게 됐다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션 로직도 함께 확인했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 쪽 처리 흐름은 대략 아래와 같았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DB에서 이미지 파일명을 조회한다.&lt;/li&gt;
&lt;li&gt;기준 디렉터리와 파일명을 조합해 실제 파일 경로를 만든다.&lt;/li&gt;
&lt;li&gt;서버 로컬 경로 기준으로 파일 존재 여부를 확인한다.&lt;/li&gt;
&lt;li&gt;파일이 존재하면 화면에서 사용할 이미지 URL을 생성한다.&lt;/li&gt;
&lt;li&gt;파일이 존재하지 않으면 이미지 대신 대체 문구인 &lt;b&gt;&quot;사진 없음&quot;&lt;/b&gt;을 내려준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 애플리케이션은 단순히 파일명만 내려주는 구조가 아니라,&lt;br /&gt;&lt;b&gt;실제 파일 존재 여부까지 확인한 뒤 이미지 태그를 만들도록 되어 있었다&lt;/b&gt;.&lt;br /&gt;여기서 중요한 건 실제 운영 화면에서 나타난 현상이었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제로는 &amp;ldquo;사진 없음&amp;rdquo;이 아니라 깨진 이미지가 보였다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 파일이 없으면 화면에 대체 문구가 표시될 것이라고 생각했다.&lt;br /&gt;그런데 실제 운영 화면에서는 그렇게 나오지 않았다.&lt;br /&gt;대신 화면에는 &lt;b&gt;깨진 이미지 아이콘과 alt 텍스트&lt;/b&gt;가 표시되고 있었다.&lt;br /&gt;이 말은 곧, 애플리케이션이 파일이 없다고 판단한 것이 아니라 &lt;b&gt;이미지 태그 자체는 생성해서 화면에 내려주고 있었다&lt;/b&gt;는 뜻이다.&lt;br /&gt;즉 흐름은 이렇게 볼 수 있었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일 존재 여부 체크는 통과했다.&lt;/li&gt;
&lt;li&gt;애플리케이션은 이미지 URL을 포함한 &amp;lt;img&amp;gt; 태그를 생성했다.&lt;/li&gt;
&lt;li&gt;브라우저가 src 경로로 실제 이미지를 요청했다.&lt;/li&gt;
&lt;li&gt;그 이후 단계에서 이미지 응답이 정상 처리되지 못했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 문제는 파일 존재 여부를 확인하는 서비스 로직보다는,&lt;br /&gt;&lt;b&gt;브라우저가 요청한 이미지 URL을 운영 Nginx가 어떻게 처리하고 있었는지&lt;/b&gt;에 가까웠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애플리케이션 로직에는 문제가 없어 보여 Nginx 설정을 확인했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일은 실제로 존재했고,&lt;br /&gt;애플리케이션도 파일 존재 여부를 확인한 뒤 이미지 태그를 생성하고 있었다.&lt;br /&gt;즉 애플리케이션 단계에서는 &amp;ldquo;파일이 없어서 이미지가 안 뜨는 상황&amp;rdquo;이 아니었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 지점부터는 애플리케이션 코드보다,&lt;br /&gt;&lt;b&gt;브라우저가 요청한 이미지 URL을 운영 웹 서버가 어떤 규칙으로 처리하는지&lt;/b&gt;를 확인하는 게 더 중요했다.&lt;br /&gt;그래서 운영 Nginx 설정을 보기 시작했다.&lt;br /&gt;처음 디버깅의 핵심 질문은 이것이었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&amp;nbsp;혹시 특정 경로의 jpg/png 요청을 백엔드로 보내지 않고, &lt;br /&gt;Nginx의 다른 설정이 먼저 잡아가고 있는 것은 아닐까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그 결과, 운영 Nginx 설정에서 가장 먼저 눈에 띈 부분은 &lt;b&gt;이미지 확장자를 전역으로 처리하는 정규식 location&lt;/b&gt; 이었다.&lt;br /&gt;예를 들면 아래와 비슷한 형태였다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;location ~* \.(png|jpg|jpeg)$ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root /path/to/static/root;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try_files $uri $uri =404;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 설정을 봤을 때 가장 먼저 든 생각은 단순했다.&lt;br /&gt;혹시 이 location이 jpg/png 요청을 먼저 잡아가고 있는 건 아닐까?&lt;br /&gt;즉, 이미지가 안 보이는 이유가 &amp;ldquo;차단&amp;rdquo; 때문이 아니라,&lt;br /&gt;&lt;b&gt;내가 기대한 경로가 아니라 다른 location으로 먼저 들어가고 있는 것&lt;/b&gt;일 수 있다고 본 것이다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;특정 그림 파일을 막는 설정이 있는지도 먼저 확인했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 장애를 볼 때는 &amp;ldquo;명시적으로 막아둔 설정&amp;rdquo;이 있는지도 먼저 본다.&lt;br /&gt;예를 들면 아래 같은 것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 확장자를 deny 하는 설정&lt;/li&gt;
&lt;li&gt;특정 URL 패턴을 차단하는 설정&lt;/li&gt;
&lt;li&gt;특정 경로를 404 처리하는 설정&lt;/li&gt;
&lt;li&gt;이미지 파일을 백엔드 프록시가 아니라 정적 파일 루트에서만 찾게 하는 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 설정에서는 jpg/png를 아예 deny 하는 명시적 설정은 찾지 못했다.&lt;br /&gt;즉, &lt;b&gt;특정 그림 파일을 안 보이게 막아둔 설정은 없었다&lt;/b&gt;고 볼 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 대신 더 중요한 문제가 있었다.&lt;br /&gt;바로 &lt;b&gt;jpg/png 요청을 처리하는 범위가 너무 넓은 정규식 location이 존재했다는 점&lt;/b&gt;이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 이번 이슈는 정확히 말하면,&lt;br /&gt;그림 파일을 안 보이게 &amp;ldquo;설정한 문제&amp;rdquo;가 아니라&lt;br /&gt;그림 파일 요청을 &lt;b&gt;잘못된 location이 먼저 처리해서 결과적으로 안 보이게 된 문제&lt;/b&gt;&lt;br /&gt;라고 정리할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;proxy_pass 경로인데도 이미지가 안 보였던 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 더 확인해보니, 같은 파일 안에 특정 경로 요청을 백엔드로 넘기는 프록시 설정도 있었다.&lt;br /&gt;예를 들면 아래와 같은 형태다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;location /proxy-path/ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass http://backend-service;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 설정만 보면 /proxy-path/.../sample.jpg 요청은 당연히 백엔드로 전달되어야 맞다.&lt;br /&gt;그래서 초반에는 나도 이렇게 생각했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경로는 proxy_pass 대상이다.&lt;/li&gt;
&lt;li&gt;그러면 jpg/png 요청도 이 location으로 들어가야 한다.&lt;/li&gt;
&lt;li&gt;그런데 왜 운영에서는 이미지가 안 보일까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 &lt;b&gt;Nginx location 우선순위&lt;/b&gt;였다.&lt;br /&gt;겉으로는 /proxy-path/ 프록시가 있어 보여도, 실제로는 전역 이미지 정규식 location이 요청을 먼저 잡아버리면 백엔드 프록시까지 가지 못하고 Nginx가 정적 파일처럼 처리할 수 있다.&lt;br /&gt;즉 문제 흐름은 대략 이랬다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저가 /proxy-path/.../sample.jpg 요청&lt;/li&gt;
&lt;li&gt;Nginx가 .jpg 확장자에 맞는 정규식 location을 먼저 검토&lt;/li&gt;
&lt;li&gt;정적 파일 루트 기준으로 로컬 파일을 찾음&lt;/li&gt;
&lt;li&gt;원하는 파일이 없으면 404&lt;/li&gt;
&lt;li&gt;원래 가야 할 백엔드 프록시까지 도달하지 못함&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;404가 아니라 더 헷갈리게 보였던 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 더 헷갈리게 만든 설정이 하나 더 있었다.&lt;br /&gt;운영 Nginx 설정에는 아래 내용도 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1776996906026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;error_page 404 =200 /error.html;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 설정은 실제로 404가 발생했더라도, 사용자에게는 &lt;b&gt;200 응답으로 에러 페이지를 반환&lt;/b&gt;하게 만들 수 있다.&lt;br /&gt;이 말은 곧,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 개발자도구에서는 응답이 온 것처럼 보일 수 있고&lt;/li&gt;
&lt;li&gt;그런데 실제 이미지는 뜨지 않으며&lt;/li&gt;
&lt;li&gt;원인이 단순 파일 누락인지, 프록시 미동작인지 한 번에 구분하기 어려워진다는 뜻이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영 디버깅에서는 이런 설정 때문에&lt;br /&gt;&amp;ldquo;이미지가 안 뜨는데 응답은 200이네?&amp;rdquo; 같은 식으로 더 헷갈릴 수 있다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1차 디버깅 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차로 확인한 내용은 아래와 같았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 서버에 대상 파일은 실제로 존재했다.&lt;/li&gt;
&lt;li&gt;애플리케이션은 파일 존재 여부를 확인한 뒤 이미지 태그를 생성하고 있었다.&lt;/li&gt;
&lt;li&gt;실제 화면에서도 &amp;ldquo;대체 문구&amp;rdquo;가 아니라 깨진 이미지가 보였다.&lt;/li&gt;
&lt;li&gt;즉 문제는 파일 누락이나 서비스 로직이 아니라, &lt;b&gt;이미지 URL 요청 이후의 처리&lt;/b&gt;였다.&lt;/li&gt;
&lt;li&gt;Nginx에는 jpg/png 요청을 전역으로 처리하는 정규식 location이 있었다.&lt;/li&gt;
&lt;li&gt;백엔드 프록시 대상 경로가 있어도, 이미지 정규식 location이 먼저 요청을 잡아갈 수 있는 구조였다.&lt;/li&gt;
&lt;li&gt;error_page 404 =200 설정 때문에 원인 파악이 더 어렵게 보였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이번 이슈는 &lt;b&gt;파일이 없어서 안 보인 문제라고 단정하면 안 되는 사례&lt;/b&gt;였다.&lt;br /&gt;요청이 실제로 어느 location에서 처리되는지를 먼저 확인해야 했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 원인: 전역 이미지 정규식 location이 요청을 먼저 잡고 있었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 실제 원인은 이것이다. 설정 파일에는 아래 전역 이미지 처리 블록이 있었다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;location ~* \.(png|jpg|jpeg)$ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root /usr/share/nginx/html;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try_files $uri$img_ext $uri =404;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 설정이 너무 넓게 잡혀 있어서, 원래는 백엔드 프록시를 타야 하는 이미지 요청까지 먼저 처리해버렸다.&lt;br /&gt;즉 특정 그림 파일이 운영에서 안 보였던 것은 그 파일만 따로 숨기거나 막아서가 아니라,&lt;br /&gt;&lt;b&gt;jpg/png 요청 전반을 처리하는 정규식 location이 너무 넓었고, &lt;/b&gt;&lt;br /&gt;&lt;b&gt;그 결과 일부 요청이 백엔드가 아니라 정적 파일 루트로 빠졌기 때문&lt;/b&gt;이었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx try_files 때문에 jpg가 안 보였던 문제 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프록시 경로를 정규식보다 우선하게 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 명확한 방법은 프록시 경로를 ^~ 로 선언하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;location ^~ /resource/data/ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_http_version 1.1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Connection &quot;&quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass http://test-backend;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이렇게 하면 /resource/data/ 로 시작하는 요청은 뒤에 .jpg, .png 가 붙어 있어도 전역 정규식 location이 가로채지 못한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이번 이슈에서 얻은 교훈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 이슈를 겪으면서 다시 느낀 건, 운영에서 이미지가 안 보인다고 해서 무조건 파일 문제부터 확정하면 안 된다는 점이다.&lt;br /&gt;특히 아래는 반드시 함께 확인해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 요청이 실제로 어떤 location 에 매칭되는가&lt;/li&gt;
&lt;li&gt;root + try_files 로 처리되는가, proxy_pass 로 처리되는가&lt;/li&gt;
&lt;li&gt;정규식 location이 prefix location보다 넓게 잡혀 있지는 않은가&lt;/li&gt;
&lt;li&gt;error_page 설정이 실제 상태코드를 가리고 있지는 않은가&lt;/li&gt;
&lt;li&gt;애플리케이션 코드가 아니라 웹 서버 설정에서 먼저 요청을 바꿔버리고 있지는 않은가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 건은 &amp;ldquo;특정 그림 파일이 안 보이게 설정된 것&amp;rdquo;처럼 보였지만,&lt;br /&gt;실제로는 &lt;b&gt;그림 파일 요청이 잘못된 Nginx location에 걸려서 발생한 문제&lt;/b&gt;였다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 이번 문제의 핵심은 다음 한 줄로 요약할 수 있다.&lt;br /&gt;&lt;b&gt;운영에서 특정 jpg/png 이미지가 보이지 않았던 이유는, &lt;/b&gt;&lt;br /&gt;&lt;b&gt;해당 파일이 없어서가 아니라 Nginx의 전역 이미지 정규식 location이 요청을 먼저 잡아 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;백엔드 프록시까지 가지 못했기 때문이었다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;운영 디버깅에서는 파일 존재 여부만 확인하는 것으로 끝내지 말고,&lt;br /&gt;&lt;b&gt;요청이 실제로 어디에서 처리되는지&lt;/b&gt;를 끝까지 추적하는 것이 중요하다.&lt;br /&gt;나중에 비슷한 이슈를 다시 만나면, 나는 가장 먼저 아래 순서대로 볼 것 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저 요청 URL 확인&lt;/li&gt;
&lt;li&gt;실제 응답 상태 확인&lt;/li&gt;
&lt;li&gt;Nginx location 매칭 확인&lt;/li&gt;
&lt;li&gt;root / try_files / proxy_pass 흐름 확인&lt;/li&gt;
&lt;li&gt;error_page, cache, rewrite 설정 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순해 보였던 이미지 미노출 문제였지만,&lt;br /&gt;결국은 Nginx 설정의 우선순위와 처리 범위를 이해해야 해결할 수 있는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;related-box&quot;&gt;&lt;b&gt;Related&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 읽으면 좋은 글입니다.&lt;/p&gt;
&lt;a href=&quot;/12&quot;&gt;[nginx] location 우선순위 정리&lt;/a&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>nginx</category>
      <category>error_page 404 200</category>
      <category>nginx jpg 안보임</category>
      <category>nginx location 우선순위</category>
      <category>nginx png 안보임</category>
      <category>nginx regex location</category>
      <category>nginx try_files proxy_pass</category>
      <category>nginx 이미지 안 뜸</category>
      <category>nginx 정적파일 안보임</category>
      <category>proxy_pass 이미지 미노출</category>
      <category>서버에 이미지가 있는데 안 보임</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/11</guid>
      <comments>https://hwang-dev.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 24 Apr 2026 14:46:06 +0900</pubDate>
    </item>
    <item>
      <title>비전공자 SQLD 합격 후기</title>
      <link>https://hwang-dev.tistory.com/2</link>
      <description>&lt;div class=&quot;summary-box&quot;&gt;&lt;b&gt;한줄요약&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL과 DB 기초가 부족하다고 느끼던 비전공자 현직 개발자가 약 40일 동안 SQLD를 준비하고 합격한 후기&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;padding: 16px 18px; background: #fafafa; border: 1px solid #eee; margin: 20px 0; line-height: 1.7;&quot;&gt;
&lt;div style=&quot;font-size: 19px; font-weight: bold; margin-bottom: 10px;&quot;&gt;목차&lt;/div&gt;
&lt;ol style=&quot;margin: 0; padding-left: 20px; line-height: 1.9;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#reason&quot;&gt;SQLD를 준비하게 된 이유&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#why-sqld&quot;&gt;왜 SQLD를 선택했는가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#study&quot;&gt;공부 기간과 공부 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#difficulty&quot;&gt;공부하면서 느낀 어려움&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#exam&quot;&gt;시험을 보고 느낀 점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#result&quot;&gt;시험 결과와 합격 후 느낀 점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #222; text-decoration: none;&quot; href=&quot;#finish&quot;&gt;마무리&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;reason&quot; data-ke-size=&quot;size26&quot;&gt;SQLD를 준비하게 된 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;289&quot; data-start=&quot;166&quot; data-ke-size=&quot;size16&quot;&gt;나는 학원 출신으로 개발을 시작했고, 실무를 하면서도 DB를 아주 깊게 다루는 편은 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;289&quot; data-start=&quot;166&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;289&quot; data-start=&quot;166&quot; data-ke-size=&quot;size16&quot;&gt;업무를 하다 보면 SQL을 아예 안 쓰지는 않지만, 사실상 &lt;b&gt;기본적인 SELECT문 정도만 자주 사용하는 수준&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;452&quot; data-start=&quot;291&quot; data-ke-size=&quot;size16&quot;&gt;그마저도 복잡한 쿼리를 직접 작성하는 건 어려웠고,&lt;br /&gt;대부분은 &lt;b&gt;SELECT * FROM 테이블명&lt;/b&gt; 같은 단순 조회 위주로 사용하는 경우가 많았다.&lt;br /&gt;UPDATE, INSERT, DELETE 같은 가장 기본적인 DML조차도 직접 작성하려면 문법을 다시 찾아보면서 써야 했다.&lt;/p&gt;
&lt;p data-end=&quot;452&quot; data-start=&quot;291&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;그러다 보니 어느 순간 이런 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&amp;lsquo;내가 개발자인데 이것도 제대로 못하면 안 되는 거 아닌가&amp;rsquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;실무를 하고는 있지만, 정작 SQL과 DB 기초가 너무 부족하다는 걸 스스로도 느끼고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;조금 솔직하게 말하면,&lt;br /&gt;이 상태로 계속 가는 건 스스로 봐도 아쉽고 부끄럽다는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;정말로 &lt;b&gt;부끄러웠다. 내 자신이.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서라도 한 번은 제대로 공부해야겠다고 마음먹었고,&lt;br /&gt;그 계기로 SQLD 공부를 시작하게 됐다.&lt;/p&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;why-sqld&quot; data-ke-size=&quot;size26&quot;&gt;왜 SQLD를 선택했는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL과 데이터베이스 기초가 부족하다는 걸 느끼고 나니, 막연하게 &amp;ldquo;공부해야지&amp;rdquo;라고 생각하는 것만으로는 달라지지 않을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 눈에 보이는 목표를 하나 정하고, 제대로 공부해보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 선택한 자격증이 SQLD였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLD는 데이터베이스와 SQL의 기본기를 정리하기에 비교적 적절한 시험이라고 느꼈다.&lt;br /&gt;지금 내 수준에서 너무 멀지도 않고, 그렇다고 너무 가볍지도 않은 목표라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다도, 실무에서 SQL을 볼 때마다 막연하게 두려워하기보다는&lt;br /&gt;기본 개념 정도는 스스로 이해하고 다룰 수 있는 상태가 되고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;study&quot; data-ke-size=&quot;size26&quot;&gt;공부 기간과 공부 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 공부 기간은 대략 40일 정도였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평일에는 출근을 한 시간 정도 일찍 해서 유튜브 강의를 봤고,&lt;br /&gt;주말에는 문제집을 풀면서 부족한 부분을 보완했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의는 유튜브 &lt;b&gt;아이리포의 SQLD의 모든 것&lt;/b&gt;을 활용했고,&lt;br /&gt;문제집은 이른바 &lt;b&gt;노랭이 책&lt;/b&gt;을 사서 공부했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 개념이 익숙하지 않아서 강의를 통해 전체 흐름을 잡는 데 집중했다.&lt;br /&gt;그 이후에는 문제를 풀면서 자주 나오는 개념과 헷갈리는 부분을 반복해서 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 원래 SQL을 깊게 다루는 사람이 아니었기 때문에,&lt;br /&gt;처음부터 완벽하게 이해하려고 하기보다는&lt;br /&gt;반복해서 익숙해지는 방식으로 공부했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 616px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 616px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 616px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCgf1W/dJMcafl1MOS/rkJLoZgbcJzFjKzPVx4gS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCgf1W/dJMcafl1MOS/rkJLoZgbcJzFjKzPVx4gS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCgf1W/dJMcafl1MOS/rkJLoZgbcJzFjKzPVx4gS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCgf1W%2FdJMcafl1MOS%2FrkJLoZgbcJzFjKzPVx4gS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;669&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 616px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;835&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m1Fxz/dJMcaiiIGcQ/hskiO2jUsU6SnUByitCBkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m1Fxz/dJMcaiiIGcQ/hskiO2jUsU6SnUByitCBkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m1Fxz/dJMcaiiIGcQ/hskiO2jUsU6SnUByitCBkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm1Fxz%2FdJMcaiiIGcQ%2FhskiO2jUsU6SnUByitCBkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;835&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;835&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;difficulty&quot; data-ke-size=&quot;size26&quot;&gt;공부하면서 느낀 어려움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하면서 가장 힘들었던 건,&lt;br /&gt;&lt;b&gt;&amp;ldquo;어설프게 아는 상태&amp;rdquo;가 생각보다 더 위험하다는 점&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 SQL을 아예 안 써본 것은 아니다 보니, 처음에는 익숙한 내용처럼 느껴지는 부분도 있었다.&lt;br /&gt;그런데 막상 문제를 풀어보면, &lt;b&gt;본 적은 있지만 정확히 설명하지 못하는 개념&lt;/b&gt;이 생각보다 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 헷갈렸던 건 SELECT문의 실행 순서 같은 부분이었다.&lt;br /&gt;쿼리는 많이 봤지만, 시험 문제처럼 SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY가 어떤 흐름으로 처리되는지를 정확히 구분하려고 하면 애매한 순간이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN도 비슷했다.&lt;br /&gt;실무에서는 기존에 작성된 쿼리를 참고하거나 익숙한 형태를 따라 쓰는 경우가 많았지만,&lt;br /&gt;시험에서는 INNER JOIN, LEFT OUTER JOIN 같은 개념을 정확히 이해하고 있어야 보기를 확실하게 고를 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브쿼리도 어려웠다.&lt;br /&gt;단순히 쿼리 안에 쿼리가 들어간 형태라고만 알고 있었지,&lt;br /&gt;어떤 상황에서 서브쿼리를 쓰는지, 메인 쿼리와 어떤 식으로 연결해서 봐야 하는지까지는 제대로 정리되어 있지 않았다는 걸 문제를 풀면서 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화도 마찬가지였다.&lt;br /&gt;단어는 많이 들어봤고 대충 &amp;ldquo;테이블을 잘 나누는 것&amp;rdquo; 정도로만 알고 있었지만,&lt;br /&gt;막상 시험에서는 이상 현상, 함수 종속, 정규형 같은 개념이 함께 나오다 보니 단순 암기로는 버티기 어렵다는 걸 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키의 개념도 헷갈렸다.&lt;br /&gt;후보키, 기본키, 대체키, 슈퍼키처럼 비슷해 보이는 용어들은 문제를 풀 때마다 다시 구분해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 공부하면서 느낀 건,&lt;br /&gt;&lt;b&gt;&amp;lsquo;안다&amp;rsquo;고 생각하는 것과 시험에서 맞힐 수 있을 정도로 이해한 것은 완전히 다르다&lt;/b&gt;는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복해서 문제를 풀면서&lt;br /&gt;내가 진짜 이해한 개념과, 그냥 눈에 익기만 했던 개념을 조금씩 구분하게 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;exam&quot; data-ke-size=&quot;size26&quot;&gt;시험을 보고 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLD라는 시험을 준비하면서 여러 합격 후기 글을 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에는&lt;br /&gt;&amp;lsquo;전공자 3일 컷&amp;rsquo;,&lt;br /&gt;&amp;lsquo;비전공자 2주 컷&amp;rsquo;처럼&lt;br /&gt;비교적 쉽게 취득할 수 있는 자격증이라는 이야기도 종종 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나 역시 처음에는&lt;br /&gt;&amp;ldquo;뭐 대충하면 충분히 딸 수 있는 시험이겠구나&amp;rdquo;&lt;br /&gt;정도로 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 직접 시험을 보고 나니,&lt;br /&gt;&lt;b&gt;마냥 만만한 시험은 아니라는 느낌&lt;/b&gt;이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출문제를 풀어봤다면 분명 낯익은 느낌은 있다. 하지만 그렇다고 해서 문제와 답을 단순히 외운 상태로 가면,&lt;br /&gt;시험장에서 표현이 조금만 바뀌어도 흔들릴 수 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 문제를 풀면서&lt;br /&gt;&amp;ldquo;이거 분명 봤던 건데&amp;hellip;&amp;rdquo; 싶은 문제들이 꽤 있었다.&lt;br /&gt;그런데 막상 정확한 개념이 떠오르지 않으면&lt;br /&gt;보기 두 개 사이에서 오래 고민하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 SQLD는 아주 어려운 내용을 깊게 파고드는 시험이라기보다는,&lt;br /&gt;&lt;b&gt;기본 개념을 얼마나 정확하게 이해하고 구분하고 있는지&lt;/b&gt;를 묻는 시험에 더 가까웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오히려 대충 알고 있다고 생각한 상태일수록 더 헷갈릴 수 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험이 끝난 뒤에도 몇 문제는 계속 기억에 남았다.&lt;br /&gt;답을 적어내고도 확신이 서지 않아서,&lt;br /&gt;&amp;ldquo;그게 맞았나, 다른 보기였나&amp;rdquo; 하는 생각이 꽤 오래 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 시험장에서 가장 크게 느낀 건 하나였다.&lt;br /&gt;&lt;b&gt;기출은 분명 중요하지만, 결국 기본 개념 이해가 받쳐줘야 한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;result&quot; data-ke-size=&quot;size26&quot;&gt;시험 결과와 합격 후 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다행히 SQLD에 합격했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합격 자체도 기뻤지만,&lt;br /&gt;무엇보다 &amp;ldquo;내가 부족하다고 느꼈던 부분을 그냥 넘기지 않고 한 번 정리해봤다&amp;rdquo;는 점에서 의미가 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 SQLD를 땄다고 해서 갑자기 SQL을 잘하게 되는 것은 아니다.&lt;br /&gt;하지만 적어도 예전처럼 SQL이나 데이터베이스 이야기가 나올 때&lt;br /&gt;무조건 막막하게 느껴지는 상태에서는 조금 벗어난 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나처럼 실무는 하고 있지만 DB 기초가 약하다고 느끼는 사람에게는&lt;br /&gt;SQLD가 좋은 출발점이 될 수 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEtu7e/dJMcageaC02/ARzvKLsC9Ep7KQ72xwHEmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEtu7e/dJMcageaC02/ARzvKLsC9Ep7KQ72xwHEmK/img.png&quot; data-alt=&quot;자격증 취득&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEtu7e/dJMcageaC02/ARzvKLsC9Ep7KQ72xwHEmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEtu7e%2FdJMcageaC02%2FARzvKLsC9Ep7KQ72xwHEmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;642&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자격증 취득&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;finish&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLD를 준비하면서 가장 크게 느낀 건,&lt;br /&gt;기초가 부족하다고 느낄 때는 괜히 피하기보다 한 번 제대로 마주해보는 게 훨씬 낫다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전의 나처럼&lt;br /&gt;&lt;b&gt;&amp;ldquo;SELECT만 겨우 읽을 줄 아는 수준인데 내가 SQLD를 할 수 있을까?&amp;rdquo;&lt;/b&gt;&lt;br /&gt;라는 생각을 하는 사람이 있다면, 충분히 도전해볼 만하다고 말하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자격증</category>
      <category>SQLD #</category>
      <category>SQLD노랭이</category>
      <category>SQLD독학</category>
      <category>SQLD준비</category>
      <category>SQLD한달</category>
      <category>SQLD합격후기</category>
      <category>sql기초</category>
      <category>개발자자격증</category>
      <category>비전공자</category>
      <author>hwang-dev</author>
      <guid isPermaLink="true">https://hwang-dev.tistory.com/2</guid>
      <comments>https://hwang-dev.tistory.com/2#entry2comment</comments>
      <pubDate>Wed, 22 Apr 2026 08:39:45 +0900</pubDate>
    </item>
  </channel>
</rss>