<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>에스알닷컴</title>
    <link>https://seokr.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 1 Jul 2026 06:11:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Seokr</managingEditor>
    <image>
      <title>에스알닷컴</title>
      <url>https://t1.daumcdn.net/cfile/tistory/224C075056FB20050F</url>
      <link>https://seokr.tistory.com</link>
    </image>
    <item>
      <title>[SpringBatch] 일단 질러보는 배치</title>
      <link>https://seokr.tistory.com/835</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[배치 만들어보기 샘플]&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링 배치 아키텍처&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;SpringBoot Batch를 사용할 때 &lt;b&gt;설정&lt;/b&gt;해야하는 부분과 &lt;b&gt;코드작성&lt;/b&gt;해야 하는 부분을 구별하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QOLkz/btqHXEnyOHF/N2BUOslXtAd2fMuR5ESXdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QOLkz/btqHXEnyOHF/N2BUOslXtAd2fMuR5ESXdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QOLkz/btqHXEnyOHF/N2BUOslXtAd2fMuR5ESXdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQOLkz%2FbtqHXEnyOHF%2FN2BUOslXtAd2fMuR5ESXdk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;배치 Job 개요 및 시나리오&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;일일 배치로 실행&lt;/li&gt;
&lt;li&gt;공통된 포맷의 CSV 파일로 도서관 관련 데이터가 유입&lt;br /&gt;(공공데이터포털 - &lt;a href=&quot;https://www.data.go.kr/data/15013109/standard.do&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;전국도서관표준데이터&lt;/a&gt;)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;해당 데이터를 시군구, 읍면동으로 정규화하여 도서관 정보를 저장&lt;br /&gt;(추가적으로 필요하다고 생각하는 의미있는 데이터 구분 기준이 있으면 추가 가능)&lt;/li&gt;
&lt;li&gt;정규화 및 분석된 도서관 데이터를 레포트화하여 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XXnu4/btqH2zZZ6tm/1zmkBt1CGShqMtWmQZG5k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XXnu4/btqH2zZZ6tm/1zmkBt1CGShqMtWmQZG5k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XXnu4/btqH2zZZ6tm/1zmkBt1CGShqMtWmQZG5k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXXnu4%2FbtqH2zZZ6tm%2F1zmkBt1CGShqMtWmQZG5k1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ERD 설계&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CSV 파일의 데이터를 가공되지 않은 채로 DB에 저장하여 1차적으로는 컬럼의 기준을 정함&lt;/li&gt;
&lt;li&gt;가공되지 않은 데이터를 시군구, 읍면동으로 분류화 작업&lt;/li&gt;
&lt;li&gt;시군구, 읍면동을 키 값으로 갖는 도서관 데이터를 저장&lt;/li&gt;
&lt;li&gt;도서관 데이터의 기본 값과 상세값을 구분하여 데이터 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bavepo/btqHSg2bORc/yLUMaecKB0jQ1kQc1KeHT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bavepo/btqHSg2bORc/yLUMaecKB0jQ1kQc1KeHT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bavepo/btqHSg2bORc/yLUMaecKB0jQ1kQc1KeHT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbavepo%2FbtqHSg2bORc%2FyLUMaecKB0jQ1kQc1KeHT0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 목표 배치 Job 실행 프로세스 설계
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;하나의 Job으로 설계&lt;/li&gt;
&lt;li&gt;해당 Job에는 3단계의 Step 으로 구분하여 실행 (임의)&lt;/li&gt;
&lt;li&gt;데이터를 읽고 쓰는 클래스는 개발환경에 따라 유연하게 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hh04y/btqHR6d6HHD/BIrjGOJVHY5wN8FDTSPfFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hh04y/btqHR6d6HHD/BIrjGOJVHY5wN8FDTSPfFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hh04y/btqHR6d6HHD/BIrjGOJVHY5wN8FDTSPfFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHh04y%2FbtqHR6d6HHD%2FBIrjGOJVHY5wN8FDTSPfFk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개선
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;정규화 작업에 대한 Step을 추가&lt;/li&gt;
&lt;li&gt;각 작업에 대한 로그를 확인하여 배치 작업으로 인하여 수행된 수치를 상세하게 파악 가능&lt;/li&gt;
&lt;li&gt;Report 작업 수행 시 Excel File로 생성 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;742&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Efr84/btqJV6Da5bJ/ubZgal84jkeemPZon3FmL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Efr84/btqJV6Da5bJ/ubZgal84jkeemPZon3FmL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Efr84/btqJV6Da5bJ/ubZgal84jkeemPZon3FmL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEfr84%2FbtqJV6Da5bJ%2FubZgal84jkeemPZon3FmL1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;742&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개선해야하는 사항
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;JpaItemWriter를 사용하는 것처럼 JpaReader를 생성하고 있지 않다는 점&lt;/li&gt;
&lt;li&gt;복잡한 쿼리를 사용하는 경우에 대해서 대비되어 있지 않다는 점
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;현재 배치의 속도 개선을 위한 선택지가 없음 &amp;gt; 학습이 더 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-org/springbatch_summary&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/SpringBoot Batch</category>
      <category>batch</category>
      <category>Job</category>
      <category>springboot</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/835</guid>
      <comments>https://seokr.tistory.com/835#entry835comment</comments>
      <pubDate>Fri, 4 Sep 2020 11:31:06 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBatch] 예제로 공부하기</title>
      <link>https://seokr.tistory.com/834</link>
      <description>&lt;p&gt;&lt;b&gt;[SpringBatch] 예제 구현하면서 공부하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r006n/btqGJF3A2uY/UX7a0klCh1SY21PkAML8i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r006n/btqGJF3A2uY/UX7a0klCh1SY21PkAML8i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r006n/btqGJF3A2uY/UX7a0klCh1SY21PkAML8i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr006n%2FbtqGJF3A2uY%2FUX7a0klCh1SY21PkAML8i0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring/SpringBoot</category>
      <category>CustomItemWriter</category>
      <category>ItemReader</category>
      <category>ItemWriter</category>
      <category>Job</category>
      <category>springbatch</category>
      <category>step</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/834</guid>
      <comments>https://seokr.tistory.com/834#entry834comment</comments>
      <pubDate>Tue, 18 Aug 2020 21:59:47 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] JWT 기반 인증 API Server</title>
      <link>https://seokr.tistory.com/833</link>
      <description>&lt;h2&gt;스프링부트 기반 인증 서버 API 만들기&lt;/h2&gt;
