<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>출근하느라 바쁜 쭈니어</title>
    <link>https://do-ha-computer.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 12:20:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>짱쭈니어</managingEditor>
    <image>
      <title>출근하느라 바쁜 쭈니어</title>
      <url>https://tistory1.daumcdn.net/tistory/4481846/attach/6863bcf9c8fe49e6aa2dc66beafe1f36</url>
      <link>https://do-ha-computer.tistory.com</link>
    </image>
    <item>
      <title>AWS ) LocalStack 을 활용한 AWS 환경 구축</title>
      <link>https://do-ha-computer.tistory.com/entry/AWS-LocalStack-%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-AWS-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;AWS LocalStack 이란&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AWS 클라우드 환경을 로컬에서 흉내 낼 수 있게 해주는 오픈소스 플랫폼&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;즉 AWS 에서 제공하는 서비스들을 &lt;b&gt;AWS 계정 없이, 인터넷없이, 비용없이 로컬&lt;/b&gt;에서 사용가능하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;SQS&lt;/li&gt;
&lt;li&gt;SNS&lt;/li&gt;
&lt;li&gt;DynamoDB&lt;/li&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;API Gateway&lt;/li&gt;
&lt;li&gt;CloudWatch Logs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;aws CLI 와 100프로 동일하게 동작하는 awslocal 명령어도 포함되어있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점  &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비용절감 : 실제 aws 를 사용하는게 아니기에, 무료로 사용이 가능하다.&lt;/li&gt;
&lt;li&gt;보안측면 : aws 실제 자격증명 (access keyd, secret key) 을 사용하지 않아서 테스트 중에 실수로 키 노출될 위험이 줄어든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동작방식  ️&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker 위에서 AWS API 와 동일한 형태의 Mock 서버를 띄운다.&lt;/li&gt;
&lt;li&gt;예 ) 로컬에서 S3 요청을 보내면 실제 AWS 가 아닌 내가 띄어놓은 LocalStack의 S3 모듈이 응답한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;로컬환경 - LocalStack 실행&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;&amp;nbsp;공식 이미지 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1767520113744&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --rm -it \
  -p 4566:4566 \
  -p 4571:4571 \
  localstack/localstack&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.직접 docker-compose.yml 생성 &amp;amp; 작성&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 폴더 생성 mkdir localstack-test&lt;/li&gt;
&lt;li&gt;docker-compose.yml 생성 &amp;amp; 작성&lt;/li&gt;
&lt;li&gt;docker-compose up -d 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1767520163928&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.8&quot; // 위 파일은 compose 스펙 3.8 기준 

services:
  localstack:  // localstack 컨테이너 정의 
    container_name: &quot;localstack&quot; // 컨테이너 이름 
    image: localstack/localstack:0.14.3 //사용할 도커 이미지
    ports:
      - &quot;4566:4566&quot; //s3, sqs 이든 4566  하나 포트 사용 
    environment: //환경변수 설정 시작
    //localstack에서 사용할 aws서비스
      - SERVICES=sns,sqs,s3,dynamodb,ssm,secretsmanager 
      - DEBUG=${DEBUG-} //디버그 설정 여부 
      - DATA_DIR=${DATA_DIR-} // 데이터 영속적으로 저장할 디렉터리 지정용
      - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
      - DOCKER_HOST=unix:///var/run/docker.sock
      - AWS_ACCESS_KEY_ID=test
      - AWS_SECRET_ACCESS_KEY=test
      - AWS_DEFAULT_REGION=us-east-1
    volumes:
      - &quot;${TMPDIR:-/tmp}/localstack:/tmp/localstack&quot;
      - &quot;/var/run/docker.sock:/var/run/docker.sock&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table id=&quot;2aad7e38-1a1b-80cf-ab67-c38ad0e3ca12&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-801a-8b63-e0f545ce75da&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;${VAR-}&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;VAR이 설정되어 있으면 그 값, 없으면 빈 문자열이라는 쉘 문법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-800a-bcdc-cdf1744480d4&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;- SERVICES=sns,sqs,s3,dynamodb,ssm,secretsmanager&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;여섯 가지를 Localstack 에서 활성화하겠다. (mock 으로 띄우겠다)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-8098-a64a-fe619d8d1c9d&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;DATA_DIR=${DATA_DIR-}&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;컨테이너를 재시작하여도 리소스(S3 버킷/테이블) 유지할 수 있다 .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-8099-8f29-d6556b707c98&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;호스트의 임시 폴더 위치를 LocalStack에게 알려주는 설정&lt;br /&gt;/tmp/localstack 또는&amp;lt;TMPDIR&amp;gt;/localstack&lt;br /&gt;&lt;br /&gt;Localstack 이 내부에서 임시파일/마운트용으로 참고하는 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-80b0-a404-f2b43ae769bc&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;DOCKER_HOST=unix:///var/run/docker.sock&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;LocalStack이 &lt;b&gt;호스트의 Docker 데몬에 접근할 수 있게&lt;/b&gt; 하는 설정.&lt;br /&gt;&lt;br /&gt;- Localstack 이 내부에서 또 다른 컨테이너를 띄우거나, 도커 기능을 사용할 수 있는데 그 때 호스트의 도커 소캣으로 연결하기 위함&lt;br /&gt;&lt;br /&gt;- 도커 소캣을 열어주는건 호스트 제어 권한을 넘겨주는 수준이여서 보안적으로 위험하지만, 로컬개발용으론 자주 쓰이는 패턴 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;그래도, 컨테이너가 호스트 Docker를 마음대로 만질 수 있다&amp;rdquo;는 건 인지하자&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-800c-a105-c1ecfcb63ef7&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;- AWS_ACCESS_KEY_ID=test&lt;br /&gt;- AWS_SECRET_ACCESS_KEY=test&lt;br /&gt;- AWS_DEFAULT_REGION=us-east-1&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;AWS가 아니라 LocalStack이기 때문에&lt;br /&gt;&lt;b&gt;아무 문자열이나 써도 완전 상관 없음&lt;/b&gt; (test, dummy 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2aad7e38-1a1b-8034-a619-e4e77d16ac95&quot;&gt;
&lt;td id=&quot;|\lZ&quot;&gt;volumes:&lt;br /&gt;- &quot;${TMPDIR:-/tmp}/localstack:/tmp/localstack&quot;&lt;br /&gt;- &quot;/var/run/docker.sock:/var/run/docker.sock&quot;&lt;/td&gt;
&lt;td id=&quot;=nU|&quot;&gt;1. 첫번째 라인&lt;br /&gt;내 컴퓨터 /tmp/localstack 폴더에 localstack /tmp/localstack 데이터저장&lt;br /&gt;&lt;br /&gt;2. 두번째 라인&lt;br /&gt;LocalStack 컨테이너가&lt;b&gt;호스트의 Docker를 조작할 수 있게 만드는 것&lt;/b&gt;.&lt;br /&gt;(실제 서버 환경에서 사용하지 말것!!)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;LocalStack 내부 구성&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[LocalStack 컨테이너 1개] &lt;br /&gt;├─ LocalStack 메인 프로세스 &lt;br /&gt;├─ 모듈: S3 에뮬레이터 &lt;br /&gt;├─ 모듈: SQS 에뮬레이터&lt;br /&gt;├─ 모듈: SNS 에뮬레이터 &lt;br /&gt;└─ 모듈: DynamoDB 에뮬레이터&lt;/blockquote&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 컨테이너 안에서 &lt;b&gt;여러 AWS 기능&lt;/b&gt;을 모듈로 실행 (안에서 실행)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LocalStack 이 Docker 에 접근하는 이유는?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 서비스 ( 특히 Lambda : 클라우드 서비스) 실행시에 &lt;b&gt;나의 도커를 활용하여서 추가 컨테이너를 자동으로 실행할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;위의 케이스로 인해 LocalStack 에게 나의 &amp;ldquo;도커 조작 권한&amp;rdquo; 을 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AWSLocalStack</category>
      <category>localstack</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/118</guid>
      <comments>https://do-ha-computer.tistory.com/entry/AWS-LocalStack-%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-AWS-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95#entry118comment</comments>
      <pubDate>Sun, 4 Jan 2026 18:52:44 +0900</pubDate>
    </item>
    <item>
      <title>Spring Cloud - Gateway 에 대해 알아보자</title>
      <link>https://do-ha-computer.tistory.com/entry/Spring-Cloud-GateWay-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;MSA 환경에서 ClientA 는 A 서버로, ClientB 는 B서버로 동적라우팅 이 필요하다&lt;br /&gt;  Spring Cloud Gateway 를 통해 동적라우팅을 구현할 수 있다&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;Spring MVC vs Spring WebFlux&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring MVC : 전통적인 동기(블로킹) 웹 프레임워크&lt;/li&gt;
