<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Tools in hand, brain in gear.</title>
    <link>https://shurimp.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 17:50:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>shurimp</managingEditor>
    <item>
      <title>[Ktor] tailcard로 URL 파라미터 수집하기</title>
      <link>https://shurimp.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 스토리지 api를 개발하면서 슬래시가 포함된 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;버킷 내 폴더(실제로 폴더는 아니지만 path형식으로 구분하므로 이 글에서만 폴더라고 지칭)가 있는 경우, path형식의 오브젝트 키를 파라미터로 수집해서 넘겨야하는데, 보통 URL 파라미터는 슬래시로 구분되어 path parameter를 사용해도 해당 엔드포인트에 매칭이 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 오브젝트 키가 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;code&gt;one/two/three&lt;/code&gt;&lt;/span&gt;인 경우,&lt;/p&gt;
&lt;pre id=&quot;code_1674652892996&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get(&quot;/{objectKey}&quot;) {
    val objectKey = call.parameters[&quot;objectKey&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 path parameter를 쓸 경우 매칭되는 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ktor에서는 이런 상황을 해결하기 위해 tailcard문자를 지원한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eab6GQ/btrW37JAaHR/pha4EPOqfFKjboOF00kId0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eab6GQ/btrW37JAaHR/pha4EPOqfFKjboOF00kId0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eab6GQ/btrW37JAaHR/pha4EPOqfFKjboOF00kId0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feab6GQ%2FbtrW37JAaHR%2Fpha4EPOqfFKjboOF00kId0%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;737&quot; height=&quot;202&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가변인자를 a... 이라고 표시하는 것과 비슷하게, tailcard문자를 쓰면 나머지 경로들까지 모두 해당 파라미터로 수집할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용예시는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1674653237483&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get(&quot;/{objectKey...}&quot;) {
	val objectKey = call.parameters.getAll[&quot;objectKey&quot;]
}
// objectKey: List&amp;lt;String&amp;gt;?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;objectKey뒤에 ...을 붙여서 나머지 모든 URL 경로를 수집한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 &lt;span style=&quot;color: #009a87;&quot;&gt;call.parameters.getAll&lt;/span&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;그러나 내 경우, 수집해온 파라미터들을 다시 하나의 완성된 문자열로 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 하면 objectKey는 one, two, three가 담긴 리스트인데, 내가 원하는 결과는 &lt;span style=&quot;color: #009a87;&quot;&gt;one/two/three &lt;/span&gt;문자열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다시 path형식으로 합쳐주는 과정이 필요하다. 코틀린의 문자열 메소드를 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1674653830855&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get(&quot;/{objectKey...}&quot;) {
    val objectKeyList = call.parameters.getAll[&quot;objectKey&quot;]
    val objectKey = objectKeyList.joinToString(&quot;/&quot;)
}
// result: one/two/three&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;joinToString은 배열을 하나의 문자열로 합칠 때 사용하고, seperator, prefix, suffix를 지정할 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 테일카드만 단독으로 쓸 경우 나머지 path에 관계없이 직전의 URL에 매칭시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 /user/{...} 은 /user, 그리고 /user/other/theOther 모두에 매칭된다.&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;/user/*은 /user/other/theOther과 매칭되지만 /user에는 매치되지 않는다.&lt;/p&gt;</description>
      <category>Ktor</category>
      <category>Kotlin</category>
      <category>Ktor</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/89</guid>
      <comments>https://shurimp.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 25 Jan 2023 22:53:21 +0900</pubDate>
    </item>
    <item>
      <title>[AWS S3] S3 호환 타 클라우드 플랫폼의 presigned URL 생성하기</title>
      <link>https://shurimp.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS가 아닌 타 클라우드 플랫폼에서 S3 호환 API를 이용할 때, presigned url을 생성하는 방법에 대한 자료가 많이 없어서 처음에 엄청나게 헤맸다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Presigned URL이란?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;presigned url이란 미리 서명된 url로, 해당 url로 접속 시 일정기간동안 인증정보가 없어도 오브젝트 스토리지에 접근할 수 있게 해준다.&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;1146&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EczLb/btrWX4koADz/YFDgXibZuJ9wxH3iFXcp91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EczLb/btrWX4koADz/YFDgXibZuJ9wxH3iFXcp91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EczLb/btrWX4koADz/YFDgXibZuJ9wxH3iFXcp91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEczLb%2FbtrWX4koADz%2FYFDgXibZuJ9wxH3iFXcp91%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;1146&quot; height=&quot;566&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;566&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;클라이언트가 서버에 presigned url을 받기위한 요청을 날리고, url을 받으면 그 때 그 url로 직접 파일 업로드/다운로드를 하는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 퍼포먼스적인 측면에서도, 클라이언트가 직접 스토리지에 접근하게되므로 서버의 리소스 사용을 줄일 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;AWS SDK V2?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 문서에 presigned url을 생성하는 예제 코드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1674477028848&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;미리 서명된 URL을 생성하여 객체 업로드 - Amazon Simple Storage Service&quot; data-og-description=&quot;미리 서명된 URL을 생성하여 객체 업로드 미리 서명된 URL의 생성자가 해당 객체에 대한 액세스 권한을 보유할 경우, 미리 서명된 URL은 URL에서 식별된 객체에 대한 액세스 권한을 부여합니다. 즉, &quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&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;미리 서명된 URL을 생성하여 객체 업로드 - Amazon Simple Storage Service&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;미리 서명된 URL을 생성하여 객체 업로드 미리 서명된 URL의 생성자가 해당 객체에 대한 액세스 권한을 보유할 경우, 미리 서명된 URL은 URL에서 식별된 객체에 대한 액세스 권한을 부여합니다. 즉,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PutObjectRequest -&amp;gt; &lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;PutObjectPresignRequest -&amp;gt; &lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;PresignedPutObjectRequest 순으로 객체를 만들고 마지막 객체에서 만들어진 url을 반환하는 방식이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;그러나 이 방식은 AWS의 오브젝트 스토리지를 사용할 때에만 작동한다. 이유는 이 코드로 생성된 url의 엔드포인트가 aws의 스토리지 엔드포인트로 반환되기 때문이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;따로 엔드포인트를 설정할 수 있는 메소드가 분명 있을거라 생각했고, 리퀘스트 빌더에 .endpointOverride() 메소드가 있어서 여기에 타 클라우드 플랫폼의 엔드포인트 URI를 넣어주었지만, 이런 목적에 사용하는 메소드가 아닌지 런타임 익셉션을 내뱉었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #16191f;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9;&quot;&gt;여기서 정말 많은 시간을 할애했는데, 결국 SDK V1을 사용하여 해결했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9;&quot;&gt;AWS SDK V1&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&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.gradle에 의존성 추가&lt;/p&gt;
&lt;pre id=&quot;code_1674477891718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'com.amazonaws:aws-java-sdk-s3:${Version}'&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;위 sdkv2버전의 패키지명은 &lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;software.amazon.awssdk로 시작하고, v1 버전은 com.amazonaws로 시작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #16191f;&quot;&gt;v1버전에서 제공하는 클라이언트 빌더를 사용하면 타 클라우드 플랫폼과 호환되는 객체를 생성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674478202887&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val client = AmazonS3ClientBuilder
    .standard()
    .withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(&quot;스토리지 엔드포인트&quot;, &quot;kr-standard&quot;))
    .withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(&quot;accessId&quot;, &quot;accessSecret&quot;)))
    .enablePathStyleAccess()
    .build()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.withEndpointConfiguration에 사용하는 클라우드 스토리지의 엔드포인트와 리전을 문자열로 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리전은 보통 해당 클라우드의 사용가이드에 명시하고 있으니, 찾아보고 맞게 기입하면 된다. 나와있지 않은 경우, 국내 플랫폼은&amp;nbsp; &quot;kr-standard&quot;를 많이 사용하는 것 같다.&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;credentials에 발급받은 accesskey와 secret을 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;.enablePathStyleAccess()&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션이 조금 중요한데, 만약 사용하는 클라우드 플랫폼에서 virtual host형식 URL을 지원하는지 여부에 따라 해당 옵션을 추가할지 결정해야한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GET https://{endpoint}/{bucket-name}/{object-name} # path style&lt;br /&gt;ex) https://&lt;span style=&quot;color: #009a87;&quot;&gt;example.storage.com/bucket-name&lt;/span&gt;/sample-object&lt;br /&gt;GET https://{bucket-name}.{endpoint}/{object-name} # virtual host style&lt;br /&gt;ex) https://&lt;span style=&quot;color: #009a87;&quot;&gt;bucket-name.example.storage.com&lt;/span&gt;/sample-object&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path 스타일과 virtual host 스타일은 다음과 같이 약간의 차이가 있다. 보통은 virtual host 스타일을 지원하는데, 간혹 지원하지 않는 플랫폼이 있기 때문에, 만약 이 옵션을 빼고 진행했다가 &lt;span style=&quot;color: #212121;&quot;&gt;ENOTFOUND(endpoint not found)에러가 발생한다면 추가하면 된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffebe7; color: #212121;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Presigned PUT URL&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674479434132&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val presignedUrlRequest = GeneratePresignedUrlRequest(
    conf.bucketName,
    objectKey,
    HttpMethod.PUT
)
val url = client.generatePresignedUrl(presignedUrlRequest)

return url&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 URL을 생성하기 위해 GeneratePresignedUrlRequest객체를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷네임, 오브젝트키, HTTP 메서드 세가지 매개변수가 들어가고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만든 리퀘스트 객체를 클라이언트의 generatePresignedUrl 메소드의 매개변수로 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리퀘스트 객체를 생성하지 않고 바로 &lt;span&gt;generatePresignedUrl에 버킷네임과 오브젝트키만 넣어주어도 url 생성이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따로 생성한 이유는 http 메서드를 지정할 수 있기 때문이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 업로드와 다운로드 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Presigned GET URL&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674479691796&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val presignedUrlRequest = GeneratePresignedUrlRequest(
    conf.bucketName,
    objectKey,
    HttpMethod.GET
)
val url = client.generatePresignedUrl(presignedUrlRequest)

