<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>하고싶은거 하는 블로그</title>
    <link>https://wafla.tistory.com/</link>
    <description>당신도 개발 할 수 있다!!
알고리즘, 프로그램, 어플, 봇 개발

메일 : jjangguzi@naver.com</description>
    <language>ko</language>
    <pubDate>Mon, 5 Oct 2026 12:09:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wafla</managingEditor>
    <image>
      <title>하고싶은거 하는 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/6611944/attach/f6919cb8f6824f4983763bfc80d50d48</url>
      <link>https://wafla.tistory.com</link>
    </image>
    <item>
      <title>로그인 없이 텍스트 공유하는 사이트</title>
      <link>https://wafla.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;한 번씩 간단한 텍스트만 복붙하면 되는데, 메신저가 깔려있지 않거나 공용 컴퓨터라서 로그인 기록을 남기기 싫은 경우가 있습니다. 그런 경우 간단하게 기기간 텍스트를 공유할 수 있는 사이트를 만들었습니다. 네트워크만 연결되어 있으면 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ctrlcv.net&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.ctrlcv.net&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767031506018&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CtrlCV&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.ctrlcv.net&quot; data-og-source-url=&quot;https://www.ctrlcv.net&quot; data-og-url=&quot;https://www.ctrlcv.net&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.ctrlcv.net&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ctrlcv.net&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CtrlCV&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ctrlcv.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;1209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R40ei/dJMcaf6i1zr/PMrOH4Yk5jlW3u1NsyNSt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R40ei/dJMcaf6i1zr/PMrOH4Yk5jlW3u1NsyNSt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R40ei/dJMcaf6i1zr/PMrOH4Yk5jlW3u1NsyNSt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR40ei%2FdJMcaf6i1zr%2FPMrOH4Yk5jlW3u1NsyNSt1%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;1714&quot; height=&quot;1209&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;1209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사이트에 접속할 경우 이렇게 세션이 생성되고 해당 주소를 통해 다른 기기에서 접속하거나 QR 코드를 통해 모바일에서 쉽게 접속할 수 있습니다. 세션과 메시지는 2시간 뒤에 자동으로 삭제가 됩니다만 혹시 모르기 때문에 민감한 정보는 전송하지 않는 것을 추천드립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4BMZu/dJMcaiaTD25/2tCPjSORcaPLSUnrjropTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4BMZu/dJMcaiaTD25/2tCPjSORcaPLSUnrjropTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4BMZu/dJMcaiaTD25/2tCPjSORcaPLSUnrjropTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4BMZu%2FdJMcaiaTD25%2F2tCPjSORcaPLSUnrjropTK%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;343&quot; height=&quot;669&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트 생성 ai인 v0를 사용하여 만들었으나 연동된 데이터베이스(supabase)가 시간이 지나니 유료로 변해서 Oracle Cloud의 DB와 연동시키고 프론트의 불편한 부분들을 조금 수정한 뒤 따로 배포했습니다. 코드는 &lt;a href=&quot;https://github.com/wafla/ctrlcv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 확인할 수 있습니다. 불편한 점이 있다면 말씀해 주시고 많은 이용 부탁드리겠습니다!&lt;/p&gt;</description>
      <category>개발</category>
      <category>개발</category>
      <category>복붙</category>
      <category>텍스트</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/33</guid>
      <comments>https://wafla.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 30 Dec 2025 03:20:18 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 무료 클라우드 DB 사용 방법</title>
      <link>https://wafla.tistory.com/32</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&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;생성 &lt;br /&gt;5.&amp;nbsp;데이터&amp;nbsp;생성 &lt;br /&gt;6.&amp;nbsp;마치며&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 데이터베이스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Cloud에서 Instance 말고 DB도 무료로 사용할 수 있습니다. 이번에 개인 프로젝트를 진행하며 알아낸 사용 방법을 정리하고자 글을 남깁니다. 먼저 로그인 후 빌드에 ATP 데이터베이스 생성을 클릭합니다. (인스턴스 생성 방법은 &lt;a href=&quot;https://wafla.tistory.com/7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 글&lt;/a&gt;에 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1lE3J/dJMcagYmPe5/l3ybpmIxOXJsIOokgEiQmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1lE3J/dJMcagYmPe5/l3ybpmIxOXJsIOokgEiQmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1lE3J/dJMcagYmPe5/l3ybpmIxOXJsIOokgEiQmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1lE3J%2FdJMcagYmPe5%2Fl3ybpmIxOXJsIOokgEiQmK%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;507&quot; height=&quot;355&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 이름을 정해주고 트랜잭션 처리(기본 값)를 선택해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiG405/dJMcafLVEjk/akF7nyOHOSILigjYp1hYok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiG405/dJMcafLVEjk/akF7nyOHOSILigjYp1hYok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiG405/dJMcafLVEjk/akF7nyOHOSILigjYp1hYok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiG405%2FdJMcafLVEjk%2FakF7nyOHOSILigjYp1hYok%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;667&quot; height=&quot;321&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 구성은 다음과 같이 그대로 두면 됩니다. instance와 마찬가지로 2개까지 무료로 사용할 수 있다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1HSi5/dJMcabCLe3P/Fxl52enmps3FyKBKAw0qV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1HSi5/dJMcabCLe3P/Fxl52enmps3FyKBKAw0qV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1HSi5/dJMcabCLe3P/Fxl52enmps3FyKBKAw0qV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1HSi5%2FdJMcabCLe3P%2FFxl52enmps3FyKBKAw0qV1%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;694&quot; height=&quot;267&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 사용할 비밀번호를 입력하고 네트워크 액세스를 설정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAyicW/dJMcai9F3Au/rnK0bq8UQhW8KKe6iRdgOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAyicW/dJMcai9F3Au/rnK0bq8UQhW8KKe6iRdgOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAyicW/dJMcai9F3Au/rnK0bq8UQhW8KKe6iRdgOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAyicW%2FdJMcai9F3Au%2FrnK0bq8UQhW8KKe6iRdgOK%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;692&quot; height=&quot;312&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 생성을 클릭하고 생성이 완료될 때까지 기다립니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 데이터베이스 접속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 DB에 접속해 봅시다. 다음과 같은 화면에서 데이터베이스 접속을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pFktm/dJMcafrCXbq/Cqo9vZukhK8mP9KwEiXpd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pFktm/dJMcafrCXbq/Cqo9vZukhK8mP9KwEiXpd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pFktm/dJMcafrCXbq/Cqo9vZukhK8mP9KwEiXpd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpFktm%2FdJMcafrCXbq%2FCqo9vZukhK8mP9KwEiXpd0%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;769&quot; height=&quot;353&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1321&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2rD3L/dJMcadHi9hl/QJMaISVU9wlefcdnMuQOx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2rD3L/dJMcadHi9hl/QJMaISVU9wlefcdnMuQOx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2rD3L/dJMcadHi9hl/QJMaISVU9wlefcdnMuQOx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2rD3L%2FdJMcadHi9hl%2FQJMaISVU9wlefcdnMuQOx1%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;763&quot; height=&quot;432&quot; data-origin-width=&quot;1321&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1340&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sXrlD/dJMcaf6exv1/Nlk7MhnpoQFKgyNDjVMnS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sXrlD/dJMcaf6exv1/Nlk7MhnpoQFKgyNDjVMnS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sXrlD/dJMcaf6exv1/Nlk7MhnpoQFKgyNDjVMnS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsXrlD%2FdJMcaf6exv1%2FNlk7MhnpoQFKgyNDjVMnS1%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;777&quot; height=&quot;375&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 접속 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;example이라는 폴더를 생성 후 해당 폴더에 다운받은 지갑을 압축 해제했고 VS Code를 실행했습니다. 이후 db-test.js 파일을 생성해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKBODK/dJMcaiaOI5B/likle9GinJYmxnTQ9DG91K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKBODK/dJMcaiaOI5B/likle9GinJYmxnTQ9DG91K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKBODK/dJMcaiaOI5B/likle9GinJYmxnTQ9DG91K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKBODK%2FdJMcaiaOI5B%2Flikle9GinJYmxnTQ9DG91K%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;1477&quot; height=&quot;886&quot; data-origin-width=&quot;1477&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자 지갑 다운로드 화면에서 밑으로 내려가면 코드 예제가 있습니다. 접속 서비스 유형을 선택하고 언어를 선택한 뒤 코드를 복사해 db-test.js에 붙여줍니다. 그리고 다음 명령어를 입력해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765989673989&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y