&lt;p&gt;구현 스펙&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SpringBoot
&lt;ul&gt;
&lt;li&gt;Interceptor&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Json Web Token(JWT)&lt;/li&gt;
&lt;li&gt;Server
&lt;ul&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB
&lt;ul&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB7EJb/btqGrjZ67K3/boIfCQe84XCvlgnaWMEVR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB7EJb/btqGrjZ67K3/boIfCQe84XCvlgnaWMEVR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB7EJb/btqGrjZ67K3/boIfCQe84XCvlgnaWMEVR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB7EJb%2FbtqGrjZ67K3%2FboIfCQe84XCvlgnaWMEVR0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lk2NW/btqGqnWx7Ru/uTLWnF8vKHNWZduCsAwu91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lk2NW/btqGqnWx7Ru/uTLWnF8vKHNWZduCsAwu91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lk2NW/btqGqnWx7Ru/uTLWnF8vKHNWZduCsAwu91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flk2NW%2FbtqGqnWx7Ru%2FuTLWnF8vKHNWZduCsAwu91%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9BUw5/btqGpxSjIMb/d9Piogx2kAy9V2uvMKW9ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9BUw5/btqGpxSjIMb/d9Piogx2kAy9V2uvMKW9ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9BUw5/btqGpxSjIMb/d9Piogx2kAy9V2uvMKW9ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9BUw5%2FbtqGpxSjIMb%2Fd9Piogx2kAy9V2uvMKW9ck%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/SeokRae/SpringBoot_redis&quot;&gt;코드정리&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1597496367598&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;SpringFramework-Sample/springboot_redis&quot; data-og-description=&quot;SpringBoot &amp;amp; Redis. Contribute to SpringFramework-Sample/springboot_redis development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/SeokRae/SpringBoot_redis&quot; data-og-url=&quot;https://github.com/SpringFramework-Sample/springboot_redis&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gHvqg/hyG9tTe156/OfsXnCx56eGU1D6ryNBt80/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://github.com/SeokRae/SpringBoot_redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/SeokRae/SpringBoot_redis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gHvqg/hyG9tTe156/OfsXnCx56eGU1D6ryNBt80/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;SpringFramework-Sample/springboot_redis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;SpringBoot &amp;amp; Redis. Contribute to SpringFramework-Sample/springboot_redis development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;추후
&lt;ul&gt;
&lt;li&gt;SpringSecurity
&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Role 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/SpringBoot</category>
      <category>JWT</category>
      <category>mysql</category>
      <category>redis</category>
      <category>springboot</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/833</guid>
      <comments>https://seokr.tistory.com/833#entry833comment</comments>
      <pubDate>Mon, 10 Aug 2020 18:13:51 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] 성능</title>
      <link>https://seokr.tistory.com/832</link>
      <description>&lt;h1&gt;[Redis]&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;얼마나 많은 요청을 한꺼번에 처리할 수 있는지&lt;/li&gt;
&lt;li&gt;요청 하나를 처리하는 데 걸리는 시간(Latency) 등을 측정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Redis 성능 확인하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;명령어를 사용하기 위한 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;root@97bd8fbf6f8d:/usr# &lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;명령어 결과&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;root@97bd8fbf6f8d:/usr# redis-benchmark
====== PING_INLINE ======
  100000 requests completed in 2.79 seconds        # 만 개의 명령을 처리하는 데 걸린 시간 2.79초
  50 parallel clients                            # 50개의 클라이언트 동시 연결
  3 bytes payload                                # 저장 데이터의 크기 3바이트
  keep alive: 1                                    # 클라이언트 연결 유지 상태 정보
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.00% &amp;lt;= 0.4 milliseconds
0.08% &amp;lt;= 0.5 milliseconds
2.13% &amp;lt;= 0.6 milliseconds
10.04% &amp;lt;= 0.7 milliseconds
25.00% &amp;lt;= 0.8 milliseconds
43.93% &amp;lt;= 0.9 milliseconds
63.18% &amp;lt;= 1.0 milliseconds
78.15% &amp;lt;= 1.1 milliseconds
87.81% &amp;lt;= 1.2 milliseconds
93.52% &amp;lt;= 1.3 milliseconds
96.47% &amp;lt;= 1.4 milliseconds
98.01% &amp;lt;= 1.5 milliseconds
98.73% &amp;lt;= 1.6 milliseconds
99.11% &amp;lt;= 1.7 milliseconds
99.33% &amp;lt;= 1.8 milliseconds
99.45% &amp;lt;= 1.9 milliseconds
99.54% &amp;lt;= 2 milliseconds
99.92% &amp;lt;= 3 milliseconds
99.98% &amp;lt;= 4 milliseconds
100.00% &amp;lt;= 4 milliseconds
35842.29 requests per second                    # 초당 처리된 명령 수

====== PING_BULK ======
  100000 requests completed in 2.71 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

54.86% &amp;lt;= 1 milliseconds
99.78% &amp;lt;= 2 milliseconds
99.98% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
36954.91 requests per second

====== SET ======
  100000 requests completed in 2.87 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

65.98% &amp;lt;= 1 milliseconds
99.96% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
34867.50 requests per second

====== GET ======
  100000 requests completed in 2.83 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

66.07% &amp;lt;= 1 milliseconds
99.98% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
35360.68 requests per second

====== INCR ======
  100000 requests completed in 2.82 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

66.94% &amp;lt;= 1 milliseconds
99.99% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
35486.16 requests per second

====== LPUSH ======
  100000 requests completed in 2.87 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

64.74% &amp;lt;= 1 milliseconds
99.95% &amp;lt;= 2 milliseconds
99.99% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
34806.82 requests per second

====== RPUSH ======
  100000 requests completed in 2.87 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

64.67% &amp;lt;= 1 milliseconds
99.87% &amp;lt;= 2 milliseconds
99.99% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
34867.50 requests per second

====== LPOP ======
  100000 requests completed in 2.89 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

65.48% &amp;lt;= 1 milliseconds
99.92% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
34602.07 requests per second

====== RPOP ======
  100000 requests completed in 2.91 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

64.82% &amp;lt;= 1 milliseconds
99.96% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
34399.72 requests per second

====== SADD ======
  100000 requests completed in 2.94 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.00% &amp;lt;= -33 milliseconds
0.03% &amp;lt;= -32 milliseconds
0.04% &amp;lt;= 0 milliseconds
58.66% &amp;lt;= 1 milliseconds
99.55% &amp;lt;= 2 milliseconds
99.98% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
34013.61 requests per second

====== HSET ======
  100000 requests completed in 2.96 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