return url&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 URL도 동일하고, http메서드만 GET으로 바꿔주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;postman으로 테스트시, 반환받은 url로 직접 업로드, 다운로드가 정상 수행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;2486&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/snehh/btrW0P8iT9L/NOYq021XotP9FRL3Lraq2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/snehh/btrW0P8iT9L/NOYq021XotP9FRL3Lraq2K/img.png&quot; data-alt=&quot;상세정보 모자이크 처리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/snehh/btrW0P8iT9L/NOYq021XotP9FRL3Lraq2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsnehh%2FbtrW0P8iT9L%2FNOYq021XotP9FRL3Lraq2K%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;2486&quot; height=&quot;840&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;2486&quot; data-origin-height=&quot;840&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmop9P/btrWYMYixi3/tMg3xOFqmjdbGlAyVpXOC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmop9P/btrWYMYixi3/tMg3xOFqmjdbGlAyVpXOC0/img.png&quot; data-alt=&quot;^ㅠ^&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmop9P/btrWYMYixi3/tMg3xOFqmjdbGlAyVpXOC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmop9P%2FbtrWYMYixi3%2FtMg3xOFqmjdbGlAyVpXOC0%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;2340&quot; height=&quot;902&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;902&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;서버에서 반환받은 url로 get 요청시 이미지가 정상 로드된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buvhpY/btrWZXeeYwv/hz4ijuIoWBFiGdnDrYA8Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buvhpY/btrWZXeeYwv/hz4ijuIoWBFiGdnDrYA8Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buvhpY/btrWZXeeYwv/hz4ijuIoWBFiGdnDrYA8Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuvhpY%2FbtrWZXeeYwv%2Fhz4ijuIoWBFiGdnDrYA8Uk%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;1266&quot; height=&quot;400&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 동일한 url이어도 유효기간이 지난 후에 요청을 보내면 거부된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qEwXL/btrWU635z2B/aEkcosXwwkjGpLaw11TKRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qEwXL/btrWU635z2B/aEkcosXwwkjGpLaw11TKRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qEwXL/btrWU635z2B/aEkcosXwwkjGpLaw11TKRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqEwXL%2FbtrWU635z2B%2FaEkcosXwwkjGpLaw11TKRk%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;1746&quot; height=&quot;544&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url에는 다음과 같은 정보들이 쿼리 파라미터로 제공되는데, aws signature 4버전 서명에 사용되는 파라미터들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파라미터에 대한 상세설명은 다음 링크에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 4버전 이전의 서명버전들은 deprecated되었다고 한다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LnnLI/btrWUdh6XeW/dUpPs0CqG4Ggh2yCuSmkKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LnnLI/btrWUdh6XeW/dUpPs0CqG4Ggh2yCuSmkKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LnnLI/btrWUdh6XeW/dUpPs0CqG4Ggh2yCuSmkKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLnnLI%2FbtrWUdh6XeW%2FdUpPs0CqG4Ggh2yCuSmkKk%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;1455&quot; height=&quot;103&quot; data-origin-width=&quot;1455&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1674481425821&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;Common Parameters - Amazon Elastic Container Registry&quot; data-og-description=&quot;Common Parameters The following list contains the parameters that all actions use for signing Signature Version 4 requests with a query string. Any action-specific parameters are listed in the topic for that action. For more information about Signature Ver&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/AmazonECR/latest/APIReference/CommonParameters.html&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;Common Parameters - Amazon Elastic Container Registry&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Common Parameters The following list contains the parameters that all actions use for signing Signature Version 4 requests with a query string. Any action-specific parameters are listed in the topic for that action. For more information about Signature Ver&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 전체 샘플 코드이다. 언어는 코틀린으로 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 글 작성을 위해 핵심 부분만 작성했으니, 상황에 맞게 응용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1674480710195&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val client = AmazonS3ClientBuilder
    .standard()
    .withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(&quot;스토리지 엔드포인트&quot;, &quot;kr-standard&quot;))
    .withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(&quot;accessId&quot;, &quot;accessSecret&quot;)))
//    .enablePathStyleAccess() 		virtual host style 미지원시 추가
    .build()

fun presignPut(bucketName: String, objectKey: String) {
    val presignedUrlRequest = GeneratePresignedUrlRequest(
        bucketName,
        objectKey,
        HttpMethod.PUT
    )
    val url = client.generatePresignedUrl(presignedUrlRequest)
    
    return url
}