npm install oracledb&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;이후 user에 ADMIN을 입력하고 자신이 설정한 password도 입력해줍니다. 또한 wallet을 사용하기 위해 connection에 주석처리 되어있는 부분을 해제하고 경로와 비밀번호를 설정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxLWnn/dJMcajgro9I/YBuHhqlML8cZcvnYfQDIa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxLWnn/dJMcajgro9I/YBuHhqlML8cZcvnYfQDIa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxLWnn/dJMcajgro9I/YBuHhqlML8cZcvnYfQDIa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxLWnn%2FdJMcajgro9I%2FYBuHhqlML8cZcvnYfQDIa1%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;1478&quot; height=&quot;886&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 node db-test.js로 파일을 실행해주면 다음과 같이 응답이 옵니다. 이로써 DB 생성과 접속에 성공했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ztA8p/dJMcagYmPC7/LehszOQoomK0uQ44rAZno1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ztA8p/dJMcagYmPC7/LehszOQoomK0uQ44rAZno1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ztA8p/dJMcagYmPC7/LehszOQoomK0uQ44rAZno1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FztA8p%2FdJMcagYmPC7%2FLehszOQoomK0uQ44rAZno1%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;379&quot; height=&quot;123&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 테이블 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scripts 폴더를 생성하고 schema.sql 파일을 생성해줍니다. 제가 사용한 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765990309174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- SESSIONS TABLE
CREATE TABLE sessions (
  id VARCHAR2(36) DEFAULT RAWTOHEX(sys_guid()) PRIMARY KEY,
  session_code VARCHAR2(10) UNIQUE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT SYS_EXTRACT_UTC(SYSTIMESTAMP),
  expires_at TIMESTAMP WITH TIME ZONE DEFAULT (SYS_EXTRACT_UTC(SYSTIMESTAMP) + INTERVAL '2' HOUR),
  is_active NUMBER(1) DEFAULT 1 CHECK (is_active IN (0, 1))
);

CREATE INDEX idx_sessions_code ON sessions(session_code);
CREATE INDEX idx_sessions_active ON sessions(is_active, expires_at);&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;db-test.js에서 해당 sql을 실행할 수 있도록 코드를 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765990416645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fs = require('fs');