&lt;li&gt;SpringWebFlux : 비동기(논블로킹) 리액티브 웹 프레임워크
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액티브 : 요청을 보내고 결과를 기다리지 않은 채 다음일을 먼저 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 128px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구분&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring MVC&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring WebFlux&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;처리방식&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;동기(Blocking)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;비동기(Non-blocking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;대표의존성&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;spring-boot-starter-web&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;spring-boot-starter-webflux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;HTTP server&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기본 : 톰캣&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;기본 : Netty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;변수 주요 타입&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;String, List&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Mono, Flux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요청 처리방법&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요청마다 쓰레드 할당&lt;br /&gt;하나의 요청이 들어왔을 때 스레드 하나가 끝날때까지 처리하는 방식&lt;br /&gt;요청이 끝날때까지 스레드를 점유 (DB 조회할때 스레드 하나가 계속 기다리는)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;쓰레드를 재활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단순요청에 빠름&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;고동시성 상황에 강함 실시간처리, 대규모 요청 처리(채팅)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원래 자바는 동기 /블로킹 언어&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767518842540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예: 데이터를 가져올때까지 기다림 -&amp;gt; 동기/블로킹 언어
String result = userRepository.findById(id);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액티브 프로그래밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;리액티브 프로그래밍&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 들어오면 작업시켜놓고, 기다리지 않고 다음일을 먼저 처리 ( 비동기 논블로킹방식)&lt;/li&gt;
&lt;li&gt;API 들의 동시처리(동시성) 원할 때&lt;/li&gt;
&lt;li&gt;데이터의 흐름(스트림) 와 변화(이벤트) 반응하는 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과가 준비되면 이벤트로 반응하여 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Mono vs Flux - WebFlux 사용하는 변수타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mono : 0 개 또는 1개의 데이터를 비동기로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux : 0 개 이상의 데이터 (스트림)을 비동기로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용환경 Mono/Flux 사용여부&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용환경&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Mono/Flux 사용여부&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;일반 java 프로젝트&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;spring mvc&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;spring webflux&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;✅&amp;nbsp;기본으로 사용 논블로킹&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전통적인 Java 스타일 (명령형 코드)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;for(String s : strList){
	System.out.println(s);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액티브 스트림 기반(함수형 스타일)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;list.stream()
	.filter(s -&amp;gt; !s.equals(&quot;b&quot;))
	.forEach(System.out::println); // ***spring mvc 에서도 리액티브 스타일로 작성 가능*** 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Spring WebFlux vs Spring Cloud Gateway&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring WebFlux : 비동기/논블로킹 리액티브 &lt;b&gt;웹 프레임워크&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Spring Cloud Gateway : WebFlux 위에 만든 API Gateway 서버, &lt;b&gt;라우팅과 필터링&lt;/b&gt;에 특화됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필터를 수행하고 라우팅하여 다른 서버에 전달 &lt;b&gt;( = 프록시 역할을 수행)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자체 컨트롤러가 거의 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;cloud Gateway 는 webflux 환경에서만 유효&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;API Gateway 서버&lt;/b&gt; : 클라이언트의 API 요청을 받아 1개 이상의 백엔드 서비스로 전달하고 응답을 취합해 클라이언트에 반환하는 &lt;b&gt;중개자&lt;/b&gt; 역할의 서버 (길 안내 역할)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽 관리, 인증, 로깅, 모니터링같은 보안 및 관리 기능을 단일 진입점에서 처리할 수 있음&lt;/li&gt;
&lt;li&gt;MSA환경에서 백엔드 서비스들의 복잡성을 관리하고 확장성을 높임&lt;/li&gt;
&lt;/ul&gt;
-참고 &lt;b&gt;네트워크 프록시&lt;/b&gt; : 클라이언트와 실제 서버 사이에 위치하여 클라이언트 요청을 대신 서버에 전달, 서버 응답을 받아 다시 클라이언트에게 전달하는 중간자 역할을 수행&lt;/li&gt;
&lt;li&gt;Spring Cloud Gateway Proxy : 네트워크 프록시와 동일한 역할 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;Spring Cloud Gateway 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트 &amp;rarr; 요청 도착&lt;/li&gt;
&lt;li&gt;GlobalFilter 실행 ( 공통필터 실행 )&lt;/li&gt;
&lt;li&gt;동적라우팅 &amp;rarr; client A 는 A서버, B는 B 서버 Target 되는곳으로 라우팅 ( gateway 를 통해 들어왔다면, 나가는것도 gateway 를 통해야한다)&lt;/li&gt;
&lt;li&gt;Gateway가 백엔드서비스로 응답받음 (client A 는 A서버, B는 B 서버)&lt;/li&gt;
&lt;li&gt;응답 받은 후에 필터처리 ( 적용할 수도 있고, 안해도 되고 ex : 응답 로깅 , 응답 헤더 조작 등등)&lt;/li&gt;
&lt;li&gt;클라이언트에게 응답 반환&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;GlobalFilter 실행하는 방법&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역필터를 설정하는 방법은 크게 2가지가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Cloud Gateway - default-filters 설정&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt; // application.yml 설정
  cloud:
    gateway:
      default-filters:
        - name: DetailedRequestResponseLogFilter
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gateway 에서 공통필터 default-filters 모든 route 공통적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도 적용하지 않아도 자동적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Cloud Gateway - GlobalFilter로도 전역필터로도 가능&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
	
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GlobalFilter 로도 모든 라우터에 필터 자동적용 가능&lt;/li&gt;
&lt;li&gt;default-filter , GlobalFilter 둘 다 함께 동시에 사용 가능하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기준이 조금 다르긴함&lt;/li&gt;
&lt;li&gt;default-filter : 모든 route 정의에 공통적 적용필터&lt;/li&gt;
&lt;li&gt;GlobalFilter: 모든 요청에 실행되는 전역필터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록된 GlobalFilter 들이 순서대로 실행된다. 순서를 적용하는 방법은 2가지 :&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애노테이션 사용 @Order(-1) (우선순위 낮을수록 먼저 실행)&lt;/li&gt;
&lt;li&gt;Ordered 인터페이스 구현&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
* 아래 메서드형태로 필터 메서드 구성 
* ServerWebExchange: 요청과응답을 캡슐화한 객체,불변(immutable)객체처럼 설계(변경불가)
* 필터를 요청에 적용할 수도 있고, 응답을 해주기전에도 필터적용이 가능하다 
* GatewayFilterChain = 여러 필터들을 차례대로 실행시키는 &amp;ldquo;줄 담당자&amp;rdquo;
*/
Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	...
		chain.filter(exchange);
}

// 1. Order 애노테이션 적용**
@Component
@Order(-1) // 낮을수록 우선순위 높음
public class AuthGlobalFilter implements GlobalFilter {

	@Override
	 Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		
      // 예: 헤더 검사
      String auth = exchange.getRequest()
						      .getHeaders()
						      .getFirst(&quot;Authorization&quot;);

      if (auth == null) {
          exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
          return exchange.getResponse().setComplete();
      }

      // 다음 필터로 넘어감 (chain 너 다음으로 넘어가 exchange 가지고) 
      return chain.filter(exchange);
	}
}

//-----------------------------------------------//

// 2. Ordered 인터페이스 구현
@Component
public class AuthGlobalFilter implements GlobalFilter,Ordered {