fun presignGet(bucketName: String, objectKey: String) {
    val presignedUrlRequest = GeneratePresignedUrlRequest(
        bucketName,
        objectKey,
        HttpMethod.GET
    )
    val url = client.generatePresignedUrl(presignedUrlRequest)

    return url
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DEVELOP</category>
      <category>AWS</category>
      <category>Kotlin</category>
      <category>s3</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/88</guid>
      <comments>https://shurimp.tistory.com/88#entry88comment</comments>
      <pubDate>Mon, 23 Jan 2023 22:47:54 +0900</pubDate>
    </item>
    <item>
      <title>[nGrinder] 서버 성능을 테스트하는 방법</title>
      <link>https://shurimp.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 서비스의 GS인증을 받기 위한 준비를 하고 있다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 찾아보다 발견한 것이 nGrinder이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 n이 붙은 것으로 알 수 있듯, 네이버에서 개발한 오픈소스이다. The Grinder라는 오픈소스를 기반으로 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 부하 테스트(stress test)를 제공하는 플랫폼으로, 직접 스크립트를 작성해 가상의 시나리오를 작성할 수 있고, 다양한 커스터마이징을 제공한다.&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;&lt;a href=&quot;https://naver.github.io/ngrinder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://naver.github.io/ngrinder/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671463274700&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;nGrinder&quot; data-og-description=&quot;Please post questions in Discussions not Issues. nGrinder 3.5.5-p1 version is now available. Check the changes at here. nGrinder is a platform for stress tests that enables you to execute script creation, test execution, monitoring, and result report gener&quot; data-og-host=&quot;naver.github.io&quot; data-og-source-url=&quot;https://naver.github.io/ngrinder/&quot; data-og-url=&quot;https://naver.github.io/ngrinder/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://naver.github.io/ngrinder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://naver.github.io/ngrinder/&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;nGrinder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Please post questions in Discussions not Issues. nGrinder 3.5.5-p1 version is now available. Check the changes at here. nGrinder is a platform for stress tests that enables you to execute script creation, test execution, monitoring, and result report gener&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;naver.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 다운로드하여 사용할 수도 있지만, dockerhub에 이미지로도 제공되어 간편하게 사용할 수 있다.&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;nGrinder는 크게 두가지만 알아두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Agent&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Controller: 스크립트를 생성하고 부하 테스트를 실행시키는 웹 어플리케이션, 즉 부하를 발생하는 주체.&lt;br /&gt;Agent: 요청을 생성하는 가상의 유저.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;nGrinder 설치&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker가 설치되어있다는 전제에서, 다음 명령어를 실행해 이미지를 pull받는다.&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;nGrinder Controller&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671464240207&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller -p 8080:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller:3.4&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;다음으로는 agent를 생성한다.&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;nGrinder Agent&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671464329849&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run -v ~/ngrinder-agent:/opt/ngrinder-agent -d ngrinder/agent:3.4 &amp;lt;controller_ip&amp;gt;:&amp;lt;controller_port&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;controller_ip&amp;gt;:&amp;lt;controller_port&amp;gt; 이 부분에 컨트롤러의 ip주소와 port번호를 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 로컬에서 컨트롤러를 구성 후 80번 포트를 열었다면, &amp;lt;내 로컬ip 주소&amp;gt;:80 이렇게 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(* 이 때 localhost가 아닌, ipv4 주소를 정확히 입력해야한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 브라우저 주소창에 열어둔 ip주소와 포트번호를 입력하여 nGrinder 사이트에 접속, 로그인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 id와 비밀번호는 admin, admin이다. 보안이 필요할 경우 최초 로그인 후 유저관리에서 다른 사용자는 삭제하고, admin의 패스워드를 변경해서 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1671464773154&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:80		// 로컬 접속시

id: admin
password: admin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3n5K/btrT6YUnefy/geyK1kOPH6RakTc0mRZcnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3n5K/btrT6YUnefy/geyK1kOPH6RakTc0mRZcnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3n5K/btrT6YUnefy/geyK1kOPH6RakTc0mRZcnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3n5K%2FbtrT6YUnefy%2FgeyK1kOPH6RakTc0mRZcnk%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;1020&quot; height=&quot;550&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;550&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;&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;1010&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqwH1c/btrT3pzcFS2/TKCKV56CkKzlpTr69So1K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqwH1c/btrT3pzcFS2/TKCKV56CkKzlpTr69So1K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqwH1c/btrT3pzcFS2/TKCKV56CkKzlpTr69So1K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqwH1c%2FbtrT3pzcFS2%2FTKCKV56CkKzlpTr69So1K0%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;1010&quot; height=&quot;680&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;680&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;로그인하면 다음과 같은 창이 뜨는데, 프로필 - Agent Management에 내가 생성한 agent가 있는지 먼저 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mu9ZX/btrT7E9j7yG/3siV1TG4PwwrJXaqeofTa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mu9ZX/btrT7E9j7yG/3siV1TG4PwwrJXaqeofTa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mu9ZX/btrT7E9j7yG/3siV1TG4PwwrJXaqeofTa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmu9ZX%2FbtrT7E9j7yG%2F3siV1TG4PwwrJXaqeofTa1%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;973&quot; height=&quot;286&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 agent가 뜨면 정상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;nGrinder 사용법&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;955&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw0JVJ/btrT6X80y0y/FrCFTTUVdzsSEf5oo4EIM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw0JVJ/btrT6X80y0y/FrCFTTUVdzsSEf5oo4EIM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw0JVJ/btrT6X80y0y/FrCFTTUVdzsSEf5oo4EIM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw0JVJ%2FbtrT6X80y0y%2FFrCFTTUVdzsSEf5oo4EIM1%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;955&quot; height=&quot;183&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 메인으로 돌아와서, quick start에 테스트할 서버의 url 입력 후 start test 버튼을 누르면 테스트할 수 있는 페이지로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMzrDx/btrT5bmuTMR/vlvokkDNUVo3cAdcKKQ6C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMzrDx/btrT5bmuTMR/vlvokkDNUVo3cAdcKKQ6C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMzrDx/btrT5bmuTMR/vlvokkDNUVo3cAdcKKQ6C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMzrDx%2FbtrT5bmuTMR%2FvlvokkDNUVo3cAdcKKQ6C1%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;989&quot; height=&quot;753&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 테스트 환경 설정을 할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Agent&lt;/b&gt;: agent 수(내가 생성한 agent의 수만큼 max값으로 설정할 수 있다.)&lt;br /&gt;&lt;b&gt;vuser per agent&lt;/b&gt;: agent당 가상 유저 수&lt;br /&gt;&lt;b&gt;script&lt;/b&gt;: 테스트가 시작되면 보낼 요청 스크립트, 편집으로 원하는 유저 시나리오를 작성할 수 있다.&lt;br /&gt;&lt;b&gt;Target Host&lt;/b&gt;: 테스트할 서버의 url&lt;br /&gt;&lt;b&gt;Duration&lt;/b&gt;: 기간설정(Default: 1분)&lt;br /&gt;&lt;b&gt;Run Count&lt;/b&gt;: 스레드당 수행될 테스트 단위&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Script 오른쪽 R Head 버튼을 클릭하여 스크립트를 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qACEb/btrTZyXKiqx/YOkMqJWkAaDbgL1xBhCKu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qACEb/btrTZyXKiqx/YOkMqJWkAaDbgL1xBhCKu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qACEb/btrTZyXKiqx/YOkMqJWkAaDbgL1xBhCKu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqACEb%2FbtrTZyXKiqx%2FYOkMqJWkAaDbgL1xBhCKu0%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;996&quot; height=&quot;236&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;236&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;기본적으로 단위테스트와 비슷한 코드방식으로 작성되어 있는데, 알맞게 편집하여 원하는 api를 테스트할 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용하면 단순히 요청 하나씩만 보내는 것을 넘어 가상의 시나리오를 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ex. 로그인 -&amp;gt; 페이지 조회 -&amp;gt; 결제 요청)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpiXp4/btrT4GNU9pQ/mSvWoaPqgnAGCyXoGWKOt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpiXp4/btrT4GNU9pQ/mSvWoaPqgnAGCyXoGWKOt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpiXp4/btrT4GNU9pQ/mSvWoaPqgnAGCyXoGWKOt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpiXp4%2FbtrT4GNU9pQ%2FmSvWoaPqgnAGCyXoGWKOt1%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;1084&quot; height=&quot;662&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;662&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 data-ke-size=&quot;size16&quot;&gt;Detailed Report에서 더 상세한 결과와, csv 파일도 제공된다.&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;TPS를 눈여겨봐야하는데, Test Per Seconds의 약자로, 초당 몇 번의 테스트가 수행되었는지 나타내는 단위다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pinpoint를 함께 사용해서 어디에서 요청이 지체되는지 등을 확인할 수도 있다.&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;nGrinder를 이용하면 다양한 환경에 맞는 테스트를 간편하게 할 수 있어 서버 문제점 파악과 성능 개선에 많은 도움이 될 것 같다.&lt;/p&gt;</description>
      <category>ETC</category>
      <category>ngrinder</category>
      <category>server</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/87</guid>
      <comments>https://shurimp.tistory.com/87#entry87comment</comments>
      <pubDate>Tue, 20 Dec 2022 01:20:15 +0900</pubDate>
    </item>
    <item>
      <title>java.lang.NoClassDefFoundError 원인 및 해결방법</title>
      <link>https://shurimp.tistory.com/86</link>
      <description>&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;200 응답이 나오고 데이터도 정상적으로 뜨는데 밑에 계속 java.lang.NoClassDefFoundError라는 에러가 나왔다.&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;해당 에러는 &lt;span style=&quot;color: #009a87;&quot;&gt;컴파일 환경에서는 클래스 참조가 되었으나 실행환경에서는 찾을 수 없는 경우&lt;/span&gt; 발생한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 전체 gradle project를 리로드하니 해결되었다.&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;652&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXUs4/btrT2QQmhLR/AQYI7HHwMU9hef3l04cZz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXUs4/btrT2QQmhLR/AQYI7HHwMU9hef3l04cZz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXUs4/btrT2QQmhLR/AQYI7HHwMU9hef3l04cZz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXUs4%2FbtrT2QQmhLR%2FAQYI7HHwMU9hef3l04cZz0%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;652&quot; height=&quot;82&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;82&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;(Gradle 탭은 우측 notifcations 탭 위에 위치해있다.)&lt;/p&gt;</description>
      <category>ETC</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/86</guid>
      <comments>https://shurimp.tistory.com/86#entry86comment</comments>
      <pubDate>Mon, 19 Dec 2022 14:31:18 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] Coroutine과 Multi-Thread</title>
      <link>https://shurimp.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 언어에서는 &lt;span style=&quot;color: #009a87;&quot;&gt;Coroutine&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Process와 Thread, 그리고 Multi-Thread&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Coroutine을 이해하기에 앞서, 먼저 프로세스와 스레드에 대한 개념부터 정리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Thread&lt;/span&gt;는 단어 자체의 의미로는 '실'이라는 뜻입니다. 프로그래밍에서는 실행흐름을 뜻합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Process&lt;/span&gt;는 메모리에 올라와 실행되고 있는 프로그램의 인스턴스입니다. 독립적인 개체로, 각각의 프로세스는 독립된 &lt;span style=&quot;color: #009a87;&quot;&gt;Heap&lt;/span&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;&quot;프로세스는 최소 1개의 스레드를 가지게 된다&quot;에서 알 수 있듯이, 스레드는 프로세스의 하위에 종속되는 단위입니다. 각 스레드는 독립된 &lt;span style=&quot;color: #009a87;&quot;&gt;Stack&lt;/span&gt; 메모리 영역을 가집니다. 프로세스의 힙 메모리 영역은 모든 스레드가 공유할 수 있고, 스레드끼리는 스택 메모리를 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드의 경우, 하나의 프로세스에 스레드가 2개 이상인 경우를 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Coroutine이란? (Coroutine &amp;ne; Thread)&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;718&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccbN0B/btrPrl2GYRi/fTPOXwRM66koPDRpI6zepK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccbN0B/btrPrl2GYRi/fTPOXwRM66koPDRpI6zepK/img.png&quot; data-alt=&quot;Kotlin docs.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccbN0B/btrPrl2GYRi/fTPOXwRM66koPDRpI6zepK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccbN0B%2FbtrPrl2GYRi%2FfTPOXwRM66koPDRpI6zepK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;88&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Kotlin docs.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 공식문서에는 코루틴을 그저 light-weight thread(경량화 된 스레드)라고 생각할 수도 있지만, 스레드와 확실히 구분되는 주요사항들이 있다고 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코루틴은 작업 각각에 Coroutine &lt;span style=&quot;color: #009a87;&quot;&gt;Object&lt;/span&gt;를 할당합니다. 객체이기 때문에 Heap 메모리에 적재됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작업의 단위를 Object로 축소하면서 하나의 스레드가 여러개의 코루틴을 가질 수 있습니다. 이를 정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;하나의 &lt;span style=&quot;color: #009a87;&quot;&gt;Process&lt;/span&gt;는 여러개의 &lt;span style=&quot;color: #009a87;&quot;&gt;Thread&lt;/span&gt;를 가집니다. 그리고 하나의 &lt;span style=&quot;color: #009a87;&quot;&gt;Thread&lt;/span&gt;는 다시 여러개의 &lt;span style=&quot;color: #009a87;&quot;&gt;Coroutine&lt;/span&gt;을 가집니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 생각하기에 가장 핵심이 되는 한 문장이라 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Multi-Thread 방식의 한계점&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드는 동시에 여러 작업을 수행할 수 있습니다. 그러나 여기엔 한계점이 존재합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;#1. 단위가 Thread이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Thread는 비싼 자원입니다. 비싼 자원을 많이 사용할 수록 메모리 낭비가 심하므로, 가급적이면 이러한 자원들은 최소한의 사용으로 효율을 내는 것이 좋습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;#2. Blocking 문제&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;예를들어 한 스레드가 다른 스레드의 결과를 전달받아야만 진행될 때, 해당 스레드는 작업 결과를 받기 전까지는 대기상태가 됩니다. 이를 &lt;span style=&quot;color: #009a87;&quot;&gt;Blocking&lt;/span&gt;이라 하는데, 비싼 자원인 스레드가 아무 작업도 안하고 대기상태로 있기 때문에 자원이 낭비되는 문제가 발생합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Coroutine을 사용한다면?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;#1. 단위가 Thread인 것을 해소할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;기존에는 여러 개의 작업을 동시에 처리하기 위해 비싼 자원인 스레드를 여러 개 만들어야 했습니다.&lt;br /&gt;그러나 이제는 스레드 대신 코루틴을 사용하여 동시에 작업을 처리할 수 있습니다. 이 말은 &lt;span style=&quot;color: #009a87;&quot;&gt;스레드가 여러개일 필요가 없다는 뜻&lt;/span&gt;이기도 합니다!&lt;br /&gt;&lt;br /&gt;단일 스레드 내에 여러 Coroutine Object를 두고, 작업단위를 코루틴으로 관리할 수 있습니다. 물론 멀티 스레드 환경에서도 코루틴을 가질 수 있지만, 단일 스레드 내에 여러개의 코루틴을 두는 방법을 코틀린에서는 권장하고 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;#2. Blocking문제를 해소할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;위에서 언급했던 한계점 #2의 멀티스레드 상황에서, 만약 스레드에 코루틴이 추가되어있다면 다음과 같이 작동하게 됩니다.&lt;br /&gt;&lt;br /&gt;1. 스레드1은 코루틴 1, 2를 가집니다.&lt;br /&gt;2. 스레드1의 코루틴1이 스레드2의 결과를 전달받아야 하는 작업A을 수행합니다. 이 때 스레드 2의 작업이 끝날 때 까지 코루틴1은 일시중단됩니다.&lt;br /&gt;3. 스레드1의 코루틴2가 새로운 작업(작업B)을 수행합니다.&lt;br /&gt;4. 스레드2에서 작업A'이 끝나고 결과를 코루틴1에게 전달하면, 코루틴1은 작업A를 재개합니다.&lt;br /&gt;&lt;br /&gt;기존 스레드만 있었을 때는 작업A가 끝날 때 까지 다른 작업을 수행하지 못했습니다. 그러나 코루틴으로 관리하게 되니 작업A를 수행하는 동안에도 작업B를 수행할 수 있게 되었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;References::&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/coroutines-basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/docs/coroutines-basics.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/139&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinworld.com/139&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@haero_kim/Thread-vs-Coroutine-%EB%B9%84%EA%B5%90%ED%95%B4%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@haero_kim/Thread-vs-Coroutine-%EB%B9%84%EA%B5%90%ED%95%B4%EB%B3%B4%EA%B8%B0&lt;/a&gt;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <category>Kotlin</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/85</guid>
      <comments>https://shurimp.tistory.com/85#entry85comment</comments>
      <pubDate>Tue, 25 Oct 2022 00:44:55 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] DAO / DTO / VO 정리</title>
      <link>https://shurimp.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 스프링을 접하게 되면 가장 헷갈리는 개념 중 하나가 DAO, DTO, VO가 아닐까 생각이 듭니다.&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;DTO란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;먼저 DTO입니다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;줄임말을 풀어쓰는 순간 이해가 훨씬 쉽습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;DTO는 &lt;span style=&quot;color: #009a87;&quot;&gt;Data Transfer Object&lt;/span&gt;입니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&quot;데이터 전송 객체&quot; 라는 뜻입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이언트에서 서버로 데이터를 전송할 때, 이 DTO 객체에 필드들을 매핑하여 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;필드의 이름과 타입만 맞춘다면 디스패쳐 서블릿이 자동으로 데이터들을 이 DTO객체에 매핑해서 전달합니다.&lt;/span&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;&lt;span style=&quot;color: #333333;&quot;&gt;Spring은 &lt;span style=&quot;color: #009a87;&quot;&gt;POJO(Plain-Java-Object)&lt;/span&gt;기반이기 때문에 이 DTO클래스 또한 POJO로 작성합니다. 롬복을 사용할 경우 &lt;span style=&quot;color: #009a87;&quot;&gt;@Data&lt;/span&gt; 어노테이션으로 DTO객체를 빠르게 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그렇다면 이 DTO객체의 구성요소엔 어떤 것들이 있을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSnQuB/btrNIKJ9HIl/Q2BT6KaFjgHUCcadJQYUZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSnQuB/btrNIKJ9HIl/Q2BT6KaFjgHUCcadJQYUZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSnQuB/btrNIKJ9HIl/Q2BT6KaFjgHUCcadJQYUZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSnQuB%2FbtrNIKJ9HIl%2FQ2BT6KaFjgHUCcadJQYUZ0%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;734&quot; height=&quot;172&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롬복의 @Data 어노테이션의 설명입니다. 사진의 @ToString 이후부터는 DTO객체의 필수사항은 아닙니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;DTO의 구성요소&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;(1) Getter&lt;br /&gt;(2) Setter&lt;br /&gt;(3) RequiredArgsConstructor&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;(1) Getter / Setter&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO클래스는 POJO임과 동시에 자바빈즈 클래스 규약을 따릅니다. 따라서 모든 필드들은 private하게 작성해야 하고, 대신 게터와 세터를 통해 필드에 값을 세팅하고, 얻어올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;(2) RequiredArgsConstructor&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어노테이션은 초기화되지 않은 final필드, @NonNull이 붙은 필드에 대한 생성자를 생성합니다. 둘다 없을경우 @NoArgsConstructor와 같이 매개변수 없는 기본 생성자가 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;VO란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VO는 &lt;span style=&quot;color: #009a87;&quot;&gt;Value Object&lt;/span&gt;, &quot;값 객체&quot;라는 뜻입니다.&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;DTO가 데이터를 전송하기 위한 객체였다면 VO는 값 그자체를 가지고 있는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롬복에서는 &lt;span style=&quot;color: #009a87;&quot;&gt;@Value&lt;/span&gt; 어노테이션으로 VO객체를 빠르게 만들 수 있도록 제공합니다.&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;580&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0XcI/btrNQSlA9r7/fTq8k54NgwU1kxZZJ1N0RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0XcI/btrNQSlA9r7/fTq8k54NgwU1kxZZJ1N0RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0XcI/btrNQSlA9r7/fTq8k54NgwU1kxZZJ1N0RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0XcI%2FbtrNQSlA9r7%2FfTq8k54NgwU1kxZZJ1N0RK%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;743&quot; height=&quot;164&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;128&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;VO 역시 자바빈즈 클래스로 작성합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;VO의 구성요소&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;(1) Getter&lt;br /&gt;(2) AllArgsConstructor&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VO는 매핑된 이후에는 &lt;span style=&quot;color: #009a87;&quot;&gt;값을 변경하는 것을 허용하지 않습니다, 즉 불변입니다.&lt;/span&gt;&amp;nbsp;따라서 setter가 없고, getter만 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 초기화할 때에는 AllArgsConstructor, 즉 모든 필드를 매개변수로 가지는 생성자가 있고, 그 생성자를 통해서만 초기화 할 수 있습니다. 따라서 처음 VO가 생성될 때 값이 저장되고, 이후 데이터의 변경이 불가능합니다.&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;마이바티스와 같은 프레임워크들은 데이터베이스의 쿼리문 수행 결과를 VO로 지정하면, 자동으로 값을 매핑하여 전달해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;DTO와 VO, 언제 사용할까?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 DTO와 VO는 언제 사용할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 자료들을 찾아본 결과 DTO와 VO의 차이는 분명히 있지만, 그걸 엄격하게 구분해서 사용할 필요는 없다는 의견이 대부분입니다. 그러나 협업을 할 때에 DTO와 VO의 용도를 확실히 구분했다면, 그 용도에 따르는 것이 좋다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 협업을 했을 때에는 클라이언트 -&amp;gt; DB로 데이터를 전송할 때에는 DTO를 사용하고, DB에서 반환되는 데이터 타입은 VO를 사용하기로 약속했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 규약에 따르면 둘의 &lt;span style=&quot;color: #009a87;&quot;&gt;방향&lt;/span&gt;이 완전하게 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJRFpN/btrNPAS2w92/IC13XXkW0sMkf0cuXQvJf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJRFpN/btrNPAS2w92/IC13XXkW0sMkf0cuXQvJf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJRFpN/btrNPAS2w92/IC13XXkW0sMkf0cuXQvJf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJRFpN%2FbtrNPAS2w92%2FIC13XXkW0sMkf0cuXQvJf1%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;632&quot; height=&quot;239&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;377&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;+) VO를 사용할 때의 주의점&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;VO는 setter가 없고, 생성자를 통해서만 값을 초기화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 SQL 실행결과를 VO에 매핑할 때에는 반드시 SELECT한 컬럼의 순서와 갯수, 타입을 VO와 맞춰주어야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;DTO의 경우 setter로 값을 매핑하기 때문에 순서와 갯수가 일치하지 않아도 해당하는 필드명을 찾기 때문에 지키지 않아도 됩니다. 비어있는 필드는 null로 세팅됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그러나 생성자는 그렇지 못하기 때문에 만약 순서나 갯수가 다르다면 그대로 예외를 발생시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;DAO란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO는 &lt;span style=&quot;color: #009a87;&quot;&gt;Data Access Object&lt;/span&gt;, &quot;데이터 접근 객체&quot; 입니다.&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;웹 계층 중 Business Layer(Service Layer)와 데이터베이스를 연결하는 연결고리 역할을 합니다.&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;JDBC, MyBatis, JPA 등 persistence framework를 사용하여 데이터베이스와 연결을 할 때, 실제로 연결을 해주는 역할을 합니다. 각 프레임워크들의 동작방식은 약간씩 다르지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection Pool을 생성하고 -&amp;gt; SQL 쿼리문을 실행하고 -&amp;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;마이바티스를 예로들면, 마이바티스는 DAO 인터페이스를 만들고, 구현 클래스를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 클래스에는 마이바티스의 xml파일에 등록된 네임스페이스 + &quot;.&quot; + ID를 통해 작성해둔 SQL쿼리를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;span style=&quot;color: #009a87;&quot;&gt;* 마이바티스의 경우엔 3이 나오면서 DAO를 사용하지 않고, DAO의 역할을 자동으로 수행해주는 매퍼 인터페이스 방식을 사용합니다.&lt;/span&gt;)&lt;/p&gt;</description>
      <category>Spring</category>
      <category>java</category>
      <category>Spring</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/84</guid>
      <comments>https://shurimp.tistory.com/84#entry84comment</comments>
      <pubDate>Wed, 5 Oct 2022 01:31:43 +0900</pubDate>
    </item>
    <item>
      <title>Callback 함수란 무엇인가? 재귀함수와의 차이는?</title>
      <link>https://shurimp.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 프로그래밍을 하다보면 콜백 함수(Callback Function)이라는 용어를 많이 접하게 됩니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpN3CT/btrNCgHtNQu/zldvKNwWbJJSmiMl5z3ZK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpN3CT/btrNCgHtNQu/zldvKNwWbJJSmiMl5z3ZK1/img.png&quot; data-alt=&quot;네이버 로그인 개발가이드 中&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpN3CT/btrNCgHtNQu/zldvKNwWbJJSmiMl5z3ZK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpN3CT%2FbtrNCgHtNQu%2FzldvKNwWbJJSmiMl5z3ZK1%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;905&quot; height=&quot;282&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;282&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Callback 함수란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;1125&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE3ynK/btrNAMUaqqT/QLVy0NUcA9WkdVyIBPYEuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE3ynK/btrNAMUaqqT/QLVy0NUcA9WkdVyIBPYEuK/img.png&quot; data-alt=&quot;출처: 위키백과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE3ynK/btrNAMUaqqT/QLVy0NUcA9WkdVyIBPYEuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE3ynK%2FbtrNAMUaqqT%2FQLVy0NUcA9WkdVyIBPYEuK%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;702&quot; height=&quot;254&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;407&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;span style=&quot;color: #333333;&quot;&gt;자바스크립트는 어떤 함수를 실행할 때, 매개변수로 함수를 전달할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 매개변수로 전달되는 함수를 콜백 함수라고 합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3sRY/btrNwkdfbtV/AVwJf82drLyfqr8ETXJRfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3sRY/btrNwkdfbtV/AVwJf82drLyfqr8ETXJRfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3sRY/btrNwkdfbtV/AVwJf82drLyfqr8ETXJRfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3sRY%2FbtrNwkdfbtV%2FAVwJf82drLyfqr8ETXJRfK%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;471&quot; height=&quot;246&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;246&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 두 개 만들었는데, &lt;span style=&quot;color: #009a87;&quot;&gt;첫 번째 fn함수&lt;/span&gt;에서는 매개변수로 받은 함수를 호출하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;두 번째 iAmCallback 함수&lt;/span&gt;는 매개변수를 콘솔에 출력하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fn만 호출하되, 매개변수로 두 번째 함수를 전달하면 콘솔에는 다음과 같이 찍히게 됩니다.&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;402&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T1o3R/btrNzJ4stHX/8lUEc5lIZY3ARmkCWkjDL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T1o3R/btrNzJ4stHX/8lUEc5lIZY3ARmkCWkjDL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T1o3R/btrNzJ4stHX/8lUEc5lIZY3ARmkCWkjDL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT1o3R%2FbtrNzJ4stHX%2F8lUEc5lIZY3ARmkCWkjDL0%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;402&quot; height=&quot;178&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;178&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단지 그 매개변수가 함수, 즉 실행 가능한 코드일 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;재귀함수(Recursion)와의 차이?&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;span style=&quot;color: #009a87;&quot;&gt;자기 자신&lt;/span&gt;을 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 내부에서 자기 자신을 호출하는 코드를 만나면, 해당함수를 그 안에서 다시 실행하고, 또 그 안에서 다시 실행하게 됩니다. 따라서 재귀함수를 작성할 때에는 &lt;span style=&quot;color: #009a87;&quot;&gt;탈출 조건(break point)&lt;/span&gt;를 반드시 두어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한루프(while(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;span style=&quot;color: #009a87;&quot;&gt;StackOverflowError&lt;/span&gt;가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;460&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mX2TZ/btrNANZREID/TKjCCQdN6kHs9vV5tzN9hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mX2TZ/btrNANZREID/TKjCCQdN6kHs9vV5tzN9hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mX2TZ/btrNANZREID/TKjCCQdN6kHs9vV5tzN9hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmX2TZ%2FbtrNANZREID%2FTKjCCQdN6kHs9vV5tzN9hk%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;460&quot; height=&quot;182&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;182&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;/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;수행결과는 어떻게 될까요?&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;384&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yI1zw/btrNxanYZCx/YaZvJ5SPiEVlnEfSIycJ71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yI1zw/btrNxanYZCx/YaZvJ5SPiEVlnEfSIycJ71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yI1zw/btrNxanYZCx/YaZvJ5SPiEVlnEfSIycJ71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyI1zw%2FbtrNxanYZCx%2FYaZvJ5SPiEVlnEfSIycJ71%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;384&quot; height=&quot;109&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;109&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;1부터 5를 더한 15가 출력되게 됩니다.&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;첫번째 iAmRecursion(5)를 수행하게 되면, return문에서 자기자신을 호출하는 코드를 만나게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 그 함수는 다시 동일한 함수를 호출하게 되고, 똑같이 return문에서 만나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 반복하다가 num이 1이 되면 재귀를 만나기 전 return에 걸려 탈출하게 되고, 전체 수행결과를 반환하게 됩니다.&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;return문만 놓고 보면 순서는 다음과 같습니다.&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;889&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6ElqC/btrNvZtXXOQ/wBxz57zkkRR9dYW7Glod60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6ElqC/btrNvZtXXOQ/wBxz57zkkRR9dYW7Glod60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6ElqC/btrNvZtXXOQ/wBxz57zkkRR9dYW7Glod60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6ElqC%2FbtrNvZtXXOQ%2FwBxz57zkkRR9dYW7Glod60%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;804&quot; height=&quot;434&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;480&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;/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;615&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDlbPQ/btrNw9vOA1z/nF5ZfnIBOkj7f1v5W0IOS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDlbPQ/btrNw9vOA1z/nF5ZfnIBOkj7f1v5W0IOS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDlbPQ/btrNw9vOA1z/nF5ZfnIBOkj7f1v5W0IOS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDlbPQ%2FbtrNw9vOA1z%2FnF5ZfnIBOkj7f1v5W0IOS0%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;615&quot; height=&quot;196&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;196&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로그램 언어에서는 무한 호출을 막기 위해 맥시멈 호출량이 정해져있고, 이 호출량을 넘어서면 에러를 발생시킵니다.&lt;/p&gt;</description>
      <category>ETC</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/83</guid>
      <comments>https://shurimp.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 3 Oct 2022 02:11:52 +0900</pubDate>
    </item>
    <item>
      <title>[Wee.T] 아임포트(Iamport) 결제모듈을 연동하여 결제시스템 구현하기</title>
      <link>https://shurimp.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chai-iamport.gitbook.io/iamport/ready&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://chai-iamport.gitbook.io/iamport/ready&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664089214350&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;결제 연동 준비하기 - 결제연동 메뉴얼&quot; data-og-description=&quot;아임포트 회원가입 이후 관리자 페이지 내 결제연동 -&amp;gt; 테스트 연동 관리 탭에서 연동하고자 하는 PG사를 선택합니다.&quot; data-og-host=&quot;chai-iamport.gitbook.io&quot; data-og-source-url=&quot;https://chai-iamport.gitbook.io/iamport/ready&quot; data-og-url=&quot;https://chai-iamport.gitbook.io/iamport/ready&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://chai-iamport.gitbook.io/iamport/ready&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chai-iamport.gitbook.io/iamport/ready&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;결제 연동 준비하기 - 결제연동 메뉴얼&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;아임포트 회원가입 이후 관리자 페이지 내 결제연동 -&amp;gt; 테스트 연동 관리 탭에서 연동하고자 하는 PG사를 선택합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chai-iamport.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아임포트 결제 API를 사용하면 실제 결제가 가능한 서비스를 구현해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기설정 메뉴얼은 해당 가이드를 참고하여 회원가입과 PG사 설정, 인증키를 발급받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;결제서비스의 전체적인 흐름&lt;/b&gt;&lt;/span&gt;&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vJ5P3/btrNEwDiBr3/xrTZVeo6y8w6BO1kMPCUvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vJ5P3/btrNEwDiBr3/xrTZVeo6y8w6BO1kMPCUvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vJ5P3/btrNEwDiBr3/xrTZVeo6y8w6BO1kMPCUvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvJ5P3%2FbtrNEwDiBr3%2FxrTZVeo6y8w6BO1kMPCUvK%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;1020&quot; height=&quot;498&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 클라이언트에서 결제창을 통해 결제 요청을 보낸다. (아임포트 API 사용)&lt;br /&gt;2. 결제 성공시 ajax통신을 사용하여 결제를 검증하는 컨트롤러로 요청을 보낸다.&lt;br /&gt;3. 서버에서 검증처리를 하고, 검증 성공시 DB에 저장하는 서비스 메소드를 수행한다.&lt;br /&gt;4. 서비스 메소드에서는 (1) 결제정보 저장, (2) 쿠폰사용 업데이트, (3) 수강 테이블에 등록하는 로직을 수행한다.&lt;br /&gt;4-1. 이를 트랜잭션으로 처리 후 결과를 반환한다. (ex. &quot;SUCCESS&quot;)&lt;br /&gt;5. ajax에서 받은 응답에 따라 성공시 결제완료창으로 이동한다.&lt;br /&gt;6. 결제 완료창에서는 파라미터로 주문번호를 전달 받는다. (주문 테이블의 PK)&lt;br /&gt;7. 해당 PK로 결제정보 조회 후 model에 담아 화면에 뿌려준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 서비스를 구현할 때에는 특히 다음 두가지 고려사항을 염두에 두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;결제 서비스 구현 시 고려한 점&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1. 서버에서 한번 더 결제검증 절차를 거칠 것&lt;br /&gt;2. 결제완료 후 결제정보를 안전하게 DB에 저장할 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;1. 결제를 요청하고 결제정보를 검증하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p0oVP/btrMUxLnwW3/Qb0ZVoYCfgkKv6scLogt31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p0oVP/btrMUxLnwW3/Qb0ZVoYCfgkKv6scLogt31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p0oVP/btrMUxLnwW3/Qb0ZVoYCfgkKv6scLogt31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp0oVP%2FbtrMUxLnwW3%2FQb0ZVoYCfgkKv6scLogt31%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;734&quot; height=&quot;122&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;122&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/iamport/iamport-rest-client-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/iamport/iamport-rest-client-java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664089843460&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - iamport/iamport-rest-client-java: JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다&quot; data-og-description=&quot;JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다. Contribute to iamport/iamport-rest-client-java development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/iamport/iamport-rest-client-java&quot; data-og-url=&quot;https://github.com/iamport/iamport-rest-client-java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QlwPb/hyPTYxWcNf/mbMhOODCJkTjMwaNYjfBn0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/iamport/iamport-rest-client-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/iamport/iamport-rest-client-java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QlwPb/hyPTYxWcNf/mbMhOODCJkTjMwaNYjfBn0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - iamport/iamport-rest-client-java: JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다. Contribute to iamport/iamport-rest-client-java development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 사용자를 위한 아임포트 REST API연동 모듈을 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664089957837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;repositories&amp;gt;
    &amp;lt;repository&amp;gt;
        &amp;lt;id&amp;gt;jitpack.io&amp;lt;/id&amp;gt;
        &amp;lt;url&amp;gt;https://jitpack.io&amp;lt;/url&amp;gt;
    &amp;lt;/repository&amp;gt;