async function runSchemaSQL(conn) {
    const schemaSql = fs.readFileSync(&quot;scripts/schema.sql&quot;, &quot;utf8&quot;);

    const statements = schemaSql
        .split(&quot;;&quot;)
        .map(s =&amp;gt; s.trim())
        .filter(s =&amp;gt; s.length &amp;gt; 0);

    for (const stmt of statements) {
        try {
            await conn.execute(stmt);
            console.log(&quot;[OK]&quot;, stmt.slice(0, 50));
        } catch (err) {
            console.log(&quot;[SKIP]&quot;, err.message);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runApp에서 위의 함수를 실행해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765990518019&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(&quot;Running schema.sql...&quot;);
await runSchemaSQL(connection);&lt;/code&gt;&lt;/pre&gt;
&lt;p 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-filename=&quot;blob&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrocK/dJMb99Ze9Nq/rPmllNJmH0fQVYJ3KpASW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrocK/dJMb99Ze9Nq/rPmllNJmH0fQVYJ3KpASW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrocK/dJMb99Ze9Nq/rPmllNJmH0fQVYJ3KpASW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqrocK%2FdJMb99Ze9Nq%2FrPmllNJmH0fQVYJ3KpASW0%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;1357&quot; height=&quot;886&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1623&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWHGUR/dJMcahXiqNn/CVCYHEKXeLy2FzBouQBZJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWHGUR/dJMcahXiqNn/CVCYHEKXeLy2FzBouQBZJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWHGUR/dJMcahXiqNn/CVCYHEKXeLy2FzBouQBZJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWHGUR%2FdJMcahXiqNn%2FCVCYHEKXeLy2FzBouQBZJk%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;1623&quot; height=&quot;397&quot; data-origin-width=&quot;1623&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1705&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgKZxh/dJMcadN3ztN/mdlaaamL8LvBGvH1qvIsS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgKZxh/dJMcadN3ztN/mdlaaamL8LvBGvH1qvIsS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgKZxh/dJMcadN3ztN/mdlaaamL8LvBGvH1qvIsS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgKZxh%2FdJMcadN3ztN%2FmdlaaamL8LvBGvH1qvIsS0%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;1705&quot; height=&quot;652&quot; data-origin-width=&quot;1705&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1708&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhoTwg/dJMcah32NrD/o5hvKS5vHw6iIOezuTcLw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhoTwg/dJMcah32NrD/o5hvKS5vHw6iIOezuTcLw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhoTwg/dJMcah32NrD/o5hvKS5vHw6iIOezuTcLw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhoTwg%2FdJMcah32NrD%2Fo5hvKS5vHw6iIOezuTcLw1%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;1708&quot; height=&quot;582&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 데이터 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 db-test.js에서 다음 코드를 실행하여 SESSION 테이블에 데이터를 생성해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1765991049415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;connection.execute(`
            INSERT INTO sessions (session_code)
            VALUES (:code)
            `,
        { code: 'ABC123' },
        { autoCommit: true }
        );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행한 뒤 분석 - 테이블에서 SESSIONS를 클릭합니다. 이후 Run을 클릭하면 데이터가 잘 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQqYz/dJMcaaqjS53/FtKfhkkopuHs4DYQLvF7RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQqYz/dJMcaaqjS53/FtKfhkkopuHs4DYQLvF7RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQqYz/dJMcaaqjS53/FtKfhkkopuHs4DYQLvF7RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQqYz%2FdJMcaaqjS53%2FFtKfhkkopuHs4DYQLvF7RK%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;1706&quot; height=&quot;1254&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Cloud에서 DB를 생성하고 사용하는 방법을 알아봤습니다. 데이터를 생성할 때 분석에서 SQL을 작성하면 잘 안됐던 기억이 있습니다. 분석에서는 조회만 사용하길 권장합니다. 프로젝트에서 유용하게 사용하길 바라며 마치겠습니다.&lt;/p&gt;</description>
      <category>개발/클라우드</category>
      <category>ATP 데이터베이스</category>
      <category>db</category>
      <category>oracle</category>
      <category>Oracle Cloud</category>
      <category>데이터베이스</category>
      <category>오라클</category>
      <category>오라클 클라우드</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/32</guid>
      <comments>https://wafla.tistory.com/32#entry32comment</comments>
      <pubDate>Thu, 18 Dec 2025 02:19:08 +0900</pubDate>
    </item>
    <item>
      <title>delete 했는데 왜 유니크 제약 위반이 날까?</title>
      <link>https://wafla.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%ED%86%A0%EB%8D%95-to-duck-%EC%84%B1%EC%9D%B8-adhd%EC%9D%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%86%A0%EB%8B%A5%EC%9E%84/id6502951629&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;To.duck&lt;/a&gt;이라는 어플의 백엔드 개발을 하고 있는데 어느 날 프론트엔드 개발자님한테서 연락이 왔다. 일기에 등록한 키워드를 수정하려는데 오류가 뜬다는 것이었다. 오류 메시지도 내가 예외 처리를 한 것이 나온 게 아니라 서버 오류라고만 나와 정확한 원인을 파악하는데 애를 먹었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2YMWO/dJMcadUOJ3v/QknAgMNNJL20InMkqnLorK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2YMWO/dJMcadUOJ3v/QknAgMNNJL20InMkqnLorK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2YMWO/dJMcadUOJ3v/QknAgMNNJL20InMkqnLorK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2YMWO%2FdJMcadUOJ3v%2FQknAgMNNJL20InMkqnLorK%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;403&quot; height=&quot;234&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&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;딱히 구현에 문제가 있어 보이지 않았는데 GPT와 대화해보니 유니크 제약 조건을 위배하고 있다는 답을 받을 수 있었다.&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;1. Hibernate 작업 순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 코드는 다음과 같이 기존 값들을 지우고 다시 생성하도록 했었다. 겉으로 보기에는 여기서 왜 유니크 제약 위반이 발생하지 싶었지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;Hibernate는 엔티티 변경을 코드 순서대로 즉시 SQL로 실행하지 않고, flush 시점에 ActionQueue에 쌓인 작업들을 정해진 실행 순서에 따라 한꺼번에 SQL로 실행한다&lt;/span&gt;는 사실을 알게 되었고 오류의 원인을 파악 할 수 있었다. 코드를 순서대로 실행하면 데이터 삭제 이후 저장을 해야하지만 실제로는 저장을 먼저하고 삭제가 수행된다. 따라서 이미 저장된 값을 또 저장하려고 하기 때문에 오류가 발생하는 것이다. 결국 delete 이후 flush를 호출해 delete가 먼저 실행되도록 강제하여 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(자세한 내용은 &lt;a href=&quot;https://lahuman.github.io/jpa_flush_order/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lahuman.github.io/jpa_flush_order/&lt;/a&gt; 에서 확인할 수 있다)&lt;/p&gt;
&lt;pre id=&quot;code_1765808245184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public void createDiaryKeywords(Diary diary, List&amp;lt;UserKeyword&amp;gt; userKeywords) {
    diaryKeywordRepository.deleteByDiaryId(diary.getId());
    diaryKeywordRepository.flush(); // 추가한 부분

    List&amp;lt;DiaryKeyword&amp;gt; diaryKeywords = userKeywords.stream()
        .map(uk -&amp;gt; DiaryKeyword.builder()
            .diary(diary)
            .userKeyword(uk)
            .build()
        ).toList();

    diaryKeywordRepository.saveAll(diaryKeywords);
}&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;2. Soft Delete&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flush를 추가한 뒤에 해결했나 싶었지만 여전히 같은 오류가 발생했다. 이번에는 soft delete가 원인이었는데 soft delete는 실제로 값을 삭제하지 않고, 컬럼의 deleted_at 값만 null에서 삭제 시간으로 변경 시키는 방식이다. 따라서 JPA/Hibernate 입장에서는 삭제된 것처럼 보이지만, DB 입장에서는 여전히 동일한 row가 존재하게 되고, delete 이후 insert를 했을 때 유니크 제약 위반이 발생했던 것이다. 결국 논리 삭제를 물리 삭제로 바꿔서 오류를 해결할 수 있었다.&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;</description>
      <category>팁, 오류</category>
      <category>hibernate</category>
      <category>jpa</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/31</guid>
      <comments>https://wafla.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 15 Dec 2025 23:39:49 +0900</pubDate>
    </item>
    <item>
      <title>2025 KUMOH ASK CONTEST 문제 출제 후기</title>
      <link>https://wafla.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 출제를?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 금오공과대학교 프로그래밍 경진대회가 열렸고 출제진으로 참여를 하게 됐다. 백준에서 문제를 출제하게 될 줄은 몰랐는데 사람 운이 좋았던 것 같다. 학교에 PS를 하는 사람이 있긴 하지만 열심히 하는 사람은 거의 없어 교내 대회 같은 게 활성화돼있지 않았고 UCPC나 ICPC 같은 팀 대회는 어떻게 사람들을 모아 나갈 수 있었지만 학교 대회를 여는건 생각조차 하지 못했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5WEUH/btsQwPVBReJ/KcRo9jjXJuiHW8KRCIaTik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5WEUH/btsQwPVBReJ/KcRo9jjXJuiHW8KRCIaTik/img.png&quot; data-alt=&quot;처음 보는 관리자 표시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5WEUH/btsQwPVBReJ/KcRo9jjXJuiHW8KRCIaTik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5WEUH%2FbtsQwPVBReJ%2FKcRo9jjXJuiHW8KRCIaTik%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;499&quot; height=&quot;98&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처음 보는 관리자 표시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대회 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 어느날 작년에 같이 ICPC에 팀을 구성해 출전했던 @chika님한테서 학교 대회를 열 생각이 있는데 문제를 출제해달라고 연락이 왔고 그렇게 올해 2월부터 문제를 준비하기 시작했다. 나 말고도 졸업생 @redcube231님과 대회 개최를 도와주신 @swoon님도 문제를 출제해주셨다.&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;a href=&quot;https://www.acmicpc.net/problem/34323&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;A&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/34325&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;C&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/34326&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;D&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;문제 기반을 다져놓고 @chika님이 학교에서 예산을 따오는동안 채점 데이터를 생성했다. 문제 출제를 위해 codeforces polygon을 처음 사용하게 됐는데 시작이 어렵지 사용하다보니 익숙해졌고 boj 스택도 마찬가지였다.&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;문제를 만들며 느낀건데 지문을 잘 작성하는 것이 정말 중요하다는 것이다. 내가 문제를 만들다보니 푸는 입장에서 헷갈릴만한 요소들이 무엇인지 알아채기 힘든데 검수진분들이 친절하게 도와주셔서 더 좋은 문제가 될 수 있었고 대회도 잘 마무리 될 수 있었다. 이 자리를 빌어 검수진분들에게 감사의 말씀을 드린다. 또한 문제를 만들며 신경 쓰지 않고 놓쳤던 사소한 부분들이 나중에 다시 지적이 된 경우가 많았고 처음 할 때 신경써서 잘해야겠다는 생각이 들었다. 데이터 생성, validator 등..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MQZKN/btsQzcV1TMI/QuLD1gFbPz5rgYyxHvNMV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MQZKN/btsQzcV1TMI/QuLD1gFbPz5rgYyxHvNMV0/img.png&quot; data-alt=&quot;polygon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MQZKN/btsQzcV1TMI/QuLD1gFbPz5rgYyxHvNMV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMQZKN%2FbtsQzcV1TMI%2FQuLD1gFbPz5rgYyxHvNMV0%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;505&quot; height=&quot;111&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;polygon&lt;/figcaption&gt;
&lt;/figure&gt;
&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;대회 일주일 전에 C번의 정해를 OEIS에서 쉽게 구할 수 있다는 지적을 받아 각 개수의 합에 K^i를 곱하는 식으로 문제를 수정했다. OEIS라는 것도 처음 들어봐서 당황했는데 여기서도 또 검수진분들이 의견을 제시해주셔서 도움을 받았다. (처음에는 낼 수 없는 문제인줄 알고 엄청 당황했다) 바뀐 문제가 일반항 딸깍으로 풀 수 없기에 출제 의도에 더 가까워서 오히려 좋았다(아님말고..). 대회 당일까지 지문에 이상이 없는지 계속 확인하고 수정하며 준비했고 대회가 시작됐다. 시작하기 직전까지 최선을 다한다는 건 그 일에 진심이라는 것이고 좋은 징조라고 생각한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대회 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 대회와 오픈 콘테스트가 열렸다. 본 대회는 3인 1팀으로 생각보다는 사람들이 모이지 않았지만 꽤 많은 분들이 참석해주셨다. 그런데 오랫동안 A번을 푸는 팀이 나오지 않아 당황했다. A번은 0솔 방지로 출제한 문제다. 실수 오차 때문에 정수 나눗셈을 하면 쉽게 풀리는데 실수 연산으로 접근하고 맞왜틀 하는 팀들이 많았다.&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;결국 마지막까지 A번을 못 푼 팀들이 많이 있었으니 내 목적은 실패했다. 다음에 출제를 하게 된다면 A번으로 나눗셈이 들어간 연산 문제를 내면 안되겠다. 많은 팀들이 맞추지 못하는 모습에 나도 고통스러웠다.. 그럼에도 무려 28, 29트만에 풀고 성불한 팀이 존재했다. 끈기에 박수를 보내며 꾸준히 연습하여 다음에는 더 좋은 성적을 기대한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mg2ij/btsQyyrrNka/VTK8PoAEliqePuBKQIulAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mg2ij/btsQyyrrNka/VTK8PoAEliqePuBKQIulAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mg2ij/btsQyyrrNka/VTK8PoAEliqePuBKQIulAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMg2ij%2FbtsQyyrrNka%2FVTK8PoAEliqePuBKQIulAK%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;176&quot; height=&quot;145&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한동안 초록색이 보이지 않았는데 대회 후반에 문제들이 하나씩 풀리기 시작했다. 오랫동안 제출이 나오지 않아 사람들이 포기한건가 싶었는데 현장에 가보니 다행히 다들 포기하지 않고 끝까지 문제를 풀어주었다. 그럼에도 A번 때문인가 참가자 분들이 예상보다 문제를 많이 못푸셔서 아쉬웠다. 구현 문제도 꽤 있어 문제 해결에 시간이 걸린 것 같다.&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;오픈 콘테스트는 분위기가 달랐는데 A번을 해결하지 못한 분들은 거의 없었고 총 161명으로 많은 분들이 참가해주셨다. 사비를 들여 뱃지와 배경을 제공해주길 잘한 것 같다. 게다가 대회 시간 중에 풀 수 있을까 싶던 마지막 문제를 포함하여 모든 문제가 해결되었다. &lt;s&gt;역시 고인물들은 달라...&lt;/s&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;h3 data-ke-size=&quot;size23&quot;&gt;대회 끝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 대회에서 1등 팀은 6문제를 해결했다. 조금 더 잘했다면 8문제까지도 노릴 수 있었던 것 같다. 오픈 콘테스트 1등분은 무려 14문제 중 13문제를 해결했다. &lt;s&gt;같은 사람인가 싶다.&lt;/s&gt; 그리고 예상외로 LLM을 사용하여 리더보드 상위권을 차지하는 사람이 보이지 않았다. 상품을 지급해야 하는데 일 할 거리가 줄어들어 다행이다. 또한 대회가 잘 마무리되어 다행이다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 대회를 계기로 재학생들이 PS에 더 관심을 가져주면 좋겠고 대회가 내년에도 이어지면 좋겠다. 특히 팀 대회에 재미를 느꼈다면 ICPC를 준비하는 것도 대학생 때만 할 수 있기에 좋은 경험이 될 것이다.&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;대회가 끝나니 1년간(실제론 반년이지만) 지은 농사가 잘 마무리 된 느낌이다. 다른 일들도 잘 되었으면 좋겠다. 이 글을 읽는 분들도 마찬가지로 모든 일이 잘 되길 바란다.&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;끝으로 다시 한 번 대회를 개최하기 위해 정말 많이 고생하신 @chika님과 좋은 문제를 출제해 주시고 운영에도 도움을 주신 @redcube231, @swoon님 그리고 대회 직전까지 문제를 꼼꼼히 봐주신 검수진분들, 마지막으로 대회 참가자분들께 감사의 말씀을 드린다.&amp;nbsp;&lt;/p&gt;</description>
      <category>후기</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/30</guid>
      <comments>https://wafla.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 14 Sep 2025 03:39:34 +0900</pubDate>
    </item>
    <item>
      <title>DB의 시간대는 나와 다를 수 있다</title>
      <link>https://wafla.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여기서(&lt;a href=&quot;https://wafla.tistory.com/28&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wafla.tistory.com/28&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;일기 스트릭 API를 작성하고 있는데 날짜 관련하여 오류가 나서 문제를 해결하고 왔다.&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;분명히 그에 맞게 SQL을 작성했지만 테스트 코드에서 그저께까지 쓰고 어제, 오늘 작성하지 않은 경우 0이 반환되어야 하는데 그저께까지의 스트릭이 반환되는 문제가 생겼다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;983&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmDo1E/btsPUZjaeyw/OC7keelRk3dzB0xQhWKSS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmDo1E/btsPUZjaeyw/OC7keelRk3dzB0xQhWKSS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmDo1E/btsPUZjaeyw/OC7keelRk3dzB0xQhWKSS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmDo1E%2FbtsPUZjaeyw%2FOC7keelRk3dzB0xQhWKSS1%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;983&quot; height=&quot;364&quot; data-origin-width=&quot;983&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&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;TestContainers를 사용하다 보니 시간대가 맞지 않았다.&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;오늘은 8월 17일인데 DB에서는 16일로 잡혔고 DB 기준에서는 어제까지 일기가 작성됐으니 0이 아닌 3이 반환된 것이었다.&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;pre id=&quot;code_1755363033202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Container
static MySQLContainer&amp;lt;?&amp;gt; mysql = new MySQLContainer&amp;lt;&amp;gt;(&quot;mysql:8.0&quot;)
    .withDatabaseName(&quot;testdb&quot;)
    .withUsername(&quot;test&quot;)
    .withPassword(&quot;test&quot;)
    .withEnv(&quot;TZ&quot;, &quot;Asia/Seoul&quot;);

@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
    String jdbcUrl = mysql.getJdbcUrl() + &quot;?serverTimezone=Asia/Seoul&quot;;
    registry.add(&quot;spring.datasource.url&quot;, () -&amp;gt; jdbcUrl);
    registry.add(&quot;spring.datasource.username&quot;, mysql::getUsername);
    registry.add(&quot;spring.datasource.password&quot;, mysql::getPassword);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXjnAD/btsPSTkeA4U/K4IzFlWpoWCPofoJ1GuAGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXjnAD/btsPSTkeA4U/K4IzFlWpoWCPofoJ1GuAGK/img.png&quot; data-alt=&quot;편안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXjnAD/btsPSTkeA4U/K4IzFlWpoWCPofoJ1GuAGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXjnAD%2FbtsPSTkeA4U%2FK4IzFlWpoWCPofoJ1GuAGK%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;288&quot; height=&quot;280&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;280&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;p data-ke-size=&quot;size16&quot;&gt;항상 나와 같은 시간대일거라고 생각하지 말아야겠다.&lt;/p&gt;</description>
      <category>팁, 오류</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/29</guid>
      <comments>https://wafla.tistory.com/29#entry29comment</comments>
      <pubDate>Sun, 17 Aug 2025 01:57:39 +0900</pubDate>
    </item>
    <item>
      <title>org.hibernate.exception.SQLGrammarException:</title>
      <link>https://wafla.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스트릭을 구하는 SQL을 작성하고 테스트 코드를 작성한 후 테스트 실행을 눌렀는데 다음과 같은 오류가 뜨며 실행이 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;org.hibernate.exception.SQLGrammarException:&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문에 DATE_SUB라는 문법이 있었는데 테스트 환경의 DB는 H2였고,&lt;br /&gt;H2에서는 DATE_SUB가 지원되지 않는 것이 문제였다.&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;스트릭 기능 테스트만을 위해 전체 테스트 DB 설정을 바꾸는 것은 무리가 있었다.&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;특정 테스트 케이스만 DB 설정을 바꿀 수 있다&lt;/b&gt;는 대답을 들었고&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;h3 data-ke-size=&quot;size23&quot;&gt;Testcontainers&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Testcontainers는 테스트 실행 시 Docker 컨테이너를 자동으로 띄워주는 라이브러리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H2 같은 인메모리 DB 대신 실제 DBMS를 컨테이너로 실행하여 테스트 할 수 있어 운영 환경과 동일한 조건에서 테스트 가능하다.&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;먼저 gradle에 다음 의존성을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1755268623110&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;testImplementation &quot;org.testcontainers:testcontainers:1.19.0&quot;
testImplementation &quot;org.testcontainers:junit-jupiter:1.19.0&quot;
testImplementation &quot;org.testcontainers:mysql&quot;&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;그 다음 MySQL 테스트 환경 설정을 위해 application-mysql-test.yml 파일을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1755268786383&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb
    username: test
    password: test
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        show_sql: true
        format_sql: true&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;pre id=&quot;code_1755268852802&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Testcontainers
@ActiveProfiles(&quot;mysql-test&quot;)
class DiaryUseCaseTest extends ServiceTest {&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;마지막으로 MySQL 컨테이너 설정과 동적 프로퍼티 연결 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1755268921446&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Container
static MySQLContainer&amp;lt;?&amp;gt; mysql = new MySQLContainer&amp;lt;&amp;gt;(&quot;mysql:8.0&quot;)
    .withDatabaseName(&quot;testdb&quot;)
    .withUsername(&quot;test&quot;)
    .withPassword(&quot;test&quot;);

@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
    registry.add(&quot;spring.datasource.url&quot;, mysql::getJdbcUrl);
    registry.add(&quot;spring.datasource.username&quot;, mysql::getUsername);
    registry.add(&quot;spring.datasource.password&quot;, mysql::getPassword);
}&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;이렇게 설정을 하고 테스트 코드를 실행하면 도커에서 MySQL 컨테이너가 자동으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 사용하던 DB와 별개로 독립된 공간에서 테스트를 진행할 수 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFZklZ/btsPTCoELzV/rxwillm6Cbi5vL5WauPqp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFZklZ/btsPTCoELzV/rxwillm6Cbi5vL5WauPqp0/img.png&quot; data-alt=&quot;편안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFZklZ/btsPTCoELzV/rxwillm6Cbi5vL5WauPqp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFZklZ%2FbtsPTCoELzV%2Frxwillm6Cbi5vL5WauPqp0%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;368&quot; height=&quot;208&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;208&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;</description>
      <category>팁, 오류</category>
      <category>Docker</category>
      <category>MySQL</category>
      <category>SpringBoot</category>
      <category>testContainers</category>
      <category>개발</category>
      <category>백엔드</category>
      <category>테스트환경</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/28</guid>
      <comments>https://wafla.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 15 Aug 2025 23:49:58 +0900</pubDate>
    </item>
    <item>
      <title>IntelliJ - finished with non-zero exit value 1</title>
      <link>https://wafla.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;잘 돌아가던 프로그램이 갑자기 finished with non-zero exit value 1가 뜨면서 실행이 안됐다.&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;구글에 검색해보니 거의 다 Build and run using에서 Gradle을 IntelliJ IDEA로 바꾸라는 말뿐이었고 나에게는 도움이 되지 않았다.&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;새로 짠 코드가 문제인가 싶어 Clone도 새로 해보고 DB도 다시 깔아보고 Invalidate Caches에서 캐시도 날려봤지만 돌아오는 응답은 finished with non-zero exit value 1뿐이었다...&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-origin-width=&quot;481&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWRwp2/btsPE8mB7tP/jwSGowKmMaCCnLYWkNRBu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWRwp2/btsPE8mB7tP/jwSGowKmMaCCnLYWkNRBu0/img.png&quot; data-alt=&quot;6시간 동안 이것만 봤다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWRwp2/btsPE8mB7tP/jwSGowKmMaCCnLYWkNRBu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWRwp2%2FbtsPE8mB7tP%2FjwSGowKmMaCCnLYWkNRBu0%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;270&quot; height=&quot;181&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;6시간 동안 이것만 봤다&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;GPT가 하라는대로 해봐도 실행 창에서는 어떤 부분이 문제인지를 전혀 알려주지 않아서 미칠뻔했는데 개발 세팅 문서를 처음부터 다시 읽어보다 Profile 활성화가 눈에 들어왔고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lucas-owner.tistory.com/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lucas-owner.tistory.com/22 &lt;/a&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-origin-width=&quot;464&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpAeFP/btsPCJ3aLCI/PIV2kt59QO3l1sH0TA6lv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpAeFP/btsPCJ3aLCI/PIV2kt59QO3l1sH0TA6lv0/img.png&quot; data-alt=&quot;드디어 지옥 탈출..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpAeFP/btsPCJ3aLCI/PIV2kt59QO3l1sH0TA6lv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpAeFP%2FbtsPCJ3aLCI%2FPIV2kt59QO3l1sH0TA6lv0%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;278&quot; height=&quot;204&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;341&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;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;size14&quot;&gt;-Dspring.profiles.active=local&lt;/p&gt;</description>
      <category>팁, 오류</category>
      <category>IntelliJ</category>
      <category>java</category>
      <category>SpringBoot</category>
      <category>백엔드</category>
      <category>오류</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/27</guid>
      <comments>https://wafla.tistory.com/27#entry27comment</comments>
      <pubDate>Fri, 1 Aug 2025 02:25:30 +0900</pubDate>
    </item>
    <item>
      <title>외부 모니터 인식 안되는 오류 (c to HDMI)</title>
      <link>https://wafla.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 노트북으로 코딩할 일이 많아져서 집에서 사용하고 있던 모니터 2개에 노트북을 연결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 1920x1080에 144Hz까지 지원되고 다른 하나는 3440x1440에 165Hz까지 지원됩니다(DP 기준).&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;HDMI 포트가 하나여서 HDMI to HDMI 선 하나와 c to HDMI 선 하나로 두 모니터와 연결을 했고 열심히 코딩을 하기 전에 갑자기 바퀴벌레에 대해 궁금해져서 나무위키에 바퀴벌레를 검색하고 정독했습니다.&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;뜬금없긴 한데 이 생각을 하기 전에는 지금 살고 있는 지구가 처음으로 문명화된 지구인지 아니면 과거에 문명화 됐지만 핵전쟁 등으로 문명이 사라지고 다시 긴 세월동안 문명화가 진행되고 있는 n번째 지구인지에 대한 생각을 하고 있었습니다. 그렇다면 또 다시 문명이 사라진다면 인간은 멸종해도 바퀴벌레는 살아 있을 수 있을까? 라는 의식의 흐름입니다.&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;나무위키를 열심히 읽고 있는데 불편한 점이 생겼습니다. 스크롤을 내리는데 화면이 너무 일렁일렁거렸습니다. 그래서 바로 디스플레이 설정을 키고 주사율을 최대로 올렸습니다. 메인 화면도 주사율이 60Hz로 설정돼있겠구나 싶어 아무생각 없이 메인 화면도 주사율을 최대로 (120Hz) 올리고 화면이 잘나오는지 확인도 하지 않고 변경사항 저장을 눌렀습니다.&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;그런데 화면이 나오지 않습니다...&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;대부분의 선들이 2K까지 고주사율을 지원하는데 제 메인 모니터는 2K가 넘는 와이드 모니터입니다. 한계를 넘는 값을 선택하고 아무생각 없이 저장을 누르니 모니터가 맛이 갈 수밖에... &lt;s&gt;(120Hz를 애초에 선택 할 수 없었다면 이런 일은 안일어 났을텐데)&lt;/s&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;태블릿과 모니터를 c to HDMI로 연결해본 결과 잘 나왔고 노트북과 모니터를 HDMI to HDMI로 연결했을 때도 잘 나왔습니다. 다행히 모니터를 다시 안사도 됩니다. 이후 GPT에게 해결방법을 물어봤고 다음과 같은 방법들을 시도해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 그래픽 드라이버 삭제 + 재부팅 + 그래픽 드라이버 설치&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b90xFf/btsMErh6hrc/e7EigNdyqAkAqehfYVen00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b90xFf/btsMErh6hrc/e7EigNdyqAkAqehfYVen00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b90xFf/btsMErh6hrc/e7EigNdyqAkAqehfYVen00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb90xFf%2FbtsMErh6hrc%2Fe7EigNdyqAkAqehfYVen00%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;535&quot; height=&quot;392&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 노트북은 인텔 내장 그래픽을 사용하고 있습니다. 그래픽 카드를 제거 했다가 재부팅하고 최신 그래픽 드라이버를 깔면 해결될 수 있다고 GPT가 말했습니다. 혹시 그래픽 카드를 제거하면 화면이 안나올까 걱정하지 않으셔도 됩니다. 하지만 저는 이 방법으로 해결할 순 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. DDU&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2bIuV/btsMErPVESV/kKzYSOvRP6cETRlOSujLXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2bIuV/btsMErPVESV/kKzYSOvRP6cETRlOSujLXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2bIuV/btsMErPVESV/kKzYSOvRP6cETRlOSujLXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2bIuV%2FbtsMErPVESV%2FkKzYSOvRP6cETRlOSujLXK%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;435&quot; height=&quot;587&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Display Driver Uninstaller의 약자로 그래픽 드라이버를 제거해주고 모니터에 설정된 레지스터 값들을 전부 날려줍니다. &lt;a href=&quot;https://www.wagnardsoft.com/display-driver-uninstaller-DDU-&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이곳&lt;/a&gt;에서 설치할 수 있습니다. 모니터에 문제가 생긴 뒤에 해당 모니터가 아예 잡히지도 않았는데 DDU를 실행하고 나니 화면은 안나와도 모니터가 잡히긴 했습니다. 하지만 역시 문제는 그대로...&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;이외에도 선을 다른 포트에 끼운다거나 안전 모드(저해상도)로 부팅하거나 해봤지만 아무 소용이 없었습니다. 결국 HDMI는 포기하고 c to DP 선을 주문한 뒤 잠에 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 인텔 그래픽 제어 센터 (해결)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2025-03-06 214255.png&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIFXUn/btsMDghw2Mn/25kg07MLJzsYvVk3Q8RtY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIFXUn/btsMDghw2Mn/25kg07MLJzsYvVk3Q8RtY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIFXUn/btsMDghw2Mn/25kg07MLJzsYvVk3Q8RtY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIFXUn%2FbtsMDghw2Mn%2F25kg07MLJzsYvVk3Q8RtY0%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;501&quot; height=&quot;392&quot; data-filename=&quot;화면 캡처 2025-03-06 214255.png&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 22시간이 지나고 난 뒤 집에 도착해서 혹시나 하는 마음에 다시 디스플레이 설정을 살펴봤습니다. DDU를 실행한 뒤라 그런지 모니터가 잡히긴하는데 화면이 여전히 안나오고 해상도나 주사율 등을 설정할 수는 없었습니다. 그런데 이러한 문구를 발견했습니다. '디스플레이2가 &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;s&gt;(c to DP선 괜히샀다)&lt;/s&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;/p&gt;</description>
      <category>팁, 오류</category>
      <category>DDU</category>
      <category>dp</category>
      <category>hdmi</category>
      <category>그래픽드라이버</category>
      <category>모니터 오류</category>
      <category>와이드모니터</category>
      <category>외부모니터</category>
      <category>울트라와이드모니터</category>
      <category>주사율</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/26</guid>
      <comments>https://wafla.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 6 Mar 2025 22:37:05 +0900</pubDate>
    </item>
    <item>
      <title>점프 투 장고 추가 기능(이메일 인증)</title>
      <link>https://wafla.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이메일 인증 기능 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이보 추가 기능 항목에는 없지만 필요한 기능인 것 같아 추가했습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SMTP 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 이메일을 보내기 위해 SMTP 설정을 해줍니다. 저는 네이버를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SMTP란? Simple Mail Transfer Protocol의 약자로 이메일을 송신하는 데 사용되는 인터넷 표준 프로토콜입니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 메일에 접속한 후 환경설정 - IMAP/SMTP 설정에서 사용함을 체크하고 저장하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;1056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvP5KD/btsKNW0DAFu/j8TMbMgUBu8g03h26VbSlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvP5KD/btsKNW0DAFu/j8TMbMgUBu8g03h26VbSlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvP5KD/btsKNW0DAFu/j8TMbMgUBu8g03h26VbSlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvP5KD%2FbtsKNW0DAFu%2Fj8TMbMgUBu8g03h26VbSlk%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;1450&quot; height=&quot;1056&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;1056&quot;/&gt;&lt;/span&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;b&gt;config/settings/base.py&lt;/b&gt;에서 SMTP를 사용할 수 있도록 설정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EMAIL_HOST_USER에 계정 정보에 적힌 아이디를, PASSWORD에는 네이버 비밀번호를 적으면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;보안에 유의하세요!&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# SMTP 설정
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.naver.com'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = '아이디'
EMAIL_HOST_PASSWORD = '비밀번호'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;common/models.py&lt;/b&gt;에 다음과 같이 모델을 생성해줍니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import uuid

class EmailVerification(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    verification_code = models.UUIDField(default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)

    def is_valid(self):
        return timezone.now() &amp;lt; self.created_at + timezone.timedelta(minutes=10)&lt;/code&gt;&lt;/pre&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;uuid를 통해 인증번호를 생성하고 이를 유저에게 값을 할당해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;is_valid는 10분 이내에 인증이 됐는지를 확인하는 함수입니다.&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;python manage.py makemigrations와 python manage.py migrate로 DB에 적용해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 오타 등으로 수정한 뒤 다시 적용 시키려면 common/migrations 폴더에 해당되는 파일을 제거하고 다시 migrate해주면 됩니다.(예: 0001_EmailVerification.py)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인증 기능을 구현해봅시다. 우선, signup 함수를 다음과 같이 수정합니다.&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;common/views.py&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from .models import EmailVerification
def signup(request):
    if request.method == &quot;POST&quot;:
        form = UserForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)
            user.is_active = False # 이메일 인증 전까지 비활성화
            user.save()

            # 이메일 인증 객체 생성 및 저장
            verification = EmailVerification.objects.create(user=user)
            verification_code = verification.verification_code

            # 이메일 발송
            send_verification_email(user, verification_code)
            return redirect('common:verify_email') # 인증 코드 입력 페이지로 이동
    else:
        form = UserForm()
    return render(request, 'common/signup.html', {'form': form})&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;이메일 발송 함수는 다음과 같습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from django.core.mail import EmailMessage
def send_verification_email(user, verification_code):
    subject = '이메일 인증'
    message = f'인증 코드는 {verification_code} 입니다'
    from_email = '네이버아이디@naver.com'
    to_email = [user.email]

    email = EmailMessage(subject, message, from_email, to_email)
    email.send(fail_silently=False)&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;다음은 인증 코드를 입력받고 확인하는 코드입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;def verify_email(request):
    if request.method == &quot;POST&quot;:
        # 사용자로부터 POST로 인증 코드 받기
        code = request.POST.get('verification_code')
        code = code.strip() # 공백 제거
        try:
            verification = EmailVerification.objects.get(verification_code=code)
            if verification.is_valid():
                user = verification.user
                user.is_active = True  # 계정 활성화
                user.save()
                verification.delete()
                login(request, user)
                return redirect('index')
            else:
                return render(request, 'common/verify_email.html', {'error': '인증 코드가 만료되었습니다.'})
        except EmailVerification.DoesNotExist:
            return render(request, 'common/verify_email.html', {'error': '잘못된 인증 코드입니다.'})

    # GET 요청 시 인증 코드 입력 폼 페이지를 렌더링
    return render(request, 'common/verify_email.html')&lt;/code&gt;&lt;/pre&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;확인이 완료되면 user.is_active가 True로 바뀌며 계정이 활성화 됩니다. 활성화된 아이디의 인증 객체는 삭제합니다.&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;common/urls.py&lt;/b&gt;에 path도 추가해줍니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;path('verify_email/', views.verify_email, name='verify_email'),&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;templates/common/verify_email.html&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;{% extends &quot;base.html&quot; %}
{% block content %}
&amp;lt;div class=&quot;container my-3&quot;&amp;gt;
    &amp;lt;h3&amp;gt;이메일 인증&amp;lt;/h3&amp;gt;
    {% if error %}
        &amp;lt;div class=&quot;alert alert-danger&quot;&amp;gt;{{ error }}&amp;lt;/div&amp;gt;
    {% endif %}
    &amp;lt;form method=&quot;post&quot;&amp;gt;
        {% csrf_token %}
        &amp;lt;div class=&quot;mb-3&quot;&amp;gt;
            &amp;lt;label for=&quot;verification_code&quot;&amp;gt;인증 코드&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;verification_code&quot; id=&quot;verification_code&quot; required&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&amp;gt;인증하기&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}&lt;/code&gt;&lt;/pre&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;기능 구현 결과 화면입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ1.PNG&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c10ZiS/btsKPyKL9yk/fkuhGw5tKekKK1sjqNrUC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c10ZiS/btsKPyKL9yk/fkuhGw5tKekKK1sjqNrUC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c10ZiS/btsKPyKL9yk/fkuhGw5tKekKK1sjqNrUC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc10ZiS%2FbtsKPyKL9yk%2FfkuhGw5tKekKK1sjqNrUC0%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;1714&quot; height=&quot;660&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ1.PNG&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ㅎㅇㄱㅇ2.PNG&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMrglX/btsKOJ7anbp/FzWPrK53GgfRWMe6k82UHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMrglX/btsKOJ7anbp/FzWPrK53GgfRWMe6k82UHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMrglX/btsKOJ7anbp/FzWPrK53GgfRWMe6k82UHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMrglX%2FbtsKOJ7anbp%2FFzWPrK53GgfRWMe6k82UHk%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;552&quot; height=&quot;260&quot; data-filename=&quot;ㅎㅇㄱㅇ2.PNG&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ3.PNG&quot; data-origin-width=&quot;1711&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daWVkt/btsKOlFDZHD/NGwFXCeDauey5KGnlczkDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daWVkt/btsKOlFDZHD/NGwFXCeDauey5KGnlczkDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daWVkt/btsKOlFDZHD/NGwFXCeDauey5KGnlczkDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaWVkt%2FbtsKOlFDZHD%2FNGwFXCeDauey5KGnlczkDk%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;1711&quot; height=&quot;416&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ3.PNG&quot; data-origin-width=&quot;1711&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ4.PNG&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckEkDc/btsKN4qyHPM/yN4qH261dmhCKCLC77GqEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckEkDc/btsKN4qyHPM/yN4qH261dmhCKCLC77GqEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckEkDc/btsKN4qyHPM/yN4qH261dmhCKCLC77GqEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckEkDc%2FbtsKN4qyHPM%2FyN4qH261dmhCKCLC77GqEK%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;1714&quot; height=&quot;459&quot; data-filename=&quot;edited_ㅎㅇㄱㅇ4.PNG&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/django</category>
      <category>Django</category>
      <category>백엔드</category>
      <category>장고</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/25</guid>
      <comments>https://wafla.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 20 Nov 2024 00:15:15 +0900</pubDate>
    </item>
    <item>
      <title>점프 투 장고 추가 기능(카테고리)</title>
      <link>https://wafla.tistory.com/24</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5L0ez/btsKDTVvAHt/AKPmzkKErndKtH3brnsoZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5L0ez/btsKDTVvAHt/AKPmzkKErndKtH3brnsoZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5L0ez/btsKDTVvAHt/AKPmzkKErndKtH3brnsoZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5L0ez%2FbtsKDTVvAHt%2FAKPmzkKErndKtH3brnsoZ1%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;1086&quot; height=&quot;192&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 카테고리 모델을 생성합니다. 이름과 설명 항목을 넣었습니다. 그리고 Question 모델에서 카테고리를 참조하도록 합니다.&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;models.py&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)

    def __str__(self):
        return self.name
        
class Question(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_question')
    subject = models.CharField(max_length=200)
    content = models.TextField()
    create_date = models.DateTimeField()
    modify_date = models.DateTimeField(null=True, blank=True)
    voter = models.ManyToManyField(User, related_name='voter_question')
    category = models.ForeignKey(Category, on_delete=models.CASCADE) # 카테고리 추가

    def __str__(self):
        return self.subject&lt;/code&gt;&lt;/pre&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;모델을 생성했으니 makemigrations을 실행해 줘야 하는데 기존에 있던 질문 글들은 카테고리가 없는 상태라 오류가 발생합니다. 따라서 우선 Question 모델에서 category를 다음과 같이 null 값이 올 수 있도록 설정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731168713059&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='questions', null=True)&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;그리고 python manage.py shell을 실행하여 질문게시판, 자유게시판 카테고리를 생성하고 기존 글들의 카테고리를 설정해준다음 migration을 진행했습니다.&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;다음 코드는 shell을 실행하여 기존의 글들을 질문게시판으로 옮기는 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731169028998&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py shell

from pybo.models import Category, Question

# '질문 게시판' 카테고리 객체 가져오기
question_category = Category.objects.get(name='질문게시판')

# 모든 질문의 category를 '질문 게시판'으로 업데이트
Question.objects.update(category=question_category)&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;forms.py&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question # 사용할 모델
        fields = ['subject', 'content', 'category'] # QuestionForm에서 사용할 Question 모델의 속성
        labels = {
            'subject': '제목',
            'content': '내용',
        }

        category = forms.ModelChoiceField(queryset=Category.objects.all(), empty_label='카테고리 선택', widget=forms.Select())&lt;/code&gt;&lt;/pre&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;b&gt;question_form.html&lt;/b&gt;에서 카테고리를 선택할 수 있도록 해줍니다. 저는 오류표시 밑에 코드를 추가했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;mb-3&quot;&amp;gt;
    &amp;lt;label for=&quot;category&quot; class=&quot;form-label&quot;&amp;gt;카테고리&amp;lt;/label&amp;gt;
    &amp;lt;select class=&quot;form-select&quot; id=&quot;category&quot; name=&quot;category&quot;&amp;gt;
        &amp;lt;option value=&quot;&quot; disabled selected&amp;gt;카테고리 선택&amp;lt;/option&amp;gt;
        {% for category in form.category.field.queryset %}
            &amp;lt;option value=&quot;{{ category.id }}&quot; {% if category.id == form.category.value %}selected{% endif %}&amp;gt;
                {{ category.name }}
            &amp;lt;/option&amp;gt;
        {% endfor %}
    &amp;lt;/select&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkIv94/btsKDbQalgS/kegHcjiG6dEl3zAKkzGehk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkIv94/btsKDbQalgS/kegHcjiG6dEl3zAKkzGehk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkIv94/btsKDbQalgS/kegHcjiG6dEl3zAKkzGehk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkIv94%2FbtsKDbQalgS%2FkegHcjiG6dEl3zAKkzGehk%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;1382&quot; height=&quot;954&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&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;이제 카테고리에 맞게 게시글들을 페이징하고 context에 담아 넘겨주면 됩니다. question_detail 화면에서 카테고리를 선택하면 해당 카테고리의 값을 url에 추가하고 이를 통해 무슨 카테고리인지 판별하면 됩니다.&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;아무 값도 없다면 카테고리를 구분하지 않고 모든 글들을 보여주도록 코드를 작성했습니다. filter 기능을 사용해 카테고리를 쉽게 분류할 수 있습니다. 그리고 다른 카테고리가 추가되더라도 수정할 일이 없도록 코드를 작성했습니다.&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;&lt;b&gt;base_views.py&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;def index(request):
    page = request.GET.get('page', '1') # 페이지
    kw = request.GET.get('kw', '') # 검색어
    category_name = request.GET.get('category', '')
    if category_name != '':
        question_list = Question.objects.filter(category__name=category_name).order_by('-create_date')
    else:
        question_list = Question.objects.order_by('-create_date')
    if kw:
        question_list = question_list.filter(
            Q(subject__icontains=kw) | # 제목 검색
            Q(content__icontains=kw) | # 내용 검색
            Q(answer__content__icontains=kw) | # 답변 내용 검색
            Q(author__username__icontains=kw) | # 질문 글쓴이 검색
            Q(answer__author__username__icontains=kw) # 답변 글쓴이 검색
        ).distinct()
    paginator = Paginator(question_list, 10) # 페이지당 10개씩 보여주기
    page_obj = paginator.get_page(page)
    categories = Category.objects.all()
    context = {'question_list': page_obj, 'page': page, 'kw': kw, 'categories': categories, 'category_name': category_name,}
    return render(request, 'pybo/question_list.html', context)&lt;/code&gt;&lt;/pre&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;b&gt;question_list.html&lt;/b&gt;을 수정해줍니다. 카테고리를 선택하는 코드를 추가했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;row my-3&quot;&amp;gt;
    &amp;lt;div class=&quot;col-2&quot;&amp;gt;
        &amp;lt;a href=&quot;{% url 'pybo:question_create' %}&quot; class=&quot;btn btn-primary&quot;&amp;gt;질문 등록하기&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;col-4&quot;&amp;gt;
        &amp;lt;div class=&quot;input-group&quot;&amp;gt;
            &amp;lt;select class=&quot;form-control&quot; id=&quot;category_select&quot; name=&quot;category&quot;&amp;gt;
                &amp;lt;option value=&quot;&quot; {% if category_name == &quot;&quot; %}selected{% endif %}&amp;gt;전체&amp;lt;/option&amp;gt;
                {% for category in categories %}
                    &amp;lt;option value=&quot;{{ category.name }}&quot; {% if category.name == category_name %}selected{% endif %}&amp;gt;
                        {{ category.name }}
                    &amp;lt;/option&amp;gt;
                {% endfor %}
            &amp;lt;/select&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;col-2&quot;&amp;gt;

    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;col-4&quot;&amp;gt;
        &amp;lt;div class=&quot;input-group&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;search_kw&quot; class=&quot;form-control&quot; value=&quot;{{ kw|default_if_none:'' }}&quot;&amp;gt;
            &amp;lt;div class=&quot;input-group-append&quot;&amp;gt;
                &amp;lt;button class=&quot;btn btn-outline-secondary&quot; type=&quot;button&quot; id=&quot;btn_search&quot;&amp;gt;찾기&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;form id=&quot;searchForm&quot; method=&quot;get&quot; action=&quot;{% url 'index' %}&quot;&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; id=&quot;kw&quot; name=&quot;kw&quot; value=&quot;{{ kw|default_if_none:''}}&quot;&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; id=&quot;page&quot; name=&quot;page&quot; value=&quot;{{ page }}&quot;&amp;gt;
    &amp;lt;input type=&quot;hidden&quot; id=&quot;category&quot; name=&quot;category&quot; value=&quot;{{ category_name }}&quot;&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;script type='text/javascript'&amp;gt;
    const category_select = document.getElementById(&quot;category_select&quot;);
    category_select.addEventListener('change', function() {
        document.getElementById('category').value = this.value;
        document.getElementById('page').value = 1;
        document.getElementById('searchForm').submit();
    });
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZgcd/btsKEbhjRHF/f1pJM3I7XaFOQ8nezVIpo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZgcd/btsKEbhjRHF/f1pJM3I7XaFOQ8nezVIpo0/img.png&quot; data-alt=&quot;전체&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZgcd/btsKEbhjRHF/f1pJM3I7XaFOQ8nezVIpo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZgcd%2FbtsKEbhjRHF%2Ff1pJM3I7XaFOQ8nezVIpo0%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;1382&quot; height=&quot;954&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전체&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QamaF/btsKDp1KRnd/BfN28rGiePwWJJPJmcTXsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QamaF/btsKDp1KRnd/BfN28rGiePwWJJPJmcTXsk/img.png&quot; data-alt=&quot;질문게시판&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QamaF/btsKDp1KRnd/BfN28rGiePwWJJPJmcTXsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQamaF%2FbtsKDp1KRnd%2FBfN28rGiePwWJJPJmcTXsk%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;1382&quot; height=&quot;954&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;질문게시판&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0UWTI/btsKCjV2x7Q/oheSRbYdnzJUFgobBUEcYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0UWTI/btsKCjV2x7Q/oheSRbYdnzJUFgobBUEcYk/img.png&quot; data-alt=&quot;자유게시판&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0UWTI/btsKCjV2x7Q/oheSRbYdnzJUFgobBUEcYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0UWTI%2FbtsKCjV2x7Q%2FoheSRbYdnzJUFgobBUEcYk%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;1382&quot; height=&quot;954&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자유게시판&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발/django</category>
      <category>Django</category>
      <category>백엔드</category>
      <category>장고</category>
      <author>wafla</author>
      <guid isPermaLink="true">https://wafla.tistory.com/24</guid>
      <comments>https://wafla.tistory.com/24#entry24comment</comments>
      <pubDate>Sun, 10 Nov 2024 01:32:11 +0900</pubDate>
    </item>
  </channel>
</rss>