61.09% &amp;lt;= 1 milliseconds
99.89% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
33726.81 requests per second

====== SPOP ======
  100000 requests completed in 2.74 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

64.93% &amp;lt;= 1 milliseconds
99.94% &amp;lt;= 2 milliseconds
99.99% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
36496.35 requests per second

====== LPUSH (needed to benchmark LRANGE) ======
  100000 requests completed in 2.88 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

63.70% &amp;lt;= 1 milliseconds
99.95% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
34698.12 requests per second

====== LRANGE_100 (first 100 elements) ======
  100000 requests completed in 3.45 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

47.14% &amp;lt;= 1 milliseconds
99.90% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
28993.91 requests per second

====== LRANGE_300 (first 300 elements) ======
  100000 requests completed in 5.92 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.01% &amp;lt;= 1 milliseconds
97.25% &amp;lt;= 2 milliseconds
99.96% &amp;lt;= 3 milliseconds
100.00% &amp;lt;= 3 milliseconds
16889.04 requests per second

====== LRANGE_500 (first 450 elements) ======
  100000 requests completed in 7.05 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.00% &amp;lt;= 1 milliseconds
84.77% &amp;lt;= 2 milliseconds
99.68% &amp;lt;= 3 milliseconds
99.96% &amp;lt;= 4 milliseconds
100.00% &amp;lt;= 4 milliseconds
14176.35 requests per second

====== LRANGE_600 (first 600 elements) ======
  100000 requests completed in 8.47 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.00% &amp;lt;= -32 milliseconds
0.00% &amp;lt;= -31 milliseconds
0.02% &amp;lt;= -30 milliseconds
0.03% &amp;lt;= 0 milliseconds
0.04% &amp;lt;= 1 milliseconds
20.78% &amp;lt;= 2 milliseconds
97.89% &amp;lt;= 3 milliseconds
99.84% &amp;lt;= 4 milliseconds
99.94% &amp;lt;= 5 milliseconds
99.99% &amp;lt;= 6 milliseconds
100.00% &amp;lt;= 6 milliseconds
11800.80 requests per second

====== MSET (10 keys) ======
  100000 requests completed in 3.03 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

49.40% &amp;lt;= 1 milliseconds
99.90% &amp;lt;= 2 milliseconds
100.00% &amp;lt;= 2 milliseconds
32959.79 requests per second
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;성능 측정 세부 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;50 바이트와 1,024바이트의 데이터를 문자열, 리스트, set 데이터형으로 저장하고 읽어내는 데 걸리는 시간 측정 결과를 진행률을 제외하고 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;root@97bd8fbf6f8d:/usr# redis-benchmark -d 50 -q -t get,set,lpush,lpop,sadd,spop
SET: 34566.20 requests per second
GET: 35511.36 requests per second
LPUSH: 34698.12 requests per second
LPOP: 34518.46 requests per second
SADD: 33978.93 requests per second
SPOP: 34770.52 requests per second

root@97bd8fbf6f8d:/usr# redis-benchmark -d 1024 -q -t get,set,lpush,lpop,sadd,spop
SET: 34129.69 requests per second
GET: 34734.29 requests per second
LPUSH: 33715.44 requests per second
LPOP: 33222.59 requests per second
SADD: 34806.82 requests per second
SPOP: 34566.20 requests per second&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;데이터의 크기가 20배 정도 늘었으나 읽기와 쓰기 횟수는 10% 정도 차이 밖에 나지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;백만 개의 키를 문자열 데이터로 추가하는 테스트 명령&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;root@97bd8fbf6f8d:/usr# redis-benchmark -t set -n 1000000 -r 1000000
====== SET ======
  1000000 requests completed in 29.68 seconds        # 백만 개의 데이터를 입력하는 데 약 29초 소요
  50 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration &quot;save&quot;: 3600 1 300 100 60 10000
  host configuration &quot;appendonly&quot;: no
  multi-thread: no

0.00% &amp;lt;= -32.8 milliseconds
0.00% &amp;lt;= -32.7 milliseconds
0.00% &amp;lt;= -32.6 milliseconds
0.00% &amp;lt;= -32.5 milliseconds
0.00% &amp;lt;= -32.4 milliseconds
0.00% &amp;lt;= -32.3 milliseconds
0.00% &amp;lt;= -32.2 milliseconds
0.00% &amp;lt;= -32.0 milliseconds
0.00% &amp;lt;= 0.4 milliseconds
0.01% &amp;lt;= 0.5 milliseconds
0.52% &amp;lt;= 0.6 milliseconds
4.87% &amp;lt;= 0.7 milliseconds
18.67% &amp;lt;= 0.8 milliseconds
39.65% &amp;lt;= 0.9 milliseconds
60.82% &amp;lt;= 1.0 milliseconds
77.10% &amp;lt;= 1.1 milliseconds
87.37% &amp;lt;= 1.2 milliseconds
93.07% &amp;lt;= 1.3 milliseconds
96.13% &amp;lt;= 1.4 milliseconds
97.75% &amp;lt;= 1.5 milliseconds
98.59% &amp;lt;= 1.6 milliseconds
99.03% &amp;lt;= 1.7 milliseconds
99.28% &amp;lt;= 1.8 milliseconds
99.43% &amp;lt;= 1.9 milliseconds
99.51% &amp;lt;= 2 milliseconds
99.87% &amp;lt;= 3 milliseconds
99.99% &amp;lt;= 4 milliseconds
100.00% &amp;lt;= 5 milliseconds
100.00% &amp;lt;= 5 milliseconds
33697.26 requests per second                    # 초당 3만 3천개 이상의 데이터를 저장&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 레디스 성능 측정 도구 옵션&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%; text-align: center;&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%; text-align: center;&quot;&gt;&lt;b&gt;기본값&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-h &amp;lt;hostname&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트를 수행하기 위해 접속할 레디스 서버의 호스트명&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;127.0.0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-p &amp;lt;port&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트를 수행하기 위해 접속할 레디스 서버의 포트&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;6379&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-s &amp;lt;socket&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트를 수행하기 위해 접속할 레디스 서버의 유닉스 서버 소켓&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-c &amp;lt;clients&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트를 위한 가상 클라이언트의 동시 접속 수&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-n &amp;lt;requests&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;각 명령의 테스트 횟수&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-d &amp;lt;size&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트에 사용할 데이터 크기&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;3*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-k &amp;lt;boolean&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트를 위한 가상 클라이언트의 접속 유지 여부&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;1: 접속유지, &lt;br /&gt;0: 접속 유지하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-r &amp;lt;keyspacelen&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트에 사용할 랜덤 키의 범위&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-P &amp;lt;numreq&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;파이프라인 명령을 사용한 테스트와 파이프라인당 요청할 명령의 개수&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;0: 파이프라인 미사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-q&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트 진행 상황을 출력하지 않고 결과만 출력하기&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;--csv&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;테스트 결과를 csv 포맷으로 출력하기&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-l&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;브레이크&lt;span style=&quot;color: #333333;&quot;&gt;(ctrl + c)&lt;/span&gt;를 걸기 전까지 계속 수행&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-t &amp;lt;tests&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;쉼표로 구분된 테스트 명령의 목록&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.0077%;&quot;&gt;-l&lt;/td&gt;
&lt;td style=&quot;width: 62.7519%;&quot;&gt;명령 전송 없는 연결 생성 후 브레이크 입력(ctrl + c) 때까지 대기&lt;/td&gt;
&lt;td style=&quot;width: 21.2403%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/Redis</category>
      <category>redis</category>
      <category>Redis 성능</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/832</guid>
      <comments>https://seokr.tistory.com/832#entry832comment</comments>
      <pubDate>Sun, 2 Aug 2020 00:12:24 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] CRUD 전략</title>
      <link>https://seokr.tistory.com/831</link>
      <description>&lt;h2&gt;JPA 영속성 컨텍스트의 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SpringBoot를 하면서 JPA의 개념적으로 모르고 사용했었기에 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;영속성 컨텍스트와 식별자 값&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분한다.&lt;/li&gt;