&amp;lt;/repositories&amp;gt;

&amp;lt;dependencies&amp;gt;
	&amp;lt;dependency&amp;gt;
	    &amp;lt;groupId&amp;gt;com.github.iamport&amp;lt;/groupId&amp;gt;
	    &amp;lt;artifactId&amp;gt;iamport-rest-client-java&amp;lt;/artifactId&amp;gt;
	    &amp;lt;version&amp;gt;0.2.22&amp;lt;/version&amp;gt;
	&amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom.xml에 repository와 dependency 설정이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PayController.java&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw41ee/btrMUfc9i4p/96DGxbiQdEphslXFklHw41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw41ee/btrMUfc9i4p/96DGxbiQdEphslXFklHw41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw41ee/btrMUfc9i4p/96DGxbiQdEphslXFklHw41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw41ee%2FbtrMUfc9i4p%2F96DGxbiQdEphslXFklHw41%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;996&quot; height=&quot;348&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에 IamportClient를 필드로 추가하고, 생성자에서 초기화 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수로는 관리자콘솔에 있는 API Key와 Secret을 추가합니다.&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;949&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mt6HS/btrMUNHdtzG/EX2oBmFVBndPZUmOQIN4Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mt6HS/btrMUNHdtzG/EX2oBmFVBndPZUmOQIN4Gk/img.png&quot; data-alt=&quot;(메소드의 리턴타입인 APIResponse는 응답정보를 저장하기 위해 직접 작성한 클래스입니다.)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mt6HS/btrMUNHdtzG/EX2oBmFVBndPZUmOQIN4Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmt6HS%2FbtrMUNHdtzG%2FEX2oBmFVBndPZUmOQIN4Gk%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;949&quot; height=&quot;377&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(메소드의 리턴타입인 APIResponse는 응답정보를 저장하기 위해 직접 작성한 클래스입니다.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664090299771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Payment payment = this.api.paymentByImpUid(imp_uid).getResponse(); // 검증처리&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;해당 메소드가 검증처리를 담당합니다. 클라이언트에서 처음 결제를 수행하게되면 아임포트 서버에서 고유의 imp_uid를 반환합니다. 이 uid를 Pathvariable로 가지는 uri에 POST요청을 보내고 검증 메소드의 매개변수로 전달하도록 작성하였습니다.&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;메소드의 리턴값은 Payment타입으로 반환되고, getter를 통해 구매자 이름, 결제금액, 결제 수단 등의 정보를 얻어낼 수 있습니다.&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;Payment객체는 status를 가집니다. 이 status는 문자열이며, &quot;&lt;span style=&quot;color: #009a87;&quot;&gt;paid&lt;/span&gt;&quot;와 &quot;&lt;span style=&quot;color: #009a87;&quot;&gt;failed&lt;/span&gt;&quot;로 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;paid일 시 결제 처리가 성공되었다는 뜻이고, failed이면 결제처리에 실패했다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch문을 사용하여 status가 paid일 시 결제정보를 저장하고, failed일 시 실패했다는 것을 클라이언트에게 알려주도록 하였습니다.&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;935&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r7Kdf/btrMXjd6HMv/cAoBK3BpiL1KNkq4L2i5w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r7Kdf/btrMXjd6HMv/cAoBK3BpiL1KNkq4L2i5w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r7Kdf/btrMXjd6HMv/cAoBK3BpiL1KNkq4L2i5w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr7Kdf%2FbtrMXjd6HMv%2FcAoBK3BpiL1KNkq4L2i5w0%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;935&quot; height=&quot;560&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;2. 결제 정보를 DB에 저장하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wee.T의 서비스는 결제 시 크게 3가지의 DB조작 로직이 필요합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 결제정보를 저장&lt;br /&gt;2. 쿠폰 사용 완료 처리&lt;br /&gt;3. 수강 테이블에 회원을 등록&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 로직들은 수행과정에서 어느 하나라도 오류가 나거나 부분처리돼서는 안됩니다. 따라서 &lt;span style=&quot;color: #009a87;&quot;&gt;트랜잭션 처리&lt;/span&gt;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sgp6z/btrMZDJWLrt/3psttaKaNw1XJ9o28TNOX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sgp6z/btrMZDJWLrt/3psttaKaNw1XJ9o28TNOX0/img.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;210&quot; data-is-animation=&quot;false&quot; width=&quot;210&quot; style=&quot;width: 51.1028%; margin-right: 10px;&quot; data-widthpercent=&quot;52.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sgp6z/btrMZDJWLrt/3psttaKaNw1XJ9o28TNOX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsgp6z%2FbtrMZDJWLrt%2F3psttaKaNw1XJ9o28TNOX0%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;470&quot; height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NPZX5/btrMWQ4b9XP/U3ZJ9zplY6Xgj1oQ3V81Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NPZX5/btrMWQ4b9XP/U3ZJ9zplY6Xgj1oQ3V81Sk/img.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;331&quot; data-is-animation=&quot;false&quot; style=&quot;width: 29.5244%; margin-right: 10px;&quot; data-widthpercent=&quot;30.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NPZX5/btrMWQ4b9XP/U3ZJ9zplY6Xgj1oQ3V81Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNPZX5%2FbtrMWQ4b9XP%2FU3ZJ9zplY6Xgj1oQ3V81Sk%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;428&quot; height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn3M8P/btrMUShmybx/r2NrEECv5ddL58vymJkLoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn3M8P/btrMUShmybx/r2NrEECv5ddL58vymJkLoK/img.png&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;588&quot; data-is-animation=&quot;false&quot; style=&quot;width: 17.0472%;&quot; data-widthpercent=&quot;17.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn3M8P/btrMUShmybx/r2NrEECv5ddL58vymJkLoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn3M8P%2FbtrMUShmybx%2Fr2NrEECv5ddL58vymJkLoK%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;439&quot; height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/div&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;간단한 insert문과 update문을 만들고 mapper 인터페이스에 등록해두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PayServiceImpl.java&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b07NA4/btrMYqKPoX0/UJRRqMKMCqFKHodWyzdvI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b07NA4/btrMYqKPoX0/UJRRqMKMCqFKHodWyzdvI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b07NA4/btrMYqKPoX0/UJRRqMKMCqFKHodWyzdvI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb07NA4%2FbtrMYqKPoX0%2FUJRRqMKMCqFKHodWyzdvI1%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;956&quot; height=&quot;881&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;881&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 data-ke-size=&quot;size16&quot;&gt;그리고 PaymentDTO 객체에 payment객체의 getter를 사용하여 저장할 정보들을 세팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 매퍼 메소드를 수행하고, 모두 수행시 &quot;SUCCESS&quot;를 반환하고, 문제가 있을 경우 FAIL에 번호를 붙여 리턴하도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;오류코드를 반환하는 이유?&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;@Transactional 어노테이션을 붙였기 때문에 자동으로 오류가 발생할 시 모두 롤백됩니다. 그러나 메소드 별로 체크해서 오류코드를 다르게 반환한 이유는, 시스템적으로는 잘 수행되었는데 insert가 0이거나 update가 0일 경우를 한번 더 체크하기 위해서입니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PayController.java&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2KYLA/btrMXHsptMt/wXYvHkQSWPz7PukP2NtlcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2KYLA/btrMXHsptMt/wXYvHkQSWPz7PukP2NtlcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2KYLA/btrMXHsptMt/wXYvHkQSWPz7PukP2NtlcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2KYLA%2FbtrMXHsptMt%2FwXYvHkQSWPz7PukP2NtlcK%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;981&quot; height=&quot;858&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;858&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;다시 컨트롤러로 돌아와서, 메소드 실행 결과를 문자열 result에 담아 응답객체에 추가하고, 주문번호와 함께 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;3. 클라이언트 처리&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;1058&quot; data-origin-height=&quot;925&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5GmmI/btrMU6T58oO/v4aOLhZs7oy1vNKck2a861/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5GmmI/btrMU6T58oO/v4aOLhZs7oy1vNKck2a861/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5GmmI/btrMU6T58oO/v4aOLhZs7oy1vNKck2a861/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5GmmI%2FbtrMU6T58oO%2Fv4aOLhZs7oy1vNKck2a861%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;1058&quot; height=&quot;925&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;925&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 rsp.success 부분에 결제검증 컨트롤러로 요청을 보내는 로직을 작성하면 됩니다.&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;&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;692&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/budP5n/btrMWejPklv/eyxKkIn2DT4a5GyPg01BsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/budP5n/btrMWejPklv/eyxKkIn2DT4a5GyPg01BsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/budP5n/btrMWejPklv/eyxKkIn2DT4a5GyPg01BsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbudP5n%2FbtrMWejPklv%2FeyxKkIn2DT4a5GyPg01BsK%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;553&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;553&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;컨트롤러에서 요구하는 파라미터를 수집한 후, ajax를 사용해 해당 uri로 POST요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성해둔 대로 result에는 문자열이 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SUCCESS시 결제완료 창으로 이동하면 됩니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEQ7xz/btrM2qXKf9W/olzGN28ntCl5lVHOTYbV8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEQ7xz/btrM2qXKf9W/olzGN28ntCl5lVHOTYbV8k/img.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;932&quot; data-is-animation=&quot;false&quot; width=&quot;571&quot; height=&quot;396&quot; style=&quot;width: 56.1337%; margin-right: 10px;&quot; data-widthpercent=&quot;56.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEQ7xz/btrM2qXKf9W/olzGN28ntCl5lVHOTYbV8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEQ7xz%2FbtrM2qXKf9W%2FolzGN28ntCl5lVHOTYbV8k%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;1344&quot; height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DYK3c/btrMXIEPmVA/6aakCXUhF00t5aHeCXipp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DYK3c/btrMXIEPmVA/6aakCXUhF00t5aHeCXipp1/img.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;845&quot; data-is-animation=&quot;false&quot; width=&quot;587&quot; height=&quot;535&quot; style=&quot;width: 42.7035%;&quot; data-widthpercent=&quot;43.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DYK3c/btrMXIEPmVA/6aakCXUhF00t5aHeCXipp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDYK3c%2FbtrMXIEPmVA%2F6aakCXUhF00t5aHeCXipp1%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;927&quot; height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/div&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z3w2O/btrMXH61UxS/1t8sCXfpO170aYqDbrhKQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z3w2O/btrMXH61UxS/1t8sCXfpO170aYqDbrhKQK/img.png&quot; data-alt=&quot;t_pay&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z3w2O/btrMXH61UxS/1t8sCXfpO170aYqDbrhKQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz3w2O%2FbtrMXH61UxS%2F1t8sCXfpO170aYqDbrhKQK%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;1099&quot; height=&quot;46&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;t_pay&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3chtx/btrMVGnhAEe/FEDm1hsNn3XLeqieX1iJ6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3chtx/btrMVGnhAEe/FEDm1hsNn3XLeqieX1iJ6K/img.png&quot; data-alt=&quot;t_class_listener&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3chtx/btrMVGnhAEe/FEDm1hsNn3XLeqieX1iJ6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3chtx%2FbtrMVGnhAEe%2FFEDm1hsNn3XLeqieX1iJ6K%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;508&quot; height=&quot;44&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;t_class_listener&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dh8rzF/btrMXILC4sy/76Xb6hpMv9v2qn2AY2aPx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dh8rzF/btrMXILC4sy/76Xb6hpMv9v2qn2AY2aPx1/img.png&quot; data-alt=&quot;t_coupon_usage&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dh8rzF/btrMXILC4sy/76Xb6hpMv9v2qn2AY2aPx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdh8rzF%2FbtrMXILC4sy%2F76Xb6hpMv9v2qn2AY2aPx1%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;547&quot; height=&quot;54&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;t_coupon_usage&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;DB에도 모두 정상적으로 추가가 된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;* 전체 소스코드 보기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/java/com/weet/app/pay&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/weeeeeeet/weet/tree/main/src/main/java/com/weet/app/pay&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664095487491&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - weeeeeeet/weet&quot; data-og-description=&quot;Contribute to weeeeeeet/weet development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/java/com/weet/app/pay&quot; data-og-url=&quot;https://github.com/weeeeeeet/weet&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bf1J2z/hyPVryxNPS/mSmkkg6lfBuxe1W9IW1GtK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/java/com/weet/app/pay&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/java/com/weet/app/pay&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bf1J2z/hyPVryxNPS/mSmkkg6lfBuxe1W9IW1GtK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - weeeeeeet/weet&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to weeeeeeet/weet development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DEVELOP/Wee.T</category>
      <category>java</category>
      <category>Spring</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/82</guid>
      <comments>https://shurimp.tistory.com/82#entry82comment</comments>
      <pubDate>Sun, 18 Sep 2022 01:47:28 +0900</pubDate>
    </item>
    <item>
      <title>[Wee.T] 스케줄러(@Scheduled)를 사용해 주기적으로 자동실행되는 로직 구현하기</title>
      <link>https://shurimp.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wee.T에서 PT를 개설하면, 종료일이라는 개념이 없고, 횟수차감제로 운영됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 실제 헬스 PT가 이루어지는 방식을 재현하고자 한 것인데, 정해진 횟수를 결제하고(ex. 30회) 헬스장에 갈 때마다 횟수가 차감되어 30회를 다 채우면 종료되는 방식입니다.&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;&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;798&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PBFF9/btrMjufDMEv/j0lvJQVMocGJUPqcg0pLH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PBFF9/btrMjufDMEv/j0lvJQVMocGJUPqcg0pLH1/img.png&quot; data-alt=&quot;PT에 관련된 ERD&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PBFF9/btrMjufDMEv/j0lvJQVMocGJUPqcg0pLH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPBFF9%2FbtrMjufDMEv%2Fj0lvJQVMocGJUPqcg0pLH1%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;798&quot; height=&quot;477&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PT에 관련된 ERD&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;PT 테이블(t_class)의 ERD 중 스케줄과 등록 관계를 나타내는 관계만 나타내었습니다. 형광펜으로 표시한 컬럼들이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t_class_listenr(PT 수강테이블) 테이블에 PT를 등록한 사람들의 진행현황을 나타내기 위한 progress 컬럼과, 해당 PT가 종료되었는지 여부를 나타내는 status 컬럼을 추가하였습니다.&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;t_class 테이블의 class_count 컬럼은 해당 PT가 몇회짜리 수업인지 나타내는 컬럼이고, 해당 클래스의 스케줄정보는 t_class_schedule테이블에 저장되어 있습니다. cs_day컬럼에 요일을 ('월', '화', '수', ..) 형식으로 저장하고 있습니다. 스케줄 정보는 1주일간 반복되는 시간표를 저장하고 있습니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 현재날짜와 유저가 등록한 pt의 스케줄 요일을 비교하여, 있으면 progress 컬럼을 1씩 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) progress가 PT의 지정된 횟수에 도달하면 status를 종료상태로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) (1), (2) 과정을 시간에 따라 자동으로 수행되도록 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;1. Mapper&lt;/span&gt;&lt;/b&gt;&lt;/h3&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개의 UPDATE문과 1개의 SELECT문을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;progress 업데이트, status 업데이트, 그리고 등록된 전체 클래스 ID를 가져오는 SQL 쿼리문 입니다. SELECT문은 비교적 간단하기 때문에 어노테이션으로 처리하였습니다.&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;mapper interface&lt;/p&gt;
&lt;pre id=&quot;code_1663311615113&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 클래스 ID 조회
@Select(&quot;SELECT class_id FROM t_class&quot;)
public abstract List&amp;lt;String&amp;gt; selectAllClassId() throws DAOException;

// 진행상황 업데이트
public abstract int updateProgress(String classId) throws DAOException;

// 종료여부 업데이트
public abstract int updateStatus(String classId) throws DAOException;&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;mapper.xml&lt;/p&gt;
&lt;pre id=&quot;code_1663311513522&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;update id=&quot;updateProgress&quot;&amp;gt;
    UPDATE t_class_listenr
    SET progress = progress + 1
    WHERE 
        class_id = #{classId}
        AND status = 0
        AND to_char(current_date - 1, 'dy') IN (
            SELECT
                cs_day
            FROM
                t_class_schedule
            WHERE
                class_id = #{classId}
        )
&amp;lt;/update&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;to_char(current_date - 1, 'dy')의 결과는 전날의 요일을 '월', '화' 이런 형식으로 반환합니다. cs_day컬럼이 해당 형식으로 요일이 저장되어있기 때문에, 클래스ID에 해당하는 요일들을 IN연산자로 비교하여, 전날의 요일이 스케줄 안에 있으면 progress를 1 증가시키도록 작성하였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663311861795&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;update id=&quot;updateStatus&quot;&amp;gt;
    UPDATE t_class_listenr
    SET status = 1
    WHERE 
        class_id = #{classId}
        AND status = 0
        AND progress &amp;gt;= (
            SELECT
                class_count
            FROM
                t_class
            WHERE
                class_id = #{classId}
        )
&amp;lt;/update&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료여부를 업데이트하는 쿼리입니다. progress가 지정된 class_count보다 같거나 커지는 순간 status를 1(종료됨)로 업데이트 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;2. Service&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Service interface&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1663311957255&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 진행상황 관리
public abstract void ptScheduler() throws ServiceException;&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;ServiceImpl&lt;/p&gt;
&lt;pre id=&quot;code_1663311978504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
@Scheduled(cron = &quot;0 0 0 * * *&quot;)
public void ptScheduler() throws ServiceException {
    log.trace(&quot;ptScheduler() invoked.&quot;);

    try { 
        List&amp;lt;String&amp;gt; classIds = this.mapper.selectAllClassId();
        log.info(&quot;\t 클래스 아이디 리스트: {}&quot;, classIds);

        int progress = 0;
        int status = 0;

        for(String classId : classIds) {
            progress += this.mapper.updateProgress(classId);
            status += this.mapper.updateStatus(classId);
        } // enhanced for

        log.trace(&quot;\t+ *** Scheduler - 업데이트 완료 ***&quot;);
        log.trace(&quot;\t+ *** 1. progress 업데이트 행 수 : {} ***&quot;, progress);
        log.trace(&quot;\t+ *** 2. status 업데이트 행 수 : {} ***&quot;, status);
    } catch(DAOException e) { throw new ServiceException(e); } // try-catch
} // ptScheduler&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;먼저 전체 클래스ID를 가져오는 쿼리를 실행한 결과를 String타입 List로 받아옵니다.&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 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;&lt;span style=&quot;color: #009a87;&quot;&gt;@Scheduled&lt;/span&gt;&lt;/b&gt; 어노테이션이 스케줄러 역할을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;cron&lt;/span&gt;&lt;/b&gt; 속성에 주기를 지정하면, 원하는 주기에 메소드를 실행하게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0 0 0 * * *은 매일 자정에 반복하겠다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cron은 6자리로 주기를 표현할 수 있으며, 앞에서부터 순서대로 초, 분, 시간, 일, 월, 요일 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;3. root-context.xml 설정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Scheduled 어노테이션을 사용하기 위해서는 root-context.xml에 설정을 해주어야 합니다.&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;773&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bej7Qp/btrMjdyLAV1/XKyrBb8gZjKsAbSiCyKRLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bej7Qp/btrMjdyLAV1/XKyrBb8gZjKsAbSiCyKRLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bej7Qp/btrMjdyLAV1/XKyrBb8gZjKsAbSiCyKRLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbej7Qp%2FbtrMjdyLAV1%2FXKyrBb8gZjKsAbSiCyKRLK%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;485&quot; height=&quot;553&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;881&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;[Namespaces] 탭에서 task를 활성화 시킨 후, 소스코드에 해당 코드를 추가해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(스케줄러를 사용한 서비스 패키지가 아직 scan되지 않았다면 context:component-scan으로 등록하는 코드도 작성해야합니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1663312481249&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;task:annotation-driven /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;구현 예시&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;임의로 cron설정을 10초마다 반복하도록 설정한 후, 동작이 잘 되는지 실행해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;글을 작성한 시간 기준으로 전날은 목요일이고, 목요일은 class2의 스케줄(월, 목 반복)에 등록되어있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;class2의 횟수는 20회로 설정되어 있어서, 수강한 유저의 진행상황을 18로 해놓고 서버를 구동하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bg2aE/btrMiDygIGm/ZKhOTSBx2QsHvJe7FzD7Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bg2aE/btrMiDygIGm/ZKhOTSBx2QsHvJe7FzD7Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bg2aE/btrMiDygIGm/ZKhOTSBx2QsHvJe7FzD7Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBg2aE%2FbtrMiDygIGm%2FZKhOTSBx2QsHvJe7FzD7Qk%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;460&quot; height=&quot;66&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;66&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;&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;510&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epssjo/btrMjX9PNQl/CFU1HjcHh4tpb4ILaU8Nl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epssjo/btrMjX9PNQl/CFU1HjcHh4tpb4ILaU8Nl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epssjo/btrMjX9PNQl/CFU1HjcHh4tpb4ILaU8Nl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fepssjo%2FbtrMjX9PNQl%2FCFU1HjcHh4tpb4ILaU8Nl0%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;510&quot; height=&quot;70&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQUWEV/btrMjlpQMA7/wkCcbKYklpbFGHTkrOTfc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQUWEV/btrMjlpQMA7/wkCcbKYklpbFGHTkrOTfc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQUWEV/btrMjlpQMA7/wkCcbKYklpbFGHTkrOTfc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQUWEV%2FbtrMjlpQMA7%2FwkCcbKYklpbFGHTkrOTfc1%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;65&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enMvbU/btrMjli3Yf6/kgRt1RDr7lq9l3JPXzHkb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enMvbU/btrMjli3Yf6/kgRt1RDr7lq9l3JPXzHkb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enMvbU/btrMjli3Yf6/kgRt1RDr7lq9l3JPXzHkb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenMvbU%2FbtrMjli3Yf6%2FkgRt1RDr7lq9l3JPXzHkb0%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;64&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 progress만 업데이트 되고, 20회가 되자 status가 종료상태로 변경되는 것을 확인할 수 있습니다.&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;468&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWb679/btrMjvsgqvN/70AFeVicCyQKqIOMgOkvOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWb679/btrMjvsgqvN/70AFeVicCyQKqIOMgOkvOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWb679/btrMjvsgqvN/70AFeVicCyQKqIOMgOkvOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWb679%2FbtrMjvsgqvN%2F70AFeVicCyQKqIOMgOkvOK%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;468&quot; height=&quot;65&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;65&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;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;사실 실제처럼 구현하려면 시간까지 체크하고, 각 회원의 출석도 모두 체크해야 하며 주기도 매번 바뀌기 때문에 Quartz같은 job scheduler 프레임워크를 사용해야 할 것 입니다. 그러나 그렇게 구현하려면 기존 프로젝트에서 변경해야할 부분이 너무 많고 이 기능 하나를 구현하기 위해 너무 많은 시간을 할애해야 할 것 같아 이정도 로직으로 구현해 보았습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>DEVELOP/Wee.T</category>
      <category>java</category>
      <category>scheduled</category>
      <category>Spring</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/81</guid>
      <comments>https://shurimp.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 16 Sep 2022 13:09:04 +0900</pubDate>
    </item>
    <item>
      <title>[Wee.T] 게시글 임시저장 기능 구현하기</title>
      <link>https://shurimp.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1723&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AjHPC/btrM1d5kgiF/pVHX8hhGleRNfMx5c6ds00/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AjHPC/btrM1d5kgiF/pVHX8hhGleRNfMx5c6ds00/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AjHPC/btrM1d5kgiF/pVHX8hhGleRNfMx5c6ds00/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/AjHPC/btrM1d5kgiF/pVHX8hhGleRNfMx5c6ds00/img.gif&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;818&quot; height=&quot;444&quot; data-origin-width=&quot;1723&quot; data-origin-height=&quot;936&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 개발한 커뮤니티 REST API를 사용하여 임시저장 기능을 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 쪽 코드는 완성되어있기 때문에 이번에는 클라이언트에서 ajax통신으로 데이터를 주고받는 스크립트 코드 위주로 정리하였습니다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;1. 임시저장의 작성 / 수정 / 삭제&lt;br /&gt;2. 임시저장 게시글을 불러오기&lt;br /&gt;3. 임시저장한 글을 불러와서 실제 게시글 작성을 하면 해당 게시글은 임시저장 리스트에서 사라지도록 하기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ds7Xj/btrMVuHbYvj/ROYkNA0Yx4gjbyed5i1aeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ds7Xj/btrMVuHbYvj/ROYkNA0Yx4gjbyed5i1aeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ds7Xj/btrMVuHbYvj/ROYkNA0Yx4gjbyed5i1aeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDs7Xj%2FbtrMVuHbYvj%2FROYkNA0Yx4gjbyed5i1aeK%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;1132&quot; height=&quot;272&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;272&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;DB의 게시글 테이블입니다. 게시글들은 하나의 테이블에서 관리하고, 임시저장 여부를 컬럼으로 구분하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블을 이렇게 설계해놓았기 때문에, 사실 임시저장의 CRUD로직은 게시글 CRUD로직과 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;comm_tempsave의 컬럼값만 다르게 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;임시저장 등록 / 삭제&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;1452&quot; data-origin-height=&quot;583&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6TtnO/btrMUFvPh7q/BxEQMHxEKGi2KT9ObKDKw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6TtnO/btrMUFvPh7q/BxEQMHxEKGi2KT9ObKDKw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6TtnO/btrMUFvPh7q/BxEQMHxEKGi2KT9ObKDKw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6TtnO%2FbtrMUFvPh7q%2FBxEQMHxEKGi2KT9ObKDKw1%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;1452&quot; height=&quot;583&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;583&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;게시글 작성 API는 다음과 같이 설계해 놓았으므로, 클라이언트 쪽에서 필요한 파라미터로 수집하여 요청을 보내면 됩니다.&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;753&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci0xD0/btrMYqKQ7PT/EYJKFzsScqPf4J2oP7fIE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci0xD0/btrMYqKQ7PT/EYJKFzsScqPf4J2oP7fIE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci0xD0/btrMYqKQ7PT/EYJKFzsScqPf4J2oP7fIE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci0xD0%2FbtrMYqKQ7PT%2FEYJKFzsScqPf4J2oP7fIE1%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;753&quot; height=&quot;631&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시글 등록 ajax 요청을 보내는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 게시글 등록과 임시저장의 차이점은 tmpsave 컬럼의 값이기 때문에 이 값만 매개변수로 넣어줄 수 있도록 작성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;success 부분에서는 tmpSave값에 따라 실행할 로직을 구분하여 작성했습니다. 임시저장일 경우 리스트를 다시 출력하고, 게시글 등록일 경우 등록확인 모달창을 띄웁니다.&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;638&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb8rSn/btrMWeqGhoi/9P8z71QjXyaC1Hk6MUksg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb8rSn/btrMWeqGhoi/9P8z71QjXyaC1Hk6MUksg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb8rSn/btrMWeqGhoi/9P8z71QjXyaC1Hk6MUksg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb8rSn%2FbtrMWeqGhoi%2F9P8z71QjXyaC1Hk6MUksg1%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;638&quot; height=&quot;271&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;271&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;삭제를 할 때에도, 게시글 삭제 API로 DELETE 요청을 보낸 후 리스트를 다시 보여주면 됩니다. 휴지통 모양 아이콘의 onclick 이벤트로 걸어주었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;임시저장 목록 조회&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;919&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FJwCB/btrMZEhPD2Z/ebzTMqP2SIyoqUBPXB0ieK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FJwCB/btrMZEhPD2Z/ebzTMqP2SIyoqUBPXB0ieK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FJwCB/btrMZEhPD2Z/ebzTMqP2SIyoqUBPXB0ieK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFJwCB%2FbtrMZEhPD2Z%2FebzTMqP2SIyoqUBPXB0ieK%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;919&quot; height=&quot;588&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;588&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 data-ke-size=&quot;size16&quot;&gt;해당함수를 [임시저장] 버튼의 onclick이벤트로 걸어두어 버튼을 클릭하면 offcanvas 창에 리스트를 뿌려주도록 작성했습니다. 이 getTmpList 함수는 임시저장을 등록 / 삭제했을 때에도 수행되도록 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;async: false옵션을 설정한 이유?&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;함수를 순차적으로 호출하도록 하기 위함입니다. 등록 / 삭제 시 알림창이 3초간 나타났다 사라지는 함수 호출 후 getTmpList를 호출하도록 작성했는데, async가 true일 경우 비동기 방식이기 때문에 이 순서가 보장되지 않습니다. 알림창 함수는 서버로 전송되지 않는 단순 DOM조작 함수이지만 사용자 경험 면에서 부자연스럽게 느껴질 수 있다고 생각되어 false옵션을 지정했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;임시저장 불러오기&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;이는 이전에 작성했던 게시글 상세조회 API를 사용하여 구현했습니다.&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;725&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/msKRT/btrMUFvQfQF/N38JdpPpbP5NIWZHU07ak1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/msKRT/btrMUFvQfQF/N38JdpPpbP5NIWZHU07ak1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/msKRT/btrMUFvQfQF/N38JdpPpbP5NIWZHU07ak1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmsKRT%2FbtrMUFvQfQF%2FN38JdpPpbP5NIWZHU07ak1%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;725&quot; height=&quot;356&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수로 글번호를 받으면, 그 글번호에 해당하는 상세 정보를 반환하는 API로 GET 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 받아온 데이터를 input value에 각각 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문영역의 경우 Summernote 텍스트 에디터를 사용했기 때문에, DB에는 HTML태그와 함께 저장이 됩니다.&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;&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;750&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3px0N/btrMYTzqYWd/05ooGlaN7t1vyZ0GMJQayk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3px0N/btrMYTzqYWd/05ooGlaN7t1vyZ0GMJQayk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3px0N/btrMYTzqYWd/05ooGlaN7t1vyZ0GMJQayk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3px0N%2FbtrMYTzqYWd%2F05ooGlaN7t1vyZ0GMJQayk%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;750&quot; height=&quot;129&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;129&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;이 함수를 임시저장 리스트의 제목부분에 onclick 이벤트로 걸어주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;임시저장 게시글을 불러와서 등록할 때의 처리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시저장을 하지 않고 완전히 새롭게 게시글을 등록할 때와, 임시저장한 게시글을 불러와서 게시글을 등록할 때의 로직은 다릅니다. 첫번째는 등록(CREATE)이고, 두번째는 수정(UPDATE)로 처리되어야 합니다.&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;
&lt;pre id=&quot;code_1664098068530&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let commId = document.querySelector('input[name=commId]').value;&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;이미 있는 게시글의 수정처리를 할 때나 임시저장리스트에서 getBoard함수를 호출하면, 이 commId의 값을 받아온 데이터의 글번호로 대입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tRY7V/btrMUxrgdIz/o3idAjbV9BIVhRBeyql3h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tRY7V/btrMUxrgdIz/o3idAjbV9BIVhRBeyql3h1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tRY7V/btrMUxrgdIz/o3idAjbV9BIVhRBeyql3h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtRY7V%2FbtrMUxrgdIz%2Fo3idAjbV9BIVhRBeyql3h1%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;748&quot; height=&quot;103&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;103&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;그 후 게시글 등록버튼의 click 이벤트에 if문으로 조건을 걸어두고 만약 commId가 존재할 경우 수정요청을 보내는 함수를 실행하고, 없을경우 새 게시글을 등록하는 함수를 호출합니다.&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;이렇게 하면 불러온 게시글을 등록할 때는 수정처리가 되고, tmpsave 값은 0이 되어 자동으로 임시저장 리스트에는 출력이 되지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;자동으로 사라지는 알림창 구현&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj2bcd/btrMVbVkBZe/IC3ZgxsdI8CPgItcJa8Xr1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj2bcd/btrMVbVkBZe/IC3ZgxsdI8CPgItcJa8Xr1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj2bcd/btrMVbVkBZe/IC3ZgxsdI8CPgItcJa8Xr1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bj2bcd/btrMVbVkBZe/IC3ZgxsdI8CPgItcJa8Xr1/img.gif&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;205&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;205&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7pDXj/btrMVdTbTY0/OKp3PTKuRjetytu7vRkp01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7pDXj/btrMVdTbTY0/OKp3PTKuRjetytu7vRkp01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7pDXj/btrMVdTbTY0/OKp3PTKuRjetytu7vRkp01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7pDXj%2FbtrMVdTbTY0%2FOKp3PTKuRjetytu7vRkp01%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;908&quot; height=&quot;218&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트스트랩의 alert를 사용하면 색깔별 알림메시지를 띄울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입과 메시지를 변경할 수 있도록 매개변수를 추가하고, div태그를 생성하여 알림을 띄울 영역에 추가합니다.&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;그리고 setTimeout 함수를 이용해 2초가 지나면 알림영역의 innerHTML을 비워서 알림이 사라지게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 전체 소스코드 (javascript)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/webapp/resources/js/board&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/weeeeeeet/weet/tree/main/src/main/webapp/resources/js/board&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664100654549&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - weeeeeeet/weet&quot; data-og-description=&quot;Contribute to weeeeeeet/weet development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/webapp/resources/js/board&quot; data-og-url=&quot;https://github.com/weeeeeeet/weet&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5aUgq/hyPT5qodbE/5yLpAWuvKqtTkS2obN7lTk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/webapp/resources/js/board&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/weeeeeeet/weet/tree/main/src/main/webapp/resources/js/board&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5aUgq/hyPT5qodbE/5yLpAWuvKqtTkS2obN7lTk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - weeeeeeet/weet&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to weeeeeeet/weet development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DEVELOP/Wee.T</category>
      <category>java</category>
      <category>javascript</category>
      <category>Spring</category>
      <author>shurimp</author>
      <guid isPermaLink="true">https://shurimp.tistory.com/77</guid>
      <comments>https://shurimp.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 16 Sep 2022 09:58:03 +0900</pubDate>
    </item>
  </channel>
</rss>