<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기억의 편린</title>
    <link>https://seorinari.tistory.com/</link>
    <description>seori.nyan@gmail.com
개인일상 및 개발관련 이야기</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 03:41:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>최서리나리</managingEditor>
    <image>
      <title>기억의 편린</title>
      <url>https://tistory1.daumcdn.net/tistory/3091782/attach/b063bb70877c487d975077de714b1f4f</url>
      <link>https://seorinari.tistory.com</link>
    </image>
    <item>
      <title>기타 - 주민등록번호를 입력받아 YYYYMMDD형식의 생년월일과 성별을 return하는 함수</title>
      <link>https://seorinari.tistory.com/16</link>
      <description>&lt;pre id=&quot;code_1598321543437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	/**
	 * 주민등록번호 뒷자리를 입력받아 성별을 return, M : 남성 / F : 여성
	 * str : 주민번호 앞자리
	 * @param str
	 * @return
	 */
	public static String fn_getGender(String str){
		int genderCode = Integer.parseInt(str.substring(0, 1));
		String gender = null;
		if(genderCode % 2 &amp;gt; 0){
			gender = &quot;M&quot;;
		}else if(genderCode % 2 == 0){
			gender = &quot;F&quot;;
		}
		return gender;
	}

	/**
	 * 주민등록번호 앞, 뒷자리를 입력받아 YYYYMMDD형식으로 생년월일 return
	 * str1 : 주민번호 앞자리, str2 : 주민번호 뒷자리
	 * @param str1
	 * @param str2
	 * @return
	 */
	public static String fn_getDateOfBirth(String str1, String str2){
		int divisionCode = Integer.parseInt(str2.substring(0, 1));
		String dateOfBirth = null;
		if(divisionCode == 1 || divisionCode == 2 || divisionCode == 5 || divisionCode == 6){
			// 한국인 1900~, 외국인 1900~
			dateOfBirth = &quot;19&quot;+str1;
		}else if(divisionCode == 3 || divisionCode == 4 || divisionCode == 7 || divisionCode == 8){
			// 한국인 2000~, 외국인 2000~
			dateOfBirth = &quot;20&quot;+str1;
		}else if(divisionCode == 9 || divisionCode == 0){
			// 한국인 1800~
			dateOfBirth = &quot;18&quot;+str1;
		}
		return dateOfBirth;
	}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발 이야기/JAVA</category>
      <category>Util</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/16</guid>
      <comments>https://seorinari.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 25 Aug 2020 11:12:32 +0900</pubDate>
    </item>
    <item>
      <title>ORACLE - 한글 데이터가 포함된 COLUMN, TABLE 조회 (by owner)</title>
      <link>https://seorinari.tistory.com/15</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;글은 추후에 다시 다듬을 예정.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 프로시저 생성 (DATA_TYPE 조건문에 데이터 유형 적용)&lt;/p&gt;
&lt;pre id=&quot;code_1596702611437&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create or replace PROCEDURE SHOW_KR_COLUMN
    (
        OWNER_PARAM IN VARCHAR2
    )
    IS

    CURSOR TABLE_COLUMN IS
        SELECT
            OWNER,
            TABLE_NAME,
            COLUMN_NAME
        FROM ALL_TAB_COLUMNS
        WHERE OWNER = OWNER_PARAM
        AND (DATA_TYPE = 'VARCHAR2' OR DATA_TYPE = 'NVARCHAR2')
        ORDER BY OWNER ASC;