&lt;li&gt;영속 상태는 식별자 값이 반드시 있어야 한다.&lt;/li&gt;
&lt;li&gt;식별자 값이 없으면 예외가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;영속성 컨텍스트와 데이터베이스 저장&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영(&lt;strong&gt;동기화&lt;/strong&gt;)한다.(플러시: Flush)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;영속성 컨텍스트가 엔티티를 관리할 때 장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1차 캐시&lt;/li&gt;
&lt;li&gt;동일성 보장&lt;/li&gt;
&lt;li&gt;트랜잭션을 지원하는 쓰기 지연&lt;/li&gt;
&lt;li&gt;변경 감지(Dirty Checking)&lt;/li&gt;
&lt;li&gt;지연 로딩(Lazy Loading)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;엔티티 조회&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3I0ln/btqFHbN3Lbn/UA0znmMid3cHQJwylrsoZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3I0ln/btqFHbN3Lbn/UA0znmMid3cHQJwylrsoZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3I0ln/btqFHbN3Lbn/UA0znmMid3cHQJwylrsoZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3I0ln%2FbtqFHbN3Lbn%2FUA0znmMid3cHQJwylrsoZK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;엔티티 등록&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf88tN/btqFCoIBGMJ/rVYoWlri6JZ7ezW3v2Pp70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf88tN/btqFCoIBGMJ/rVYoWlri6JZ7ezW3v2Pp70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf88tN/btqFCoIBGMJ/rVYoWlri6JZ7ezW3v2Pp70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf88tN%2FbtqFCoIBGMJ%2FrVYoWlri6JZ7ezW3v2Pp70%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;엔티티 수정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnBbuN/btqFDwlNQu7/eVmfJVJ6W9Sr7qDjvxR7Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnBbuN/btqFDwlNQu7/eVmfJVJ6W9Sr7qDjvxR7Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnBbuN/btqFDwlNQu7/eVmfJVJ6W9Sr7qDjvxR7Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnBbuN%2FbtqFDwlNQu7%2FeVmfJVJ6W9Sr7qDjvxR7Yk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;엔티티 삭제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmUotf/btqFElRGQFi/L4ltyNRcxAKsQot1b60UYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmUotf/btqFElRGQFi/L4ltyNRcxAKsQot1b60UYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmUotf/btqFElRGQFi/L4ltyNRcxAKsQot1b60UYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmUotf%2FbtqFElRGQFi%2FL4ltyNRcxAKsQot1b60UYk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Basic/JPA</category>
      <category>JPA</category>
      <category>엔티티</category>
      <category>자바 ORM 표준 JPA 프로그래밍</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/831</guid>
      <comments>https://seokr.tistory.com/831#entry831comment</comments>
      <pubDate>Tue, 14 Jul 2020 20:10:24 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] Builder</title>
      <link>https://seokr.tistory.com/830</link>
      <description>&lt;h3&gt;SpringBoot 공부 중에 @Builder 패턴에 대한 이해가 필요하여 @Builder 어노테이션설정으로 인하여 어떻게 동작 하는지 알아보기 위하여 정리&lt;/h3&gt;