	@Override
	 Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
						...
		chain.filter(exchange);
	}
	
	@Override
	public int getOrder() {
	    return Integer.MIN_VALUE; // return 0; 이렇게도 가능, 낮을수록 우선순위 높음 
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2. chain.filter(exchange) 호출&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 GlobalFilter 는 자신이 작업을 마친 뒤에, 반드시 chain.filter(exchange) 를 호출해야한다. 위 호출을 통해 다음필터 또는 라우터/컨트롤러로 요청이 넘어갈 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chain.filter(exchange) 호출하면 Spring 내부 필터체인이 다음이 어디인지 이미 알고있어서, 다음경로를 명시하지 않아도 알아서 다음으로 이동한다 &amp;rarr; 관리하는 곳 : GatewayFilterChain (필터순서관리자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3. 동적 라우팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RouteLocatorBuilder : Spring Cloud Gateway 에서 라우팅 규칙을 자바코드로 정의할 때 사용하는 빌더 클래스&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()  // 이제 여러개 routes 등록할거야
        .route(&quot;dysignic_route&quot;, r -&amp;gt; r
            .path(&quot;/dynamic/**&quot;)  // 1. 이 endpoint 로 요청이 왔을 때, 
            .filters(f -&amp;gt; f.stripPrefix(1)) //3. 추가필터적용( 접두사(앞)부분을 잘라내)
            .uri(&quot;&amp;lt;http://localhost:8081&amp;gt;&quot;)) // 2. 여기로 라우팅하되 
         .route(&quot;user_route&quot;, r -&amp;gt; r
            .path(&quot;/user/**&quot;)
            .filters(f -&amp;gt; f.stripPrefix(1))
            .uri(&quot;&amp;lt;http://localhost:8082&amp;gt;&quot;))
        .build();
}

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client 가 /dynamic/** 로 요청을 보냈을 때 http://localhost:8081로 요청을 전달(proxy) 하되, prefix /dynamic 을 잘라낸 상태로 보냄&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RouteLocator&lt;/td&gt;
&lt;td&gt;Spring Cloud Gateway 가 사용하는 라우팅 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;builder.routes()&lt;/td&gt;
&lt;td&gt;여러 개의 라우트를 구성하기 위한 시작점 ( 여러개 route 등록할거야!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.route(&quot;dynamic_route&quot;, r -&amp;gt; r&lt;/td&gt;
&lt;td&gt;하나의 라우트를 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;rdquo;dynamic_route&amp;rdquo; 는 이 라우트의 고유한 ID ( 로그, 모니터링, 에러 메시지에서 사용가능)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.path(&quot;/dynamic/**&quot;)&lt;/td&gt;
&lt;td&gt;요청경로가 &amp;ldquo;/dynamic/**&amp;rdquo; 이라면 해당 라우트를 사용하겠단 뜻 (이런 endpoint 로 들어오면)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.filters(f -&amp;gt; f.stripPrefix(1))&lt;/td&gt;
&lt;td&gt;filter : 라우팅 전/후로 요청/응답을 조작하는 역할을 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.filters : 이 라우트에 적용할 필터를 설정&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stripPrefix(1): 경로의 앞에서 1개의 경로 조각제거&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(ex: /dynamic/hello &amp;rarr; /hello)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.uri(&quot;&lt;a href=&quot;http://localhost:8081&quot;&gt;http://localhost:8081&lt;/a&gt;&quot;))&lt;/td&gt;
&lt;td&gt;실제로 요청을 보낼 백엔드 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;위의 조건에 맞는 요청이 오면 이 주소로 프록시(전달) 된다.&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.build()&lt;/td&gt;
&lt;td&gt;위에서 정의한 모든 라우트를 최종적으로 빌드해서 RouteLocator 객체로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//Example code 

// 환경설정값 주입 - @Value 애노테이션 이용하여 프로퍼티 주입 
@Value(&quot;${api.url.member}&quot;)
private String memberServiceUrl;

@Value(&quot;${api.url.cusmer}&quot;)
	private String cusmerServiceUrl;

@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(&quot;order&quot;, r -&amp;gt; r //id
						.path(&quot;/order/**&quot;) // 요청된 경로
						.and()
						 //Body있는경우 -&amp;gt; 이렇게 조건을 추가해도 된다.
						.readBody(String.class, i -&amp;gt; !StringUtils.isEmpty(i))
						// 필터 없을 수도 있다
						.uri(&quot;&amp;lt;https://cookie.service.order.com&amp;gt;&quot;)
				)
				// Body가 없는 경우 (get요청)
				.route(&quot;member&quot;, r -&amp;gt; r
					//.path(&quot;/member/**&quot;, &quot;/sign/**&quot;, &quot;/info/**&quot;) // 가변인자(여러개)는 불가
						.path(&quot;/member/**&quot;)
						.or()
						.path(&quot;/sign/**&quot;)
						.or()
						.path(&quot;/info/**&quot;)
						// 또는 .path(&quot;/{segment:member|sign|info}/**&quot;) 정규식 사용 
							.and()
							.uri(memberServiceUrl)) //환경설정값 주입 
				.route(&quot;cusmer server&quot;, r -&amp;gt; r
						.path(&quot;/cusmer/**&quot;)
						.and()
						.readBody(String.class, i -&amp;gt; !StringUtils.isEmpty(i))
						.filters(f -&amp;gt; f.rewritePath(&quot;/v2/cusmer&quot;))
						.uri(cusmerServiceUrl))
						}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;f.rewritePath(&quot;/v2/cusmer&quot;))&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RewritePath 적용한다면 요청 URL의 Path 부분을 다른 Path로 치환하는 기능이다. 도메인(uri)은 변경하지 않으며, 변경된 Path는 uri와 합쳐져 최종 호출 주소가 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 &lt;a href=&quot;https://jungha.supermarket.com/&quot;&gt;https://jun.supermarket.com/&lt;/a&gt;cusmer/check 요청&lt;/li&gt;
&lt;li&gt;rewrite 만나서 뒤에 path 가 v2/cusmer/check 로 변경&lt;/li&gt;
&lt;li&gt;도메인까지 적용하면 &lt;a href=&quot;https://jungha.service.cusmer.com/&quot;&gt;https://jun.service.cusmer.com/&lt;/a&gt;v2/cusmer/check&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우팅을 적용하는 파일은? (라우팅 관련 코드 작성하는 위치)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 @Configuration 클래스로 분리 - 정석적인 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 별도의 @Configuration 클래스
@Configuration
public class GatewayRouteConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route(&quot;user-service&quot;, r -&amp;gt; r
                .path(&quot;/user/**&quot;)
                .uri(&quot;&amp;lt;https://user.jun.com&amp;gt;&quot;))
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.Gateway가 백엔드서비스로 응답받음 (client A 는 A서버, B는 B 서버)&lt;br /&gt;5. 응답 받은 후에 필터처리 ( 적용할 수도 있고, 안해도 되고 ex : 응답 로깅 , 응답 헤더 조작 등등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post (응답 후) 필터 : .then(Mono.fromRunnable(() -&amp;gt; {} 으로 적용! (응답 받은 후 실행하라)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; @Override
    public Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long start = System.currentTimeMillis(); // 시작할 때 시간

        // pre (요청 전)
        return chain.filter(exchange)
            // post (응답 후)
            .then(Mono.fromRunnable(() -&amp;gt; {  //응답받은 후 실행해라
                long time = System.currentTimeMillis() - start;
                var res = exchange.getResponse();
                var req = exchange.getRequest();

                // 이 값을 로그로 만들어 로그추적에 용이하게 만들수도 있음!
                System.out.printf(&quot;[POST] %s %s -&amp;gt; %s (%dms)%n&quot;,
                        req.getMethod(), req.getURI().getPath(), status, time);

                // 예: 응답 헤더 추가
                res.getHeaders().add(&quot;X-Gateway-Processed&quot;, &quot;true&quot;);
                res.getHeaders().add(&quot;X-Response-Time-Ms&quot;, String.valueOf(took));
            }));
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 라우트에서만 응답필터 적용도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;.route(&quot;customer&quot;, r -&amp;gt; r
    .path(&quot;/cusmer/**&quot;)
    .filters(f -&amp;gt; f.filter((exchange, chain) -&amp;gt;
        // pre filter
        chain.filter(exchange) // 다음필터로 넘겨라(없다면 라우팅)
            // post filter
            .then(Mono.fromRunnable(() -&amp;gt; {
                exchange.getResponse().getHeaders()
                        .add(&quot;X-Customer-Route&quot;, &quot;handled&quot;);
            }))
    ))
    .uri(&quot;&amp;lt;https://jungha.service.cusmer.com&amp;gt;&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Spring Cloud Gateway 는 MSA 의 입구 역할을 수행한다. &lt;br /&gt;동적라우팅뿐만 아니라 공통필터, Prefilter, Postfilter 사용도 가능하다.&lt;br /&gt;JWT 인증을 수행하기도 하며, 인증되지 않는다면 바로 return 도 가능하다.&lt;br /&gt;기존에 api 가 v1 &amp;rarr; v2 로 변경되더라도 해당 api 를 요청하는곳(프론트)을 변경할 필요도 없다.&lt;br /&gt;공통필터에 로그를 남기어서 로그를 ELK 에 보낸다면 로그 도표화도 가능하다. 사용자마다&lt;br /&gt;즉 Spring Cloud Gateway 덕분에 MSA 각 서비스는 비즈니스 로직만 집중하여 수행할 수도 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>spring</category>
      <category>gateway</category>
      <category>springcloud</category>
      <category>SpringCloudGateway</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/117</guid>
      <comments>https://do-ha-computer.tistory.com/entry/Spring-Cloud-GateWay-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90#entry117comment</comments>
      <pubDate>Sun, 4 Jan 2026 18:41:25 +0900</pubDate>
    </item>
    <item>
      <title>Spring AOP 가 필요한 경우, 무엇인가, 관련 용어</title>
      <link>https://do-ha-computer.tistory.com/entry/Spring-AOP-%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B2%BD%EC%9A%B0-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EA%B4%80%EB%A0%A8-%EC%9A%A9%EC%96%B4</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;인프런 스프링 입문 - 김영한 강의를&amp;nbsp; 참고하였습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;a. Spring&amp;nbsp;AOP&amp;nbsp;가&amp;nbsp;필요한&amp;nbsp;경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 모든 메서드의 호출 시간을 측정 ( 인프런 스프링 입문 - 김영한 강의)&lt;/li&gt;
&lt;li&gt;ex) 사용자 무슨 액션을 하는지 로그를 쌓는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
* 사용자 상품 주문
* - 상황 : 사용자가 몇시에, 어떤 액션을 했는지 알기 위해서 사용자 로그를 쌓아야한다. 
* - 핵심 로직 (상품 주문) + 부가 로직 (로그 쌓기) 
*/

@Transactional
public List&amp;lt;Order&amp;gt; saveOrder(Long goodsId){
	
	log.debug(&quot;== 사용자 {} 상품 주문 시작 ==&quot;,goodsId);
	
	orderRepository.save(goodsId);
	
	log.debug(&quot;== 사용자 {} 상품 주문 저장 완료, 사용자 로그에도 저장 필요 ==&quot;,goodsId);
	
	userLogsRepository.save('order',goodsId);  //주문과 관련 없는 부가 로직을 메서드에 작성
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 로그를 쌓아달라는 요청에 따라서, 위의 코드처럼 로직을 짜게 된다면 메서드내에서 &lt;b&gt;핵심 관심 사항(core concern) 과 공통 관심 사항(cross-cutting concern)&lt;/b&gt; 이 혼용되게 된다&lt;/li&gt;
&lt;li&gt;사용자 로그 로직은 애플리케이션의 공통 관심 사항이다.&lt;/li&gt;
&lt;li&gt;핵심 관심 사항의 로직과 공통 관심 사항 로직이 섞이게 되면 유지보수가 어렵다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 코드가 복잡하다면, 핵심 관심 사항과 공통 관심 사항 구분하기 어려울 수도 있다.&lt;/li&gt;
&lt;li&gt;또한 공통 관심 사항 로직이 변경된다면, 작성한 모든 메서드를 찾아가서 수정해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그래서 이런 경우에 필요한게 &lt;b&gt;AOP ( Aspect Oriented Programming)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;b. AOP 는 무엇인가&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AOP는Aspect Oriented Programming의 약자로 &lt;b&gt;관점 지향 프로그래밍&lt;/b&gt;이라고 불린다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈화 : 어떤 공통된 로직이나 기능을 하나의 단위로 묶는것로직을 기준으로&lt;br /&gt;핵심 관점, 공통 부가 관점으로 나누어 즉 관점을 기준으로 모듈화 하겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이렇게 흩어진 관심사들을 &lt;b&gt;핵심 로직에서 분리하여서 재사용&lt;/b&gt; 하겠다 &amp;rArr; AOP의 취지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;흩어진 관심사 (Crosscutting Concers) : 소스 코드상 다른 부분에서 반복해서 사용되는 코드들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주로 로깅, 보안, 트랜잭션과 같은 공통 기능들을 AOP 로 적용함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;c. AOP 관련 용어 정리&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Aspect&lt;/b&gt; : 공통 관심사를 모듈화한 객체, 즉 AOP 를 적용할 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring AOP 에서 Aspect 를 구현하는 방식은 2가지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스키마 기반 접근 :xml 기반 설정&lt;/li&gt;
&lt;li&gt;@AspectJ 스타일 : 애노테이션 기반 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@AspectJ 방식이 Spring 생태계에서 표준처럼 자리 잡고 있는 상태&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Target&lt;/b&gt; : 핵심 관심사를 담고 있는 모듈 (ex: 상품저장, 회원가입) &amp;rArr; 핵심 기능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Advice&lt;/b&gt;: Aspect의 동작을 정의하는 실제 코드, Aspect가 어떻게 동작할 건지 정의한 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 AOP 를 언제! 적용하고 싶다 설정하는건 &amp;rArr; &lt;b&gt;Advice + Point Cut 을 조합&lt;/b&gt;으로 정의&lt;/li&gt;
&lt;li&gt;실행 시점에 따라서 여러 종류로 나눔
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Before : 타겟 메서드 실행 이전에 동작&lt;/li&gt;
&lt;li&gt;@After : 타겟 메서드 실행 후 성공 /실패 여부에 상관없이 동작&lt;/li&gt;
&lt;li&gt;@AfterReturning : 타겟 메서드 정상 실행 완료시 동작&lt;/li&gt;
&lt;li&gt;@AfterThrowing : 타겟 메서드 예외 발생하면 동작&lt;/li&gt;
&lt;li&gt;@Around : 타겟 메서드 실행 전후에 모두 동작
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타겟 메서드 실행을 직접 제어 proceed() 메서드 통해서 할 수 있다.&lt;/li&gt;
&lt;li&gt;가장 강력한 어드바이스로 로깅, 성능 모니터링 등등에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Join Point&lt;/b&gt; : &lt;b&gt;Advice&lt;/b&gt;가 적용될 수 있는 특정 지점&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Point Cut&lt;/b&gt;: &lt;b&gt;Joinpoint 중에서 Advice를 실제로 적용할 대상을 필터링하는 조건&lt;/b&gt;을 표현식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 나는 특정 컨트롤러가 실행될 때 Advice 부르겠다. &amp;rArr; 이거를 표현식으로 표현&lt;/li&gt;
&lt;li&gt;포인트 컷 표현식 (외울 필요는 없고 사용할때 정의를 다시 찾아봐서 사용할 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ProceedingJoinPoint&lt;/b&gt; : Spring AOP 가 구현될 때 사용되는 중요한 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JoinPoint 를 확장한 것으로, 메서드 호출 제어/실행을 할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;proceed() : 실제 타겟 메서드를 실행 // 호출하지 않으면 타겟 메서드 실행 안됌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주로 @Around Advice 에서 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>spring</category>
      <category>AOP</category>
      <category>SpringAOP</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/116</guid>
      <comments>https://do-ha-computer.tistory.com/entry/Spring-AOP-%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B2%BD%EC%9A%B0-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EA%B4%80%EB%A0%A8-%EC%9A%A9%EC%96%B4#entry116comment</comments>
      <pubDate>Tue, 15 Apr 2025 18:30:26 +0900</pubDate>
    </item>
    <item>
      <title>DB Replication 에 대해</title>
      <link>https://do-ha-computer.tistory.com/entry/DB-Replication-%EC%97%90-%EB%8C%80%ED%95%B4</link>
      <description>&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f8f8f7;&quot; data-token-index=&quot;0&quot;&gt;a. DB Replication 란&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 DB를 복제하여, Master/Slave 구조를 활용하여 DB 의 부하를 분산시키는기술&lt;/li&gt;
&lt;li&gt;DB를 오랫동안 점유하는 쿼리는 DeadLock 이 발생할 확률 ⬆️
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rArr; 성능저하 &amp;rArr; 분산시켜서 성능을 올리자!&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deDLmE/btsMI5gLpAQ/7YE7hjk6XRF90WWsl4w391/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deDLmE/btsMI5gLpAQ/7YE7hjk6XRF90WWsl4w391/img.png&quot; data-alt=&quot;Master - Slave 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deDLmE/btsMI5gLpAQ/7YE7hjk6XRF90WWsl4w391/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeDLmE%2FbtsMI5gLpAQ%2F7YE7hjk6XRF90WWsl4w391%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;581&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Master - Slave 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Insert/Update/Delete는 Master DB 로 바라보게 구성&lt;/li&gt;
&lt;li&gt;Select 는 Slave DB를 바라보게 구성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 Select 일까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 없는 여러개 조인, 너무 많은 데이터로 &amp;rArr; full Scan 을 생길 수 있음&lt;/li&gt;
&lt;li&gt;모든 테이블을 읽어야하는 full Scan 과 동시에 쓰기작업이 일어난다면 DeadLock 발생할 가능성 높아짐!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만, 락이 걸리는 방식과 데이터베이스마다 트랜잭션 격리수준은 다름!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API 를 통해 변경되는 데이터는 Master DB를 통해서 반영되고, Slave에는 Replication을 통해 반영된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f8f8f7;&quot; data-token-index=&quot;0&quot;&gt;b. DB Replication 방법 종류&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 데이터를 복제하는 방법&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동기&lt;/b&gt; : Master에 데이터변경이 있는 경우, Slave까지 동시에 적용되야 API 끝&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기&lt;/b&gt; : Master에 반영된 후에 API끝, Slave알아서복제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master는 Slave하고 무관하게 단일DB처럼 행동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;두개를 반반씩 합친 세미 복제 방법도 있긴하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f8f8f7;&quot; data-token-index=&quot;0&quot;&gt;c. Mysql Replication 동작 원리 &lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mysql은 기본적으로 비동기 복제 방식 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;Master 에 변경된 데이터에 대한 이력을 바이너리 로그에 기록한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Replication Master Thread가 바이너리 로그를 비동기적으로 읽어 Slave에 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dd6ucL/btsMKqqhNOf/ei2KrhEOXXdjFKUkKmjvF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dd6ucL/btsMKqqhNOf/ei2KrhEOXXdjFKUkKmjvF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dd6ucL/btsMKqqhNOf/ei2KrhEOXXdjFKUkKmjvF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdd6ucL%2FbtsMKqqhNOf%2Fei2KrhEOXXdjFKUkKmjvF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;870&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f8f8f7;&quot; data-token-index=&quot;0&quot;&gt;d. Replication DB Config 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&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;application.yml설정
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Yml에 Master,Slave DB 모두 설정한다.&lt;br /&gt;밑에 예시처럼 application.yml 에다가 DB정보를 모두 입력한다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;spring:
  datasource:
    master:
      hikari:
        driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
        username: ##root##
        password: ##password##
        jdbc-url: jdbc:log4jdbc:mysql://~ masterDB주소
    
    slave:
      hikari:
        driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
        username: ##root##
        password: ##password##
        jdbc-url: jdbc:log4jdbc:mysql://~ slaveDB주소
      max-lifetime: 28797000
      maximum-pool-size: 50
      validation-timeout: 2000
      connection-timeout: 30000&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp; DataBaseConfig 설정&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Default DB 설정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741857563765&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ... import 관련 코드 삭제 

@Configuration
@Profile({&quot;도입하고 싶은 환경&quot;}) // replication 을 모든 환경에 적용한다면 삭제해도 된다.
public class DataBaseConfig {
    public static final String MASTER_DATASOURCE = &quot;masterDataSource&quot;;
    public static final String SLAVE_DATASOURCE = &quot;slaveDataSource&quot;;

    @Bean(MASTER_DATASOURCE)
    @ConfigurationProperties(prefix = &quot;spring.datasource.master.hikari&quot;) 
    public DataSource masterDataSource() {
        return DataSourceBuilder
            .create()
            .type(HikariDataSource.class)
            .build();
    }

    @Bean(SLAVE_DATASOURCE)
    @ConfigurationProperties(prefix = &quot;spring.datasource.slave.hikari&quot;)
    public DataSource slaveDataSource() {
        return DataSourceBuilder
            .create()
            .type(HikariDataSource.class)
            .build();
    }

    @Primary
    @Bean
    @DependsOn({MASTER_DATASOURCE, SLAVE_DATASOURCE})
    public DataSource routingDataSource(
        @Qualifier(MASTER_DATASOURCE) DataSource masterDataSource,
        @Qualifier(SLAVE_DATASOURCE) DataSource slaveDataSource) {

        Map&amp;lt;Object, Object&amp;gt; datasourceMap = new HashMap&amp;lt;&amp;gt;() {
            {
                put(&quot;master&quot;, masterDataSource);
                put(&quot;slave&quot;, slaveDataSource);
            }
        };
        RoutingDataSource routingDataSource = new RoutingDataSource(); // 라우팅 관련도 구현필요
        routingDataSource.setDefaultTargetDataSource(masterDataSource); //default 를 설정해야함
        routingDataSource.setTargetDataSources(datasourceMap);
        routingDataSource.afterPropertiesSet();
        //LazyConnection 을 통해서, 트랜잭션이 시작되기 전까지는 실제 커넥션 결정되지 않음
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Routing 관련 구현&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractRoutingDataSource : Spring Framework 에서 제공하며 멀티 데이터 소스를 동적으로 라우팅할 수 있도록 지원하는 추상클래스&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741857930914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ... import 관련 코드 삭제 

// AbstractRoutingDataSource 상속받아서 라우팅 규칙을 정의

public class RoutingDataSource extends AbstractRoutingDataSource {

	// determineCurrentLookupKey() : 현재 어떤 데이터 소스를 사용할지 결정하는 메서드 
    @Override
    protected Object determineCurrentLookupKey() {
    
    // readOnly 이면 SlaveDB 바라보게 한다
        boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();

        //@Transaction(readOnly = true) 이면 slave DB
        if (readOnly) {
            return &quot;slave&quot;;
        } else {
            return &quot;master&quot;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. DB 연결 확인&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741858638474&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//... import 삭제 

@Slf4j
@Service
@RequiredArgsConstructor
public class ClusterCheckServiceImpl implements CheckService{

    @Override
    public String findCustomer(Long userId) {
        log.info(&quot;1명 Customer 고객 조회 ==&amp;gt; masterDB 로 연결 &quot;);
        log.info(&quot;@Transactional 애노테이션 달지 않으면,setDefault DB로 연결&quot;);
        return null;
    }

    @Override
    @Transactional(readOnly = true)
    public List&amp;lt;String&amp;gt; findCustomerList() {
        log.info(&quot;Customer 고객리스트 조회 ==&amp;gt; slaveDB 로 연결 &quot;);
        return null;
    }

    @Override
    @Transactional
    public List&amp;lt;String&amp;gt; createCustomer() {
        log.info(&quot;Customer 고객리스트 신규 저장  ==&amp;gt; masterDB 로 연결 &quot;);
        log.info(&quot;@Transactional  ==&amp;gt; readOnly 작성안하게 된다면, 기본값 false이므로 ==&amp;gt; masterDB로 연결 &quot;);
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f8f8f7;&quot; data-token-index=&quot;0&quot;&gt;e. 결론&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 부하를 줄이기 위해서, Replication 방법을 사용할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실상 DB 부하에 영향이 큰건 Read, 이거를 분리시키자!&lt;/li&gt;
&lt;li&gt;application 에 따라서 slave 가 n개가 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동적으로 Data source 를 변경하기 위해서는 DataBaseConfig 를 따로 생성해놔야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 SlaveDB 를 바라보게한 기준을 @Transational(readOnly = true) 로 기준을 잡아놨다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Transational(readOnly = true) 를 사용하지 않으면 Default 인 MasterDB 로 향하게 한다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;잡아놓은 기준을 적극 사용해서 DefaultDB 와 연결되지 않게 하자 -&amp;gt; SlaveDB 로 향하게 하자!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>cs</category>
      <category>masterslave</category>
      <category>mysql복제</category>
      <category>replicationdb</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/115</guid>
      <comments>https://do-ha-computer.tistory.com/entry/DB-Replication-%EC%97%90-%EB%8C%80%ED%95%B4#entry115comment</comments>
      <pubDate>Thu, 13 Mar 2025 18:39:42 +0900</pubDate>
    </item>
    <item>
      <title>@Column(updateable = false) 작동 확인</title>
      <link>https://do-ha-computer.tistory.com/entry/Columnupdateable-false-%EC%9E%91%EB%8F%99-%ED%99%95%EC%9D%B8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Column(updateable = false)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 엔티티속 수정되지 않은 컬럼에다가 &lt;span style=&quot;color: #d9730d;&quot; data-token-index=&quot;0&quot;&gt;@Column(updateable = false)&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;설정을 하곤한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #d9730d;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 애노테이션이 과연 어떻게 작동하는가? 알아보려 한다. &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d9730d;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Column(updateable = false) 테스트 코드 작성&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트할 Customer 엔티티 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Entity
public class Customer{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;

    private String lastName;

    @Column(updatable = false)
    private String createBy; //생성자

    @Column(updatable = false)
    private LocalDateTime createdDate; //생성 시간

    private String lastModifiedBy; //수정자

    private LocalDateTime lastModifiedDate; //수정 시간

  	// 테스트하기 위해서 생성, 수정시간 업데이트
    void updatePlusDay(Integer plusDay){
        createdDate = LocalDateTime.now().plusDays(plusDay);
        lastModifiedDate = LocalDateTime.now().plusDays(plusDay);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 코드 작성&lt;/p&gt;
&lt;pre id=&quot;code_1739509659897&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;	@Test
	@Transactional
	@Rollback(value = true)
	void testCreatedDateNotUpdateAble(){
		// 1. save a new customer
		LocalDateTime now = LocalDateTime.now();
		customerRepository.save(Customer.builder()
			.firstName(&quot;star&quot;)
			.lastName(&quot;Moon&quot;)
			.build());

		// 2. DB 반영
		entityManager.flush();
		entityManager.clear();

		log.info(&quot;----- 최초 고객 정보 확인 -----&quot;);
                Customer savedCustomer = customerRepository.findById(1L);
		System.out.println(savedCustomer.toString());

		//3. 생성 시간 , 수정 시간 변경
		savedCustomer.updatePlusDay(2);

		entityManager.flush();
		entityManager.clear();

		// 4. 결과 확인
		log.info(&quot;----- 고객 정보 생성 시간 확인 -----&quot;);
		Customer checkedCustomer = customerRepository.findById(1L);
		System.out.println(checkedCustomer.toString());

		assertThat(checkedCustomer.getCreatedDate().truncatedTo(ChronoUnit.DAYS))
			.isEqualTo(now.truncatedTo(ChronoUnit.DAYS));
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 결과&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;updatePlusDay&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;메서드에서 생성,수정시간을 업데이트 치고 있다 =&amp;gt; 하지만&amp;nbsp; &lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;@Column(updateable = false) &lt;span style=&quot;color: #000000;&quot;&gt;에 의해서 수정시간만 업데이트 된다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;createDate = 2025-02-03 , lastModifiedDate&amp;nbsp; = 2025-02-05 로 수정시간만 업데이트가 되었다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnBoEl/btsMjDjqebR/UUMRhn2P6XB6NkGVcpLK7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnBoEl/btsMjDjqebR/UUMRhn2P6XB6NkGVcpLK7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnBoEl/btsMjDjqebR/UUMRhn2P6XB6NkGVcpLK7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnBoEl%2FbtsMjDjqebR%2FUUMRhn2P6XB6NkGVcpLK7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;881&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 update 쿼리가 나갈때 &lt;span style=&quot;color: #ee2323; text-align: left;&quot;&gt;@Column(updateable = false)&lt;span&gt;&amp;nbsp; 컬럼은 제외하고 수행된다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (3).png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzlLgy/btsMhRQScB2/o72kCCzk2a5ANVCU73ZCkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzlLgy/btsMhRQScB2/o72kCCzk2a5ANVCU73ZCkK/img.png&quot; data-alt=&quot;생성정보 컬럼 제외 업데이트쿼리로 나감&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzlLgy/btsMhRQScB2/o72kCCzk2a5ANVCU73ZCkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzlLgy%2FbtsMhRQScB2%2Fo72kCCzk2a5ANVCU73ZCkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;429&quot; height=&quot;422&quot; data-filename=&quot;image (3).png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성정보 컬럼 제외 업데이트쿼리로 나감&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>spring</category>
      <category>@Column</category>
      <category>@column(updateable = false)</category>
      <category>JPA</category>
      <category>update=false</category>
      <category>엔티티</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/114</guid>
      <comments>https://do-ha-computer.tistory.com/entry/Columnupdateable-false-%EC%9E%91%EB%8F%99-%ED%99%95%EC%9D%B8#entry114comment</comments>
      <pubDate>Fri, 14 Feb 2025 14:14:15 +0900</pubDate>
    </item>
    <item>
      <title>Redis +Spring Cache 조회했을때 성능비교</title>
      <link>https://do-ha-computer.tistory.com/entry/Redis-Spring-Cache-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Cache&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주 사용하는 데이터를 빠르게 접근하기 위한 임시저장공간&amp;nbsp;&lt;/li&gt;
&lt;li&gt;데이터 조회 속도 향상과 성능최적화를 목적으로 한다. --&amp;gt; DB 부하를 줄일 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Spring Cache 동작 방식&amp;nbsp;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;캐시 애노테이션이 적용된 메서드를 호출&lt;/li&gt;
&lt;li&gt;캐싱된 데이터가 있는지 확인&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;있으면 바로 반환&amp;nbsp;&lt;/li&gt;
&lt;li&gt;없으면 DB 조회 후에, 결과 캐싱에 저장&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;이후 같은 메서드, 같은 키 값 호출시 캐시에서 값 가져옴&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Spring Cache 저장 위치&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 저장소 (JVM memory) 또는 외부 캐시 저장소 (Redis.. 등등) 필요&lt;/li&gt;
&lt;li&gt;기본 저장소 (JVM memory)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 개발단계에서만 사용, 실무에서는 외부 캐시 저장소 사용&lt;/li&gt;
&lt;li&gt;JVM Heap memory 에 저장되기에, 서버 재시작시 캐싱된 데이터 없어짐&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redis&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인메모리 데이터 저장소 (RAM 메모리에 저장한다)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;-&amp;gt; Mysql 은 디스크기반 저장소&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인메모리 데이터 저장소이기에, 초고속 데이터 처리 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RAM 메모리이면 휘발성데이터일까? -&amp;gt; NO!&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis 는 이것을 방지하기 위해 영속성 옵션을 제공한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;0&quot;&gt;&amp;nbsp;Redis는 인메모리 방식의 초고속 성능&lt;/span&gt;&lt;span style=&quot;color: #37352f;&quot; data-token-index=&quot;1&quot;&gt;을 유지하면서도,&lt;/span&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;2&quot;&gt; 영속성&lt;/span&gt;&lt;span style=&quot;color: #37352f;&quot; data-token-index=&quot;3&quot;&gt;을 보장합니다!&lt;/span&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;4&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;4&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Spring Cache 관련 애노테이션&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 20px;&quot;&gt;애노테이션명&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 20px;&quot;&gt;역할 / 기능&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 18px;&quot;&gt;@Cacheable&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 18px;&quot;&gt;캐싱된 값 있는지 조회 -&amp;gt; 없으면 DB 조회 후 캐싱 저장(있으면 캐싱된 값 바로 반환)&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 18px;&quot;&gt;@CachePut&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 18px;&quot;&gt;항상 DB 조회 후 캐싱 저장한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 18px;&quot;&gt;@CacheEvict&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 18px;&quot;&gt;캐싱된 데이터 삭제한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 18px;&quot;&gt;@Caching&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 18px;&quot;&gt;여러개의 캐시 애노테이션을 조합한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; text-align: center; height: 18px;&quot;&gt;@CachingConfig&lt;/td&gt;
&lt;td style=&quot;width: 73.7209%; text-align: center; height: 18px;&quot;&gt;클래스레벨에서 공통 캐시 설정을 지정한다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Redis + Spring Cache 구현&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&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;&amp;nbsp;테스트할 수 있는 &lt;b&gt;레디스&lt;/b&gt; 준비 (저는 로컬에 컨테이너 형태로&amp;nbsp; 띄웠습니다)&lt;/li&gt;
&lt;li&gt;의존성 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1739498604387&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//cache
implementation 'org.springframework.boot:spring-boot-starter-cache'

//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. application.yml 에 redis 정보 추가&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739498660572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  data:
    redis:
      host: (본인이 사용하는 레디스 쓰기)
      port: (본인이 사용하는 레디스 쓰기)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;4. Redis 관련 Config 설정&lt;/p&gt;
&lt;pre id=&quot;code_1739498691040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration // spring의 설정클래스 나타내는 애노케이션
@EnableCaching // 캐시 활성화
public class RedisConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)) // TTL 10분 설정
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%; text-align: center;&quot;&gt;코드&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;EnableCaching&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;캐시 관련 애노테이션을 동작할 수 있도록 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;RedisCacheManager&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;SpringCache 에서 Redis 를 캐시저장소로 사용할 수 있게 한다&amp;nbsp;&lt;br /&gt;(다른 저장소를 사용한다면 다른것일듯)&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;RedisConnectionFactory&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;Redis 서버와 연결을 관리하는 객체&amp;nbsp;&lt;br /&gt;application.yml 에 입력한 Redis 로 연결할 수 있게 한다.&amp;nbsp;&lt;br /&gt;Lettce라는 클라이언트 사용 (Spring 2.x 부터는 기본 Redis 연결하는 클라이언트)&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;entryTtl&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;TTL (Time-to-live) 유효시간을 설정할 수 있다.&amp;nbsp;&lt;br /&gt;10 분동안 유지 -&amp;gt; 후에는 자동 삭제&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;serializeKeysWith&lt;/span&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;바이너리 -&amp;gt; 문자열로 변환하여 key 값을 저장한다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 22.093%;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;serializeValuesWith&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 77.907%;&quot;&gt;바이너리 -&amp;gt; Json 으로 변환하여 value 값을 저장한다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  만약 직렬화/역직렬화 설정을 하지 않으면,Redis에 데이터가 &lt;b&gt;바이너리 형태&lt;/b&gt;로 저장된다 &amp;rArr; 사람이 읽기 어려움&lt;/p&gt;
&lt;table id=&quot;199d7e38-1a1b-8027-a9f9-cbb7e8cb07b9&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;199d7e38-1a1b-801a-b828-fc58d5d0ab37&quot;&gt;
&lt;td id=&quot;Tp&amp;lt;Z&quot;&gt;&lt;b&gt;직렬화 (Serialization)&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;TNHn&quot;&gt;Java 객체 &amp;rarr; JSON 문자열 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;199d7e38-1a1b-8092-8a0c-e910248f5416&quot;&gt;
&lt;td id=&quot;Tp&amp;lt;Z&quot;&gt;&lt;b&gt;역직렬화 (Deserialization)&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;TNHn&quot;&gt;JSON 문자열 &amp;rarr; Java 객체 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. @Cacheable 적용된 메서드 생성하기&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739499083620&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService {

    private final BookRepository bookRepository;

    @Override
    @Cacheable(value = &quot;BOOKSTORE&quot;, key = &quot;#bookId&quot;)
    @Transactional(readOnly = true)
    public BookReadResponse findBook(Long bookId) {

      return bookRepository.findById(bookId)
          .map(BookReadResponse::toModel)
          .orElseThrow(() -&amp;gt; new HttpClientErrorException(HttpStatus.BAD_REQUEST, &quot;찾는 책이 없습니다.&quot;));

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cif87N/btsMiq5GoLy/zTANCqUtkomnAG7c7TakZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cif87N/btsMiq5GoLy/zTANCqUtkomnAG7c7TakZ0/img.png&quot; data-alt=&quot;redis 에 저장된 데이터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cif87N/btsMiq5GoLy/zTANCqUtkomnAG7c7TakZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcif87N%2FbtsMiq5GoLy%2FzTANCqUtkomnAG7c7TakZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;307&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;redis 에 저장된 데이터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키를 BOOKSTORE::#bookId 로 했기에&amp;nbsp; -&amp;gt; BOOKSTORE::1 이 키 값이 된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;value는 json 형식으로 DB 조회된 값이 반영된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; margin-bottom: 5px; border-right-width: 0px; word-spacing: 3px; margin-top: 5px; border-bottom: #2A52BE 2px solid; border-left: #2A52BE 12px solid; letter-spacing: 1px; line-height: 1.5; border-top-width: 0px; margin-right: 0px; border-image: initial; padding: 3px 5px 3px 5px;&quot; data-ke-size=&quot;size23&quot;&gt;Cache 성능 확인&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐싱된 데이터를 가져오는게 정말 빠른건지 성능을 확인하기 위해서 AOP 사용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739499445323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Aspect
@Component
@Slf4j
public class ApiTrackingAspect {

    @Pointcut(&quot;execution(* test.api.erp.book.controller..*.*(..))&quot;)
    private void trackingController() { /* Point CUT이므로 로직 없음 */ }
    
    
    @Around(&quot;trackingController()&quot;)
    public Object checkCachePerformance(ProceedingJoinPoint joinPoint) throws Throwable{
        long start = System.currentTimeMillis();

        log.info(&quot;API 실행&quot;);
        Object proceed = joinPoint.proceed();
        
        long executionTime = System.currentTimeMillis() - start;
        System.out.println(joinPoint.getSignature() + &quot; executed in &quot; + executionTime + &quot;ms&quot;);

        return proceed;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;float: none; clear: none;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과 확인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;208ms -&amp;gt; 28ms&lt;/b&gt; 로 10배정도 줄여진것을 확인할 수 있었다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;단순하게 컬럼 2개가진 데이터를 캐싱해도 10배 성능차이를 가지는데,&amp;nbsp; 데이터가 클수록 성능차이를 크게 효과 볼 수 있을 것 같다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2192&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YxSRC/btsMjeXRiZl/8O9eA83W2eqwrJqkY7ath0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YxSRC/btsMjeXRiZl/8O9eA83W2eqwrJqkY7ath0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YxSRC/btsMjeXRiZl/8O9eA83W2eqwrJqkY7ath0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYxSRC%2FbtsMjeXRiZl%2F8O9eA83W2eqwrJqkY7ath0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2192&quot; height=&quot;338&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2192&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>spring</category>
      <category>@Cacheable</category>
      <category>cache</category>
      <category>caching</category>
      <category>Redis</category>
      <category>Spring</category>
      <category>springcache</category>
      <category>레디스</category>
      <category>캐시</category>
      <category>캐싱</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/113</guid>
      <comments>https://do-ha-computer.tistory.com/entry/Redis-Spring-Cache-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B0%A8%EC%9D%B4#entry113comment</comments>
      <pubDate>Fri, 14 Feb 2025 11:23:16 +0900</pubDate>
    </item>
    <item>
      <title>M:N 다대다 연관 관계</title>
      <link>https://do-ha-computer.tistory.com/entry/MN-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%97%B0%EA%B4%80-%EA%B4%80%EA%B3%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;!-- 소제목1 시작 --&gt;&lt;/h2&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;&amp;nbsp;관계형 DB 에서 M:N 연관관계 &lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;!-- 소제목1 종료 --&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;비즈니스 서비스를 DB 를 모델링 하는 과정에서 다대다 관계가 나올 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1개의 주문은 N개의 상품을 주문한다.&lt;/li&gt;
&lt;li&gt;1개의 상품은 M개 주문에 포함될 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;이렇게 되면 관계형DB 입장에서는 관계를 표시할 수가 없다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 레코드에서 하나의 FK 만 가져야 하기 때문이다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;콩나물&amp;rdquo; 상품을 M개 주문했다면 상품테이블에 주문 외래키로 뭐를 가져야할지?&lt;/li&gt;
&lt;li&gt;1번 주문에 여러개 상품을 주문했다면, 주문테이블에 상품 외래키로 뭐를 가져야할지?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 서로의 외래키가 애매해진다..&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gm3fP/btsNiXh3AtZ/nLofFxB8oB7VQDGGV7vAGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gm3fP/btsNiXh3AtZ/nLofFxB8oB7VQDGGV7vAGK/img.png&quot; data-alt=&quot;주문과 상품의 다대다 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gm3fP/btsNiXh3AtZ/nLofFxB8oB7VQDGGV7vAGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGm3fP%2FbtsNiXh3AtZ%2FnLofFxB8oB7VQDGGV7vAGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;214&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;주문과 상품의 다대다 관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목1 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;&amp;nbsp;b. 다대다 연관관계 해소를 위한 중간테이블 필요&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목1 종료 --&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 레코드에서 한 개의 FK 명시를 위해서는 중간테이블이 필요하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간테이블을 만들어서, 1:N 관계를 2개를 만든다.&lt;/li&gt;
&lt;li&gt;1개 주문에 N개의 주문 상품을, 1개의 상품은 N개의 주문 상품을 바라보게 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0fmTI/btsNmMznDmU/MDrpC6El67pSYSYBOckMlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0fmTI/btsNmMznDmU/MDrpC6El67pSYSYBOckMlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0fmTI/btsNmMznDmU/MDrpC6El67pSYSYBOckMlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0fmTI%2FbtsNmMznDmU%2FMDrpC6El67pSYSYBOckMlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;738&quot; height=&quot;237&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;!-- 소제목1 시작 --&gt;&lt;/h2&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;&amp;nbsp;c. 사용하다가 추후 다대다 관계가 생겼다면?&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a, b 테이블&amp;nbsp;기존에는 두 테이블간의 관계정립이 필요하지 않은 채, 따로 놀고 있는 테이블
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로 생겨야하는 기능으로, 두 테이블간의 관계 정립이 필요해짐&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그러나, M:N 관계이다보니, 외래키로 연관관계 매핑이 안되어서 원하는 값을 찾을 수가 없없다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간 테이블을 생성하여서 두 테이블간의 연관관계를 1:N 관계 2개로 풀기로 하였다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목1 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;d. 다대다 관계 해소를 위한 중간테이블 생성 방법 &lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 테이블을 기존의&amp;nbsp; 테이블들의 PK 를 외래키로 가져야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;// 가상의 예를 들었습니다. 
create table goods_location
(
    goods_id          bigint unsigned        not null ,
    location_seq      int unsigned           not null comment '순번',
   // ... 내용 생략
    primary key (goods_id, location_seq)
)comment '상품_위치';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 가상의 예를 들었습니다.
create table location_info
(
    location_id    varchar(30)                  not null ,
    label_no        char(3) charset utf8mb4      not null ,
     // ... 내용 생략
    primary key (location_id, label_no)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 테이블들이 있을때, 새로운 테이블을 위 테이블들과 연관관계를 맺어야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그러기 위해,&lt;span style=&quot;background-color: #9feec3;&quot;&gt; 새로운 테이블은 위 테이블들의 PK 를 외래키로 가져야한다.&lt;/span&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;create table goods_location_info
(
   goods_location_info_id bigint unsigned not null AUTO_INCREMENT PRIMARY KEY
    
    goods_id        bigint unsigned       not null ,
    location_seq    int unsigned          not null comment '순번' ,
    
    location_id     varchar(20)   not null ,
    label_no        char(3)       not null ,

    foreign key (goods_id,location_seq) references 
    goods_location (goods_id, location_seq),
    
    foreign key (location_id, label_no) references 
    location_info (location_id, label_no)
) ;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>spring</category>
      <category>객체</category>
      <category>다대다관계</category>
      <category>중간테이블</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/112</guid>
      <comments>https://do-ha-computer.tistory.com/entry/MN-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%97%B0%EA%B4%80-%EA%B4%80%EA%B3%84#entry112comment</comments>
      <pubDate>Mon, 10 Feb 2025 14:54:38 +0900</pubDate>
    </item>
    <item>
      <title>객체 연관관계, 영속성 컨텍스트</title>
      <link>https://do-ha-computer.tistory.com/entry/%EA%B0%9D%EC%B2%B4-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;a. 객체 설계 단계에서는 단방향 연관관계로 끝내야 한다. &lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단방향으로만으로도 관계 매핑이 완료된거임 &amp;rarr; 양뱡향으로 해버리면 객체 입장에서 고려할것이 많아짐&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;양방향은 사실 단방향의 2개일 뿐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테이블 설계하면서 객체 설계 같이 진행할 것 &amp;rarr; 이미 테이블간의 관계는 이미 정해짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무엇이 1:1 관계이고, 1:N 관계인지 N:M 관계인지는 테이블 설계하면서 나올것임 이때 &amp;rarr; &lt;b&gt;외래키를 갖고 있는 객체&lt;/b&gt;가 &lt;b&gt;주인으로&lt;/b&gt; 단방향 연관관계 맺으면 됌&lt;/li&gt;
&lt;li&gt;다중성이 헷갈릴때에는 반대로 생각해보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중성 : @ManyToMany 인지, @OneToMany인지..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실무에서 다대다는 지양해야함!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;양방향은 언제 ?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계 단계에선 하지 않음, 실무하다가 정말 필요하다 생각되면 그 때 추가하면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 수정할 필요도 없고! (테이블 영향도 없음) mappedBy 관계 맺으면 끝&lt;/li&gt;
&lt;li&gt;단, &lt;b&gt;양방향하게 되면 무한호출 조심&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무한호출을 야기하는 toString()이나 JSON 변환을 하지 않기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또한 양방향 관계시, &lt;b&gt;연관관계 편의 메서드&lt;/b&gt; 필요하면 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두개의 테이블에서 공통된 컬럼 값 업데이트를 일일히 하지 않고, 메서드 생성하여 두개의 컬럼 모두 업데이트!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Order 1 : OrderItem N 관계에서 order.getOrderItem 필요한 경우에 양방향 하면 되는데
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;orderItem.findByOrder() 이렇게 find 한번 하는게 나을 수 있음&lt;/li&gt;
&lt;li&gt;최대한 양방향 피할수있으면 피하기 (고려해야할게 많아지기 때문에)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;b. 양방향 매핑 규칙&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2관계중 &lt;b&gt;외래키가 있는곳으로 주인&lt;/b&gt;으로 하여, 관계중 주인을 정해야 한다.&lt;/li&gt;
&lt;li&gt;주인이 아니면 읽기만 가능, &lt;b&gt;mappedBy&lt;/b&gt; 속성으로 주인을 지정해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// order : orderItem 1:N 관계 

@Entity
@Table (name =&quot;`order`&quot;)
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Order{

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY) 
  private Long order_id;

 // OrderItem 엔티티에 Order 엔티티 부르는 변수명을 mappedBy 해줘야함 
  @OneToMany(mappedBy = &quot;order&quot;) 
  private List&amp;lt;OrderItem&amp;gt; orderItem = new ArrayList&amp;lt;&amp;gt;(); 

}

@Entity
@Table (name =&quot;order_item&quot;)
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class OrderItem{
 ...
 
 @ManyToOne
 @JoinColumn (name = &quot;order_id&quot;) //외래키 컬럼  
 private Order order;

}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;c. 영속성 컨텍스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속성 컨텍스트를 이해해야 실제 JPA가 어떻게 작동하는지 알 수 있다.&lt;/li&gt;
&lt;li&gt;API 요청에 따라 EntityManagerFactory 는 EntityManager 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;EntityManager.persist(entity);&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 저장이 아닌, 영속성 컨텍스트에 저장을 하겠다.&lt;/li&gt;
&lt;li&gt;엔티티 매니저를 통해서 영속성 컨텍스트에 접근!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영속상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;em.persist() 한 상태&lt;/li&gt;
&lt;li&gt;객체를 저장하여서 , 영속성 컨텍스트가 관리하는 상태 &amp;rArr; But 아직 DB 저장 전&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비영속상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성만 한 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 커밋 했을때 영속성 컨텍스트안에 있는 쿼리 날림&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;JPA 는 영속성컨텍스트 덕분에 조금이나마 효율! &amp;rarr; SQL 을 모아서 보내기 때문에
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이바티스는 DB 로 바로 쿼리 보냄&lt;/li&gt;
&lt;li&gt;효율이 있긴 하나, 크진 않음 &amp;rarr; 영속성 컨텍스트가 트랜잭션 커밋을 할때 DB에 보내고 비워지기에, 하나의 트랜잭션안의 쿼리만 같이 DB 에 전송하는것이기 때문이다 (많은 처리를 하는 API 의 경우는 도움됌)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Member member = new Member(); // 비영속상태
member.setName(&quot;haha&quot;); 
em.persist(member); // 영속상태 -&amp;gt; 이때 save 쿼리 안나감 / 커밋될때 나감

String whatName = member.getName(); // 영컨에서 값 가져오는것&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;c-1. 트랜잭션 커밋될때, 영속성컨텍스트 동작 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;flush() 호출&lt;/li&gt;
&lt;li&gt;Entity 와 스냅샷 비교
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스냅샷: DB 에서 가져와서 1차 캐시한 엔티티&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;바뀐 값에 대해 Update query &lt;b&gt;쓰기지연SQL 저장소&lt;/b&gt;에 저장&lt;/li&gt;
&lt;li&gt;flush() : SQL query 디비에 전송&lt;/li&gt;
&lt;li&gt;Commit&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;e. flush()&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;flush() 가 하는일&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;변경감지 (더티체킹)&lt;/li&gt;
&lt;li&gt;수정된 엔티티를 쓰기 지연 SQL 저장소에 등록&lt;/li&gt;
&lt;li&gt;쓰기 지연소 SQL 저장소의 쿼리를 데이터베이스에 전송한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;flush() 가 호출되는 순간 : 영속성 컨텍스트의 변경 내용 즉시 DB 반영&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;em.flush() : 직접호출하는 방법 (사용할 일 없음/ 테스트할때 사용)&lt;/li&gt;
&lt;li&gt;transaction commit : flush 자동호출&lt;/li&gt;
&lt;li&gt;JPQL 쿼리 실행 : flush 자동호출
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL 쿼리 실행되기전에 flush 한 후에 JPQL 쿼리 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;flush() 되어도 영속성 컨텍스트가 비워져도&lt;b&gt; 1차 캐시는 값 유효하다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739165770546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Member firstMemeber = new Member();
firstMember.setName(&quot;ha&quot;);
memberRepository.save(firstMemeber);

em.flush(); //flush()를 해도 영속성 컨텍스트에 1차 캐시 값은 남아있다. 

System.out.println(firstMember.getName) //영속성 컨텍스트에서 캐시 값 가져오는거 -&amp;gt; DB 조회 안함&lt;/code&gt;&lt;/pre&gt;</description>
      <category>spring</category>
      <category>flush()</category>
      <category>JPA</category>
      <category>Spring</category>
      <category>엔티티매니저</category>
      <category>연관관계</category>
      <category>영속성컨텍스트</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/111</guid>
      <comments>https://do-ha-computer.tistory.com/entry/%EA%B0%9D%EC%B2%B4-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8#entry111comment</comments>
      <pubDate>Mon, 10 Feb 2025 14:37:41 +0900</pubDate>
    </item>
    <item>
      <title>java 코딩테스트 준비 - 문자열</title>
      <link>https://do-ha-computer.tistory.com/entry/java-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%EB%AC%B8%EC%9E%90%EC%97%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;값 입력받기 &lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 직접 값을 입력할 때, System.in.read(), Scanner 를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675299449707&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.in.read()
Scanner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 종료 --&gt;1. System.in.read() 사용법&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675299598143&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//System.in.read() 입력 받기 
//정수, 문자 다 저장하는데, char 로 저장된 값은 아스키코드로 저장

import java.io.IOException;
public class Main{
	public static void main(String[] args)throws IOException{
     		char c1 = System.in.read();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Scanner 사용 법&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675301436458&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Scanner 객체는 유니코드로 문자를 입력받는다. 
import java.util.Scanner;
public class Main{
	public static void main(String[] args){
    	Scanner sc = new Scanner(System.in);
       
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;주어진 글자의 아스키 코드값을 출력 &lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;----백준 11654번-----&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;주어진 글자의 아스키 코드값을 출력하라&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675301602170&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//글자를 아스키 코드 값으로 받기 
public class Main{
	public static void main(String[] args)throws Exception{
    	int a = System.in.read();
        System.out.print(a);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;String 을 자르고 싶을 때 Split, charAt&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;String을 자르고 싶을 때, Split, charAt 가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;인덱스를 지정해서 자르고 싶다면, charAt 를 사용하면 된다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675301734607&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String str ='number';
String[] arr = str.split(&quot;&quot;);

for(String s:arr){

}

for(int i=0; i&amp;lt;str.length(); i++){
	str.charAt(i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;글자를 숫자로 변경하고 싶다면 &lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;char, String 형을 int 형으로 바로 변환하면, 해당 숫자가 아스키코드값이 나오게 된다&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;만약에 글자 '1' 을 숫자 '1' 로 갖고싶다면 -'0' 을 해주어야한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675302798487&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String num1 = '1';
int num2 =num1.charAt(0)-'0';&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 종료 --&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 종료 --&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목2 종료 --&gt;&lt;/p&gt;</description>
      <category>java</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/100</guid>
      <comments>https://do-ha-computer.tistory.com/entry/java-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84-%EB%AC%B8%EC%9E%90%EC%97%B4#entry100comment</comments>
      <pubDate>Thu, 2 Feb 2023 13:01:50 +0900</pubDate>
    </item>
    <item>
      <title>aria-controls 속성 사용</title>
      <link>https://do-ha-computer.tistory.com/entry/aria-controls-%EC%86%8D%EC%84%B1-%EC%82%AC%EC%9A%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;aria-controls 속성은 요소간의 관계를 나타내는데 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목1 시작 --&gt;&lt;/p&gt;
&lt;h3 style=&quot;box-sizing: border-box; font-family: Arial, 돋움, Dotum, AppleGothic, sans-serif; border-width: 0px 0px 2px 10px; word-spacing: 3px; border-bottom-style: solid; border-bottom-color: #cccccc; padding: 3px 5px; border-left-style: solid; border-left-color: #55555b; margin: 5px 0px; letter-spacing: 1px; line-height: 1.5; border-image: initial;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #5d5d5d;&quot;&gt;&lt;span style=&quot;font-size: 21px;&quot;&gt;aria-controls : 원인과 결과 관계를 만드는 요소&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;현재 요소에 의해 제어되는 요소를 식별한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;현재 button 은 information div 를 제어한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664959524439&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;showInfo();&quot; aria-controls=&quot;I&quot;&amp;gt;Show info&amp;lt;/button&amp;gt;
&amp;lt;div id=&quot;I&quot;&amp;gt;Information.&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;!-- 소제목1 종료 --&gt;&lt;/p&gt;</description>
      <category>ariacontrols</category>
      <author>짱쭈니어</author>
      <guid isPermaLink="true">https://do-ha-computer.tistory.com/99</guid>
      <comments>https://do-ha-computer.tistory.com/entry/aria-controls-%EC%86%8D%EC%84%B1-%EC%82%AC%EC%9A%A9#entry99comment</comments>
      <pubDate>Wed, 5 Oct 2022 17:46:39 +0900</pubDate>
    </item>
  </channel>
</rss>