BEGIN
    FOR VAR IN TABLE_COLUMN LOOP
        EXECUTE IMMEDIATE 'INSERT INTO TB_SHOW_KR_COLUMN(KR_CNT, TABLE_NAME, COLUMN_NAME, OWNER) ' || 'SELECT A.KR_CNT, A.TABLE_NAME, A.COLUMN_NAME, A.OWNER FROM (SELECT COUNT(*) AS KR_CNT, '''||VAR.TABLE_NAME||''' AS TABLE_NAME, '''||VAR.COLUMN_NAME||''' AS COLUMN_NAME, '''||VAR.OWNER||''' AS OWNER FROM '||VAR.TABLE_NAME||' WHERE REGEXP_LIKE('||VAR.COLUMN_NAME||', ''[가-힝]'') AND ROWNUM = 1) A WHERE A.KR_CNT &amp;gt; 0';
        COMMIT;
    END LOOP;
END SHOW_KR_COLUMN;
/

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 테이블 생성&lt;/p&gt;
&lt;pre id=&quot;code_1596702701295&quot; class=&quot;sql&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table TB_SHOW_KR_COLUMN
(
	KR_CNT NUMBER,
	TABLE_NAME VARCHAR2(256),
	COLUMN_NAME VARCHAR2(256),
	OWNER VARCHAR2(256)
)
/

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. 실행&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596702745363&quot; class=&quot;sql&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;call YTH_ACT.SHOW_KR_COLUMN('OWNER_NAME');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4. 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&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/Llpx9/btqGiS8XW45/K739ptqFmMp8ORqLJKW7NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Llpx9/btqGiS8XW45/K739ptqFmMp8ORqLJKW7NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Llpx9/btqGiS8XW45/K739ptqFmMp8ORqLJKW7NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLlpx9%2FbtqGiS8XW45%2FK739ptqFmMp8ORqLJKW7NK%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;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이야기</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/15</guid>
      <comments>https://seorinari.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 6 Aug 2020 17:35:34 +0900</pubDate>
    </item>
    <item>
      <title>intellij - weblogic 배포환경 구성 / 연동</title>
      <link>https://seorinari.tistory.com/14</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;unnamed.png&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v3hn1/btqFKEbKvi3/kdHnNBSbgCmTbe18mKT380/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v3hn1/btqFKEbKvi3/kdHnNBSbgCmTbe18mKT380/img.png&quot; data-alt=&quot;weblogic은 오라클의 WAS이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v3hn1/btqFKEbKvi3/kdHnNBSbgCmTbe18mKT380/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv3hn1%2FbtqFKEbKvi3%2FkdHnNBSbgCmTbe18mKT380%2Fimg.png&quot; data-filename=&quot;unnamed.png&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&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;figcaption&gt;weblogic은 오라클의 WAS이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로젝트 진행간 턱시도 연동 환경을 구성해야 할 일이 생겼다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 프로젝트의 운영환경은 웹로직이었고, 개발환경은 톰캣으로 구성되어 있었는데&lt;/p&gt;
&lt;p&gt;당연한 이야기겠지만.. 톰캣에서 턱시도를 연결하기 위해서는, 턱시도 서버측에서도 수정이 필요하고 프로젝트도 수정이 필요하다.&lt;/p&gt;
&lt;p&gt;이 방법은 과감하게 포기.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 차선으로 선택한것이 그냥 운영환경에서 사용되는 웹로직을 WAS로 사용하여 배포하는것인데,&lt;/p&gt;
&lt;p&gt;생각보다 정보가 많이 없어서 직접 작성하게 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각자의 프로젝트에 따라 설정방법은 조금씩 달라지겠지만, 거의 동일하다고 봐도 무방할듯 하다.&lt;/p&gt;
&lt;p&gt;(운영환경 버전이 12.3인 관계로 해당 버전으로 진행)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;웹로직 설치
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://www.oracle.com/middleware/technologies/weblogic-server-installers-downloads.html&quot;&gt;https://www.oracle.com/middleware/technologies/weblogic-server-installers-downloads.html&lt;/a&gt; 에서 웹로직을 다운&lt;br /&gt;설치형이 아닌 zip 아카이브 형식으로 다운로드를 권장한다.&lt;/li&gt;
&lt;li&gt;웹로직 압축해제 후 적당한 위치로 옮겨둔다. (환경변수 설정이 필요하다.)&lt;/li&gt;
&lt;li&gt;웹로직 사용시 MW_HOME이라는 환경변수와 JAVA_HOME이라는 환경변수를 필요로 한다.&lt;br /&gt;MW_HOME은 적당한 위치로 옮겨 둔 웹로직 디렉토리로 지정하면 된다.&lt;br /&gt;ex) export MW_HOME=&quot;/usr/local/weblogic/wls12130&quot;&lt;/li&gt;
&lt;li&gt;MW_HOME 디렉토리로 이동하여 configure.*를 실행한다. (window라면 cmd, unix-like라면 sh)&lt;br /&gt;이후 빌드과정 진행&lt;/li&gt;
&lt;li&gt;WSL 설정 완료 후 새로운 도메인을 생성할것인지에 대한 물음이 발생된다.&lt;br /&gt;(Do you want to configure a new domain?)&lt;br /&gt;절차에 따라 새로운 도메인을 만들어주자.&lt;br /&gt;아이디와 비밀번호를 입력받는데 해당 내용은 이후 서버 콘솔에서 사용될 아이디 비밀번호다.&lt;br /&gt;&lt;br /&gt;도메인 설정이 완료되면 즉시 서버가 시작되며, ctrl+c로 종료하면 서버는 자동으로 종료된다.&lt;br /&gt;여기까지 진행했다면 웹로직 설치는 완료&lt;br /&gt;&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzACbM/btqFKmoUcEq/gkLKU72bJUkgwDk0RIBKrk/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bzACbM/btqFKmoUcEq/gkLKU72bJUkgwDk0RIBKrk/img.png&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.40.41.png&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;1698&quot; /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;intellij 웹로직 배포환경 설정
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;인텔리제이 프로젝트에서, Run/Debug Configurations - Server를 통해 WebLogic Server (local)을 추가한다.&lt;br /&gt;추가 후 웹로직 설치 디렉토리 경로를 잡아주자.&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;783&quot; height=&quot;NaN&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.45.34.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;693&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uzx2n/btqFIjUqZhd/yQG0pPuuLx215FzHFLbvtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uzx2n/btqFIjUqZhd/yQG0pPuuLx215FzHFLbvtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uzx2n/btqFIjUqZhd/yQG0pPuuLx215FzHFLbvtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuzx2n%2FbtqFIjUqZhd%2FyQG0pPuuLx215FzHFLbvtK%2Fimg.png&quot; width=&quot;783&quot; height=&quot;NaN&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.45.34.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;693&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;&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;인텔리제이 project structure - artifacts에서 생성된 Web Application: Exploded를 매핑해야 한다.&lt;br /&gt;Run/Debug Configurations - Deployment에 다음과 같이 추가인텔리제이 연동&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;730&quot; height=&quot;681&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.52.06.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;681&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNJcW5/btqFKCyhA5F/Zfv4mYjosg496IBGYO5ayK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNJcW5/btqFKCyhA5F/Zfv4mYjosg496IBGYO5ayK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNJcW5/btqFKCyhA5F/Zfv4mYjosg496IBGYO5ayK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNJcW5%2FbtqFKCyhA5F%2FZfv4mYjosg496IBGYO5ayK%2Fimg.png&quot; width=&quot;730&quot; height=&quot;681&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.52.06.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;681&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;/li&gt;
&lt;li&gt;다시 Run/Debug Configurations - Server로 돌아와서, 나머지 설정을 진행한다.&lt;br /&gt;User, Password에는 최초 설치 configuration시 사용했던 아이디와 비밀번호를&lt;br /&gt;Domain Path에는 웹로직이 설치된 경로 하위 도메인 디렉토리를&lt;br /&gt;Admin Server는 Domain Path가 잡히면 자동으로 잡아 줄 것이다.&lt;br /&gt;완성된 모습은 다음과 같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;설정완료 후 저장&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.56.48.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;769&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EkcjL/btqFJrcXsk8/QtWczSt5Ka5qVDrpxn71r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EkcjL/btqFJrcXsk8/QtWczSt5Ka5qVDrpxn71r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EkcjL/btqFJrcXsk8/QtWczSt5Ka5qVDrpxn71r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEkcjL%2FbtqFJrcXsk8%2FQtWczSt5Ka5qVDrpxn71r1%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-07-16 오후 3.56.48.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;769&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;/li&gt;
&lt;li&gt;웹로직 서버를 실행하도록 하자.&lt;br /&gt;실행 후 별 다른 에러가 없다면? 어플리케이션이 정상적으로 실행 될 것이다.&lt;br /&gt;정상적으로 실행되었다면 /console을 통해 다음과 같은 화면을 확인 할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-07-16 오후 4.01.24.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;716&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOLmWV/btqFIjmABBj/SdwintA98fRmzerpXNRYq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOLmWV/btqFIjmABBj/SdwintA98fRmzerpXNRYq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOLmWV/btqFIjmABBj/SdwintA98fRmzerpXNRYq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOLmWV%2FbtqFIjmABBj%2FSdwintA98fRmzerpXNRYq0%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-07-16 오후 4.01.24.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;716&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;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-07-16 오후 4.01.56.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;801&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgYKMO/btqFJcAkqAp/IPkST05BZos2KkHVMu2Dw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgYKMO/btqFJcAkqAp/IPkST05BZos2KkHVMu2Dw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgYKMO/btqFJcAkqAp/IPkST05BZos2KkHVMu2Dw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgYKMO%2FbtqFJcAkqAp%2FIPkST05BZos2KkHVMu2Dw1%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-07-16 오후 4.01.56.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;801&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;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;br /&gt;이상이 연동 과정이며, 제대로 따라만 했다면 별 문제 없이 잘 연동 될 것이다.&lt;br /&gt;디폴트 포트가 7001이기에 8080으로 변경하고자 한다면..&lt;br /&gt;&lt;br /&gt;admin web console (/console) - 서버 - 변경하고자 하는 서버 클릭 - 수신포트에 7001로 되어있는것을 8080이나 희망포트로 바꿔서&lt;br /&gt;사용하면 된다.&lt;br /&gt;변경된 정보는 저장만 하면 즉시 반영되며 재시작이 필요없다.&lt;br /&gt;intellij 서버 정보에도 변경된 포트가 반영 되어 있을것이다.&lt;br /&gt;&lt;br /&gt;도메인 이름 변경이나, 서버 이름 변경이 필요 할 경우 웹로직 도큐먼트를 참고하도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이야기/JAVA</category>
      <category>intellij</category>
      <category>Java</category>
      <category>Spring</category>
      <category>Weblogic</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/14</guid>
      <comments>https://seorinari.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 16 Jul 2020 16:10:49 +0900</pubDate>
    </item>
    <item>
      <title>저공해자동차 스티커 발급받기</title>
      <link>https://seorinari.tistory.com/13</link>
      <description>&lt;p&gt;거주자 우선주차 / 공영주차장 친환경차 할인을 받기위해 열심히 검색하던 중&lt;/p&gt;
&lt;p&gt;구청 시설관리공단으로 전화해보니 '자동차등록증(반드시 저공해차 인증이 필요)' 또는 '저공해자동차 인증 스티커 사진'이 필요하다고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구글 검색을 해보니 여러가지 정보들이 너무 난잡하게 정보가 분산되어있기도 하고, 관련 게시글을 따라하다가 시행착오도 겪었다.&lt;/p&gt;
&lt;p&gt;혹시나 검색을 통해 이 글을 보게된다면.. 이 글이 현 시점에서는 가장 정확한 정보이니 그대로 따라하면 된다.&lt;/p&gt;
&lt;p&gt;(한줄요약 : 자동차등록증, 제조사 발부 저공해자동차 인증서 필요없다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 아래 홈페이지에서 차량번호로 조회한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hybridbonus.or.kr/EP020401000SF01.do&quot;&gt;https://www.hybridbonus.or.kr/EP020401000SF01.do&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1583246466698&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;친환경차 종합정보 지원시스템&quot; data-og-description=&quot;저공해차 확인결과 저공해차 확인결과 제작사명 저공해차종류 차명 제원번호 인증번호 차대번호 판매일자 등록일자&quot; data-og-host=&quot;www.hybridbonus.or.kr&quot; data-og-source-url=&quot;https://www.hybridbonus.or.kr/EP020401000SF01.do&quot; data-og-url=&quot;https://www.hybridbonus.or.kr/EP020401000SF01.do&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.hybridbonus.or.kr/EP020401000SF01.do&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.hybridbonus.or.kr/EP020401000SF01.do&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;&gt;친환경차 종합정보 지원시스템&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;저공해차 확인결과 저공해차 확인결과 제작사명 저공해차종류 차명 제원번호 인증번호 차대번호 판매일자 등록일자&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.hybridbonus.or.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 다음과 같이 저공해차종류 1, 2, 3종에 포함된다면 3항 진행&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-03-03 오후 11.41.56.png&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1vjQj/btqCrNRcsmn/aDy5llzY8EOtRuQRkwEPGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1vjQj/btqCrNRcsmn/aDy5llzY8EOtRuQRkwEPGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1vjQj/btqCrNRcsmn/aDy5llzY8EOtRuQRkwEPGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1vjQj%2FbtqCrNRcsmn%2FaDy5llzY8EOtRuQRkwEPGk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-03-03 오후 11.41.56.png&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;564&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. 소재지 차량등록사업소로 직접 찾아가서 '저공해자동차 스티커 발부받으러 왔어요' 라고 하면 한큐에 발급해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 발급에 걸리는 시간도 약 3분이내&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; (거주지 인근 차량등록사업소가 구청에 있었다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4. 발급완료!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;F9D4264B-C11E-4297-99F2-6F2FF6EE0B5E_1_201_a.png&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3phxh/btqCrjiwy61/mKs7FqkBK8XC3R7rstQksK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3phxh/btqCrjiwy61/mKs7FqkBK8XC3R7rstQksK/img.png&quot; data-alt=&quot;매직펜으로 이렇게 써준다. 아날로그 감성..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3phxh/btqCrjiwy61/mKs7FqkBK8XC3R7rstQksK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3phxh%2FbtqCrjiwy61%2FmKs7FqkBK8XC3R7rstQksK%2Fimg.png&quot; data-filename=&quot;F9D4264B-C11E-4297-99F2-6F2FF6EE0B5E_1_201_a.png&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&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;figcaption&gt;매직펜으로 이렇게 써준다. 아날로그 감성..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 간단한걸 구글검색하다가 오히려 시간만 더 버렸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 이 글을 쓰게된 과정은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 구글검색&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - 어? 차량제조사 고객센터에 전화해서 인증서를 받으라고? 주변 대리점이나 센터에 가야겠구나.&lt;/p&gt;
&lt;p&gt;2. 제조사 전화 (현대자동차)&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - Q : &lt;span style=&quot;color: #333333;&quot;&gt;저공해자동차 인증서 발부때문에 연락드렸는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - A : 고객님 저희쪽에서는 인증서 발부 업무를 하고있지 않아요~ 관련 기관 연락처를 알려드릴테니 이쪽으로 연락해보세요.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (한국환경공단[친환경자동차 종합정보 안내센터])&lt;/p&gt;
&lt;p&gt;3. 관련 기관 전화&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - Q : 저공해자동차 인증서 발부때문에 연락드렸는데요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - A : 저희쪽에서도 인증서를 발부하고 있지는 않아요~&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - Q : 아니 현대차에서도 발부안한다 그러고 공단에서도 발부안한다 그러면 저공해스티커는 어떻게 받으란거에요?&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 인증서가 꼭 있어야 된다던데요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - A : 아~ 그게 옛날엔 그랬는데 지금은 다 전산화가 되어서 그러실필요 없어요. 차량등록사업소에서 차량번호만 불러주셔도&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 전산으로 다 조회됩니다. 인근 사업소 가셔서 바로 발부받으시면 되요~&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - Q : 아.. 감사합니다 (머쓱)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;민망하다 민망해..&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>일상</category>
      <category>저공해자동차</category>
      <category>저공해자동차 스티커</category>
      <category>친환경차</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/13</guid>
      <comments>https://seorinari.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 3 Mar 2020 23:58:43 +0900</pubDate>
    </item>
    <item>
      <title>Spring - 객체캐싱 (SSM, memcached)</title>
      <link>https://seorinari.tistory.com/12</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-11-14 오후 5.08.17.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgODnJ/btqzJFQa1ej/6LSkgLvXGRk2mdgskaVie1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgODnJ/btqzJFQa1ej/6LSkgLvXGRk2mdgskaVie1/img.png&quot; data-alt=&quot;캐시가 도입된 서비스의 구성도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgODnJ/btqzJFQa1ej/6LSkgLvXGRk2mdgskaVie1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgODnJ%2FbtqzJFQa1ej%2F6LSkgLvXGRk2mdgskaVie1%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-11-14 오후 5.08.17.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;캐시가 도입된 서비스의 구성도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘날 빠른 속도의 웹서비스를 제공하기 위한 전략들은 여러가지가 있다.&lt;/p&gt;
&lt;p&gt;이미지 캐싱, 객체 캐싱, CDN.. 그리고 스케일아웃 - 스케일업 까지.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 여러가지 전략들 중에 가장 쉽고 효과가 좋은녀석은 개인적으로 캐싱이 아닐까 한다.&lt;/p&gt;
&lt;p&gt;오늘은 웹서비스의 주요 병목구간인 (비즈니스 레이어 - RDB) 구간을 캐싱하여 성능향상을 꾀해보고자 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구성도에도 나와있지만 (비즈니스 레이어 - RDB) 구간 사이에 캐시를 넣고&lt;/p&gt;
&lt;p&gt;클라이언트의 요청이 발생 할 경우 캐시에서 처리가 가능한 부분은 캐시에서 객체로 리턴, 캐시에서 처리가 불가능한 (데이터가 없다거나..) 사항들은 rdb로 다시 요청하고 수신된 객체를 캐시에 저장해두는 단순한 흐름이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러 캐시 시스템들이 있지만 (redis, memcached 그리고 스프링에서 자체적으로 지원하는 캐시들 등등...)&lt;/p&gt;
&lt;p&gt;글에서는 memcached를 사용 할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 개발환경&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- maven&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- spring 5 (java config)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;memcached의 설치에 대한 사항은 해당 글에서 언급하지 않는다.&lt;/p&gt;
&lt;p&gt;아래의 글을 확인하여 각자 설치 후 진행&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Linux : &lt;a href=&quot;https://idchowto.com/?p=38249&quot;&gt;https://idchowto.com/?p=38249&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1573719910016&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;idchowto.com - 스마일서브(Cloudv.kr)&quot; data-og-description=&quot;IDC구축,운영,보안등 모든 지식을 공유합니다&quot; data-og-host=&quot;idchowto.com&quot; data-og-source-url=&quot;https://idchowto.com/?p=38249&quot; data-og-url=&quot;https://idchowto.com/?p=38249&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXeAJJ/hyDHKw4G9n/V3Itn2EZIWM7MydpgwHMj1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/cdGcHj/hyDGdVdbRd/UlndgHLg0I0Bzq2xTDGEdk/img.png?width=1871&amp;amp;height=986&amp;amp;face=0_0_1871_986,https://scrap.kakaocdn.net/dn/bISocD/hyDGeT6Ivx/KK7V1Fb41SVZjvFK1afrY0/img.png?width=1871&amp;amp;height=986&amp;amp;face=0_0_1871_986&quot;&gt;&lt;a href=&quot;https://idchowto.com/?p=38249&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://idchowto.com/?p=38249&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXeAJJ/hyDHKw4G9n/V3Itn2EZIWM7MydpgwHMj1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/cdGcHj/hyDGdVdbRd/UlndgHLg0I0Bzq2xTDGEdk/img.png?width=1871&amp;amp;height=986&amp;amp;face=0_0_1871_986,https://scrap.kakaocdn.net/dn/bISocD/hyDGeT6Ivx/KK7V1Fb41SVZjvFK1afrY0/img.png?width=1871&amp;amp;height=986&amp;amp;face=0_0_1871_986');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;idchowto.com - 스마일서브(Cloudv.kr)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;IDC구축,운영,보안등 모든 지식을 공유합니다&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;idchowto.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;Windows : &lt;a href=&quot;https://dev18.tistory.com/11&quot;&gt;https://dev18.tistory.com/11&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1573719921612&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;memcached 윈도우 설치 및 세션공유&quot; data-og-description=&quot;일반적으로 php에서 세션은 파일로 많이 사용하십니다. 하지만 웹 서버가 2대 이상일경우 로그인이 풀리는 문제가 발생될 수 있습니다. A서버에서 로그인을 해서 세션을 얻었는데 B서버로 페이지 이동을 하면 바..&quot; data-og-host=&quot;dev18.tistory.com&quot; data-og-source-url=&quot;https://dev18.tistory.com/11&quot; data-og-url=&quot;https://dev18.tistory.com/11&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OT5Ke/hyDF3E4iYU/lh8V7WDnSusfkEy5yREWL1/img.jpg?width=793&amp;amp;height=156&amp;amp;face=313_19_557_128,https://scrap.kakaocdn.net/dn/PLqvB/hyDHLW4uZ5/sJMlvgVPxDcMU182l0V4y1/img.jpg?width=793&amp;amp;height=156&amp;amp;face=313_19_557_128&quot;&gt;&lt;a href=&quot;https://dev18.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev18.tistory.com/11&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OT5Ke/hyDF3E4iYU/lh8V7WDnSusfkEy5yREWL1/img.jpg?width=793&amp;amp;height=156&amp;amp;face=313_19_557_128,https://scrap.kakaocdn.net/dn/PLqvB/hyDHLW4uZ5/sJMlvgVPxDcMU182l0V4y1/img.jpg?width=793&amp;amp;height=156&amp;amp;face=313_19_557_128');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;memcached 윈도우 설치 및 세션공유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;일반적으로 php에서 세션은 파일로 많이 사용하십니다. 하지만 웹 서버가 2대 이상일경우 로그인이 풀리는 문제가 발생될 수 있습니다. A서버에서 로그인을 해서 세션을 얻었는데 B서버로 페이지 이동을 하면 바..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;dev18.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;설치 후 memcached서버에 정상적으로 접속이 된다면 완료된것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 후 spring에서 memcached를 사용하기 위해서 아래 라이브러리들이 maven에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- pom.xml&lt;/p&gt;
&lt;pre id=&quot;code_1573719560978&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        &amp;lt;!-- memcached --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.google.code.simple-spring-memcached&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-cache&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;3.6.1&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.google.code.simple-spring-memcached&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;simple-spring-memcached&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;3.6.1&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.google.code.simple-spring-memcached&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;xmemcached-provider&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;3.6.1&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;캐시에 사용될 기본적인 설정사항들을 전역변수화 할것이다.&lt;/p&gt;
&lt;p&gt;맴캐시드의 소켓 타임아웃, 캐시 유지시간, 캐시 이름, 서버정보를 프로퍼티에 작성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- *.properties&lt;/p&gt;
&lt;pre id=&quot;code_1573719779881&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 캐시이름
memcached.cacheName=test
# 소켓타임아웃
memcached.socketTimeOut=10000
# 캐시유지시간 (s)
memcached.expiration=300
# 서버정보
memcached.servers=localhost:11211&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;스프링에서 사용할 수 있도록 설정해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;* 아래 주석처리 되어있는 부분의 setUseNameAsKeyPrefix와 setKeyPrefixSeparator는 키 구분자를 사용하겠다는 의미이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp;아래와 같이 설정하게 되면, 키 구조가 다음과 같이 이루어 진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp;(test:rdbService:getApi:23)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;* &lt;span&gt;@EnableAspectJAutoProxy는 반드시 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- MemCacheConfig.java&lt;/p&gt;
&lt;pre id=&quot;code_1573720099719&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
@EnableAspectJAutoProxy
public class MemCacheConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {

        final String servers = GetProperties.getProperty(&quot;memcached.servers&quot;);
        final String operationTimeout = GetProperties.getProperty(&quot;memcached.socketTimeOut&quot;);

        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setConsistentHashing(true);
        cacheConfiguration.setUseBinaryProtocol(true);
        cacheConfiguration.setOperationTimeout(Integer.getInteger(operationTimeout));
        // Cache Name Key Prefix
        cacheConfiguration.setUseNameAsKeyPrefix(true);
        cacheConfiguration.setKeyPrefixSeparator(&quot;:&quot;);

        MemcacheClientFactoryImpl cacheClientFactory = new MemcacheClientFactoryImpl();

        AddressProvider addressProvider = new DefaultAddressProvider(servers);

        CacheFactory cacheFactory = new CacheFactory();
        cacheFactory.setCacheName(GetProperties.getProperty(&quot;memcached.cacheName&quot;));
        cacheFactory.setCacheClientFactory(cacheClientFactory);
        cacheFactory.setAddressProvider(addressProvider);
        cacheFactory.setConfiguration(cacheConfiguration);

        Cache object = null;
        try {
            object = cacheFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        SSMCache ssmCache = new SSMCache(object, Integer.parseInt(GetProperties.getProperty(&quot;memcached.expiration&quot;)), true);
        ArrayList ssmCaches = new ArrayList&amp;lt;&amp;gt;();
        ssmCaches.add(0, ssmCache);
        ExtendedSSMCacheManager ssmCacheManager = new ExtendedSSMCacheManager();
        ssmCacheManager.setCaches(ssmCaches);
        return ssmCacheManager;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 설정한 MemcacheConfig를 Initializer Context에 등록한다. (xml config의 경우 initializer에 대응되는 context에 주입)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1573720872085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(MemCacheConfig.class);
        servletContext.addListener(new ContextLoaderListener(rootContext));
        this.dispatcherServlet(servletContext);
        ...
    }

    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 하면 기본적인 사용준비는 끝난다.&lt;/p&gt;
&lt;p&gt;실제로 사용은 아래와같이 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1573721414626&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /**
     * 메뉴 정보 GET
     * @param vo
     * @return
     */
    @UpdateMultiCache(namespace = &quot;menuList&quot;)
    @ReadThroughMultiCache(namespace = &quot;menuList&quot;)
    @Cacheable(value=&quot;test&quot;, key=&quot;'menuDetail'.concat(':').concat(#vo.getMenu_info_seq()).concat(':').concat(#vo.getMenu_link())&quot;)
    public Map&amp;lt;String, Object&amp;gt; getMenuDetail(MenuVO vo) {
        Map&amp;lt;String, Object&amp;gt; rtnMap = new HashMap&amp;lt;&amp;gt;();
        List&amp;lt;MenuVO&amp;gt; menuVOList = menuMapper.getMenuDetail(vo);
        
        ...
        
        return rtnMap;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;@ReadThroughMultiCache : 캐시확인 &amp;gt; 없을경우 함수전체내용 실행 &amp;gt; return되는 객체를 캐시에 저장 &amp;gt; return&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;@UpdateMultiCache : 기존 값을 제거하지 않고 덮어씌움 (성능이점)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;@Cacheable : 캐시사용 (value : properties에 정의한 캐시이름 / key : 캐시이름 하단에 적재될 키 명칭)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;자세한 명세가 필요하다면 다음 블로그를 참고하자.&amp;nbsp; &lt;a href=&quot;https://blog.naver.com/akaroice/220298608077&quot;&gt;https://blog.naver.com/akaroice/220298608077&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1573721675631&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;SSM(Simple Spring Memcached)&quot; data-og-description=&quot;소개spymemcached, xmemcached 클라이언트(provider)와 annotation, Spring/AspectJ AOP 를 이용한 ...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/akaroice/220298608077&quot; data-og-url=&quot;https://blog.naver.com/akaroice/220298608077&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJGURw/hyDGbb1q7O/1n7j5oIWMENK6zxas5hFSk/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270&quot;&gt;&lt;a href=&quot;https://blog.naver.com/akaroice/220298608077&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/akaroice/220298608077&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJGURw/hyDGbb1q7O/1n7j5oIWMENK6zxas5hFSk/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;SSM(Simple Spring Memcached)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;소개spymemcached, xmemcached 클라이언트(provider)와 annotation, Spring/AspectJ AOP 를 이용한 ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;실제 작동은 아래와같이 이루어진다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;* 맴캐시드 빈 등록&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-11-14 오후 5.59.05.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zNF0F/btqzKZAqR2f/UWKhEBKQ2nExDqvIt424IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zNF0F/btqzKZAqR2f/UWKhEBKQ2nExDqvIt424IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zNF0F/btqzKZAqR2f/UWKhEBKQ2nExDqvIt424IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzNF0F%2FbtqzKZAqR2f%2FUWKhEBKQ2nExDqvIt424IK%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-11-14 오후 5.59.05.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;* Cacheable에 정의된 key로 적재준비&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2019-11-14&amp;nbsp;17:58:27,319&amp;nbsp;DEBUG&amp;nbsp;[&lt;a href=&quot;org.springframework.cache.annotation.AnnotationCacheOperationSource]&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;org.springframework.cache.annotation.AnnotationCacheOperationSource]&lt;/a&gt;&amp;nbsp;Adding&amp;nbsp;cacheable&amp;nbsp;method&amp;nbsp;'getMenuDetail'&amp;nbsp;with&amp;nbsp;attribute:&amp;nbsp;[Builder[public&amp;nbsp;&lt;a href=&quot;java.util.Map&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;java.util.Map&lt;/a&gt; &lt;a href=&quot;kr.co.sekorea.application.site.menu.service.MenuService.getMenuDetail(kr.co.sekorea.application.site.menu.vo.MenuVO)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;site.menu.service.MenuService.getMenuDetail(application.site.menu.vo.MenuVO)&lt;/a&gt;&lt;a href=&quot;java.lang.Exception]&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;]&lt;/a&gt; caches=[test] | key=''menuDetail'.concat(':').concat(#vo.getMenu_info_seq()).concat(':').concat(#vo.getMenu_link())' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false']&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 맴캐시드 서버와 통신&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.04.17.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cENxfU/btqzMfJlV9W/S0D9cgbwxwjOOOmZ59QoXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cENxfU/btqzMfJlV9W/S0D9cgbwxwjOOOmZ59QoXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cENxfU/btqzMfJlV9W/S0D9cgbwxwjOOOmZ59QoXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcENxfU%2FbtqzMfJlV9W%2FS0D9cgbwxwjOOOmZ59QoXk%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.04.17.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;* 캐시 적재된 데이터가 존재하지 않을경우 (MISS) RDB조회 후 캐시적재&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.05.51.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Vho2/btqzLi7A4sI/5AG9U5c9I0S25kEMUu6v0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Vho2/btqzLi7A4sI/5AG9U5c9I0S25kEMUu6v0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Vho2/btqzLi7A4sI/5AG9U5c9I0S25kEMUu6v0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Vho2%2FbtqzLi7A4sI%2F5AG9U5c9I0S25kEMUu6v0k%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.05.51.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;* 캐시 히트에 성공했을 경우&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.08.33.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJlo0m/btqzMeDEg3U/8cenA880I7kaKK0E2Ep47k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJlo0m/btqzMeDEg3U/8cenA880I7kaKK0E2Ep47k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJlo0m/btqzMeDEg3U/8cenA880I7kaKK0E2Ep47k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJlo0m%2FbtqzMeDEg3U%2F8cenA880I7kaKK0E2Ep47k%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-11-14 오후 6.08.33.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;기타 캐시와 관련된 좋은 게시글들&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;a href=&quot;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&quot;&gt;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1573721759541&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Cache에 대하여.. (Spring+EHCache) | Carrey&amp;#96;s 기술블로그&quot; data-og-description=&quot;들어가며.. 엔터프라이즈 급 Application에서는 DBMS의 부하를 줄이고, 성능을 높이기 위해 캐시(Cache)를 사용한다. 우리 회사에서도 EHCache, Redis를 사용하지만, EHCache쪽은 공부한 적이 없어서 이번 기회에 정리하면서 공부해 보게 되었다. 대부분 docs.spring.io의 문서를 번역한 내용을 위주로 정리 하였다. Cache란? Cache의 사전적인 의미를 알아보기 위해 위키백과를 검색해 보았다. 캐시(cache, 문화어&quot; data-og-host=&quot;jaehun2841.github.io&quot; data-og-source-url=&quot;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&quot; data-og-url=&quot;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/869wP/hyDHIeYRBJ/KWAFie0YmeAUG8kVzrH8R0/img.jpg?width=453&amp;amp;height=332&amp;amp;face=0_0_453_332&quot;&gt;&lt;a href=&quot;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/869wP/hyDHIeYRBJ/KWAFie0YmeAUG8kVzrH8R0/img.jpg?width=453&amp;amp;height=332&amp;amp;face=0_0_453_332');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Cache에 대하여.. (Spring+EHCache) | Carrey`s 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;들어가며.. 엔터프라이즈 급 Application에서는 DBMS의 부하를 줄이고, 성능을 높이기 위해 캐시(Cache)를 사용한다. 우리 회사에서도 EHCache, Redis를 사용하지만, EHCache쪽은 공부한 적이 없어서 이번 기회에 정리하면서 공부해 보게 되었다. 대부분 docs.spring.io의 문서를 번역한 내용을 위주로 정리 하였다. Cache란? Cache의 사전적인 의미를 알아보기 위해 위키백과를 검색해 보았다. 캐시(cache, 문화어&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;jaehun2841.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://charsyam.wordpress.com/2016/07/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-%EC%99%9C-cache%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80/&quot;&gt;https://charsyam.wordpress.com/2016/07/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-%EC%99%9C-cache%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1573721800752&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[입 개발] 왜 Cache를 사용하는가?&quot; data-og-description=&quot;가끔씩 Redis 가 뭐예요? Memcached 가 뭐예요? 또는 Cache를 왜 써요? 라는 저도 모르는 근원적인 질문을 받을 때가 있습니다. 일단 그 근원적인 질문에 답하기 위해서는 먼저 Cache 란 무엇인가로 부터 시작해야 될것 같습니다. 일단 Cache는 &amp;ldquo;많은 시간이나 연산이 필요한 일에 대한 결과를 저장해 두는 것&amp;rdquo; &amp;hellip;&quot; data-og-host=&quot;charsyam.wordpress.com&quot; data-og-source-url=&quot;https://charsyam.wordpress.com/2016/07/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-%EC%99%9C-cache%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80/&quot; data-og-url=&quot;https://charsyam.wordpress.com/2016/07/27/%ec%9e%85-%ea%b0%9c%eb%b0%9c-%ec%99%9c-cache%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94%ea%b0%80/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6kwTC/hyDF78wNJz/2Zef1wSE11wbVMIEOZdJ41/img.png?width=454&amp;amp;height=246&amp;amp;face=0_0_454_246,https://scrap.kakaocdn.net/dn/Sk6KC/hyDF5JEBdW/yPKr5TWazzIwFt5TIORc61/img.png?width=454&amp;amp;height=246&amp;amp;face=0_0_454_246&quot;&gt;&lt;a href=&quot;https://charsyam.wordpress.com/2016/07/27/%ec%9e%85-%ea%b0%9c%eb%b0%9c-%ec%99%9c-cache%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94%ea%b0%80/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://charsyam.wordpress.com/2016/07/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-%EC%99%9C-cache%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6kwTC/hyDF78wNJz/2Zef1wSE11wbVMIEOZdJ41/img.png?width=454&amp;amp;height=246&amp;amp;face=0_0_454_246,https://scrap.kakaocdn.net/dn/Sk6KC/hyDF5JEBdW/yPKr5TWazzIwFt5TIORc61/img.png?width=454&amp;amp;height=246&amp;amp;face=0_0_454_246');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[입 개발] 왜 Cache를 사용하는가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;가끔씩 Redis 가 뭐예요? Memcached 가 뭐예요? 또는 Cache를 왜 써요? 라는 저도 모르는 근원적인 질문을 받을 때가 있습니다. 일단 그 근원적인 질문에 답하기 위해서는 먼저 Cache 란 무엇인가로 부터 시작해야 될것 같습니다. 일단 Cache는 &amp;ldquo;많은 시간이나 연산이 필요한 일에 대한 결과를 저장해 두는 것&amp;rdquo; &amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;charsyam.wordpress.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>memcached</category>
      <category>Spring</category>
      <category>Spring Cache</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/12</guid>
      <comments>https://seorinari.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 14 Nov 2019 17:42:52 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data JPA - JpaRepository 사용시 persist간 제네릭 인자 이외의 클래스를 사용하는 방법 (using JpaRepository multiple class)</title>
      <link>https://seorinari.tistory.com/10</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;spring-boot-spring-data-jpa.png&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/cl9RT6/btqxoooL0qU/mONvKZ7IPOa5I1XNE7kD91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl9RT6/btqxoooL0qU/mONvKZ7IPOa5I1XNE7kD91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl9RT6/btqxoooL0qU/mONvKZ7IPOa5I1XNE7kD91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl9RT6%2FbtqxoooL0qU%2FmONvKZ7IPOa5I1XNE7kD91%2Fimg.png&quot; data-filename=&quot;spring-boot-spring-data-jpa.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;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;JpaRepository를 상속받아 사용 할 경우 상속시 제네릭 인자로 던진 클래스만 JpaRepository의 persist가 가능해진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정확하게는 1depth child까지만.&lt;/p&gt;
&lt;p&gt;JpaRepository사용시 인자클래스와 PK type을 받는데 pk 조회의 기준은 인자 클래스가 된다.&lt;/p&gt;
&lt;p&gt;그렇기때문에 OneToOne이든, OneToMany든 자식엔티티 까지는 해당 PK Column이 존재하기때문에 JpaRepository에서 접근이 가능하지만, 손자까지는 사용이 불가능한것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다고 손자와 인자 클래스간 관계를 맺어 줄 사람은 없다고 생각한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;보통은 이런경우 Repository를 모두 분리한다.&lt;/p&gt;
&lt;p&gt;ParentRepository, ChildRepository, GrandChildRepository ... 와 같이.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;현재 프로젝트의 구조는 One Repository, One Service, One Controller.&lt;/p&gt;
&lt;p&gt;Repository를 분리해서는 안된다는 강박관념에 휩싸이기 시작했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇게 찾은 방법은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;persist&lt;/span&gt; 할 수 없는 상황을 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 현재 Entity의 구조이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 부모클래스&lt;/p&gt;
&lt;pre id=&quot;code_1565464949258&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@Setter
@Table(
        name = &quot;TB_EVENT_INFO&quot;
)
public class TB_EVENT_INFO {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;EVENT_SEQ&quot;, nullable = false)
    private int eventSeq; // PK

    ...

    @OneToMany(fetch = FetchType.EAGER, mappedBy = &quot;tb_event_info&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&amp;lt;TB_EVENT_QUESTION_LIST&amp;gt; tb_event_question_lists; // 자식

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 자식클래스&lt;/p&gt;
&lt;pre id=&quot;code_1565465014702&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@Setter
@Table(
        name = &quot;TB_EVENT_QUESTION_LIST&quot;
)
public class TB_EVENT_QUESTION_LIST {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;QUESTION_SEQ&quot;, nullable = false)
    private int questionSeq; // PK

    @Column(name = &quot;EVENT_SEQ&quot;, nullable = false, insertable = false, updatable = false)
    private int eventSeq; // 부모 PK

    ...

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = &quot;EVENT_SEQ&quot;)
    private TB_EVENT_INFO tb_event_info; // 부모

    @OneToMany(fetch = FetchType.LAZY, mappedBy = &quot;tb_event_question_list&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&amp;lt;TB_EVENT_QUESTION_ANSWER_LIST&amp;gt; tb_event_question_answer_lists; // 자식

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 손자클래스&lt;/p&gt;
&lt;pre id=&quot;code_1565465082484&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@Setter
@Table(
        name = &quot;TB_EVENT_QUESTION_ANSWER_LIST&quot;
)
public class TB_EVENT_QUESTION_ANSWER_LIST {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;ANSWER_SEQ&quot;, nullable = false)
    private int answerSeq; // PK
    
    @Column(name = &quot;QUESTION_SEQ&quot;, nullable = false, insertable = false, updatable = false)
    private int questionSeq; // 부모 PK

    ...

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = &quot;QUESTION_SEQ&quot;)
    private TB_EVENT_QUESTION_LIST tb_event_question_list; // 부모 클래스

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;JpaRepository는 다음과 같이 사용하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1565465257506&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JpaRepository&amp;lt;TB_EVENT_INFO, Integer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이런 경우에는 JpaRepository의 save를 사용 할 경우 부모, 자식클래스는 정상적으로 작동하지만&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;손자클래스의 경우에는 에러를 뿜어낸다. TB_EVENT_INFO의 @Id로 매핑된 객체를 찾지 못했기 떄문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음과 같이 손자클래스를 수정하면 save 함수를 오버라이딩하여 사용 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 손자클래스&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1565465516152&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@Setter
@Table(
        name = &quot;TB_EVENT_QUESTION_ANSWER_LIST&quot;
)
public class TB_EVENT_QUESTION_ANSWER_LIST {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;ANSWER_SEQ&quot;, nullable = false)
    private int answerSeq; // PK
    
    @Column(name = &quot;QUESTION_SEQ&quot;, nullable = false, insertable = false, updatable = false)
    private int questionSeq; // 부모 PK
    
    @Transient
    private int eventSeq; // 할아버지의 PK를 추가, Transient 어노테이션을 통해 ddl, dml에서 제외시킴

    ...

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = &quot;QUESTION_SEQ&quot;)
    private TB_EVENT_QUESTION_LIST tb_event_question_list; // 부모 클래스

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- repository&lt;/p&gt;
&lt;pre id=&quot;code_1565465553483&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TB_EVENT_QUESTION_ANSWER_LIST save(TB_EVENT_QUESTION_ANSWER_LIST tb_event_question_answer_list);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 줄에도 명시해두었지만, 어디까지나 편법으로 사용하는 방법이다.&lt;/p&gt;
&lt;p&gt;가능하다면 Repository를 분리하여 운영 할 수 있도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝.&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>hibernate</category>
      <category>Spring</category>
      <category>SpringDataJPA</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/10</guid>
      <comments>https://seorinari.tistory.com/10#entry10comment</comments>
      <pubDate>Sun, 11 Aug 2019 04:34:03 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot Webflux - Spring Security Form Based Login &amp;amp;  AuthenticationManager Custom</title>
      <link>https://seorinari.tistory.com/9</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bppmda/btqw79YVHbO/cJXTF3kRhTMEWWWQWHvrX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bppmda/btqw79YVHbO/cJXTF3kRhTMEWWWQWHvrX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bppmda/btqw79YVHbO/cJXTF3kRhTMEWWWQWHvrX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbppmda%2Fbtqw79YVHbO%2FcJXTF3kRhTMEWWWQWHvrX1%2Fimg.png&quot; data-filename=&quot;1.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Spring Boot Webflux 환경에서 Security (form based / authentication manager customize)를 연동하는 방법에 대해 소개한다.&lt;/p&gt;
&lt;p&gt;기본적인&amp;nbsp;initializr과정은 생략한다.&lt;/p&gt;
&lt;p&gt;(webflux / security 필수)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기준버전은 2.1.6&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 글에서는 Spring Security 구성, ReactiveAuthenticationManager를 상속받은 AutenticationManager 구현, 로그인 실패시 처리하는&lt;/p&gt;
&lt;p&gt;ServerAuthenticationFailureHandler를 상속받은 LoginFailureHandler 구현만을 다루며 기본적인 사항은 다루지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;ReactiveAuthenticationManager를 상속받은 AutenticationManager를 구현하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;전체 코드는 아래와 같으며, DAO 및 Business Logic은 직접 구현해야 한다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1564316324634&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class AuthenticationManager implements ReactiveAuthenticationManager {

    
    @Resource (name = &quot;loginService&quot;)
    LoginService loginService; // business logic

    /**
     * 로그인 인증
     * ERR01 = 아이디 없음
     * ERR02 = 비밀번호 틀림
     * ERR03 = 탈퇴회원
     * ERR04 = 가입대기
     * ERR05 = 가입반려
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Mono&amp;lt;Authentication&amp;gt; authenticate(Authentication authentication) {
        String userId = (String)authentication.getPrincipal();
        String userPwd = (String) authentication.getCredentials();
        USER_LIST userList = new userList(userId, userPwd, &quot;inner&quot;);
        return loginService.getUser(userList)
                .switchIfEmpty(Mono.error(new BadCredentialsException(&quot;err01&quot;)))
                .flatMap(v -&amp;gt; {
                    if (&quot;Y&quot;.equals(v.getPwd_check())) {
                        if (&quot;Y&quot;.equals(v.getOutYn())) {
                            throw new BadCredentialsException(&quot;err03&quot;);
                        } else {
                            switch (v.getUserStatus()){
                                case &quot;UST_001&quot; :
                                    throw new BadCredentialsException(&quot;err04&quot;);
                                case &quot;UST_003&quot; :
                                    throw new BadCredentialsException(&quot;err05&quot;);
                            }
                            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userId, userPwd, Arrays.asList(new SimpleGrantedAuthority(&quot;ROLE_ADMIN&quot;)));
                            result.setDetails(loginService.getUserDetail(v));
                            return Mono.just(result);
                        }
                    } else {
                        throw new BadCredentialsException(&quot;err02&quot;);
                    }
                });
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 다음과 같이 작동된다.&lt;/p&gt;
&lt;pre id=&quot;code_1564316425365&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return loginService.getUser(userList)
                .switchIfEmpty(Mono.error(new BadCredentialsException(&quot;err01&quot;)))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비즈니스 레이어에서는 계정정보를 조회 한 뒤 USER_LIST 객체를 Mono형식으로 리턴해준다.&lt;/p&gt;
&lt;p&gt;USER_LIST 정보를 가지고올때, 반환값이 없을경우 예외처리를 실시한다.&lt;/p&gt;
&lt;p&gt;반환값이 없는 경우는 계정정보가 없을 경우이므로 그에맞는 에러코드를 메세지로 넘겨준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564316548633&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.flatMap(v -&amp;gt; {
                    if (&quot;Y&quot;.equals(v.getPwdCheck())) {
                        if (&quot;Y&quot;.equals(v.getOutYn())) {
                            throw new BadCredentialsException(&quot;err03&quot;);
                        } else {
                            switch (v.getUserStatus()){
                                case &quot;UST_001&quot; :
                                    throw new BadCredentialsException(&quot;err04&quot;);
                                case &quot;UST_003&quot; :
                                    throw new BadCredentialsException(&quot;err05&quot;);
                            }
                            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userId, userPwd, Arrays.asList(new SimpleGrantedAuthority(&quot;ROLE_ADMIN&quot;)));
                            result.setDetails(loginService.getUserDetail(v));
                            return Mono.just(result);
                        }
                    } else {
                        throw new BadCredentialsException(&quot;err02&quot;);
                    }
                });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;USER_LIST정보가 있을 경우에는 다음과같이 처리가 이루어진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 비밀번호가 일치 할 경우&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- 회원이 탈퇴 된 경우 : 예외처리&lt;/p&gt;
&lt;p&gt;&amp;nbsp; - 탈퇴된 회원이 아닐 경우&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp;- 가입승인 대기중인 회원인 경우 : 예외처리&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp;- 가입승인 반려된 회원인 경우 : 예외처리&lt;/p&gt;
&lt;p&gt;로그인 성공&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 비밀번호가 일치하지 않을 경우&lt;/p&gt;
&lt;p&gt;예외처리&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 처리는 예시일뿐이니, 각자 프로젝트의 요구사항에 맞게 구현하면 된다.&lt;/p&gt;
&lt;p&gt;중요한 부분은 다음이다.&lt;/p&gt;
&lt;pre id=&quot;code_1564316944888&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userId, userPwd, Arrays.asList(new SimpleGrantedAuthority(&quot;ROLE_ADMIN&quot;)));
result.setDetails(loginService.getUserDetail(v));
return Mono.just(result);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 부분은 기존 mvc security와 별 차이가 없다.&lt;/p&gt;
&lt;p&gt;비즈니스로직을 모두 거친 후&amp;nbsp;UsernamePasswordAuthenticationToken을 생성하고, details에 유저정보를 담은 후&lt;/p&gt;
&lt;p&gt;Mono&amp;lt;UsernamePasswordAuthenticationToken&amp;gt; 형식으로 리턴해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수의 반환값이&amp;nbsp;Mono&amp;lt;Authentication&amp;gt;이기 때문에,&amp;nbsp; flatMap을 통해 이를 상속받은&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Mono&amp;lt;UsernamePasswordAuthenticationToken&amp;gt;형식으로 리턴해주는것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 &lt;span style=&quot;color: #333333;&quot;&gt;AutenticationManager의 구성은 끝났다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음은 로그인 실패시 그에따른 사유를 출력해주는&amp;nbsp;LoginFailureHandler 클래스를 구현하자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317382495&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class LoginFailureHandler implements ServerAuthenticationFailureHandler {

    private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();

    @Override
    public Mono&amp;lt;Void&amp;gt; onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException e) {
        if(e instanceof BadCredentialsException){
            return redirectStrategy.sendRedirect(webFilterExchange.getExchange(), URI.create(&quot;/login?err=&quot;+e.getMessage()));
        }
        return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AuthenticationManager에서 구성한 예외처리 부분을 해당 부분에서 처리한다.&lt;/p&gt;
&lt;p&gt;예외처리에 따른 상세로직은 프로젝트 요구사항에 맞게 구현하도록 하자.&lt;/p&gt;
&lt;p&gt;해당 소스에서는 예외발생시 /login으로 리다이렉트 하고, parameter에 에러메세지를 같이 전달하도록 하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 위 클래스들을 실제로 사용하게 될 SecurityConfig 클래스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317518447&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@ComponentScan(basePackages = {&quot;package...&quot;})
public class SecurityConfiguration {
    
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
    
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http.authorizeExchange()
            .pathMatchers(&quot;/pattern/**&quot;)
                .hasRole(&quot;ADMIN&quot;)
            .anyExchange()
                .permitAll()
            .and()
            .httpBasic()
            .and()
            .addFilterAt(new MultipartCsrfFilter(), SecurityWebFiltersOrder.FIRST)
            .csrf()
            .and()
            .formLogin()
                .loginPage(&quot;/login&quot;)
                .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(&quot;/url&quot;))
                .authenticationFailureHandler(loginFailureHandler)
            .and()
            .logout()
                .logoutUrl(&quot;/logout&quot;)
                .logoutSuccessHandler(logoutSuccessHandler(&quot;/login&quot;))
            .and()
            .authenticationManager(authenticationManager)
            .exceptionHandling()
                .accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
            .and()
            .build();
    }
    
    public ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {
        RedirectServerLogoutSuccessHandler successHandler = new RedirectServerLogoutSuccessHandler();
        successHandler.setLogoutSuccessUrl(URI.create(uri));
        return successHandler;
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317579024&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ComponentScan(basePackages = {&quot;package...&quot;})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선 해당 부분에서 위 작성한 컴포넌트들을 스캔 할 수 있도록 지정해주어야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317664597&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private LoginFailureHandler loginFailureHandler;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 후 @Autowired를 통해 컴포넌트를 와이어링 해주자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317711760&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {
        RedirectServerLogoutSuccessHandler successHandler = new RedirectServerLogoutSuccessHandler();
        successHandler.setLogoutSuccessUrl(URI.create(uri));
        return successHandler;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LogoutSuccessHandler를 함수로 구현하였다.&lt;/p&gt;
&lt;p&gt;단순히 로그아웃 성공시 Redirect만을 수행하면 되므로, 상속받아 별도의 컴포넌트로 구현하지는 않았다.&lt;/p&gt;
&lt;p&gt;만약 그러한 상황이 발생한다면, LoginFailureHandler와 같이 상속받아 구성하도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564317801904&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http.authorizeExchange()
            .pathMatchers(&quot;/pattern/**&quot;)
                .hasRole(&quot;ADMIN&quot;)
            .anyExchange()
                .permitAll()
            .and()
            .addFilterAt(new MultipartCsrfFilter(), SecurityWebFiltersOrder.FIRST)
            .csrf()
            .and()
            .formLogin()
                .loginPage(&quot;/login&quot;)
                .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(&quot;/url&quot;))
                .authenticationFailureHandler(loginFailureHandler)
            .and()
            .logout()
                .logoutUrl(&quot;/logout&quot;)
                .logoutSuccessHandler(logoutSuccessHandler(&quot;/login&quot;))
            .and()
            .authenticationManager(authenticationManager)
            .exceptionHandling()
                .accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
            .and()
            .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 부분에서&amp;nbsp;SecurityWebFilterChain 빈을 주입한다.&lt;/p&gt;
&lt;p&gt;권한롤에 따른 url매핑이나 등등등.. 기존 mvc security와 큰 차이는 없지만 체이닝 함수명 변동이나 기능이 변경된 부분들이 일부 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중요한 부분은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564318269700&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.addFilterAt(new MultipartCsrfFilter(), SecurityWebFiltersOrder.FIRST)
.csrf()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;csrf 활성화&lt;/p&gt;
&lt;p&gt;자세한 사항은 아래 글 참고&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://seorinari.tistory.com/7&quot;&gt;https://seorinari.tistory.com/7&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1564318627966&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://seorinari.tistory.com/7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-original-url=&quot;https://seorinari.tistory.com/7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eJrmC/hyCbPe819d/bGhsKtnAj7Cr6BuJZXLmfK/img.png?width=720&amp;amp;height=401&amp;amp;face=0_0_720_401,https://scrap.kakaocdn.net/dn/bQcRT1/hyB9VH237l/KZsJTa5kfx7WuJwETjgPS1/img.png?width=2470&amp;amp;height=1150&amp;amp;face=0_0_2470_1150,https://scrap.kakaocdn.net/dn/zjoMI/hyB92HavxI/2nfKk7voH3Tk21XQn2ZFOK/img.png?width=1750&amp;amp;height=1150&amp;amp;face=0_0_1750_1150');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Spring Boot WebFlux - Multipart(multipart/form-data) 전송시 Security CSRF(XSRF) Filter 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;WebFlux로 프로젝트를 진행하던 중, Multipart로 전송시 Spring Security에 설정한 CSRF 관련 사항과 충돌이 발생하여 이에대한 대응책을 남긴다. (invalid csrf token) Multipart의 경우 FormData가 ServerReque..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;seorinari.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564318314119&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.formLogin()
	.loginPage(&quot;/login&quot;)
	.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(&quot;/url&quot;))
	.authenticationFailureHandler(loginFailureHandler)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;form 로그인 사용 및 로그인 URL(실제 프로세스가 수행될 URL) 정의&lt;/p&gt;
&lt;p&gt;그리고 로그인성공시 작동될 핸들러와 로그인이 실패 할 경우 작동될 핸들러를 정의한다.&lt;/p&gt;
&lt;p&gt;로그인성공시에는 별 다른 액션 없이 redirect되도록&amp;nbsp;RedirectServerAuthenticationSuccessHandler를 등록하였다.&lt;/p&gt;
&lt;p&gt;실패시에는 위에서 정의한 핸들러를 등록하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564318502228&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.logout()
    .logoutUrl(&quot;/logout&quot;)
    .logoutSuccessHandler(logoutSuccessHandler(&quot;/login&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그아웃 URL과 로그아웃 성공시 작동될 핸들러를 정의한다.&lt;/p&gt;
&lt;p&gt;위에서 작성한 함수를 등록하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564318448323&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.authenticationManager(authenticationManager)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 중요한 부분인 authenticationManager를 정의해준다.&lt;/p&gt;
&lt;p&gt;해당 부분을 정의함으로써 우리가 작성한 CustomAuthenticationManager가 작동되게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 컨트롤러와 thymeleaf를 통해 로그인페이지를 구성하도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564319025728&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/login&quot;)
public String loginForm(ServerHttpRequest request,
                        Model model) {
	model.addAllAttributes(request.getQueryParams().toSingleValueMap());
	return &quot;service/login&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1564319070108&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div th:switch=&quot;${err}&quot;&amp;gt;
    &amp;lt;p th:case=&quot;'err01'&quot;&amp;gt;아이디가 존재하지 않습니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&quot;'err02'&quot;&amp;gt;비밀번호가 올바르지 않습니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&quot;'err03'&quot;&amp;gt;탈퇴 된 회원 입니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&quot;'err04'&quot;&amp;gt;승인 대기중 입니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&quot;'err05'&quot;&amp;gt;반려 된 회원 입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;form th:action=&quot;@{/login}&quot; th:method=&quot;post&quot;&amp;gt;
    &amp;lt;input th:name=&quot;username&quot; placeholder=&quot;아이디를 입력하세요.&quot;/&amp;gt;
    &amp;lt;input th:name=&quot;password&quot; type=&quot;password&quot; placeholder=&quot;비밀번호를 입력하세요.&quot;/&amp;gt;
    &amp;lt;button type=&quot;submit&quot;&amp;gt;로그인&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(에러코드 처리의 경우 공통 메세지를 통해 처리하도록 하자. 해당 글에서 언급 할 주제는 아니므로 생략.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;완성!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 간략한 테스트 결과이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.13.12.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KsJZv/btqw8acviV1/GUEuriOjsK477cWusgHpyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KsJZv/btqw8acviV1/GUEuriOjsK477cWusgHpyk/img.png&quot; data-alt=&quot;존재하지 않는 아이디를 입력했을 경우 loginFailureHandler에서의 브레이크포인트. 요청이 /login에서 넘어옴을 알 수 있으며, err01메세지도 함께 넘어옴을 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KsJZv/btqw8acviV1/GUEuriOjsK477cWusgHpyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKsJZv%2Fbtqw8acviV1%2FGUEuriOjsK477cWusgHpyk%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.13.12.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;존재하지 않는 아이디를 입력했을 경우 loginFailureHandler에서의 브레이크포인트. 요청이 /login에서 넘어옴을 알 수 있으며, err01메세지도 함께 넘어옴을 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.16.14.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6YcAG/btqw4m6MRve/PmTTG28fP0SIki44tYYy91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6YcAG/btqw4m6MRve/PmTTG28fP0SIki44tYYy91/img.png&quot; data-alt=&quot;Handler 이후의 로그. 302 redirect로 /login?err=err01을 호출함을 알 수 있다.&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6YcAG/btqw4m6MRve/PmTTG28fP0SIki44tYYy91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6YcAG%2Fbtqw4m6MRve%2FPmTTG28fP0SIki44tYYy91%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.16.14.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Handler 이후의 로그. 302 redirect로 /login?err=err01을 호출함을 알 수 있다.&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.17.51.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddZ7L0/btqw56oPueU/cMfrvu72IRcYrinWOUaeL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddZ7L0/btqw56oPueU/cMfrvu72IRcYrinWOUaeL1/img.png&quot; data-alt=&quot;뷰페이지에서의 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddZ7L0/btqw56oPueU/cMfrvu72IRcYrinWOUaeL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddZ7L0%2Fbtqw56oPueU%2FcMfrvu72IRcYrinWOUaeL1%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-28 오후 10.17.51.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;뷰페이지에서의 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그인에 성공 할 경우 Controller에서&amp;nbsp;&lt;span&gt;Authentication나&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;@AuthenticationPrincipal를&lt;/span&gt;&amp;nbsp;인자로 받거나,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;ReactiveSecurityContextHolder를 통해 principal 정보를 확인 할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;끝&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>Spring</category>
      <category>Spring Security</category>
      <category>spring webflux</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/9</guid>
      <comments>https://seorinari.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 28 Jul 2019 22:24:20 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot WebFlux - Multipart(multipart/form-data) 전송시 Security CSRF(XSRF) Filter 설정</title>
      <link>https://seorinari.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;1.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UddBG/btqwXHpLiIA/GLSQwwCBmkSXT71INOXpLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UddBG/btqwXHpLiIA/GLSQwwCBmkSXT71INOXpLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UddBG/btqwXHpLiIA/GLSQwwCBmkSXT71INOXpLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUddBG%2FbtqwXHpLiIA%2FGLSQwwCBmkSXT71INOXpLK%2Fimg.png&quot; data-filename=&quot;1.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;WebFlux로 프로젝트를 진행하던 중, Multipart로 전송시&amp;nbsp; Spring Security에 설정한 CSRF 관련 사항과 충돌이 발생하여 이에대한 대응책을 남긴다.&lt;/p&gt;
&lt;p&gt;(invalid&amp;nbsp;csrf&amp;nbsp;token)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Multipart의 경우 FormData가 ServerRequest에 QueryParameter로 들어오지 않기 때문에,&lt;/p&gt;
&lt;p&gt;클라이언트 - CSRF Filter 사이에 중개필터를 하나 두고 필터 순서를 변경하여 CSRF관련 작업을 처리해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업순서는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 중개필터 작성&lt;/p&gt;
&lt;p&gt;2. Spring Security에 중개필터 추가 (Filter Order는 CSRF Filter 이전에 위치하도록 순서지정)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 중개필터를 작성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563941999967&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class MultipartCsrfFilter implements WebFilter {
    
    @Override
    public Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange serverWebExchange,
                             WebFilterChain webFilterChain) {
        if(!HttpMethod.GET.equals(serverWebExchange.getRequest().getMethod())){
            return serverWebExchange.getMultipartData().flatMap(v -&amp;gt; {
                Map&amp;lt;String, Part&amp;gt; map = v.toSingleValueMap();
                if(map.get(&quot;_csrf&quot;) != null){
                    serverWebExchange.getRequest()
                        .mutate()
                        .header(&quot;X-CSRF-TOKEN&quot;, ((FormFieldPart) map.get(&quot;_csrf&quot;)).value())
                        .build();
                }
                return webFilterChain.filter(serverWebExchange);
            });
        }else{
            return webFilterChain.filter(serverWebExchange);
        }
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Security 설정시 별다른 설정을 하지 않았다면 default token repository인&amp;nbsp;WebSessionServerCsrfTokenRepository 클래스의 정보를 따라간다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;-&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;WebSessionServerCsrfTokenRepository&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1563942454044&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WebSessionServerCsrfTokenRepository implements ServerCsrfTokenRepository {
    private static final String DEFAULT_CSRF_PARAMETER_NAME = &quot;_csrf&quot;;
    private static final String DEFAULT_CSRF_HEADER_NAME = &quot;X-CSRF-TOKEN&quot;;
    private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = WebSessionServerCsrfTokenRepository.class.getName().concat(&quot;.CSRF_TOKEN&quot;);
    private String parameterName = &quot;_csrf&quot;;
    private String headerName = &quot;X-CSRF-TOKEN&quot;;
    private String sessionAttributeName;

    ...
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 소스코드를 보다시피, default header name은 'X-CSRF-TOKEN'이며 paramater name은 '_csrf'이다.&lt;/p&gt;
&lt;p&gt;변경사항이 있다면 그에따라 필터의 내용도 변경하면 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중개필터의 내용은 단순히 GET요청이 아니면서, _csrf로 들어온 parameter가 있을 경우 헤더에 CSRF값을 담아주고&lt;/p&gt;
&lt;p&gt;다음필터로 넘기는 내용이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 Security설정이다.&lt;/p&gt;
&lt;pre id=&quot;code_1563942712749&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@ComponentScan(basePackages = {........})
public class SecurityConfiguration {
    
    // custom manager
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http.authorizeExchange()
            ... ...
            .addFilterAt(new MultipartCsrfFilter(), SecurityWebFiltersOrder.FIRST)
            .csrf()
            .and()
            .build();
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ServerHttpSecurity의 addFilterAt 함수를 통해 중개필터를 등록한다.&lt;/p&gt;
&lt;p&gt;이때 필터 순서는 가장 먼저 올 수 있도록 지정&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와같이 작성하고 실행하면 다음과 같이 프로세스가 흘러감을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;-&amp;nbsp;&lt;span&gt;MultipartCsrfFilter&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.34.50.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6DbGH/btqwZ0aEGhB/5xgFBEBoP6QKxeAaKu5EQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6DbGH/btqwZ0aEGhB/5xgFBEBoP6QKxeAaKu5EQ0/img.png&quot; data-alt=&quot;Security에 등록한 순서에 맞게 중개필터가 위치해 있음을 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6DbGH/btqwZ0aEGhB/5xgFBEBoP6QKxeAaKu5EQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6DbGH%2FbtqwZ0aEGhB%2F5xgFBEBoP6QKxeAaKu5EQ0%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.34.50.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Security에 등록한 순서에 맞게 중개필터가 위치해 있음을 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.40.46.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boLvZY/btqw0JFZ7Fy/HIX49tNkssc4YuEWi0o9n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boLvZY/btqw0JFZ7Fy/HIX49tNkssc4YuEWi0o9n0/img.png&quot; data-alt=&quot;request multipart data 중 csrf 매개변수가 있음을 알 수 있다. 해당 변수가 있으니, request header에 token정보를 넣고 다음 필터로 넘길것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boLvZY/btqw0JFZ7Fy/HIX49tNkssc4YuEWi0o9n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboLvZY%2Fbtqw0JFZ7Fy%2FHIX49tNkssc4YuEWi0o9n0%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.40.46.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;request multipart data 중 csrf 매개변수가 있음을 알 수 있다. 해당 변수가 있으니, request header에 token정보를 넣고 다음 필터로 넘길것이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- CsrfWebFilter&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.37.27.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yQ9D9/btqwYUoo5Gk/pNTKKKsaYZKLEVotIbtKX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yQ9D9/btqwYUoo5Gk/pNTKKKsaYZKLEVotIbtKX0/img.png&quot; data-alt=&quot;CSRF검증시 ServerWebExchange의 request header에 CSRF관련 부분이 포함되었음을 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yQ9D9/btqwYUoo5Gk/pNTKKKsaYZKLEVotIbtKX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyQ9D9%2FbtqwYUoo5Gk%2FpNTKKKsaYZKLEVotIbtKX0%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-24 오후 1.37.27.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;/&gt;&lt;/span&gt;&lt;figcaption&gt;CSRF검증시 ServerWebExchange의 request header에 CSRF관련 부분이 포함되었음을 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 WebFlux환경에서의 Multipart CSRF검증 로직은 끝이난다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Security 설정 부분에서 Filter 순서를 변경하지 않았다면 CsrfWebFilter가 상위에 위치하기 때문에 403 status와 함께&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;invalid&amp;nbsp;csrf&amp;nbsp;token가 출력되며,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;중개필터에서 헤더에 올바른 token name을 넣지 않았다면 마찬가지로 CsrfWebFilter에서 토큰을 찾지 못했으므로 &lt;span style=&quot;color: #333333;&quot;&gt;invalid&amp;nbsp;csrf&amp;nbsp;token가 출력되게 된다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>CSRF</category>
      <category>multipart</category>
      <category>Spring</category>
      <category>Spring Boot</category>
      <category>spring webflux</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/7</guid>
      <comments>https://seorinari.tistory.com/7#entry7comment</comments>
      <pubDate>Wed, 24 Jul 2019 13:47:40 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot WebFlux - Thymeleaf 에러 핸들링</title>
      <link>https://seorinari.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;spring-boot-logo.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb0KtF/btqwPYcWygP/YnLLTaPj6pzW7EuY8kFNSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb0KtF/btqwPYcWygP/YnLLTaPj6pzW7EuY8kFNSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb0KtF/btqwPYcWygP/YnLLTaPj6pzW7EuY8kFNSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb0KtF%2FbtqwPYcWygP%2FYnLLTaPj6pzW7EuY8kFNSk%2Fimg.png&quot; data-filename=&quot;spring-boot-logo.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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Spring Boot WebFlux - Thymeleaf 환경에서 에러 핸들링 하는 방법을 소개한다.&lt;/p&gt;
&lt;p&gt;아래는 참고한 가이드&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.baeldung.com/spring-webflux-errors&quot;&gt;https://www.baeldung.com/spring-webflux-errors&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1563343386989&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/spring-webflux-errors&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-original-url=&quot;https://www.baeldung.com/spring-webflux-errors&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g8Udh/hyB1JADtgJ/K3oOkQzl2Czk2pglVtDdp1/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Handling Errors in Spring WebFlux | Baeldung&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Have a look at different methods to gracefully handle errors in Spring Webflux.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.baeldung.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;하지만 위 예제는 error status에 따른 json return만 구현되어있기 때문에,&lt;/p&gt;
&lt;p&gt;우리가 통상적으로 생각하는 404/500에 대한 error 처리를 하기 위해서는 추가적인 작업이 필요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;완성된 코드는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563344120949&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@Order(-2)
public class ExceptionHandlerConfiguration extends AbstractErrorWebExceptionHandler {
    
    
    public ExceptionHandlerConfiguration(ErrorAttributes errorAttributes,
                                         ResourceProperties resourceProperties,
                                         ApplicationContext applicationContext,
                                         ObjectProvider&amp;lt;List&amp;lt;ViewResolver&amp;gt;&amp;gt; viewResolversProvider,
                                         ServerCodecConfigurer configurer) {
        super(errorAttributes, resourceProperties, applicationContext);
        this.setMessageWriters(configurer.getWriters());
        this.setViewResolvers(viewResolversProvider.getIfAvailable());
    }
    
    @Override
    protected RouterFunction&amp;lt;ServerResponse&amp;gt; getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    
    private Mono&amp;lt;ServerResponse&amp;gt; renderErrorResponse(ServerRequest request) {
        Map&amp;lt;String, Object&amp;gt; errorPropertiesMap = getErrorAttributes(request, false);
        
        /*int status = (int) errorPropertiesMap.get(&quot;status&quot;);
        
        // status에 따른 분기처리 (html view)
        if(status &amp;gt;= 500){
            return ok().render(&quot;error/500&quot;);
        }else{
            return ok().render(&quot;error/404&quot;);
        }*/
    
        // json return
        return ServerResponse.status(HttpStatus.BAD_REQUEST)
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .body(BodyInserters.fromObject(errorPropertiesMap));
        
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563344244450&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@Order(-2)
public class ExceptionHandlerConfiguration extends AbstractErrorWebExceptionHandler {
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;configure의 순서를 정렬한다.&lt;/p&gt;
&lt;p&gt;Spring Boot에서&amp;nbsp;DefaultErrorWebExceptionHandler가 -1,&amp;nbsp;&lt;span&gt;ResponseStatusExceptionHandler가 0으로 설정되어 있기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;-2로 내려야 위 요소들보다 먼저 배치되게 된다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563344380710&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public ExceptionHandlerConfiguration(ErrorAttributes errorAttributes,
                                         ResourceProperties resourceProperties,
                                         ApplicationContext applicationContext,
                                         ObjectProvider&amp;lt;List&amp;lt;ViewResolver&amp;gt;&amp;gt; viewResolversProvider,
                                         ServerCodecConfigurer configurer) {
        super(errorAttributes, resourceProperties, applicationContext);
        this.setMessageWriters(configurer.getWriters());
        this.setViewResolvers(viewResolversProvider.getIfAvailable());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;생성자에서&amp;nbsp;AbstractErrorWebExceptionHandler의 생성자를 호출하고,&lt;/p&gt;
&lt;p&gt;그 아래에 MessageWriter와 ViewResolver를 정의한다. (이게 가장 핵심)&lt;/p&gt;
&lt;p&gt;인자로 List&amp;lt;ViewResolver&amp;gt;를 받아오는데, 이때 사용중인 템플릿 엔진이 Thymeleaf라면 ThymeleafResolver가&lt;/p&gt;
&lt;p&gt;다른 템플릿 엔진이라면 다른 리졸버가 들어올것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 부분을 정의하지 않으면&amp;nbsp;DefaultRenderingResponseBuilder.writeToInternal에서 ViewResolver를 찾지 못하기 때문에&lt;/p&gt;
&lt;p&gt;500에러가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563344729829&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected RouterFunction&amp;lt;ServerResponse&amp;gt; getRoutingFunction(ErrorAttributes errorAttributes) {
	return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라우팅 부분이다.&lt;/p&gt;
&lt;p&gt;모든 요청에 대해 받되, ErrorAttributes 인자가 있을 경우&amp;nbsp;renderErrorResponse 함수가 실행되게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563344818140&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Mono&amp;lt;ServerResponse&amp;gt; renderErrorResponse(ServerRequest request) {
        Map&amp;lt;String, Object&amp;gt; errorPropertiesMap = getErrorAttributes(request, false);
        
        /*int status = (int) errorPropertiesMap.get(&quot;status&quot;);
        
        // status에 따른 분기처리 (html view)
        if(status &amp;gt;= 500){
            return ok().render(&quot;error/500&quot;);
        }else{
            return ok().render(&quot;error/404&quot;);
        }*/
    
        // json return
        return ServerResponse.status(HttpStatus.BAD_REQUEST)
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .body(BodyInserters.fromObject(errorPropertiesMap));
        
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 부분에서 실질적인 에러 처리를 진행한다.&lt;/p&gt;
&lt;p&gt;주석처리 된 부분은 템플릿 엔진을 사용중일 경우 해당 html view가 노출되도록 return하는 부분이며,&lt;/p&gt;
&lt;p&gt;최하단의 return은 JSON형식으로 에러정보가 return된다.&lt;/p&gt;
&lt;p&gt;(필자의 경우에는 Thymeleaf를 사용중이기 때문에, error/404를 호출시 실제로는 reousrces/templates/error/404를 바라보게 된다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;에러코드에 대한 정의와 어떤 템플릿을 보여줄것인지, 어떤 요청이 들어왔을때 어떻게 리턴해줄것인지는&lt;/p&gt;
&lt;p&gt;각 사용자가 입맛에 맞게끔 구현 해 사용하면 될것이다.&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>Spring</category>
      <category>Spring Boot</category>
      <category>Thymeleaf</category>
      <category>WebFlux</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/6</guid>
      <comments>https://seorinari.tistory.com/6#entry6comment</comments>
      <pubDate>Wed, 17 Jul 2019 15:29:52 +0900</pubDate>
    </item>
    <item>
      <title>ERROR] object references an unsaved transient instance - save the transient instance before flushing</title>
      <link>https://seorinari.tistory.com/4</link>
      <description>&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CA0E495CB3E41D2C&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;@ManyToOne 관계에서 위와같은 에러가 발생 할 경우&lt;br /&gt;기존 save 도메인에 ManyToOne으로 관계가 맺어진 객체가 생성되어있지 않은지 확인해봐야 한다.&lt;br /&gt;객체가 생성되어있을 경우 ID가 부여되므로 위와같은 에러가 발생.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/9549098/transientobjectexception-object-references-an-unsaved-transient-instance-save&quot;&gt;https://stackoverflow.com/questions/9549098/transientobjectexception-object-references-an-unsaved-transient-instance-save&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발 이야기/SPRING</category>
      <category>hibernate</category>
      <category>JPA</category>
      <category>spring-data-jpa</category>
      <category>spring-jpa</category>
      <author>최서리나리</author>
      <guid isPermaLink="true">https://seorinari.tistory.com/4</guid>
      <comments>https://seorinari.tistory.com/4#entry4comment</comments>
      <pubDate>Mon, 15 Apr 2019 10:55:24 +0900</pubDate>
    </item>
  </channel>
</rss>