&lt;h2&gt;1. Java Builder Pattern&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://projectlombok.org/features/Builder&quot;&gt;@Builder&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Response {

    private Long id;
    private String name;

    public Response(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public static ResponseBuilder builder() {
        return new ResponseBuilder();
    }

    public static class ResponseBuilder {
        private Long id;
        private String name;

        ResponseBuilder() {}

        public ResponseBuilder id(Long id) {
            this.id = id;
            return this;
        }

        public ResponseBuilder name(String name) {
            this.name = name;
            return this;
        }

        public Response build() {
            return new Response(id, name);
        }

        @Override
        public String toString() {
            return &amp;quot;ResponseBuilder{&amp;quot; + &amp;quot;id=&amp;quot; + id + &amp;quot;, name=&amp;#39;&amp;quot; + name + &amp;#39;\&amp;#39;&amp;#39; +  &amp;#39;}&amp;#39;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. @Builder&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;빌더패턴을 이용했을 때 가장 큰 장점은 멤버변수의 optional과 required를 컴파일하는 시점에서 체크할 수 있다는 점&lt;/li&gt;
&lt;li&gt;특정 멤버변수에 접근하기 위한 메서드가 정해져 있기 때문에 실수로 다른 멤버변수에 다른 값을 집어넣는 실수를 막을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Builder
public class Response {

    private Long id;
    private String name;

    public Response(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Builder를 활용하기&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import static java.lang.Thread.sleep;

public class ThreadBuilderTest {

    public static void main(String[] args) {

        ThreadOuter th = new ThreadOuter();
        th.start();
        ThreadOuter01 th01 = new ThreadOuter01();
        th01.start();

        for(int i = 11 ; i &amp;lt; 20 ; i++) {
            Response res = Response.builder()
                    .id(Long.valueOf(i))
                    .name(&amp;quot;thread&amp;quot; + i)
                    .build();
            System.out.println(res.toString() + &amp;quot; :: &amp;quot; + Thread.currentThread().getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadOuter extends Thread {

    @Override
    public void run() {
        for(int i = 0 ; i &amp;lt; 10 ; i++) {
            Response res = Response.builder()
                    .id((long) i)
                    .name(&amp;quot;thread01&amp;quot; + i)
                    .build();
            System.out.println(res.toString() + &amp;quot; :: &amp;quot; + getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class ThreadOuter01 extends Thread {

    @Override
    public void run() {
        for(int i = 21 ; i &amp;lt; 30 ; i++) {
            Response res = Response.builder()
                    .id((long) i)
                    .name(&amp;quot;thread02&amp;quot; + i)
                    .build();
            System.out.println(res.toString() + &amp;quot; :: &amp;quot; + getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Basic/DesignPattern</category>
      <category>Builder</category>
      <category>MultiThread</category>
      <category>Thread</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/830</guid>
      <comments>https://seokr.tistory.com/830#entry830comment</comments>
      <pubDate>Sun, 5 Jul 2020 15:11:15 +0900</pubDate>
    </item>
    <item>
      <title>[SpringSecurity] Authentication Process(인증 프로세스)</title>
      <link>https://seokr.tistory.com/829</link>
      <description>&lt;h2&gt;SpringSecurity Architecture&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;스프링 보안 인증 프로세스에 관련된 클래스 및 필터 목록 을 시연하기 위한 다이어그램&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X1X51/btqFoB7LoTZ/tmb4GTkHPgGCvQDewUamMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X1X51/btqFoB7LoTZ/tmb4GTkHPgGCvQDewUamMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X1X51/btqFoB7LoTZ/tmb4GTkHPgGCvQDewUamMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX1X51%2FbtqFoB7LoTZ%2Ftmb4GTkHPgGCvQDewUamMK%2Fimg.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;1. HttpRequest 수신&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스프링 시큐리티는 일련의 필터를 갖고 있다.&lt;/li&gt;
&lt;li&gt;따라서 요청이 오면 인증과 인가를 위한 필터 체인을 거치게 된다.&lt;/li&gt;
&lt;li&gt;사용자 인증 요청이 있는 경우, 인증 메커니즘/모델에 기반한 관련 인증 필터를 찾을 때까지 필터 체인을 거치게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP 기본 인증 요청은 BasicAuthenticationFilter에 도달할 때까지 필터체인을 거친다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;ex)&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP &lt;a href=&quot;http://iloveulhj.github.io/posts/http/http-digest-auth.html&quot;&gt;다이제스트 인증 요청&lt;/a&gt;이 &lt;b&gt;DigestAuthenticationFilter&lt;/b&gt;에 도달할 때까지 필터 체인을 통과한다.&lt;/li&gt;
&lt;li&gt;로그인 양식 제출 요청(로그인 양식 인증 요청)이 &lt;b&gt;UsernamePasswordAuthenticationFilter&lt;/b&gt;에 도달할 때까지 필터체인을 거친다.&lt;/li&gt;
&lt;li&gt;X509 인증 요청이 &lt;b&gt;X509AuthenticationFilter&lt;/b&gt;에 도달할 때까지 필터 체인을 통과한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 사용자 자격증명 기반 AuthenticationToken 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;관련 AuthenticationFilter에서 인증 요청을 받으면 수신된 요청에서 사용자 이름과 암호를 추출한다. (인증 메커니즘의 대부분은 사용자 이름과 암호를 요구한다.)&lt;/li&gt;
&lt;li&gt;그 후 추출된 사용자 자격 증명 기반으로 인증 객체를 생성한다.&lt;/li&gt;
&lt;li&gt;추출된 자격증명이 사용자 이름과 암호인 경우 &lt;b&gt;UsernamePasswordAuthenticationToken&lt;/b&gt;은 사용자 이름과 비밀번호를 추출/발견하여 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. AuthenticationManager에게 위임하기 위한 AuthenticationToken생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;UsernamePasswordAuthenticationToken&lt;/b&gt;객체를 생성한 후에 &lt;b&gt;AuthenticationManager&lt;/b&gt;의 &lt;b&gt;authenticate&lt;/b&gt; 메서드를 호출하기 위해 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/4.2.15.RELEASE/apidocs/org/springframework/security/authentication/AuthenticationManager.html&quot;&gt;&lt;b&gt;AuthenticationManager&lt;/b&gt;&lt;/a&gt;는 단지 인터페이스이며 실제 구현체는 &lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/4.2.15.RELEASE/apidocs/org/springframework/security/authentication/ProviderManager.html&quot;&gt;&lt;b&gt;ProviderManager&lt;/b&gt;&lt;/a&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ProviderManager&lt;/b&gt;는 사용자 요청을 인증하는데 사용하기 위해 &lt;b&gt;AuthenticationProvider&lt;/b&gt;를 설정하기 위한 목록을 갖고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ProviderManager&lt;/b&gt;는 제공된 각 &lt;b&gt;AuthenticationProvider&lt;/b&gt;를 검토하고 전달된 &lt;b&gt;Authentication&lt;/b&gt; 객체를 기반으로 사용자 인증을 시도한다. (&lt;b&gt;UsernamePasswordAuthenticationToken&lt;/b&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. AuthenticationProvider 목록으로 인증 시도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;AuthenticationProvider&lt;/b&gt;는 제공된 인증 객체로 사용자 인증을 시도한다.&lt;/li&gt;
&lt;li&gt;프레임워크와 함께 제공되는 기존 Authentication Provider 중 일부
&lt;ol&gt;
&lt;li&gt;CasAuthenticationProvider&lt;/li&gt;
&lt;li&gt;JaasAuthenticationProvider&lt;/li&gt;
&lt;li&gt;DaoAuthenticationProvider&lt;/li&gt;
&lt;li&gt;OpenIDAuthenticationProvider&lt;/li&gt;
&lt;li&gt;RememberMeAuthenticationProvider&lt;/li&gt;
&lt;li&gt;LdapAuthenticationProvider&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. UserDetailsService가 필요한가 ?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;일부 &lt;b&gt;AuthenticationProvider&lt;/b&gt;는 사용자 이름 기반으로 사용자 세부 정보를 검색하기 위하여 &lt;b&gt;UserDetailsService&lt;/b&gt;를 사용할 수 있다. (&lt;b&gt;DaoAuthenticationProvider&lt;/b&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6 ~ 7. UserDetails와 User&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;UserDetailsService&lt;/b&gt;는 username 기반으로 &lt;b&gt;UserDetail&lt;/b&gt; (실제 구현은 &lt;b&gt;User&lt;/b&gt;)를 검색한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8. Authentication 과 AuthenticationException&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 인증에 성공하면 완전히 채워진 &lt;b&gt;Authentication&lt;/b&gt; 객체가 반환된다.&lt;/li&gt;
&lt;li&gt;그렇지 않은 경우 &lt;b&gt;AuthenticationException&lt;/b&gt;가 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AuthenticationProvider&lt;/b&gt; 인터페이스에 따르면, 성공적으로 인증하는 경우 &lt;b&gt;AuthenticationProvider&lt;/b&gt;는 완전히 채워진 인증 객체를 정확히 반환하거나, 예외 발생 시 &lt;b&gt;AuthenticationProvider&lt;/b&gt; 예외를 던진다.&lt;/li&gt;
&lt;li&gt;Fully populated Authentication Object란
&lt;ul&gt;
&lt;li&gt;authenticated - true&lt;/li&gt;
&lt;li&gt;grant authorities list&lt;/li&gt;
&lt;li&gt;user credentials (username only)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AuthenticationException&lt;/b&gt;가 예외 발생하는 경우, 인증 메커니즘을 지원하는 &lt;b&gt;AuthenticationEntryPoint&lt;/b&gt;에 의해 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9. Authentication 완료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;AuthenticationManager&lt;/b&gt;는 획득한 Fully populated Authentication객체를 관련 AuthenticationFilter로 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10. SecurityContext에서 Authentication 객체를 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;관련된 &lt;b&gt;AuthenticationFilter&lt;/b&gt;가 획득한 &lt;b&gt;Authentication&lt;/b&gt; 객체를 향후 필터 사용을 위하여 &lt;b&gt;SecurityContext&lt;/b&gt;에 저장한다. (Authorization Filters를 위해 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1593874918517&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityContextHolder.getContext().setAuthentication(authentication);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring/SpringBoot</category>
      <category>SpringSecurity</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/829</guid>
      <comments>https://seokr.tistory.com/829#entry829comment</comments>
      <pubDate>Sat, 4 Jul 2020 23:57:34 +0900</pubDate>
    </item>
    <item>
      <title>[JWT] JWT(Json Web Token)</title>
      <link>https://seokr.tistory.com/828</link>
      <description>&lt;h2&gt;1. JWT(JSON Web Token) 이란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;JSON Web Token&lt;/b&gt;의 약자로 전자서명 된 URL-safe (URL로 이용할 수 있는 문자로만 구성된)의 JSON&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전자서명(Signature)은 JSON의 변조를 체크&lt;/b&gt;할 수 있게 되어 있다.&lt;/li&gt;
&lt;li&gt;속성 정보(Claims)를 JSON 데이터 구조로 표현한 토큰으로 &lt;b&gt;RFC 7519 표준&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서버와 클라이언트 간 정보를 주고 받을 때 HttpRequest header에 JSON 토큰을 넣은 후 &lt;b&gt;서버&lt;/b&gt;는 별도의 인증 과정없이 헤더에 포함되어 있는 &lt;b&gt;JWT 정보를 통해 인증&lt;/b&gt;한다.&lt;br /&gt;이때 사용되는 JSON 데이터는 URL-safe 하도록 URL에 포함할 수 있는 문자만으로 만든다.&lt;/li&gt;
&lt;li&gt;HMAC 알고리즘을 사용하여 비밀키 또는 RSA를 이용한 Public Key / Private Key 쌍으로 서명할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Base64 인코딩의 경우 &quot;+&quot;, &quot;/&quot;, &quot;=&quot;이 포함되지만 JWT는 URI에서 파라미터로 사용할 수 있도록 URL-Safe 한 Base64url 인코딩을 사용한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. JWT 구조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Claims를 userId, username으로 하고, JWA 알고리즘 SHA-256으로 암호화&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;jwt:
  secret: 12345678901234567890123456789000&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public String createToken(Long userId, String name) {
        return Jwts.builder()
                .setHeaderParam(&quot;typ&quot;, Header.JWT_TYPE)
                .signWith(key, SignatureAlgorithm.HS256)
                .claim(&quot;userId&quot;, userId)
                .claim(&quot;userName&quot;, name)
                .setExpiration(new Date(System.currentTimeMillis() + 864000000))
                .compact();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 작업을 통해 생성된 토큰 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
// Payload
eyJ1c2VySWQiOjEsInVzZXJOYW1lIjoic2VvayIsImV4cCI6MTU5MzUyMTU0MX0.
// signature
AWsWjhGSeAIr9d0LGdDuFbGK57iK_mdsEnxt983h_1o&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;[Header]&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;token의 type과 JWT를 digitally sign할 때 사용한 algorithm을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;typ&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;토큰의 타입을 지정 (&quot;JWT&quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;b&gt;alg&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;해싱 알고리즘을 지정한다.&lt;/li&gt;
&lt;li&gt;해싱 알고리즘으로는 보통 HMAC SHA256 혹은 RSA가 사용되며, 이 알고리즘은 토큰을 검증할 때 사용되는 signature 부분에서 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;typ&quot; : &quot;JWT&quot;,
    &quot;alg&quot; : &quot;HS256&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 내용을 Base64url 인코딩 한 결과&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// Header
eyJhbGciOiJIUzI1NiJ9.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;[Payload]&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JWT에 담아서 전달할 data를 정의&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;정보의 한 &quot;조각&quot;을 Claim이라 부르고, name / value의 한 쌍으로 이루어져있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;b&gt;Claim의 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;b&gt;Registered Claim&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기 위하여 이름이 이미 정해진 Claim&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;모두 선택적(optional) 이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;iss&lt;/b&gt;: 토큰 발급자(issuer)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sub&lt;/b&gt;: 토큰 제목(subject)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;aud&lt;/b&gt;: 토큰 대상자(audience)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;exp&lt;/b&gt;: 토큰의 만료시간(expiration)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;시간은 NumericDate 형식으로 되어 있어야 한다.&lt;/li&gt;
&lt;li&gt;언제나 현재 시간보다 이후로 설정되어 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;b&gt;nbf&lt;/b&gt;: Not Before을 의미한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;토큰의 활성 날짜와 비슷한 개념&lt;/li&gt;
&lt;li&gt;NumericDate 형식으로 날짜를 지정하며, 날짜가 지나가기 전까지는 토큰이 처리되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;b&gt;iat&lt;/b&gt;: 토큰이 발급된 시간(issued at)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이 값을 사용하여 토큰의 age가 얼마나 되었는지 판단 할 수 있다 .&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;&lt;b&gt;jti&lt;/b&gt;: JWT의 고유 식별자&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;주로 중복적인 처리를 방지하기 위하여 사용된다.&lt;/li&gt;
&lt;li&gt;일회용 토큰에 사용하면 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Public Claim&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;충돌이 방지된(collision-registant)이름을 가지고 있어야 한다.&lt;/li&gt;
&lt;li&gt;충돌을 방지하기 위하여 Claim 이름을 URI 형식으로 짓는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Private Claim&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 클라이언트 양측 간 협의하에 사용되는 Claim이름&lt;/li&gt;
&lt;li&gt;Public Claim과는 달리 이름이 중복될 수 있으니 사용시 유의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public String createToken(Long userId, String name) {
        return Jwts.builder()
                .setHeaderParam(&quot;typ&quot;, Header.JWT_TYPE)
                .signWith(key, SignatureAlgorithm.HS256)
                .claim(&quot;userId&quot;, userId)
                .claim(&quot;userName&quot;, name)
                .setExpiration(new Date(System.currentTimeMillis() + 864000000))
                .compact();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 내용을 암호화한 Payload&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// Payload
eyJ1c2VySWQiOjEwMDQsInVzZXJOYW1lIjoiSm9obiJ9.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;[Signature]&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JSON Web Token의 마지막 부분으로 &lt;b&gt;Header&lt;/b&gt;와 &lt;b&gt;Payload를 base64로 encode&lt;/b&gt;인코딩한 값을 합친 후 JWT secret key값을 encrypt한 값으로 Hash를 하여 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HMACSHA256(
  base64UrlEncode(header) + &quot;.&quot; +
  base64UrlEncode(payload),
  my-secret-key

) secret base64 encoded&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 내용으로 인코딩한 결과 값&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;// signature
0nwaeM3fpDPvRGc64pyIp-JYNnuigCN9t_5ApVhPClQ&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JWT Token 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;생성된 토큰은 HTTP 통신을 할 때 Authorization이라는 key의 value로 사용된다.&lt;/li&gt;
&lt;li&gt;일반적으로 value에 Beare이 앞에 붙여진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;Authorization&quot;: &quot;Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInVzZXJOYW1lIjoic2VvayIsImV4cCI6MTU5MzUyMTU0MX0.AWsWjhGSeAIr9d0LGdDuFbGK57iK_mdsEnxt983h_1o&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JWT Decoding&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JWT를 디코딩 해보는 테스트 사이트 &lt;a href=&quot;https://jwt.io/&quot;&gt;https://jwt.io/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDnCn/btqFnQj8ZO7/w19znQokkFJ5e6kwUlcrvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDnCn/btqFnQj8ZO7/w19znQokkFJ5e6kwUlcrvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDnCn/btqFnQj8ZO7/w19znQokkFJ5e6kwUlcrvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDnCn%2FbtqFnQj8ZO7%2Fw19znQokkFJ5e6kwUlcrvk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;3. JWT를 사용하는 상황&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;회원 인증&lt;/b&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 로그인을 하면, 서버는 사용자의 정보를 기반으로한 토큰을 발급&lt;/li&gt;
&lt;li&gt;그 후, 사용자가 서버에 요청을 할 때마다 JWT를 포함하여 전달&lt;/li&gt;
&lt;li&gt;서버는 클라이언트에서 요청을 받을 때 마다, 해당 토큰이 유효하고 인증되었는지 검증을 하고, 사용자가 요청한 작업에 권한이 있는지 확인하여 작업을 처리&lt;/li&gt;
&lt;li&gt;서버에서는 사용자에 대한 세션을 유지할 필요가 없다.&lt;br /&gt;즉, 사용자가 로그인되어 있는지 여부를 신경 쓸 필요가 없고, 사용자가 요청을 했을 때 토큰만 확인하면 되므로 세션 관리가 필요 없어서 서버 자원과 비용을 절감할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정보 교류&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;JWT는 두 개체 사이에서 안정정있게 정보를 교환하기에 좋은 방법&lt;/li&gt;
&lt;li&gt;정보가 서명이 되어있기 때문에 정보를 보낸이가 바뀌진 않았는지, 또 정보가 도중에 조작되지 않았는지 검증을 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;* UI Layer, REST API 서버를 따로 두는 경우 JWT를 사용
* 하나의 End Point가 아닌 Mobile / Web 등의 multiple EndPoint 환경이라면 통합적인 인증 / 인가 환경을 제공하기 위해 JWT를 사용
* Third Party에게 public하게 open 한 REST EndPoing가 존재하는 경우 해당 Third Party의 인증 인가를 관리하기 위해 JWT 사용&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring/SpringBoot</category>
      <category>Authentication</category>
      <category>Authorization</category>
      <category>JWT</category>
      <category>springboot</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/828</guid>
      <comments>https://seokr.tistory.com/828#entry828comment</comments>
      <pubDate>Sat, 4 Jul 2020 22:43:42 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker로 Oracle 사용하기</title>
      <link>https://seokr.tistory.com/827</link>
      <description>&lt;h1&gt;Install Oracle with Docker&lt;/h1&gt;
&lt;h3&gt;Mac 환경에서 Oracle 사용하기 위한 방법&lt;/h3&gt;
&lt;h2&gt;1. Docker에 Oracle image 설치&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;도커 이미지 찾아보기&lt;ul&gt;
&lt;li&gt;oracle-xe로 검색하여 oracle-xe-11g을 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;docker search oracle-xe&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;_2020-06-30__12.03.05.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;974&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cypbId/btqFe3QC4K2/GSCLEkiFfjxIUYkXQFPWP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cypbId/btqFe3QC4K2/GSCLEkiFfjxIUYkXQFPWP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cypbId/btqFe3QC4K2/GSCLEkiFfjxIUYkXQFPWP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcypbId%2FbtqFe3QC4K2%2FGSCLEkiFfjxIUYkXQFPWP1%2Fimg.png&quot; data-filename=&quot;_2020-06-30__12.03.05.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;974&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;도커 이미지 pull&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;docker pull jaspeen/oracle-xe-11g&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;974&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgSYsX/btqFds484Qr/IPH7ofR4M3uGLJteMbuhw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgSYsX/btqFds484Qr/IPH7ofR4M3uGLJteMbuhw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgSYsX/btqFds484Qr/IPH7ofR4M3uGLJteMbuhw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgSYsX%2FbtqFds484Qr%2FIPH7ofR4M3uGLJteMbuhw0%2Fimg.png&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;974&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;2. Oracle Image 실행&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker run --name oracle11g -d -p 8080:8080 -p 1521:1521 jaspeen/oracle-xe-11g&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3. Docker의 Oracle 실행 및 접근&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker exec -it oracle11g sqlplus

Enter user-name: system
Enter password: {oracle}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;4. SqlDeveloper 툴 및 그 외 접근을 위한 정보&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;사용자이름: system
비밀번호 : oracle
호스트 이름: localhost
포트 : 1521
SID : xe&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5. SqlDeveloper에서 접근 시 오류 해결&lt;/h2&gt;
&lt;h3&gt;Local not recognize 에러로 인해 접속 불가&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SqlDeveloper.app의 패키지 내용 보기&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 1.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;98&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZJxV/btqFdKYOvPg/DPhmo7BVLvaYG55w6kGwkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZJxV/btqFdKYOvPg/DPhmo7BVLvaYG55w6kGwkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZJxV/btqFdKYOvPg/DPhmo7BVLvaYG55w6kGwkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZJxV%2FbtqFdKYOvPg%2FDPhmo7BVLvaYG55w6kGwkk%2Fimg.png&quot; data-filename=&quot;Untitled 1.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;98&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;sqldeveloper.conf 파일 확인&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;778&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNrosb/btqFduonoht/2luwECCEAcyk3lngzJSfK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNrosb/btqFduonoht/2luwECCEAcyk3lngzJSfK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNrosb/btqFduonoht/2luwECCEAcyk3lngzJSfK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNrosb%2FbtqFduonoht%2F2luwECCEAcyk3lngzJSfK1%2Fimg.png&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;778&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Language, Country 설정 정보 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;72&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H0lJB/btqFdnCZ8nY/m2r9szewcZlc2J5g9Kmdw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H0lJB/btqFdnCZ8nY/m2r9szewcZlc2J5g9Kmdw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H0lJB/btqFdnCZ8nY/m2r9szewcZlc2J5g9Kmdw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH0lJB%2FbtqFdnCZ8nY%2Fm2r9szewcZlc2J5g9Kmdw1%2Fimg.png&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;72&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;6. Oracle Docker에 설치 및 실습 준비 완료&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;3360&quot; data-origin-height=&quot;2100&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/plPuF/btqFdJ6DSO9/7jleltw5zFNYkMmKhsHrJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/plPuF/btqFdJ6DSO9/7jleltw5zFNYkMmKhsHrJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/plPuF/btqFdJ6DSO9/7jleltw5zFNYkMmKhsHrJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FplPuF%2FbtqFdJ6DSO9%2F7jleltw5zFNYkMmKhsHrJK%2Fimg.png&quot; data-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;3360&quot; data-origin-height=&quot;2100&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Server/docker</category>
      <category>Docker</category>
      <category>oracle</category>
      <category>Settings</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/827</guid>
      <comments>https://seokr.tistory.com/827#entry827comment</comments>
      <pubDate>Tue, 30 Jun 2020 00:27:25 +0900</pubDate>
    </item>
    <item>
      <title>[Annotation] @MappedSuperclass</title>
      <link>https://seokr.tistory.com/826</link>
      <description>&lt;h2&gt;어노테이션 설명&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;매핑 정보가 상속되는 엔티티에 적용되는 클래스를 지정&lt;/li&gt;
&lt;li&gt;매핑된 상위클래스는 별도의 테이블이 없다.&lt;/li&gt;
&lt;li&gt;@MappedSuperclass가 지정된 클래스는 Entity와 동일한 방법으로 매핑될 수 있다.&lt;/li&gt;
&lt;li&gt;매핑은 테이블이 없으므로 하위 클래스에만 적용된다.&lt;/li&gt;
&lt;li&gt;슈퍼클래스에 적용되었을 떄, 상속받은 클래스에 상위클래스 테이블의 context를 적용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AttributeOverride&lt;/b&gt; 및 &lt;b&gt;AssociationOverride&lt;/b&gt; 어노테이션 또는 해당 XML elements를 사용하여 이러한 하위 클래스에 하여 재정의 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;어노테이션 용도&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Entity별 공통 요소를 상속 필요성&lt;/li&gt;
&lt;li&gt;@MappedSuperclass를 이용하여 공통요소를 Super Class에 정의&lt;/li&gt;
&lt;li&gt;간소화된 Entity&lt;/li&gt;
&lt;li&gt;공통 컬럼명을 override가 필요한 경우 @AttributeOverride로 재정의&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;예시 코드&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@MappedSuperclass
public class Employee {
    @Id
    protected Integer empId;
    @Version
    protected Integer version;
    @ManyToOne
    @JoinColumn(name = &quot;ADDR&quot;)
    protected Address address;

    public Integer getEmpId() { ... }

    public void setEmpId(Integer id) { ... }

    public Address getAddress() { ... }

    public void setAddress(Address addr) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// Default table is FTEMPLOYEE table
@Entity
public class FTEmployee extends Employee {

    // Inherited empId field mapped to FTEMPLOYEE.EMPID
    // Inherited version field mapped to FTEMPLOYEE.VERSION
    // Inherited address field mapped to FTEMPLOYEE.ADDR fk

    // Defaults to FTEMPLOYEE.SALARY
    protected Integer salary;

    public FTEmployee() {
    }

    public Integer getSalary() { ... }

    public void setSalary(Integer salary) { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;- @AssociationOverride 어노테이션을 통해 address 속성을 ADDR_ID를 조인컬럼으로 사용&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;PT_EMP&quot;)
@AssociationOverride(name = &quot;address&quot;, joincolumns = @JoinColumn(name = &quot;ADDR_ID&quot;))
public class PartTimeEmployee extends Employee {

    // Inherited empId field mapped to PT_EMP.EMPID
    // Inherited version field mapped to PT_EMP.VERSION
    // address field mapping overridden to PT_EMP.ADDR_ID fk
    @Column(name = &quot;WAGE&quot;)
    protected Float hourlyWage;

    public PartTimeEmployee() {
    }

    public Float getHourlyWage() { ... }

    public void setHourlyWage(Float wage) { ... }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Basic/JPA</category>
      <category>@MappedSuperclass</category>
      <author>Seokr</author>
      <guid isPermaLink="true">https://seokr.tistory.com/826</guid>
      <comments>https://seokr.tistory.com/826#entry826comment</comments>
      <pubDate>Mon, 22 Jun 2020 23:42:24 +0900</pubDate>
    </item>
  </channel>
</rss>