<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Zin0's 개발 기록 공간</title>
    <link>https://zin0-0.tistory.com/</link>
    <description>Web Full Stack 주니어 개발자의 기록 공간</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 22:08:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Zin0_0</managingEditor>
    <image>
      <title>Zin0's 개발 기록 공간</title>
      <url>https://tistory1.daumcdn.net/tistory/3862457/attach/12b02c6659814889ac1685e9e619acd1</url>
      <link>https://zin0-0.tistory.com</link>
    </image>
    <item>
      <title>MySQL Hint (옵티마이저 힌트 &amp;amp; 인데스 힌트)</title>
      <link>https://zin0-0.tistory.com/387</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hint&lt;/h2&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;code&gt;옵티마이저 힌트&lt;/code&gt; 와 &lt;code&gt;인덱스 힌트&lt;/code&gt; 두 종류로 나뉜다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵티마이저 힌트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;optimizer_switch 시스템 변수를 설정하여 제어 가능
&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;/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;optimizer_switch 보다 선행되며 다양한 범위 수준에서 적용된다.
&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;/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;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;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;주석 내에 지정해서 사용하며 SEMIJOIN, BKA 등이 있다. (상세한 것은 나중에 옵티마이저 힌트가 필요해지면 추가 정리)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 힌트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;query plan을 확인하다보면 원하지 않는 인덱스를 탄다거나, 인덱스를 타지 않는 경우, 인덱스 순서가 비효율적인 경우가 종종 발생한다.&lt;/li&gt;
&lt;li&gt;쿼리가 복잡한 경우 종종 발생하는데, 이럴 때 인덱스에 대한 힌트를 쿼리에 심어주어 원하는 인덱스를 탈 수 있도록 도울 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;FORCE INDEX&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;USE 키워드와 동일한 기능을 하지만, 옵티마이저에게 보다 강하게 해당 인덱스를 사용하도록 권장
&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;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
  FROM TEST_TABLE FORCE INDEX(INK1_TEST_TABLE)
 WHERE id = 1
   AND name = &quot;박진영&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;USE INDEX&lt;/b&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
  FROM TEST_TABLE USE INDEX(INK1_TEST_TABLE)
 WHERE id = 1
   AND name = &quot;박진영&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;IGNORE INDEX&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;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
  FROM TEST_TABLE IGNORE INDEX(INK2_TEST_TABLE)
 WHERE id = 1
   AND name = &quot;박진영&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;join과 oreder by, group by에도 인덱스 힌트를 줄 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;USE INDEX FOR JOIN&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JOIN 키워드는 테이블 간 조인 뿐만아니라 레코드 검색하는 용도까지 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USE INDEX FOR ORDER BY&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시된 인덱스를 ORDER BY 용도로만 사용하도록 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USE INDEX FOR GROUP BY&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시된 인덱스를 GROUP BY 용도로만 사용하도록 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@bae_mung/TIL-MySQL-Hint&quot;&gt;https://velog.io/@bae_mung/TIL-MySQL-Hint&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wakestand.tistory.com/562&quot;&gt;https://wakestand.tistory.com/562&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS 지식/데이터베이스</category>
      <category>Hint</category>
      <category>MySQL</category>
      <category>USE INDEX</category>
      <category>옵티마이저 힌트</category>
      <category>인덱스 힌트</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/387</guid>
      <comments>https://zin0-0.tistory.com/387#entry387comment</comments>
      <pubDate>Fri, 29 Jul 2022 15:47:03 +0900</pubDate>
    </item>
    <item>
      <title>Surrogate key vs Natural Key (대체 키 vs 자연 키)</title>
      <link>https://zin0-0.tistory.com/386</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Surrogate key vs Natural Key&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Surrogate Key(대체 키)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 의미가 없는 시스템 생성 값 (system generated)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GUID, sequence, unique identifier...&lt;/li&gt;
&lt;li&gt;컬럼들 중 유일하게 식별 가능한 단일 후보키가 존재하지 않는 경우, 임의의 식별번호로 이루어진 후보키&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하나 이상의 컬럼으로 구성 (복합키)&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;img src=&quot;https://www.mssqltips.com/tipimages2/5431_surrogate-vs-natural-keys-sql-server.001.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;img src=&quot;https://www.mssqltips.com/tipimages2/5431_surrogate-vs-natural-keys-sql-server.002.png&quot; alt=&quot;&quot; /&gt;&lt;/div&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;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;/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;/li&gt;
&lt;li&gt;데이터 삽입/업데이트&lt;/li&gt;
&lt;li&gt;데이터 Insert &amp;amp; Update, query 시 대체 키에 대한 열/인덱스에 추가 I/O가 요구됨&lt;/li&gt;
&lt;li&gt;데이터 자체에는 의미가 없으므로, 하위 테이블에 대한 더 많은 테이블 조인이 필요&lt;/li&gt;
&lt;li&gt;다른 고유 제약 조건이 없는 경우 테이블에 자연 키의 중복 값이 있을 수 있음&lt;/li&gt;
&lt;li&gt;대체 키 값은 검색 키로 사용할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Natural Key(자연 키)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블에 이미 존재하는 열 or 열의 집합 (데이터 모델 내의 엔티티 속성)
&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;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://www.mssqltips.com/tipimages2/5431_surrogate-vs-natural-keys-sql-server.003.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://www.mssqltips.com/tipimages2/5431_surrogate-vs-natural-keys-sql-server.004.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;SSN 컬럼을 보면 비즈니스 의미를 담으며 고유하게 식별 가능.&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;컬럼, 기본키 인덱스가 이미 존재하므로 대체 키를 위한 열 / 인덱스에 추가 disk가 필요하지 않음&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;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;데이터 Insert &amp;amp; Update, query 시 더 많은 I/O가 요구됨&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;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mssqltips.com/sqlservertip/5431/surrogate-key-vs-natural-key-differences-and-when-to-use-in-sql-server/&quot;&gt;https://www.mssqltips.com/sqlservertip/5431/surrogate-key-vs-natural-key-differences-and-when-to-use-in-sql-server/&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS 지식/데이터베이스</category>
      <category>Natural Key</category>
      <category>Surrogate key</category>
      <category>대체키</category>
      <category>자연키</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/386</guid>
      <comments>https://zin0-0.tistory.com/386#entry386comment</comments>
      <pubDate>Fri, 29 Jul 2022 15:20:12 +0900</pubDate>
    </item>
    <item>
      <title>LogStash</title>
      <link>https://zin0-0.tistory.com/385</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Log Stash&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;img src=&quot;https://www.elastic.co/guide/kr/logstash/current/static/images/logstash.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&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;Apache 및 Application 로그 (log4J 등), Syslog, windows 이벤트 로그, 네트워킹 및 방화벽 로그 등을 쉽게 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/products/beats/filebeat&quot;&gt;Filebeat&lt;/a&gt; 와 연계하여 보충적인 보안 로그 전달 기능 활용 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-inputs-ganglia.html&quot;&gt;Ganglia&lt;/a&gt;, &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-codecs-collectd.html&quot;&gt;collectd&lt;/a&gt;, &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-codecs-netflow.html&quot;&gt;NetFlow&lt;/a&gt;, &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-inputs-jmx.html&quot;&gt;JMX&lt;/a&gt;, 기타 여러 인프라 및 애플리케이션 플랫폼의 메트릭을 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-inputs-tcp.html&quot;&gt;TCP&lt;/a&gt; 및 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/5.4/plugins-inputs-udp.html&quot;&gt;UDP&lt;/a&gt;를 통해 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작동 방식 및 플러그인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Logstash 이벤트 처리 파이프라인에는 &lt;code&gt;입력 &amp;rarr; 필터 &amp;rarr; 출력&lt;/code&gt; 세 단계&lt;/li&gt;
&lt;li&gt;Input
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;file
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unix 명령어인 &lt;code&gt;tail -0F&lt;/code&gt;와 유사하게 filesystem의 파일을 읽을 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;syslog
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;514 포트에서 syslog 메시지를 수신 대기하고 RFC3164 형식에 따라 구문 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;redis
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;redis 서버에서 읽어오며 redis channels와 redis lists를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;beats
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/downloads/beats&quot;&gt;Beats&lt;/a&gt;에서 보낸 이벤트를 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;http
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Http나 Https로 부터 이벤트를 수신&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;udp
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UDP를 통해 이벤트를 수신&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;azure_envent_hubs, cloudwatch(AWS), es(elasticsearch), exec 등 이외에도 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/current/input-plugins.html&quot;&gt;다양한 플러그인&lt;/a&gt; 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Filter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;grok
&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;현재 Logstash에서 구조화하고 queryable하는 가장 좋은 방법 (Logstash 필터의 가장 기본적인 요소)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Logstash에 120개의 패턴이 내장되어 있어 필요에 맞는 패턴을 찾을 가능성이 크기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;kv
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key-Value 타입으로 이벤트를 파싱해주는 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mutate
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;drop
&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;clone
&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;ruby
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ruby 코드를 사용하여 명령을 할 수 있도록 도와주는 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;geoip, bytes, csv, date, json, elasticsearch, http 등 이외에도 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/current/filter-plugins.html&quot;&gt;다양한 플러그인&lt;/a&gt; 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Output
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;elasticsearch
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;es에 이벤트 데이터를 전송하는 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;file
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 데이터를 디스크의 file에 쓰는 플러그인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;email
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;email을 보내주는 플러그인&lt;/li&gt;
&lt;li&gt;발신자와 수신자, 제목, mailserver 주소, 통신 방식 등을 함께 넘겨 메일링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;csv, cloudwatch, elasticsearch, email, http, kafka 등 이외에도 &lt;a href=&quot;https://www.elastic.co/guide/en/logstash/current/output-plugins.html&quot;&gt;다양한 플러그인&lt;/a&gt; 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Codecs
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입출력의 일부로 동작가능한 stream filter로 메시지 전송과 serialization 프로세스를 분리하여 사용 가능&lt;/li&gt;
&lt;li&gt;json, masgpack, plain text가 가장 널리 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java (JVM) 11 or 17 을 이용하여 구동&lt;/li&gt;
&lt;li&gt;Elasticsearch 및 Kibana 시너지 효과&lt;/li&gt;
&lt;li&gt;수평 확장이 가능한 데이터 처리 파이프라인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플러그형 파이프라인 아키텍처&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;패턴 일치, 지리적 맵핑, 동적 조회 기능과 함께 여러 집계 및 변이 기능을 즉시 사용할 수 있음&lt;/li&gt;
&lt;li&gt;보관(Stash) 선택 (Output)
&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;File, 로그 전송 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파이프라인 (Pipe Line)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Logstash를 이용할 때, 동일한 Logstash 인스턴스 내에서 다중 파이프라인을 사용할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 인스턴스의 Logstash의 통신을 설정해야하는 경우에는 Kafka 또는 Redis 같은 중계 queue를 사용하거나 Lumberjack 출력을 Beats 입력에 연결하여 사용해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;input {
  udp {
    port =&amp;gt; 1234
    workers =&amp;gt; 10
    queue_size =&amp;gt; 25000
    add_field =&amp;gt; { &quot;@log_type&quot; =&amp;gt; &quot;firewall&quot;}
    type =&amp;gt; &quot;firewall&quot;
  }
  http {
      type =&amp;gt; &quot;l7check&quot;
  }
}

filter {
  if [type] == &quot;firewall&quot; {
      ruby {
        code =&amp;gt; &quot;event.set('@kst_date', event.timestamp.time.localtime('+09:00').strftime('%Y-%m-%d'))&quot;
      }
      grok {
          pattern_definitions =&amp;gt; { &quot;CUSTOM_DATE&quot; =&amp;gt; &quot;%{YEAR}-%{MONTHNUM}-%{MONTHDAY}&quot; }
          match =&amp;gt; { &quot;message&quot; =&amp;gt; &quot;&amp;lt;%{NUMBER:pid}&amp;gt;date=%{CUSTOM_DATE}&quot; }
      }
      kv {
          source =&amp;gt; &quot;message&quot;
          allow_duplicate_values =&amp;gt; false
          include_keys =&amp;gt; [&quot;memberNo&quot;, &quot;name&quot;, &quot;age&quot;]
      }
      mutate {
          rename =&amp;gt; {&quot;name&quot; =&amp;gt; &quot;memberName&quot;}
          remove_filed =&amp;gt; [&quot;telephone&quot;]
      }
  } else if [type] == &quot;l7Check&quot; {
      do Sth..
  }
}

output {
  if [type] == &quot;firewall&quot; {
    file {
          path =&amp;gt; &quot;logs/firewall-%{@kst_date}.log&quot;
        codec =&amp;gt; line { format =&amp;gt; &quot;Date : [%{@kst_timestamp}], Cause : %{tags}, Message : %{message}&quot;}
      }
  } else if [type] == &quot;l7Check&quot; {
      do Sth..
  }
}&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;routeros&quot;&gt;&lt;code&gt;input {
  udp {
    port =&amp;gt; 7010
    workers =&amp;gt; 10
    queue_size =&amp;gt; 25000
    add_field =&amp;gt; { &quot;@log_type&quot; =&amp;gt; &quot;firewall&quot;}
    type =&amp;gt; &quot;firewall&quot;
  }
  http {
      type =&amp;gt; &quot;l7check&quot;
  }
}

filter {
  if [type] == &quot;firewall&quot; {
      ruby {
        code =&amp;gt; &quot;event.set('@kst_date', event.timestamp.time.localtime('+09:00').strftime('%Y-%m-%d'))&quot;
      }
      grok {
          pattern_definitions =&amp;gt; { &quot;CUSTOM_DATE&quot; =&amp;gt; &quot;%{YEAR}-%{MONTHNUM}-%{MONTHDAY}&quot; }
          match =&amp;gt; { &quot;message&quot; =&amp;gt; &quot;&amp;lt;%{NUMBER:pid}&amp;gt;date=%{CUSTOM_DATE}&quot; }
      }
      kv {
          source =&amp;gt; &quot;message&quot;
          allow_duplicate_values =&amp;gt; false
          include_keys =&amp;gt; [&quot;memberNo&quot;, &quot;name&quot;, &quot;age&quot;]
      }
      mutate {
          rename =&amp;gt; {&quot;name&quot; =&amp;gt; &quot;memberName&quot;}
          remove_filed =&amp;gt; [&quot;telephone&quot;]
      }
  } else if [type] == &quot;l7Check&quot; {
      do Sth..
  }
}

output {
  if [type] == &quot;firewall&quot; {
    pipeline {
        send_to =&amp;gt; &quot;file_pipeline&quot;
    }
  } else if [type] == &quot;l7Check&quot; {
      do Sth..
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;input {
    pipeline {
        address =&amp;gt; &quot;file_pipeline&quot;
    }
}

output {
    file {
          path =&amp;gt; &quot;logs/firewall-%{@kst_date}.log&quot;
        codec =&amp;gt; line { format =&amp;gt; &quot;Date : [%{@kst_timestamp}], Cause : %{tags}, Message : %{message}&quot;}
      }
}
&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;send_to
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전달하고자 하는 pipeline의 주소를 적어준다. 이 때, 전달하려는 주소는 받는 곳의 address와 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;address
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pipeline이 연결될 주소 (자신의 주소)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;- pipeline.id: main_pipeline
  queue.type: persisted
  pipeline.workers: 1
  pipeline.batch.size: 1
  path.config: &quot;./logstash.conf&quot;
- pipeline.id: file_pipeline
  path.config: &quot;./logstash-file.conf&quot;&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;다중 파이프라인을 사용하기 위해서는 pipeline.yml 파일에서 pipeline id와 config 위치를 설정해주어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/logstash/current/index.html&quot;&gt;https://www.elastic.co/guide/en/logstash/current/index.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tool</category>
      <category>logstash</category>
      <category>로그스태시</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/385</guid>
      <comments>https://zin0-0.tistory.com/385#entry385comment</comments>
      <pubDate>Thu, 24 Feb 2022 11:07:28 +0900</pubDate>
    </item>
    <item>
      <title>주니어 개발자의 2021년 회고</title>
      <link>https://zin0-0.tistory.com/384</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;취업 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년은 2020년의 연장선으로 취업 준비가 이어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운이 좋게 2020년에 부스트캠프라는 좋은 부트캠프에서 개발자로서 어떻게 스스로 성장해 나가야하는지, 현재 어떤 내용들을 깊게 학습해야하는지 배울 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육 이후, 부스트캠프 채널에서 열리는 채용 공고와 직접 채용 공고를 찾아가며 서류를 내고, 시험과 면접을 보며 1월과 2월을 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 고배를 마신 후, 현재 다니고 있는 회사에 인턴 모집과정에 합격하여 감사하게도 현재 개발자로 일을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년여 간의 취업 준비 기간동안 불합격이라는 경험 속에서 많은 것들을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;아쉬운 점&lt;/b&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;/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;부스트캠프 교육을 듣기 전, 반년 이상 PS에 몰두했었기 때문에 조금은 대충해도 붙을 수 있을거라는 착각을 했었다... 정말 오만한 생각이었다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;시험을 보면서 조금만 더 신경써서 준비했더라면 쉽게 풀었을 텐데, 이전에 풀었던 내용인데 기억이 안나네..&lt;/u&gt; 라고 생각했던 부분이 정말 많았다.&lt;/li&gt;
&lt;li&gt;&lt;s&gt;다시 돌아간다 해도 이 부분은 신경쓰지 못할 것 같다.. 면접 준비까지 너무 치열하게 준비를 했던 기억이..&lt;/s&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;잘한 점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면접이 회사가 '나'라는 사람을 평가하는 자리지만, 나에게 맞는 회사인지도 검증하는 자리라고 생각한 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당시에 가장 중요하게 생각하던 것이 개발 문화였다. 해당 회사와 팀에서는 어떤 식으로 의사 결정을 하는지 (예를 들면, 스크럼 회의나 애자일한 의사 결정 등이 있겠다.) 가장 먼저 물어봤던 것 같다.&lt;br /&gt;더불어 테스트 코드를 작성하는지, 중요하게 생각하시는지, 스터디 활동이나 기술 공유 &amp;amp; 코드 리뷰는 어떻게 진행이 되는지 꼬치꼬치 물어봤었다.&lt;br /&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;/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 data-ke-size=&quot;size23&quot;&gt;인턴, 그리고 입사&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1년여 기간 동안 정말 꿈에도 그리던 회사에 입사를 했다!!! 그런데 인턴..?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 그때로 다시 돌아가고싶지 않을 정도로 후회도 없고 열심히 준비했다고 생각하는데, 다시 한번 검증을 받아야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 인턴 지원하기 전에는 자신이 넘쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인턴 합격만하면 당연히 나라는 사람을, 개발자로서 얼마나 성장할 수 있을지 기대되게 해줄 자신이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 당연하게도 해당 기간 동안 떨어지면 정말 다시는 개발자 못할 것 같다는 생각의 많은 스트레스와 합격해야한다는 압박감, 인턴 사원이지만 회사에 존재감이 없는 탈소속감 등 너무 위태위태했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래도 개발자라면 어떻게 해야하는지 잘 알고 있었다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 인턴 평가는 일정 기간 동안 프로젝트를 요구 사항에 따라 구현하는 것이었는데, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;개발자로서 역량을 보여주면 그만이었기 때문에, 프로젝트 완성도를 높이려고 밤낮과 주말없이 달렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;변수 하나, 메소드 하나 선언할 때마다 정말 많은 고민을 담았고, 더 좋은 구현 방법은 없을지 고민하는 시간을 늘렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;코드 리뷰를 받으면서도 위의 고민 사항들에 대해 질문을 남겼고, 피드백 주신 부분에 대해 궁금하다면 질문을, 다르게 생각하면 나의 생각을 주고받으며 완성도를 높이려고 노력했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(&lt;s&gt;사실 내가 떨어진다면 어떤게 가장 이득이될지 고민했을 때, 현직에 계신 분들에게 많은 것들을 배우자는 생각이 가장 먼저여서 위와 같이 행동했던 것 같다... ㅎㅎ&lt;/s&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 방법이 정답은 아니지만 다행히 좋게 봐주셔서 정직원 전환이되어 개발자로 살아갈 수 있게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자라는 이름으로 일을 하며 배운 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정직원 전환과 동시에 4월 경 현재 팀에 배치되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 팀은 새롭게 꾸려진 팀이어서 많이 어수선했던 기억이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 팀원 분들께서 신경을 많이 써주시며 신입 교육을 위해 신입 프로젝트를 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1주간 기획과 기술 스택 등을 정하고 구현 주에 들어가려는 순간, 다른 팀원분께서 퇴사를 하시게되어 신입 프로젝트는 스탑이되고, 현재 담당하고 있는 서비스의 부사수로 들어가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;i&gt;지금 되돌아보면 내가 가진 연혁이나 능력에 비해 감사하고 과분하지만, 당시에는 많이 혼란스러웠고 두려웠었다.&lt;/i&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 인수인계를 받게되었고, 코드와 서비스 파악, 문서화가 되어있지 않아 문서화 작업, 취약점 보완 등에 두달간 고생한 기억이 있다. 당시 서비스가 불안정하여 퇴근 후나 주말에도 간헐적으로 출근해서 원인 파악을 하는 등 원래 개발자는 이렇게 사는걸까 궁금해했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 과도기를 잘 보내면서 서비스 리빌딩까지 안정적으로 마치고, 지금은 안락한 주말을 보내고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론이 매우 길어졌는데, 신입으로 그리고 개발자로 일하면서 신선한 충격이 몇가지 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이론과 실전은 다르다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 당연한 얘기지만, 나에게는 정말 큰 충격이었다. FK 관계 설정이나 정규화 &amp;amp; 무조건 적인 성능 최적화보다는 가독성이 좋은 코드 등은 여태까지 내가 공부하고 경험했던 것들에 반하는 내용이었기에 당황스러웠다.&lt;br /&gt;FK 관계 매핑이나 정규화 부분은 수많은 연관 테이블이 casecade 될 때, RDBMS에서 안정적이지 못하게 처리되는 경우가 존재한다고 한다. (흔한 경우는 아니지만 제대로 casecade되지 않아 정합성이 어긋나는 경우가 생기는 경우가 더러 있다고 한다.) 로직 작성 시에도 정말 성능 최적화가 필요한 상황이 아니라면 최적화에 대한 고민보다는 가독성에 대한 고민을 하는 것이 sustaining 할 때, 서비스 관점에서 훨씬 이득이라는 것도 새로웠다. 내가 운영했던 프로젝트는 길어야 6개월이 전부였는데, 2년, 3년, 10년 등 어떤 서비스가 운영될 때 그 사이에 담당 개발자도 변경이 되고, 기술 스택이나 자잘한 라이브러리, 프레임워크 등 많은 부분이 변경되는게 실제 서비스라는 것을 배웠다. 물론 성능 최적화는 언제나 옳지만, 성능 최적화 작업 공수가 너무 길거나 영향이 미비하다면 오히려 다른 일을 하지 못하는 기회 비용이기 때문에 급하지 않다면 서비스 운영 측면에서 다른 사항들을 고민하는 것이 더욱 도움이 된다.&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;단위 테스트, 통합 테스트, 자체적인 사용성 테스트를 거치며 develop에 merge하는 과정까지 스스로 많은 과정을 거친다. 하지만 저 테스트는 다른 누구도 아닌 내가 짠 코드에 대한 테스트다. 그럼 당연히 내가 생각하는 사고회로 안에서만 테스트를 할 수 밖에 없다. 아무리 많은 케이스를 검증하려고 해도 빈구멍은 항상 생기기 마련이다. 그럼에도 잘해야하는 이유는 develop에 머지되는 순간 시간을 되돌릴 수 없기 때문이다. develop에 올려지면 alpha &amp;amp; beta에서 검증이 진행되는데, 이 기간에 나는 다른 task 처리를 하고 있게된다. 다른 task를 처리하다가 이전에 구현했던 사항이 터진다고 상상해보면, 왜 다 알고 있는 내용인데 적었을지 이해될 것 같다. 멀티태스킹이 정말 힘들다.. 더군다나 관련이 없는 task를 처리하고 있다면 일은 x2가 아니라 ^2가 되어버린다... 정말로.... 그래서 리마인드 차원에서 이 글을 볼 때, 다시 상기하고자 남겨본다...&lt;br /&gt;&lt;b&gt;(팀원 리뷰에서도 정확성에 대한 아쉬움을 피드백 받기도 했다..)&lt;/b&gt;&lt;br /&gt;이 정도면 될까 생각이 든다면? 그 생각을 접고 다시 여러 상황에 대한 고민을 하는게 더 현명할 것 같다.&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;br /&gt;하지만, 역설적이게도 로직이 변경되거나 기획이 변경되는 상황에서 가장 필요하고 중요한 것은 테스트 코드이다. 새롭게 기능 구현할 때야 그나마 영향이 덜하지만, 기존에 운영되고 있는 기능을 수정한다고 생각해보자.&lt;br /&gt;테스트 코드가 없다면 고려해야할 사항이 두배로 늘어난다. 개인적으로 테스트 코드는 내 코드 품질에 대한 최소한의 보증이라고 생각한다. &lt;b&gt;어떠한 상황에 대해 고려해서 처리를 했고, 기획에 따라 어떻게 처리하게 했다&lt;/b&gt; 라는 보증이 되기도 하고, &lt;b&gt;이 로직이 어떤 식으로 요청이 들어왔을 때 어떻게 동작할 것이다라는 쉬운 가이드가 되기도 한다.&lt;/b&gt; 이는 리팩토링에서 정말 큰 힘이된다. 코드 수정 범위가 넓고 로직이 변하면 정말 복잡하다. class 하나, pacakge 하나 수정은 테스트 코드가 아니더라도 처리가 가능하다. 하지만, 수정 범위가 pacakage 두개에 영향을 미친다 하더라도 수정된 코드에 대한 확신은 사라진다. 이 때, 테스트코드가 있다면 기존에 동작하던 기능이 잘 동작하는지 확인이 가능하다. 추가되는 케이스가 있다면 추가하면 그만이다. 기획 사항이나 로직이 변경되는 이유라면, 위에서 주장한 나의 관점에서는 테스트 코드는 변경되는 것이 당연하다. 처음에만 귀찮지 나중에는 내가 일을 두번하지 않아도 되니까 신나서 작성하게 될 것이다... (&lt;s&gt;적어도 나는 그랬다&lt;/s&gt;)&lt;br /&gt;가끔 많은 분들이 TDD와 테스트 코드 작성이랑 헷갈려 하시는데, TDD 같이 개발 루틴을 바꾸는 것이 아니더라도 테스트 코드는 정말 중요하다.&amp;nbsp;&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;신입은 위축되어있기 마련이다. 나도 그렇고 동기도 그렇고 선배들도 그랬다.&amp;nbsp;&lt;br /&gt;내가 지닌 경험치와 선배들의 경험치는 다를 수 밖에 없다. 절대적인 시간이 부족하기 때문이다.&lt;br /&gt;그렇기 때문에 모두가 알고있다. 모르는게 당연하다는 것을.&lt;br /&gt;진짜 모르면 찾아보고 정리해서 궁금한 사항을 물어보면 된다. &lt;br /&gt;선배들도 그걸 바랄 것이다. (혼자 전전긍긍하는 것 보다 질문하고 성장해서 빠르게 1인분을 하게되는 것을 더욱 바랄 것이다.)&lt;br /&gt;다행히도 나는 위축되어있기는 했지만, 궁금한 사항은 물어보고, 명확하지 않으면 정리된 내용으로 역질문하며 스스로 명확해지려고 노력했다. 그렇기에 위에서 정확성이 아쉽다는 동료의 피드백이 적지 않았을까싶다..&lt;/li&gt;
&lt;li&gt;내가 작성한 코드에 자신감을 가져도 좋다.&lt;br /&gt;항상 옳다라고 생각하는 것이 아니라, 내가 작성한 코드기 때문에 이유가 있어야 한다는 것이다.&lt;br /&gt;그래야 내가 어떻게 생각하고 어떤 부분을 고려하여 작성했는지 명확해진다.&lt;br /&gt;이 방향성이 우리 팀이 지향하는지, 서비스를 유지하는 관점에서 도움이 될지가 명확해진다.&lt;br /&gt;만약 내가 생각한 방향이 잘못된 방향이라면 지적을 받을 것이고, 수용하고 배우면 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스스로 아쉬운 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;욕심이 너무 많았다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;욕심이 너무 많아서 이것도 하고싶고 저것도 하고싶고 이것저것 신경이 분산되었다.&lt;br /&gt;결론적으로는 이것저것 건드린 것중에 완성이된 것이 거의 없다.&lt;br /&gt;번갈아가며 몰두하다보니 성과도 없고 빠르게 지쳤다.&lt;br /&gt;추석 전후로 지치다보니 아무것도 안하게되었다.&lt;br /&gt;일이 바쁘기도 했지만, 일이 수월한 날도 분명 있었다.&lt;br /&gt;그 시간들을 모아보면 하나의 목표를 잡고 완성도를 높일 시간이 충분히 나온다.&lt;br /&gt;하지만, 생각이 많고 잡다해지다보니 시작도 하지않고 풀어지게 되었다.&lt;br /&gt;입사 초에는 늦게 시작한만큼 연차보다 좋은 퍼포먼스를 보이기 위해 노력하자고 생각했는데, 이 생각이 독이 된 것 같다.&lt;br /&gt;첫 술에 배부르려고 했던 과오였다고 생각한다.&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;i&gt;개발자면 개발에 집중하는게 왜 아쉽지? &lt;/i&gt;라는 생각이 들 수도 있는데, 개발자는 개발만 하는 사람이 아니다.&lt;br /&gt;내가 사용한 기술 스택에 대한 정리도 필요하고, 문서화나 더욱 좋은 커뮤니케이션을 위한 노력 등 외적인 것도 정말 중요하다고 생각한다.&lt;br /&gt;일이 바쁘다는 핑계로 task 쳐내기 바쁘다는 핑계로 개발에만 몰두했는데, task가 여유롭게 들어오는 시간이 잠깐 생기면 외적인 것을 계발하거나 현재 서비스에 대한 안정성 향상 등에 고민하기보다 쉬엄쉬엄 일한 시간이 있었다.&lt;br /&gt;이런 시간도 분명 필요하지만, 너무 많으면 독이되는 것 같다는 생각이 들었다.&lt;br /&gt;다행히 이 생각이 일찍 들어서 정말 바쁜 기간이 끝난 이후부터는 조금씩 서비스 안정성을 올리기 위해 모니터링하며 버그 픽스를 진행하기도 했고, 배우고싶던 기술에 대해 검색하거나 조금 더 개선할 수 있는 기술을 리서치하거나하는 시간을 보냈다. 이런 시간들을 보내고 보니, 조금 더 일찍 이 생각을 가졌다면 좋았을텐데 라는 아쉬움까지 연결되었다.&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;br /&gt;&lt;i&gt;나보다 경험이 더 많으니 당현히 이런 부분까지 고려하여 진행하셨겠지?&lt;/i&gt; 라거나 &lt;i&gt;내가 알고있는 것이 완벽하지 못하니 잘못된 내용 공유일 수 있겠지?&amp;nbsp;&lt;/i&gt;라는 생각이 앞섰던 것 같다.&lt;br /&gt;다른 팀원들도 실수할 수 있는 여지가 있고, 내가 알고있는 내용을 모를 수도 있다.&lt;br /&gt;만약 이게 트러블 슈팅과 관련되어있다면, 해당 팀원이 고민할 수 있는 방향을 줄여주는 좋은 길잡이가 될 수도 있다.&lt;br /&gt;신입이라는 타이틀에 너무 위축되어 자유롭게 표현하지 못했다는 생각이 든다.&lt;br /&gt;선배 개발자분들도 실수하는 경우가 당연히 존재하고, 내가 잘못알고 있는 내용이라면 이에 대한 피드백을 받을 수도 있는 좋은 기회라고 느낀다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;스스로 만족한 점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;'나'를 찾으려는 노력을 했다.&lt;/b&gt;&lt;br /&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;br /&gt;주말에는 여행이나 그간 못했던 취미 혹은 새롭게 무언갈 배우려는 시도를 했었다.&lt;br /&gt;일과 멀어지면서 정말 내가 좋아하는게 무엇인지 조금 더 깊게 고민해봤고 나에 대해 알아가는 시간을 보내면서 잃었던 자존감을 되찾았다.&lt;br /&gt;그리고 이 시간들 덕분에 업무 시간에 더욱 몰두할 수 있지 않았을까 싶다.&lt;br /&gt;올해도 의도적으로 업무나 개발과 거리를 두는 시간을 가지며 나를 조금 더 탐구해보고 싶다.&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;데드라인에 맞춰 task를 쳐내다 보면 현실과 타협하자는 유혹을 정말 많이 느낀다.&lt;br /&gt;테스트 코드를 작성하지 않은 채 pr을 올려 그대로 merge를 한다던가, 어떤 원리로 동작하는지 파악하지 않은채 사용하는 코드 등 서비스 안정성이나 추후에 실수를 야기할 수 있는 유혹들이 대표적이다.&lt;br /&gt;트러블 슈팅 기록 등도 있겠지만 테스트 코드나 작동 원리를 모르는 코드가 조금 더 크리티컬하다고 생각이 들어서, 최대한 공수에 부합하여 위의 사항들을 챙기고자 노력했다.&lt;br /&gt;hotfix 사항을 제외하고는 위의 사항들이 적었기 때문에 개인적으로는 노력했다라고 칭찬해주고싶다.&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 data-ke-size=&quot;size23&quot;&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;내 서비스에서 1인분 하는 개발자가 되기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어딜가든 1인분을 하는 개발자는 정말 귀하고 개인적으로는 되기 힘들다고 생각한다.&lt;br /&gt;그러니 현재 내가 투입된 서비스들에서 만큼이라도 1인분을 할 수 있도록 더욱 많은 고민과 몰입을 해보자.&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;1년 가까이 일하면서 취미의 중요성을 크게 느낀다.&lt;br /&gt;개발을 좋아하는 편이라고 생각하지만, 취미가 개발로 이어지는 정도까지는 아니다.&lt;br /&gt;그래서 정말 공부하고 싶은 것이 없다면, 무리해서 퇴근 후나 주말까지 이어가고 싶지 않다는 생각을 가지고 있다.&lt;br /&gt;작년에 리프레시하려고 이것저것 해보면서 긍정적인 효과를 봤는데, 뭔가 새롭게 도전하는 취미들을 가지고 싶다는 생각을 가지게 됐다. 새롭게 도전하며 조금 더 나를 알아가고싶은 마음에 새로운 취미를 가지고 싶다.&lt;br /&gt;&lt;br /&gt;&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;br /&gt;8개월 동안 분명 한일이 많을텐데, 누군가 앞에서 10분을 얘기하기도 힘들다는 생각이 들어서, 이대로는 안된다는 생각이 앞섰다.&lt;br /&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;</description>
      <category>ETC</category>
      <category>2021 회고</category>
      <category>개발자 회고</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/384</guid>
      <comments>https://zin0-0.tistory.com/384#entry384comment</comments>
      <pubDate>Thu, 3 Feb 2022 18:20:52 +0900</pubDate>
    </item>
    <item>
      <title>@SpyBean vs @MockBean</title>
      <link>https://zin0-0.tistory.com/383</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SpyBean vs MockBean&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SpringBoot 1.4 부터 추가된 Annotation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SpyBean&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 레벨, @Configuration 클래스의 필드, @RunWith 클래스의 필드에 적용 가능 가능&lt;/li&gt;
&lt;li&gt;같은 컨텍스트 안의 빈은 spy로 선언된 빈으로 래핑된다.&lt;/li&gt;
&lt;li&gt;Given에서 선언한 코드 외에 전부 실체 객체를 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@SpyBean
private MyService myService;

doReturn(myObject)
    .when(myService)
    .getMyObject(params..);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;myService의 getMyObject라는 메소드는 위에서 정한 parameter로 호출할 때, myObject라는 객체를 리턴해주도록 설정해준다.&lt;br /&gt;이외의 모든 메소드, 필드 등은 실체 객체를 사용하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 기능만을 변경해서 테스트에 사용하고 싶은 경우, &lt;code&gt;@SpyBean&lt;/code&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;code&gt;@SpyBean&lt;/code&gt;을 통해 테스트하려는 경우, 테스트 범위가 너무 커지고 통합테스트와 다름 없는 테스트가 되지 않는지 먼저 확인한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 단위만 테스트하면 되는 경우에는 &lt;code&gt;Mock&lt;/code&gt;으로 구현하는 것이 더욱 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MockBean&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.10/javadoc-api/org/springframework/context/ApplicationContext.html?is-external=true&quot;&gt;&lt;code&gt;ApplicationContext&lt;/code&gt;&lt;/a&gt;에 mock을 추가하는 Annotation&lt;/li&gt;
&lt;li&gt;클래스 레벨, @Configuration 클래스의 필드, @RunWith 클래스의 필드에 적용 가능&lt;/li&gt;
&lt;li&gt;컨텍스트에 정의된 기존의 동일한 타입 빈은 MockBean으로 대체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Mock = 껍데기만 있는 객체&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;MockBean은 기존에 사용되던 Bean의 껍데기만 가져오고 내부의 구현은 사용자에게 위임한 형태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, Bean의 어떤 메소드에 어떤 값이 들어왔을 때, 어떤 값이 리턴되어야 할지를 테스트를 작성하는 개발자가 조작하여야 함&lt;/li&gt;
&lt;li&gt;기존에 사용되던 스프링 Bean이 아닌 Mock Bean을 주입&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@MockBean
private MyService myService;

given(myService.getMyObject(params..))
    .willReturn(myObject);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;given&lt;/code&gt;에서 &lt;b&gt;선언한 코드 외에는 전부 사용할 수 없음&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;given
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 Mock Bean이 &lt;b&gt;어떤 행동을 취하면 어떤 결과를 반환한다&lt;/b&gt;를 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/226&quot;&gt;https://jojoldu.tistory.com/226&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/SpyBean.html&quot;&gt;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/SpyBean.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html&quot;&gt;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/기타</category>
      <category>MockBean</category>
      <category>Mockito</category>
      <category>spring boot</category>
      <category>SpyBean</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/383</guid>
      <comments>https://zin0-0.tistory.com/383#entry383comment</comments>
      <pubDate>Mon, 11 Oct 2021 15:44:59 +0900</pubDate>
    </item>
    <item>
      <title>Arcus</title>
      <link>https://zin0-0.tistory.com/382</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Arcus 란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;memcached와 ZooKeeper를 기반으로 네이버 서비스들의 요구 사항을 반영해 개발한 메모리 캐시 클라우드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;memcached 프로토콜을 지원하고 기본 성능 혜택은 그대로 유지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드 저장소인 DB 앞단에 위치하여 hot-spot 성격의 데이터를 캐싱하여, 서비스 응용에 빠른 응답성을 제공하고 DB 부하를 감소시킴&lt;/li&gt;
&lt;li&gt;복잡한 계산에 의한 결과물 or 웹 처리 상의 중간 데이터 등을 신속하게 저장 / 조회&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;h4 data-ke-size=&quot;size20&quot;&gt;Front Cache&lt;/h4&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;ARCUS와 같이 리모트 서버에서 캐싱을 수행하면 캐싱된 데이터를 서로 공유할 수 있지만, 일시적인 많은 요청으로 인한 장비 리소스 부족, 과다한 트래픽 발생에 따라 데이터 응답 시간이 민감하게 영향을 받음
&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;Front Cache를 통해 소프트웨어 단에서 적은 비용으로 해결 가능&lt;/li&gt;
&lt;li&gt;로컬 메모리에 캐싱한다고 하여 로컬 캐시(Local Cache)라고도 부름&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;트래픽에 영향 X&lt;/li&gt;
&lt;li&gt;Spring Framework의 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/4.1.x/spring-framework-reference/html/cache.html&quot;&gt;Spring Cache&lt;/a&gt;를 ARCUS에 맞추어 구현한 &lt;a href=&quot;https://github.com/naver/arcus-spring&quot;&gt;ARCUS Spring&lt;/a&gt; 라이브러리 1.13.3 버전에서 프론트 캐시를 위한 인터페이스를 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Arcus의 Front Cache는 Ehcache를 이용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java EE 및 경량 컨테이너를위한 오픈 소스 Java 분산 캐시&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;확장(scable) - 메모리 &amp;amp; 디스크 저장 지원, 멀티 CPU의 동시 접근에 튜닝&lt;/li&gt;
&lt;li&gt;분산 지원 - 동기/비동기 복사, 피어(peer) 자동 발견&lt;/li&gt;
&lt;li&gt;높은 품질 - Hibernate, Confluence, Spring 등에서 사용되고 있으며, Gaia 컴포넌트에서도 EHCache를 사용하여 캐시를 구현함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;연결 관련 Exception&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NotExistsServiceCodeException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Admin 주소 or service code를 잘못 설정한 경우&lt;/li&gt;
&lt;li&gt;설정 파일을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TimeoutException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분 JVM GC 때문에 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;timeout이 연속적으로 발생하고 connection이 재설정 되는지 확인 (로그에 &quot;exceeded continuous timeout threshold&quot;이 남고, &quot;Cancelled&quot;가 여러 개 보인다.)&lt;/li&gt;
&lt;li&gt;timeout이 발생할 때의 GC 시간 확인&lt;/li&gt;
&lt;li&gt;Timeout값이 너무나 작게 설정되지 않았나 확인한다. 시간의 단위도 반드시 확인한다.&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;Application 시작 이후, 충분한 Warm up 전 요청이 몰리는 경우&lt;/li&gt;
&lt;li&gt;지정된 timeout 값이 GC 시간보다 작은 경우&lt;/li&gt;
&lt;li&gt;Arcus client 한 개로 요청을 처리하기 벅찬 경우&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;Full GC 시간을 측정하고 GC 튜닝을 한다&lt;/li&gt;
&lt;li&gt;Timeout 값을 조정한다. (간헐적인 timeout은 무시)&lt;/li&gt;
&lt;li&gt;TimeoutException은 재시도가 가능 ~&amp;gt; 재시도 로직을 만듦&lt;/li&gt;
&lt;li&gt;Arcus client pool을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ExecutionException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Arcus와의 연결이 끊기고 모든 대기 중인 요청이 cancle됨&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;Arcus와의 연결에서 timeout exception이 n번 이상 발생 ~&amp;gt; 자바 클라이언트는 연결에 문제가 있다고 판단하고 재연결을 시도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;continuous timeout threshold의 default는 10회&lt;/li&gt;
&lt;li&gt;timeout이 연속적으로 발생하여 연결을 재설정 하는 과정에 일시적으로 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Arcus 서버 or Arcus 서버 간 네트워크 문제&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;timeout이 연속적으로 발생하는 원인을 파악하여 조치(권장)&lt;/li&gt;
&lt;li&gt;continuous timeout exception threshold값을 조절&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Can&amp;rsquo;t serialize null
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Arcus 값 저장 시, null을 저장할 수 없음&lt;/li&gt;
&lt;li&gt;null을 저장하지 않도록 한다. (Arcus에서 key miss가 발생하면 null이 반환된다.)&lt;/li&gt;
&lt;li&gt;삭제는 null을 저장하지 않고 delete API를 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IllegalArgumentException(&quot;Non-serializable object&quot;)
&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;Arcus java client가 기본값으로 사용하는 Transcoder는 SerializingTranscoder로 non-serializable객체를 serialize할 수 없음&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;저장하려는 객체에 Serializable 인터페이스를 구현&lt;/li&gt;
&lt;li&gt;non-serialize 객체를 encode/decode할 수 있는 customized transcoder를 만들어서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SERVER_ERROR Out of memory
&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;Arcus 서버 구동 시 지정한 sticky item 영역 고갈 or 영역을 지정하지 않음&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;Sticky item 영역의 용량 산정을 다시 설정&lt;/li&gt;
&lt;/ul&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;만료시간을 30일 이상으로 지정하면 Unix time으로 변환 시, 과거의 시간이 되어 즉시 만료됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회해보면 만료된 아이템으로 취급되어 null이 반환됨&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;만료시간은 &amp;lsquo;초&amp;rsquo; 단위로 지정해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;조회하는 key와 저장하는 key가 서로 다른 경우&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;expire time값을 변경&lt;/li&gt;
&lt;li&gt;로그를 통해 저장하는 key와 조회하는 key가 동일한지 확인&lt;/li&gt;
&lt;li&gt;Future객체의 getOperationStatus()로 결과 코드를 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;REFERENCE&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/jam2in/arcus-spring%EC%9D%98-front-cache%EB%A5%BC-%EC%86%8C%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-b326948020a0&quot;&gt;https://medium.com/jam2in/arcus-spring%EC%9D%98-front-cache%EB%A5%BC-%EC%86%8C%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-b326948020a0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javacan.tistory.com/entry/133&quot;&gt;https://javacan.tistory.com/entry/133&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/기타</category>
      <category>Arcus</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/382</guid>
      <comments>https://zin0-0.tistory.com/382#entry382comment</comments>
      <pubDate>Thu, 30 Sep 2021 15:23:55 +0900</pubDate>
    </item>
    <item>
      <title>9장) 9.3 애플리케이션 아키텍처 (vol1 마지막 정리)</title>
      <link>https://zin0-0.tistory.com/381</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;9장 스프링 프로젝트 시작하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9.3 애플리케이션 아키텍처&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;아키텍처
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9.3.1 계층형 아키텍처&lt;/h4&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;/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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아키텍처와 SoC
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지는 성격이 다른 코드가 얽혀 있는 것을 분리하고, 유연한 결합을 위해 인터페이스를 두고, DI 컨테이너로 직접적인 관계를 알지 못하도록 유연한 설계와 구현 전략을 진행
&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;br /&gt;애플리케이션을 구성하는 오브젝트들을 비슷한 성격과 책임을 가진 것들끼리 묶을 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 엑세스 로직을 담당하는 DAO&lt;/li&gt;
&lt;li&gt;비즈니스 서비스 오브젝트&lt;/li&gt;
&lt;li&gt;웹을 처리하는 코드 or 독자적인 성격의 코드 등&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;테스트 가능&lt;/li&gt;
&lt;li&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;code&gt;위에서 설명한 것을 간략하게 말하면, 책임과 성격이 다른 것을 크게 그룹으로 만들어 분리해두는 것 === 계층형 아키텍처&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=== 멀티 티어 아키텍처(Multi tier Architecture)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=== 3계층 애플리케이션(3-tire or 3-layer Application)&lt;/code&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;세분화해서 더 작은 단위의 계층으로 나눌 수 있지만, 전형적인 웹 엔터프라이즈 애플리케이션은 책임과 성격으로 보면 3 계층의 논리적인 분류가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3계층 아키텍처와 수직 계층
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3 계층
&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;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;DAO 패턴을 보편적으로 사용해서, DAO 계층이라고 불림&lt;/li&gt;
&lt;li&gt;DB 외에도 ERP, 레거시 시스템, 메인 프레임 등에 접근하는 역할을 해서 EIS(Enterprise Information System) 계층이라고도 불림&lt;/li&gt;
&lt;li&gt;대개는 장기적인 데이터 저장을 목적으로 하는 DB 이용이 주된 책임&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;그림으로 표현할 때, 세로로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기본 3 계층은 기술 계층이 아닌 역할에 따라 구분 ~&amp;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;새로운 계층을 추가하면 애플리케이션 코드에 지대한 영향 ~&amp;gt; 신중하게 결정&lt;/li&gt;
&lt;li&gt;추상 계층을 새로 추가하는 것이 부담스럽고 경우에 따라 유연하게 하위 계층의 API를 활용할 필요가 있다면, 공통 기능을 분리해 유틸리티나 헬퍼 메소드 or 오브젝트로 제공하는 것도 좋은 방법&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;잘 만들어진 스프링 애플리케이션의 서비스 계층 클래스는 이상적인 POJO
&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;DAO 계층을 호출하고 활용해서 만들어짐&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;3계층 어디에서나 접근이 가능하도록 만들 수 있고, 서비스 계층을 통해 사용되도록 제한할 수도 있음&lt;/li&gt;
&lt;li&gt;코드의 특징과 장단점, 활용 예를 고려하여 적절하게 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특별한 경우가 아니라면, 추상화 수직 계층구조를 가질 필요가 없음
&lt;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;비즈니스 로직을 담은 서비스 계층과 엔터프라이즈 서비스를 제공하는 기반 서비스 계층은 다르다! (이름 때문에 혼동 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기반 서비스 계층 -&amp;gt; 서비스 계층 오브젝트를 호출하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 필요에 따라 서비스 계층 -&amp;gt; 기반 서비스 계층 API 호출&lt;/li&gt;
&lt;li&gt;스케쥴링이 기반 서비스 -&amp;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;특정 구현과 기술에 대한 종속성 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP를 통해 서비스 계층의 코드를 침범하지 않고 부가기능을 추가하는 방법 활용&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;엔터프라이즈 애플리케이션의 프레젠테이션 계층은 클라이언트 종류와 관계 없이 HTTP 프로토콜을 사용하는 서블릿이 바탕&lt;/li&gt;
&lt;li&gt;다른 계층과 달리 클라이언트까지 그 범위를 확장할 수 있음&lt;/li&gt;
&lt;li&gt;최근 추세는 프레젠테이션 로직이 Client Side에 이전됨 (CSR)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RIA, SOFEA 아키텍처가 대표 예시&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;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;/li&gt;
&lt;li&gt;각 계층은 자신의 역할에만 충실, 자신과 관련없는 기술 API의 사용을 삼가
&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;/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;SQLException이라는 JDBC 기술 종속적인 예외를 던지면, 이를 사용하는 서비스 계층에서는 SQLException을 해석해서 예외상황을 분석하고 이를 처리하는 코드를 만들어야함
&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;스프링의 DataAccessException 처럼 런타임 예외로 만들어 전달해야함&lt;/li&gt;
&lt;li&gt;또한, JDBC, JPA, hibernate 등 특정 구현 방식에 종속되지 않는 추상적인 형태로 만들어줘야함&lt;/li&gt;
&lt;li&gt;그래야 낮은 결합도를 유지하며 유연한 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프레젠테이션 계층의 오브젝트를 그대로 서비스 계층으로 전달하는 실수도 빈번
&lt;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;주로, HttpServletRequest, HttpServletResponse, HttpSession과 같은 타입을 서비스 파라메터로 전달하는 경우가 예시&lt;/li&gt;
&lt;li&gt;서비스 계층에서 웹과 관련된 예외 발생 ~&amp;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;/li&gt;
&lt;li&gt;&lt;b&gt;인터페이스는 각 계층의 경계를 넘어 들어오는 요청을 명확하게 정의하겠다는 의미&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;단순하게 자바의 interface 키워드를 사용하라는 의미 X
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스에 클래스의 모든 public 메소드를 추가하면 인터페이스 사용 가치가 떨어짐&lt;/li&gt;
&lt;li&gt;계층 내부의 예상되는 변화에도 쉽게 바뀌지 않도록 주의해서 만들어야함&lt;/li&gt;
&lt;li&gt;다른 계층에서 꼭 필요한 메소드만 노출 (접근제한자 설정)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 모든 DI는 기본적으로 오브젝트 사이의 관계를 다룸
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계층 사이의 경계나 관계에 직접 관여 X&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;스프링의 DI는 계층 사이의 관계에도 적용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DI는 계층을 구분해주지 않기 때문에 빈 사이의 의존관계를 만들 때는 주의 필요&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;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9.3.2 애플리케이션 정보 아키텍처&lt;/h4&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;/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;li&gt;정보를 단순히 값이나 값을 담기 위한 목적의 오브젝트 형태로 취급&lt;/li&gt;
&lt;li&gt;DB나 백엔드 시스템에서 가져온 정보를 값으로 다루고 그 값을 취급하는 코드를 만들어 로직을 구현하고, 값을 그대로 뷰와 연결해주는 형태
&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;비즈니스 로직이 DB 저장 프로시저나 SQL에 담겨있는 경우가 많고, DB 리턴 값을 맵이나 단순 결과 저장 오브젝트에 넣어서 전달하는 경우가 많음
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 중심 아키텍처는 비즈니스 로직을 어디에 많이 두는지에 따라&lt;br /&gt;DB에 무게를 두는 구조 vs서비스 계층의 코드에 무게를 두는 구조로 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB / SQL 중심의 로직 구현 방식
&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;ex) 사용자 이름으로 id, password, 이름, 가입일자(일부 데이터)만 검색
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 조건은 SQL로 만들어지고, SQL이 이미 화면에 어떤 식으로 출력될지 알고 있음&lt;/li&gt;
&lt;li&gt;모든 계층의 코드는 이름을 이용한 고객 조회라는 업무에 종속&lt;/li&gt;
&lt;li&gt;업무의 내용이 바뀌면 모든 계층의 코드도 함께 변경됨&lt;/li&gt;
&lt;li&gt;종속적이고 배타적이라 다른 단위 업무에 재사용되기 힘듦&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서비스 계층이 의미가 없음 ~&amp;gt; 2계층 구조에서도 비슷하게 발견됨&lt;/li&gt;
&lt;li&gt;자바 코드를 단지 DB와 웹 화면을 연결해주는 단순한 인터페이스 도구로 전락시키는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 중심의 업무 단위로 코드를 만들면 애플리케이션 내에 흘러다니는 정보는 항상 단순한 포맷의 데이터
&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;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;li&gt;중복 제거가 어려움&lt;/li&gt;
&lt;li&gt;업무 트랜잭션에 따라 필드 하나가 달라도 거의 비슷한 DAO 메소드를 새로 만들어야함&lt;/li&gt;
&lt;li&gt;로직을 DB와 SQL에 많이 담을수록 확장성이 떨어짐&lt;/li&gt;
&lt;li&gt;테스트가 어려움&lt;/li&gt;
&lt;li&gt;DB에 큰 부담
&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;/li&gt;
&lt;li&gt;여러 대의 서버를 클러스터로 하나의 서버처럼 동작하게 만들 수 있음&lt;/li&gt;
&lt;li&gt;같은 작업 대비 DB에 비해 저렴&lt;/li&gt;
&lt;li&gt;안정성이 높아지고 코드 검증이 편함&lt;/li&gt;
&lt;li&gt;오브젝트 로직을 간단히 검증 가능&lt;/li&gt;
&lt;li&gt;객체지향 분석과 모델링 결과로 나온 모델을 가져다 쉽게 오브젝트로 만들어낼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;DB에 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서, 주요 로직은 서비스 계층이 코드에서 처리&lt;/li&gt;
&lt;li&gt;SQL의 결과를 그대로 담고있는 단순한 오브젝트 or 맵을 이용해 데이터를 주고 받음
&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;li&gt;DAO가 돌려준 정보를 분석, 가공하면서 비즈니스 로직을 적용하는 책임을 서비스 계층으로 이전&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;li&gt;여러 메소드로 분산하면 메소드 크기는 줄지만 전체 클래스 코드 양은 그대로&lt;/li&gt;
&lt;li&gt;상대적으로 단순한 DAO 로직을 사용하고, 비즈니스 로직의 대부분을 서비스 계층에 집중하는 접근 방법&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;li&gt;테스트하기 수월함&lt;/li&gt;
&lt;li&gt;DAO가 다루는 SQL이 복잡하지 않음&lt;/li&gt;
&lt;li&gt;뷰와 1:1로 매핑되지 않아도 되기 때문에, 일부 DAO 코드는 여러 비즈니스 로직에서 공유하여 사용&lt;/li&gt;
&lt;li&gt;각 단위 업무별로 독립적인 개발이 가능 ~&amp;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;일반화가 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 엑세스 계층의 SQL은 서비스 계층의 비즈니스 로직의 필요에 따라 만들어지기 쉬움&lt;/li&gt;
&lt;li&gt;계층 간의 결합도가 여전히 큼&lt;/li&gt;
&lt;li&gt;본격적인 객체 지향적 설계를 적용하기 힘듦&lt;/li&gt;
&lt;li&gt;개발자 개인 코딩 습관이나 실력에 따라 비슷한 로직이라도 전혀 다른 스타일의 코드가 나올 수 있음&lt;/li&gt;
&lt;li&gt;개발 능력이 떨어지는 경우, 자바 코드의 비즈니스 로직 이해가 SQL에 비즈니스 로직이 있는 것 보다 어려울 수 있음&lt;/li&gt;
&lt;li&gt;계층별로 독립된 설계와 개발이 어려움&lt;/li&gt;
&lt;li&gt;비즈니스 로직이나 설계에 변경이 생기거나 수정사항이 생기면 코드를 수정하기 쉽지 않을 수 있음&lt;/li&gt;
&lt;li&gt;테스트가 불충분하거나 아예 없으면 SQL보다 더 다루기 힘든 코드로 전락할 수 있음&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9.3.3 오브젝트 중심 아키텍처&lt;/h4&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;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;도메인 모델은 DB 엔티티 설계에도 반영 ~&amp;gt; 유사한 형태일 가능성이 높음&lt;/li&gt;
&lt;/ul&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;카테고리와 상품이라는 두 엔티티가 있는 경우&lt;/li&gt;
&lt;li&gt;Category&lt;/li&gt;
&lt;li&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;필드명&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설정&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CategoryId&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Primary Key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;Product&lt;/li&gt;
&lt;li&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;필드명&lt;/th&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;설정&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ProductId&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Primary Key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CategoryId&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Foreign Key (Category)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;while(rs.next()) {
    Map&amp;lt;String, Object&amp;gt; resMap = new HashMap&amp;lt;&amp;gt;();
    resMap.put(&quot;categoryId&quot;, rs.getString(1));
    resMap.put(&quot;description&quot;, res.getString(2));
    ...
    list.add(resMap);
}&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;결과를 맵에 담는DAO 코드&lt;/li&gt;
&lt;li&gt;서비스 계층에서는 List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; 타입만 보고 어떤 내용인지 알 수 없기 때문에, DAO 메소드에서 두 테이블을 조인해서 각 필드 값을 가져오고, 필드 이름을 키로 갖는 맵에 값을 가져와야 알 수 있는 형태&lt;/li&gt;
&lt;li&gt;만약, 웹 페이지 내에서 해당 정보를 수정에서 DB에 다시 반영해야 하는 경우 map이나 배열에 담겨 전달 ~&amp;gt; 데이터 중심의 아키텍처는 SQL 결과에 모든 계층의 코드가 의존하게됨&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Category {
    int categoryId;
    String description;
    Set&amp;lt;Product&amp;gt; products;

    // getter &amp;amp; setter
}

public class Product {
    int productId;
    String name;
    int price;
    Category category;

    // getter &amp;amp; setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;매번 달라지는 SQL 결과를 담는 오브젝트(위의 Map 같은 경우)와 달리 애플리케이션 어디에서도 사용될 수 있는 일관된 형식의 도메인 정보를 담고 있음&lt;/li&gt;
&lt;li&gt;정보를 가공하거나 DB에 수정하는 경우에도 SQL 결과에 의존하지 않고 반영 가능&lt;/li&gt;
&lt;li&gt;Category에 속한 Product를 가져오는 경우에도, &lt;code&gt;Set&amp;lt;Product&amp;gt; products = myCategory.getProducts();&lt;/code&gt; 로 간단하게 가져올 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 모델을 따르는 오브젝트 구조를 만드려면, DB에서 가져온 데이터를 도메인 오브젝트 구조에 맞게 변환해줘야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAO는 자신이 DB에서 가져와 도메인 모델 오브젝트에 담아주는 정보가 어떤 업무 트랜잭션에 어떻게 사용될지 신경쓰지 않아도 됨&lt;/li&gt;
&lt;li&gt;서비스 계층도 DAO에서 어떤 SQL을 사용했는지 몰라도 됨&lt;/li&gt;
&lt;li&gt;프레젠테이션 계층 또한 어떤 DAO가 사용되고, 어떤 비즈니스 로직을 가쳤는지 알 필요가 없음&lt;/li&gt;
&lt;li&gt;오직 전달된 도메인 오브젝트를 활용해 필요한 정보만을 핸들링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 오브젝트를 사용하는 코드
&lt;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;pre class=&quot;fortran&quot;&gt;&lt;code&gt;public int calcTotalOfProductPrice(Category cate) {
    int sum = 0;
    for (Product product : cate.getProducts()) {
        sum += product.getPrice();
    }
    return sum;
}&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;어떤 Dao를 이용해서 Category를 가져왔는지, 어떤 조건인지 알 필요 없이 Category 오브젝트를 통해 Product들을 가져오고, product의 price만 더하는 행위에만 관심&lt;/li&gt;
&lt;li&gt;테스트하기 간단하고 로직이 변경될 때도 코드를 수정하기 수월&lt;/li&gt;
&lt;li&gt;Category 자체가 독립된 오브젝트 ~&amp;gt; 서비스 계층 어디든 Category 상품 가격을 계산할 때는 이 메소드를 사용하면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약, 데이터 중심 방식이라면 SQL 실행에 비즈니스 로직이 포함될 것이고, join 문이 적용되어 있다면 위의 메소드에 if 문을 통해 값을 필터링하는 등 복잡함이 증가&lt;/li&gt;
&lt;/ul&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;코드 이해가 쉬움&lt;/li&gt;
&lt;li&gt;로직 작성이 수월함&lt;/li&gt;
&lt;li&gt;각 영역에서는 도메인 오브젝트 구조만 알고, 해당 오브젝트를 기준으로 로직을 구성할 수 있음&lt;/li&gt;
&lt;li&gt;코드의 재사용성이 높아지고, DAO는 작고 효율적으로 만들 수 있음&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;최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능이 조금 떨어짐&lt;/li&gt;
&lt;li&gt;DAO는 비즈니스 로직의 사용 방식을 알지 못해서, 도메인 오브젝트의 모든 필드 값을 다 채워 전달해야하는 경우가 생김
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오브젝트에 담긴 필드 수가 많아지면 사용 빈도가 낮은 필드가 존재 ~&amp;gt; 낭비&lt;/li&gt;
&lt;li&gt;비즈니스 로직에 따라 필요한 정보가 달라질 수 있기 떄문에 발생하는 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가끔 비즈니스 로직에 필요한 필드가 null이 들어가는 경우 NPE가 발생할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최적화를 위해 DAO 작성 시에, 비즈니스 로직에서 각 오브젝트를 어디까지 사용할지는 알아야함&lt;/li&gt;
&lt;/ul&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;b&gt;lazy loading(지연된 로딩)&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;이에 따라 DAO 메소드가 추가돼야하고, 어느 DAO를 사용할지 서비스 계층에서 알고있어야 해서 계층 사이의 약한 결합이 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JPA, JDO, 하이버네이트 등과 같은 RDB 매핑 기술인 ORM을 사용(권장)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lazy loading을 기본적으로 제공&lt;/li&gt;
&lt;li&gt;SQL 결과를 가지고 도메인 오브젝트를 만들고 값을 채우는 등의 DAO 코드를 만들지 않아도 되고, 내부적으로 최적화된 SQL을 사용하도록 튜닝 가능&lt;/li&gt;
&lt;li&gt;자주 변경되지 않으면서 많은 로직에서 참조하는 레퍼런스 테이블은 ORM이 제공하는 오브젝트 캐시에 담아두고 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매번 DB에 접근하지 않아도 돼서 DB 부하 감소&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;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;li&gt;클래스는 속성과 행위의 조합 ~&amp;gt; 필드와 그에 대한 getter, setter만 가지고 있는 오브젝트는 불충분&lt;/li&gt;
&lt;/ul&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;도메인 오브젝트에 정보만 담겨있고, 정보를 활용하는 아무 기능도 없는 오브젝트&lt;/li&gt;
&lt;li&gt;스프링 사용에 흔히 사용&lt;/li&gt;
&lt;li&gt;거대 서비스 계층 방식의 하나라고 보면 됨 (그렇다고 비즈니스 로직이 포함된다는 의미가 아님)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비즈니스 로직은 서비스 계층으로 분리&lt;/li&gt;
&lt;li&gt;SQL에 의존적인 데이터 방식보다 유연하고 간결하지만, 서비스 계층 메소드에 대부분의 비즈니스 로직이 있어서 로직의 재사용성이 떨어지고 중복 가능성이 높음&lt;/li&gt;
&lt;li&gt;비즈니스 로직이 복잡하지 않다면 만들기 쉽고 3계층 구조의 특징을 살려 개발 가능&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;빈약한 오브젝트의 단점을 극복하고, 오브젝트의 객체지향적 특징을 잘 사용하도록 개선&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;public clas Category {
    ...
    List&amp;lt;Product&amp;gt; products;

    public int calTotalOfProductPrice() {
        int sum = 0;
        for (Product prodcut : this.products) {
            sum += product.getPrice();
        }
        return sum;
    }
}&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;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;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class InventoryService {
    private CategoryService categoryService;

    public void setCategoryService(CategoryService categoryService) {
        this.categoryService = categoryService;
    }

    public void complexInventoryAnlysis() {
        ...
        int total = this.categoryService.calTotalOfProductPrice(category)    ;
        ...
    }
}&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;여러 도메인 오브젝트에 대해 로직을 사용해야하는 복잡한 코드라면, 각 비즈니스 로직을 담고 있는 서비스 오브젝트를 DI해서 로직을 담은 메소드를 호출해야함&lt;/li&gt;
&lt;li&gt;정보를 담고있는 오브젝트가 있음에도, 그 정보를 다루는 메소드가 별개의 서비스 오브젝트에 분리되어 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class InventoryService {
    public void complexInventoryAnlysis() {
        int total = category.calTotalOfProductPrice(category);
    }
}&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;풍성한 도메인 오브젝트 방식을 사용하면, service를 DI할 필요 없이 가지고 있는 도메인 오브젝트로 해결 가능&lt;/li&gt;
&lt;li&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;도메인 오브젝트가 직접 데이터 엑세스 계층이나 기반 계층 or 다른 서비스 계층의 오브젝트에 접근할 수 없기 때문에 서비스 계층이 필요&lt;/li&gt;
&lt;li&gt;도메인 오브젝트는 스프링 컨테이너가 관리하는 오브젝트(빈)이 아니기 때문에, DAO 오브젝트를 DI 받을 수 없음 (책임과 관심이 아니기도 하지만)&lt;/li&gt;
&lt;li&gt;따라서, DAO와 기반계층 오브젝트를 DI 받아 사용할 수 있는 서비스 계층의 코드가 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 빈으로 관리되는 3계층의 오브젝트들은 도메인 오브젝트를 자유롭게 이용할 수 있지만, 반대는 안됨&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;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;/ul&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;/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;도메인 오브젝트가 기존 3계층과 같은 레벨로 격상되어 하나의 계층을 이루게하는 도메인 계층 방식&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;/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;스프링이 관리하지 않는 도메인 오브젝트에 DI를 적용하기 위해 AOP가 필요&lt;/li&gt;
&lt;li&gt;스프링 AOP 대신 AspectJ AOP를 사용하여 클래스의 생성자가 호출되면서 오브젝트가 만들어지는 시점을 조인 포인트로 사용 가능, 일반 오브젝트에도 AOP 부가기능 적용 가능&lt;/li&gt;
&lt;li&gt;오브젝트의 수정자 메소드나 DI용 애노테이션을 참고하여 DI 가능한 대상을 스프링 컨테이너에서 찾아 DI 해주는 기능을 도메인 오브젝트가 생성되는 시점에 이용&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;도메인 계층을 거치지 않고 데이터 엑세스 계층으로부터 정보를 가져와 클라이언트에 제공해야하는 경우&lt;/li&gt;
&lt;li&gt;트랜잭션 경계 설정 or 특정 도메인 로직에 포함되지 않는 애플리케이션에서 필요로 하는 기반 서비스를 이용해야하는 작업&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;편리한 방법이지만, 모든 계층에서 도메인 오브젝트를 사용하기 떄문에 심각한 혼란을 초래할 수 있음&lt;/li&gt;
&lt;li&gt;DB나 백엔드 시스템에 작업 결과를 반영하거나 도메인/비즈니스 로직 등이 담긴 도메인 오브젝트를 프레젠테이션 계층이나 뷰 등에서 사용하게 하는 것은 위험&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;/li&gt;
&lt;/ul&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;도메인 계층 밖으로 전달될 때는 별도로 준비된 정보 전달용 오브젝트에 도메인 오브젝트의 내용을 복사해서 전달 ~&amp;gt; DTO&lt;/li&gt;
&lt;li&gt;DTO는 기능을 가지고 있지 않기 때문에 안전하고, 도메인 오브젝트를 외부 계층의 코드로부터 보호해줌&lt;/li&gt;
&lt;li&gt;하지만, 도메인 오브젝트와 비슷한 구조를 가진 오브젝트를 따로 만들어야하고 이를 매번 변환해야하는 번거로움이 존재&lt;/li&gt;
&lt;li&gt;AOP와 같은 방법을 이용해 변환을 자동으로 해주도록 할 필요가 있음&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;/li&gt;
&lt;li&gt;상태 정보를 담고 있기 때문에, 싱글톤으로 사용 불가&lt;/li&gt;
&lt;li&gt;DAO나 Controller, 스프링 외의 라이브러리를 통해 오브젝트가 만들어지는 경우가 많기 때문에 스프링이 관리하는 빈으로 등록 불가 ~&amp;gt; AspectJ의 AOP를 통한 DI&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;li&gt;복잡하지 않은 애플리케이션에서 사용한다면 큰 부담&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DTO와 리포트 쿼리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리포트 쿼리(Report query)
&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;li&gt;DB 쿼리 실행 결과를 담을만한 적절한 도메인 오브젝트를 찾을 수 없기 때문에, DTO라고 불리는 단순한 자바빈이나 key, value 값을 갖는 맵에 담아 전달해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 쿼리 하나로 최종 결과를 만들기 힘들어 데이터를 가공하고 분석하는 작업이 필요한 경우 사용&lt;/li&gt;
&lt;li&gt;웹 서비스 등의 시스템과 자료를 주고 받을 때, 전송 규약에 맞춰 도메인 오브젝트에 담긴 정보를 가공해야할 경우에도 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9.3.4 스프링 애플리케이션을 위한 아키텍처 설계&lt;/h4&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;li&gt;계층형 아키텍처
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에서 가장 많이 사용되는 구조는 3계층 구조&lt;/li&gt;
&lt;li&gt;3계층은 논리적이고 개념적인 구분
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 경계설정 위치를 DAO 메소드로 삼으면 됨&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;li&gt;AOP를 이용해 트랜잭션의 경계를 설정하기 애매하기 때문
&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;프레젠테이션 계층
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVC라는 이름의 패턴 or 아키텍처를 주로 사용&lt;/li&gt;
&lt;li&gt;스프링의 대표적인 프레젠테이션 기술은 SpringMVC
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVC 패턴을 지원하고, MVC 중 부담을 가장 많이 지닌 컨트롤러에 해당하는 부분을 다시 세분화해서 여러 단계의 오브젝트로 만들 수 있도록 설계됨&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;SOFEA ~&amp;gt; 프레젠테이션 계층 코드가 서버 ~&amp;gt; 클라이언트로 다운로드&lt;/li&gt;
&lt;li&gt;클라이언트 장치 안에서 동작하며 서버의 여러 서비스 or 부분 프레젠테이션과 통신하는 구조&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;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;/li&gt;
&lt;li&gt;빈약한 도메인 오브젝트 방식으로 시작&lt;/li&gt;
&lt;li&gt;도메인 오브젝트를 계층 간 정보 전송을 위해 사용하고, 각 계층의 코드에서 활용&lt;/li&gt;
&lt;li&gt;도메인 오브젝트에 단순한 기능을 추가해보는 연습 필요&lt;/li&gt;
&lt;li&gt;도메인 오브젝트를 이용해 애플리케이션 정보를 일관된 형태로 유지하는게 스프링에 가장 잘 들어맞음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도메인 계층에 DI를 적용하기 위해 스프링의 고급 기술을 활용해야하고 여러 고려 사항이 많으므로 충분한 사전 학습 및 검증이 선행돼야함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;애플리케이션은 하나의 HTTP 요청의 범위를 넘어 유지해야하는 상태정보가 존재&lt;/li&gt;
&lt;li&gt;서버 기반의 애플리케이션의 무상태성(stateless)
&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;li&gt;클라이언트의 요청을 처리하는 매우 짧은 시간 동안만 현재 상태정보가 보관되고, 요청 결과를 return한 후 바로 폐기&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;URL, 파라미터, 폼 히든 필드, 쿠키 등을 이용&lt;/li&gt;
&lt;li&gt;많은 양의 정보는 계속해서 주고받을 수 없기 때문에 중요한 상태정보는 파일 시스템, 데이터그리드, DB 등에 저장되기도 함&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;/ul&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;/li&gt;
&lt;li&gt;서버를 여러 대로 확장하기 쉬움&lt;/li&gt;
&lt;li&gt;웹 클라이언트에 폼 정보를 출력하고 이를 수정하는 등의 작업을 위해서는 HTTP 세션을 적극 활용&lt;/li&gt;
&lt;/ul&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;JavaEE의 세부 기술과 함께 사용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP, JavaMail, JPA 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링이 직접 지원하는 오픈소스 ORM(Hibernate, iBatis), 오픈소스 JPA(OpenJPA, EclipseLink), 웹 프레임워크 (스트럿츠 1/2 등), 다양한 OXM 라이브러리 등 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;스프링이 지원하는 기술&lt;/code&gt;의 의미
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 기술을 스프링의 DI 패턴을 따라 사용 가능
&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;li&gt;DI를 통해 다른 오브젝트에서 손쉽게 활용 가능&lt;/li&gt;
&lt;li&gt;추상화 서비스를 통해 다른 리소스에 투명하게 접근 가능&lt;/li&gt;
&lt;li&gt;ex) 하이버네이트는 설정파일 정보, 설정 값의 프로퍼티, DB 연결 정보를 담은 Configuration 오브젝트를 통해 SessionFactory를 만들어야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에서는 하이버네이트의 SessionFactory를 스프링이 제공하는 빈을 등록하는 것만으로 간단히 생성 가능한 LocalSessionFactoryBean 클래스 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 외의 기술을 접목할 때는, 가장 먼저 스프링 빈으로 등록해서 DI가 가능한지가 중요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈으로 등록해서 사용할 수 있는 구조로 핵심 API나 클래스가 만들어져 있지 않다면, Factory 빈을 도입해서 사용&lt;/li&gt;
&lt;/ul&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;서비스 추상화 적용 ~&amp;gt; 기능을 제공하는 기술에 대한 일관된 접근 방법을 정의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 첫 번째 방법은 사용할 기술을 스프링 빈으로 등록하고 설정 가능하도록 지원만 해줬을 뿐, 사용 기술의 API는 애플리케이션에 그대로 노출&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;/li&gt;
&lt;li&gt;표준 기술 스펙과 다른 점은, 서비스 추상화는 이미 존재하는 다양한 기술의 공통점을 분석해서 추상화를 했다는 점&lt;/li&gt;
&lt;li&gt;따라서, 추상 서비스 인터페이스를 구현해서 각 기술과 연동하는 어댑터 클래스가 필요&lt;/li&gt;
&lt;li&gt;서비스 추상화 인터페이스를 구현한 클래스는 모두 스프링 빈으로 등록되도록 만들어졌고, 세부 기술 특성에 맞는 설정이 손쉽게 가능하도록 다양한 프로퍼티 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링이 지지하는 프로그래밍 모델을 적용
&lt;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;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;/li&gt;
&lt;li&gt;대부분의 템플릿 클래스는 빈으로 등록해서 필요한 빈에서 DI 받아서 사용&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;가장 기초는 스프링 빈으로 해당 기술의 핵심 오브젝트가 등록되도록 만드는 것&lt;/li&gt;
&lt;li&gt;코드에 의한 초기화 작업이 필요 ~&amp;gt; 팩토리 빈을 만들어 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 내의 빈들이 새로운 기술에 대해 빈을 DI하는 방법으로 접근하고,&lt;br /&gt;기존 빈 오브젝트를 새로 추가할 기술에서 사용할 수 있게 해줄 필요가 있음&lt;/li&gt;
&lt;/ul&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;네트워크 접근이나 파일 IO처럼 반복적으로 try/catch/finally 블록이 필요한 기술에 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP나 예외 전환 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>애플리케이션 아키텍처</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/381</guid>
      <comments>https://zin0-0.tistory.com/381#entry381comment</comments>
      <pubDate>Fri, 24 Sep 2021 12:11:33 +0900</pubDate>
    </item>
    <item>
      <title>8장) 8.3 POJO 프로그래밍 ~ 8.4 스프링의 기술</title>
      <link>https://zin0-0.tistory.com/380</link>
      <description>&lt;h2&gt;8장 스프링이란 무엇인가?&lt;/h2&gt;
&lt;h3&gt;8.3 POJO 프로그래밍&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스프링의 기술적 지향점&lt;ul&gt;
&lt;li&gt;스프링의 정수는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것&lt;/li&gt;
&lt;li&gt;엔터프라이즈 서비스는 보안, 트랜잭션과 같은 엔터프라이즈 시스템에서 요구되는 기술&lt;/li&gt;
&lt;li&gt;엔터프라이즈 서비스 기술과 POJO라는 애플리케이션 로직을 담은 코드를 분리했다는 뜻&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.3.1 스프링의 핵심: POJO&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;스프링의 핵심은 POJO 프로그래밍&lt;/li&gt;
&lt;li&gt;스프링 애플리케이션은 두 가지로 구분&lt;ul&gt;
&lt;li&gt;POJO를 이용해 만든 애플리케이션 코드&lt;/li&gt;
&lt;li&gt;POJO가 어떻게 관계를 맺고 동작하는지 정의해놓은 설계정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 DI 개념을 애플리케이션 전반에 걸쳐 적용하는 것이 스프링의 프로그래밍 모델&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gb5oz/btq4SnwU3IN/naUOqpUl7epjDdTy4hYWh1/img.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 주요 기술인 IoC/DI, AOP, PSA(Portable Service Abstraction)은 애플리케이션을 POJO로 개발할 수 있게 해주는 가능기술&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.3.2 POJO란 무엇인가&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;POJO는 Plain Old Java Object의 첫 글자를 따서 만든 약자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.3.3 POJO의 조건&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;POJO 프로그래밍이 충족되는 조건&lt;ul&gt;
&lt;li&gt;특정 규약에 종속되지 않는다&lt;ul&gt;
&lt;li&gt;POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 함&lt;ul&gt;
&lt;li&gt;EJB2 같이 특정 규약에 따라 비즈니스 컴포넌트를 만드는 경우는 POJO가 아님&lt;/li&gt;
&lt;li&gt;스트럿츠 1과 같이 특정 클래스를 상속해서 만들어야 하는 규약이 있는 경우도 POJO가 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 규약을 따라 만들게 하는 경우 자바의 단일 상속 제한 때문에 객체지향 설계 기법을 적용하기 어려워지고, 규약이 적용된 환경에 종속적이라 다른 환경으로 이전이 힘듦&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;객체지향 설계의 자유로운 적용이 가능한 오브젝트여야만 POJO라고 함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 환경에 종속되지 않는다&lt;ul&gt;
&lt;li&gt;특정 환경이 의존 대상 검색 방식에 종속적이라면 POJO라고 할 수 없음&lt;ul&gt;
&lt;li&gt;특정 벤더 서버나 특정 프레임워크 안에서만 동작하는 코드를 사용하는 경우&lt;/li&gt;
&lt;li&gt;환경에 종속적인 클래스나 API를 직접 쓴 경우&lt;/li&gt;
&lt;li&gt;순수한 애플리케이션 로직을 담은 오브젝트 코드가 특정 환경에 종속되게 만드는 경우 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POJO는 환경에 독립적&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;특히, 비즈니스 로직을 담고 있는 POJO 클래스는 웹 환경정보나 웹 기술을 담은 클래스나 인터페이스를 사용하면 안됨&lt;ul&gt;
&lt;li&gt;직접적으로 웹이라는 환경으로 제한해버리는 오브젝트나 API에 의존하면 안됨&lt;/li&gt;
&lt;li&gt;비즈니스 로직을 담은 코드에 HttpServletRequest나 HttpSession, 캐시 관련 API 등&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;ul&gt;
&lt;li&gt;코드로 표현하기 적절하지 않은 부가정보를 담고, 환경에 종속되지 않는다면 POJO라고 볼 수 있음&lt;ul&gt;
&lt;li&gt;애노테이션이나 엘리먼트 값에 특정 기술이나 환경에 종속적인 정보가 담긴다면 POJO가 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단순하게 자바 문법을 지키고, 순수한 JavaSE API만 사용했다고 POJO가 아니라, 객체지향적인 자바 언어의 기본에 충실하게 만들어야 POJO라고 할 수 있다&lt;ul&gt;
&lt;li&gt;객체지향적인 원칙에 충실하며, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 POJO라고 부름&lt;/li&gt;
&lt;li&gt;POJO에 애플리케이션 핵심 로직과 기능을 담아 설계하고 개발하는 방법이 POJO 프로그래밍&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.3.4 POJO의 장점&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;POJO가 될 수 있는 조건이 모두 POJO의 장점&lt;ul&gt;
&lt;li&gt;특정 기술과 환경에 종속되지 않는 오브젝트는 깔끔한 코드가 될 수 있음&lt;/li&gt;
&lt;li&gt;자동화된 테스트에 유리&lt;/li&gt;
&lt;li&gt;객체지향적인 설계를 자유롭게 적용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링은 자바 언어의 객체지향 설계와 구현 방식이 그 어떤 새로운 기술과 환경, 툴보다 더 실제 프로젝트를 성공시키는 데 중요한 요소라고 여기고 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.3.5 POJO 프레임워크&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;POJO 프레임워크&lt;ul&gt;
&lt;li&gt;스프링 프레임워크, 하이버네이트가 대표적인 POJO 프레임워크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링은 비즈니스 로직의 복잡함과 엔터프라이즈 기술의 복잡함을 분리해서 구성하지만, 자신은 기술영역에만 관여하고 비즈니스 로직을 담당하는 POJO에서는 모습을 감춤&lt;ul&gt;
&lt;li&gt;데이터 엑세스 로직이나 웹 UI 로직을 다룰 때만 최소한으로 관여&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;POJO 프로그래밍을 잘하기 위한 방법&lt;ul&gt;
&lt;li&gt;객체지향 분석과 설계에 대한 지식을 습득하고 훈련&lt;/li&gt;
&lt;li&gt;자바 언어와 JVM 플랫폼, JDK API 사용법을 잘 알아야 함&lt;/li&gt;
&lt;li&gt;디자인 패턴과 구현 패턴, 리팩토링 기술 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링은 엔터프라이즈 기술보다는 객체지향 설계와 개발의 원리에 집중할 수 있는 기회를 제공하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.4 스프링의 기술&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스프링에서 POJO 프로그래밍을 지원하는 세 가지 기능 기술&lt;ul&gt;
&lt;li&gt;IoC/DI&lt;/li&gt;
&lt;li&gt;AOP&lt;/li&gt;
&lt;li&gt;PSA&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 기술들은 스프링 프레임워크가 만들어진 진정한 목표인 POJO 기반의 엔터프라이즈 개발을 편리하게 해주는 도구일 뿐&lt;ul&gt;
&lt;li&gt;위의 세 기능 기술이 객체지향 원리를 충실히 적용해서 나온 결과&lt;/li&gt;
&lt;li&gt;스프링이 직접 제공하지 않는 기술에 대해서도 PSA를 적용할 줄 알아야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.4.1 제어의 역전(IoC) / 의존관계 주입(DI)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;스프링의 기본 기술이자 핵심 개발 원칙&lt;ul&gt;
&lt;li&gt;AOP와 PSA도 IoC/DI에 바탕을 두고 있음&lt;/li&gt;
&lt;li&gt;템플릿/콜백 패턴이 적용된 부분도 IoC/DI가 핵심 원리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI는 개방 폐쇄 원칙이라는 객체지향 원칙으로 설명됨&lt;ul&gt;
&lt;li&gt;개방 관점에서는 유연한 확장이 가능&lt;/li&gt;
&lt;li&gt;폐쇄 관점에서는 재사용이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ex) A -&amp;gt; B의 의존관계, B는 자유롭게 변경될 수 있음을 의미&lt;ul&gt;
&lt;li&gt;B1,B2,B3로 의존대상이 바뀌어도 A는 그대로 재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI의 활용 방법&lt;ul&gt;
&lt;li&gt;핵심기능 변경&lt;ul&gt;
&lt;li&gt;의존 대상의 구현을 바꾸는 것이 DI의 대표적인 적용 방법&lt;/li&gt;
&lt;li&gt;실존하는 대상이 가진 핵심기능을 DI 설정을 통해 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;핵심기능의 동적 변경&lt;ul&gt;
&lt;li&gt;오브젝트의 핵심기능 자체를 바꾸는 것&lt;/li&gt;
&lt;li&gt;일반 DI를 이용한 변경 방법과 달리 동적으로 매번 다르게 변경 가능&lt;/li&gt;
&lt;li&gt;ex) 사용자 등급에 따라 다른 DataSource를 사용&lt;/li&gt;
&lt;li&gt;동적인 방식으로 핵심 기능을 변경하는 것은 기술적인 관점에서 다이내믹 라우팅 프록시나 프록시 오브젝트 기법을 활용한 것&lt;ul&gt;
&lt;li&gt;DI가 있기 때문에 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부가기능 추가&lt;ul&gt;
&lt;li&gt;핵심기능은 그대로 두고 부가기능을 추가하는 것&lt;ul&gt;
&lt;li&gt;데코레이터 패턴&lt;/li&gt;
&lt;li&gt;인터페이스를 두고 사용하게 하고, 실제 사용할 오브젝트는 외부에서 주입하는 DI를 적용해두면 데코레이터 패턴을 쉽게 적용 가능&lt;/li&gt;
&lt;li&gt;핵심기능과 클라이언트 코드에 영향을 주지 않으면서 부가 기능을 자유롭게 추가 가능&lt;/li&gt;
&lt;li&gt;ex) 트랜잭션 기능 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부가 기능의 추가 방식을 특정 오브젝트가 아닌 많은 대상으로 일반화해서 사용하면 AOP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;부가기능을 추가하는 것 또한 DI 덕분이고, DI의 핵심 원칙인 OCP에 충실하게 맞음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장에 열려있다는 것은 전략 패턴처럼 핵심기능을 변경해서 쓰는 수준만 말하는 것이 아님&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 변경&lt;ul&gt;
&lt;li&gt;클라이언트가 사용하는 인터페이스와 실제 오브젝트 사이에 인터페이스가 일치하지 않는 경우에도 DI가 유용&lt;ul&gt;
&lt;li&gt;ex) A가 C 오브젝트를 사용을 원함, A는 B인터페이스를 사용하도록 구현되어있고, C는 B를 구현하지 않음&lt;ul&gt;
&lt;li&gt;A가 DI로 B 구현 오브젝트를 받는다면, B 인터페이스를 구현했으면서 내부에서 C를 호출해주는 기능을 가진 어댑터 오브젝트를 만들어 A에 DI하면 사용 가능&lt;/li&gt;
&lt;li&gt;A -&amp;gt; B(C로 위임) -&amp;gt; C&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인터페이스가 다른 다양한 구현을 같은 방식으로 사용하도록 중간에 인터페이스 어댑터 역할을 해주는 레이어를 하나 추가하는 방법으로 일반화한 것이 PSA&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시&lt;ul&gt;
&lt;li&gt;프록시 패턴의 전형적인 응용 방법&lt;ul&gt;
&lt;li&gt;lazy loading을 적용하려면 프록시가 필요함&lt;/li&gt;
&lt;li&gt;원격 오브젝트를 호출할 때 로컬에 존재하는 오브젝트처럼 사용하게 해주는 원격 프록시 적용에도 프록시가 필요&lt;/li&gt;
&lt;li&gt;위의 두 가지 모두 DI를 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;템플릿 콜백&lt;ul&gt;
&lt;li&gt;DI의 특별한 적용 방법&lt;/li&gt;
&lt;li&gt;고정적인 작업 흐름과 그 사이에 자주 바뀌는 부분을 분리하여 템플릿과 콜백으로 만들고, DI 원리를 응용해 적용&lt;ul&gt;
&lt;li&gt;콜백을 얼마든지 만들어서 사용할 수 있다는 것은 개방을 통한 유연한 확장성을 보여주는 것이며, 템플릿은 한 번 만들어두면 계속 재사용할 수 있다는 것은 기능 확장에도 변하지 않는다는 OCP 폐쇄 원칙에 잘 들어맞음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;싱글톤과 오브젝트 스코프&lt;ul&gt;
&lt;li&gt;DI가 필요한 중요한 이유 중 하나는 DI 할 오브젝트의 생명주기를 제어할 수 있다는 것&lt;/li&gt;
&lt;li&gt;DI를 프레임워크로 사용한다는 것은 DI 대상 오브젝트를 컨테이너가 관리한다는 의미&lt;/li&gt;
&lt;li&gt;모든 과정을 DI 컨테이너가 주관 ~&amp;gt; 오브젝트 스코프를 자유롭게 제어&lt;ul&gt;
&lt;li&gt;싱글톤&lt;ul&gt;
&lt;li&gt;전형적인 싱클톤 패턴은 오브젝트에 많은 제약 ~&amp;gt; 권장 X&lt;/li&gt;
&lt;li&gt;컨테이너가 오브젝트를 관리하는 IoC 방식이 권장&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;/li&gt;
&lt;li&gt;테스트&lt;ul&gt;
&lt;li&gt;여타 오브젝트와 협력해서 동작하는 오브젝트를 효과적으로 테스트하는 방법 ~&amp;gt; 고립&lt;ul&gt;
&lt;li&gt;다른 오브젝트와의 사이에 일어나는 일을 테스트를 위한 조작이 가능해야함&lt;ul&gt;
&lt;li&gt;Mock 오브젝트, Stub&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;h4&gt;8.4.2 애스펙트 지향 프로그래밍(AOP)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;OOP 처럼 독립적인 프로그래밍 패러다임이 아님&lt;ul&gt;
&lt;li&gt;AOP와 OOP가 배타적이 아니라는 의미&lt;/li&gt;
&lt;li&gt;복잡해져가는 애플리케이션의 요구조건과 기술적 난해함을 모두 해결하는 데 한계가 있는데, AOP가 객체지향 기술의 한계와 단점을 극복하도록 도와주는 보조적인 프로그래밍&lt;/li&gt;
&lt;li&gt;스프링의 목적인 POJO만으로 엔터프라이즈 애플리케이션을 개발하면서도 엔터프라이즈 서비스를 선언적으로 제공하는데 반드시 필요한 것이 AOP&lt;ul&gt;
&lt;li&gt;IoC/DI로 POJO에 선언적 엔터프라이즈 서비스를 제공할 수 있지만, 일부 서비스는 순수한 객체지향 방법만으로 POJO 조건을 유지하기 힘듦&lt;/li&gt;
&lt;li&gt;AOP가 해결책&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP의 적용 기법&lt;ul&gt;
&lt;li&gt;AOP를 자바 언어에 적용하는 기법&lt;ul&gt;
&lt;li&gt;스프링과 같이 다이내믹 프록시를 사용하는 방법&lt;ul&gt;
&lt;li&gt;기존 코드에 영향을 주지 않고 부가기능을 적용하는 데코레이터 패턴을 응용&lt;/li&gt;
&lt;li&gt;부가기능을 부여할 수 있는 곳은 메소드의 호출이 일어나는 지점뿐이라는 제약이 존재&lt;/li&gt;
&lt;li&gt;스프링의 기본적인 AOP 구현 방법이 다이내믹 프록시를 이용하는 프록시 AOP 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법&lt;ul&gt;
&lt;li&gt;오픈소스 AOP 툴 AspectJ &lt;ul&gt;
&lt;li&gt;프록시 방식의 AOP에서 불가능한 다양한 조인 포인트를 제공&lt;/li&gt;
&lt;li&gt;별도의 AOP 컴파일러를 이용한 빌드 과정을 거치거나, 클래스가 메모리로 로딩될 때 그 바이트 코드를 조작하는 위빙과 같은 별도의 방법을 이용&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;li&gt;AOP 적용 단계&lt;ul&gt;
&lt;li&gt;미리 준비된 AOP 이용&lt;ul&gt;
&lt;li&gt;스프링이 미리 만들어 제공하는 AOP 기능을 그대로 가져다 적용&lt;ul&gt;
&lt;li&gt;트랜잭션이 대표적인 예시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Configurable&lt;/code&gt; 애노테이션으로 도메인 오브젝트에 DI를 자동 적용해주는 것도 AOP 기능&lt;ul&gt;
&lt;li&gt;AspectJ를 이용한 AOP피가 반드시 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전담팀을 통한 정책 AOP 적용&lt;ul&gt;
&lt;li&gt;정책적으로 적용할 만한 기능에 AOP를 이용&lt;/li&gt;
&lt;li&gt;AOP를 이용해 한 번에 적용한다면 일반 개발자의 작업에는 전혀 영향 X&lt;ul&gt;
&lt;li&gt;AOP만 책임지는 소수만 신경 쓰면 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP는 언제든 기능을 추가 제거할 수 있기 때문에, 개발 과정에서 정책 검증에 사용하다가 배포 때 제거하는 식으로도 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP의 자유로운 이용&lt;ul&gt;
&lt;li&gt;개발자 스스로가 AOP를 활용하는 단계&lt;/li&gt;
&lt;li&gt;구현하는 기능에 적용하여 세부적인 AOP를 이용하고, 모듈이나 특정 기능 안에서 AOP로 분리하여 사용하는 것이 유용한 경우를 판단하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;8.4.3 포터블 서비스 추상화(PSA)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근하게 도움&lt;/li&gt;
&lt;li&gt;POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않음&lt;ul&gt;
&lt;li&gt;스프링은 JavaEE를 기본 플랫폼으로 하는 자바 엔터프라이즈 개발에 주로 사용&lt;/li&gt;
&lt;li&gt;특정 환경과 기술에 종속적이지 않다는 것이 다양한 JavaEE 기술을 사용하지 않는 다는 뜻이 아니라, POJO 코드가 그런 기술에 직접 노출되어 만들어지지 않는다는 의미&lt;/li&gt;
&lt;li&gt;이를 위해 스프링이 제공하는 기술이 일관성 있는 서비스 추상화기술&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설정을 통해 어떤 종류의 기술을 사용할지 지정&lt;ul&gt;
&lt;li&gt;AOP나 템플릿/콜백 패턴과 결합돼서 사용되는 경우, 직접 서비스 사용이 아닌 설정으로 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;직접 스프링이 제공하는 API를 사용해서 만드는 경우&lt;ul&gt;
&lt;li&gt;OXM, JavaMail 등 스프링이 정의한 추상 API를 이용해 코드를 작성 ~&amp;gt; 구체적인 기술과 설정은 XML 파일 안에서 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;직접 서비스 추상화 기법을 적용&lt;ul&gt;
&lt;li&gt;직접 추상 레이어를 도입하고 일관성 있는 API를 정의해서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서비스 추상화를 위해 필요한 기술은 오직 DI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;테스트가 어렵게 만들어진 API나 설정을 통해 주요 기능을 외부에서 제어하게 만들고 싶을 때도 이용 가능&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>POJO 프로그래밍</category>
      <category>스프링의 기술</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/380</guid>
      <comments>https://zin0-0.tistory.com/380#entry380comment</comments>
      <pubDate>Tue, 7 Sep 2021 17:04:19 +0900</pubDate>
    </item>
    <item>
      <title>8장) 8.1 스프링의 정의 ~ 8.2 스프링의 목적</title>
      <link>https://zin0-0.tistory.com/379</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;8장 스프링이란 무엇인가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8.1 스프링의 정의&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;&lt;b&gt;자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션&lt;/b&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;특정 업무 분야나 한 가지 기술에 특화된 목표를 가지고 만들어짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 웹 계층을 MVC로 쉽게 만듦, 포맷과 출력장치를 유연하게 변경할 수 있는 애플리케이션 로그 기능 제공, ORM 등&lt;/li&gt;
&lt;/ul&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;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;자바 엔터프라이즈 개발의 이상적인 프로그래밍 모델을 추구하는 데 필요한 기반이 돼주는 코드, 즉 프레임워크가 지금 스프링의 원시버전&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;/ul&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;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;스프링이 처음 등장했을 시절의 자바 주류 기술인 이전 EJB 같은 과도한 엔지니어링이 적용된 기술과 스프링을 대비해 설명했던 표현&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;&lt;i&gt;편리한 애플리케이션 개발 == 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 신경을 덜쓰고 애플리케이션의 핵심인 요구사항(비즈니스 로직)을 빠르고 효과적으로 구현하는 것&lt;/i&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EJB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EJB의 비전과 목표도 편리한 애플리케이션 개발이었지만, 이 과정에서 다른 차원의 더 큰 복잡함을 애플리케이션 개발에 끌고 들어왔음&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;li&gt;엔터프라이즈 개발의 기술적인 복잡함과 그에따른 수고를 제거&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;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;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;li&gt;라이선스 비용에 대한 부담이 X&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;h3 data-ke-size=&quot;size23&quot;&gt;8.2 스프링의 목적&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;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8.2.1 엔터프라이즈 개발의 복잡함&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2000년대 초반 자바 컨퍼런스에서 자주 논의된 주제는 &lt;code&gt;왜 자바 엔터프라이즈(JAVA EE) 프로젝트는 실패하는가?&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 원인 중 가장 대표적인 이유는, &lt;code&gt;엔터프라이즈 시스템 개발이 너무 복잡해져서&lt;/code&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;엔터프라이즈 시스템
&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;많은 사용자의 요청을 동시에 처리 ~&amp;gt; 서버 자원을 효율적으로 공유하고 분배하여 사용&lt;/li&gt;
&lt;li&gt;중요한 기업 핵심 정보 처리 및 미션 크리티컬한 시스템을 다룸 ~&amp;gt; 보안, 안정성, 확장성에 뛰어나야함&lt;/li&gt;
&lt;li&gt;타 시스템과의 자동화된 연계, 리모팅 기술, 분산 트랜잭션 지원 등 요구됨&lt;/li&gt;
&lt;li&gt;순수 비즈니스 로직 구현 외 기술적 고려 사항이 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엔터프라이즈 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함이 증가하기 때문
&lt;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;li&gt;시스템 개발 및 유지보수, 추가 개발 등의 작업에 대한 부담이 커지고 난이도가 상승&lt;/li&gt;
&lt;/ul&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;ex) 고객의 거래내역 분석 ~&amp;gt; 특성 파악 ~&amp;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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8.2.2 복잡함을 해결하려는 도전&lt;/h4&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;엔터프라이즈 개발의 근본적인 복잡함의 원인은 제거 대상 X&lt;/li&gt;
&lt;li&gt;복잡함을 효과적으로 상대할 수 있는 전략과 기법이 필요&lt;/li&gt;
&lt;li&gt;비즈니스 로직의 복잡함을 효과적으로 다루기 위한 방법,&lt;br /&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;실패한 해결책: EJB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EJB의 목표 또한 위의 두 복잡함을 분리하는 것&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;EJB라는 환경과 스펙에 종속되는 코드로 만들어져야 하는 더 큰 부담을 만듦&lt;/li&gt;
&lt;li&gt;EJB 틀 안에서 자바코드를 강제 ~&amp;gt; 자바 언어가 원래 갖고 있던 장점을 상실&lt;/li&gt;
&lt;li&gt;객체 지향적인 특성은 잃어버린 밋밋한 서비스 스크립트성 코드로 변질&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비침투적인 방식을 통한 효과적인 해결책: 스프링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EJB와 같은 목표로 출발&lt;/li&gt;
&lt;li&gt;침투적인 기술 vs 비침투적인 기술
&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;EJB처럼 어떤 기술을 적용했을 때 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우&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;/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;이 과정에서 스프링 스스로가 애플리케이션 코드에 불필요하게 나타나지 않음&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;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8.2.3 복잡함을 상대하는 스프링의 전략&lt;/h4&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;/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;ex) 트랜잭션 추상화, OXM 추상화, 데이터 엑세스 기술에 독립적으로 적용 가능한 트랜잭션 동기화 기법 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;스프링의 해결 방법 &lt;b&gt;AOP&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;AOP 적용 이전의 문제
&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;AOP는 기술을 다루는 코드로 인한 복잡함이 기술 그 자체 이상으로 불필요하게 증대되지 않도록 도와주는 가장 강력한 수단&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;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;/li&gt;
&lt;li&gt;핵심 코드에 오류가 있으면 업무에 큰 지장을 주거나 치명적인 손실을 유발&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예전에는 비즈니스 로직의 상당 부분을 DB에 두는 것이 유행
&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;많은 비용이 드는 공유 자원인 DB에 커다란 부담을 주고, 개발 &amp;amp; 유지보수, 테스트도 매우 어려움&lt;/li&gt;
&lt;/ul&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;핵심 도구: 객체지향과 DI
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기술과 비즈니스 로직의 복잡함을 해결하는 데 스프링이 공통적으로 사용하는 도구는 &lt;code&gt;객체지향oo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;스프링의 모토는 &lt;code&gt;기본으로 돌아가자&lt;/code&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;자바의 기본인 객체지향에 충실한 설계까 가능하도록 단순한 오브젝트로 개발, 구조를 만들기 위해 DI 같은 유용한 기술을 편하게 적용하도록 도와주는 것이 스프링의 기본 전략&lt;/li&gt;
&lt;li&gt;서비스 추상화, 템플릿 / 콜백, AOP와 같은 스프링의 기술은 DI없이 존재할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI는 유연하게 확장할 수 있는 오브젝트를 설계를 하다보면 자연스럽게 적용되는 객체지향 프로그래밍 기법
&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;기술적인 복잡함을 해결하는 문제나 기술적인 복잡함이 비즈니스 로직에 침범하지 못하도록 분리하는 경우에도 DI가 바탕이 된 여러 가지 기법이 활용&lt;/li&gt;
&lt;li&gt;비즈니스 로직 자체의 복잡함을 해결하려면 DI보다는 객체지향 설계 기법이 더 중요&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링의 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사요할 수 있도록 돕는 것&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>스프링의 목적</category>
      <category>스프링의 정의</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/379</guid>
      <comments>https://zin0-0.tistory.com/379#entry379comment</comments>
      <pubDate>Tue, 7 Sep 2021 17:02:55 +0900</pubDate>
    </item>
    <item>
      <title>7장) 7.6 스프링 3.1의 DI</title>
      <link>https://zin0-0.tistory.com/378</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;7장 스프링 핵심 기술의 응용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.6 스프링 3.1의 DI&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;DI가 적용된 코드를 작성할 때 사용하는 핵심 도구인 자바 언어의 대표적인 두 가지 변화
&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;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;리플렉션 API
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 버전부터 class, interface, field, method 등의 메타정보를 살펴보거나 조작하기 위해 사용&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;/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;자바 코드가 실행되는 데 직접 참여 X&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;Application 핵심 로직을 담은 자바 코드&lt;br /&gt;이를 지원하는 IoC 방식의 프레임워크&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;li&gt;앞선 학습 사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserDao에 객체지향 프로그래밍의 특징을 최대한 적용하며 유연하게 확장 가능한 코드로 다듬음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서로 영향을 주지 않고 확장 가능한 핵심 로직 코드&lt;br /&gt;핵심 코드가 런타임 시 동적으로 관계를 맺고 동작하게 하는 DaoFactory&lt;br /&gt;DaoFactory를 활용해 핵심 로직 코드가 관계를 맺고 동작하는 과정을 제어하는 클라이언트&lt;br /&gt;세 가지로 구분됨&lt;/li&gt;
&lt;li&gt;클라이언트는 일종의 IoC 프레임워크, DaoFactory는 IoC 프레임워크가 참고하는 일종의 메타정보로 의미가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XML로의 전환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserDao 한 가지가 아닌 애플리케이션을 구성하는 오브젝트 관계를 IoC/DI를 통해 프레임워크와 메타정보를 활용하는 방식으로 작성하도록 발전시키려면, 자바 코드로 만들어진 관계 설정 책임을 담은 코드가 불편&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;li&gt;코드의 동작에 직접 영향 X, 메타정보로 활용되는 데 XML에 비해 유리함
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 패키지를 변경하거나 클래스 이름을 바꾸는 경우, 해당 클래스를 참조하는 코드도 자동으로 바꿔줌 &amp;lt;-&amp;gt; XML을 매번 수정&lt;/li&gt;
&lt;/ul&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;변경할 때마다 클래스를 새로 컴파일해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 3.1부터 애노테이션을 이용한 메타정보 작성 방식으로 거의 모든 영역이 확대됐고, 애노테이션을 활용한 프로그래밍이 성행&lt;/li&gt;
&lt;/ul&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;XML
&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;li&gt;프로그래밍 언어나 API 사용법 외에 미리 정의된 많은 규칙과 관례를 기억해야 하고, 메타정보를 보고 프로그램이 어떻게 동작하는지 이해하는 등 학습 비용의 높음과 찾기 힘든 버그 양산 가능성이 높음&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;코드를 이용해 명시적으로 동작 내용을 기술하는 대신, 코드 없이도 미리 약속한 규칙 or 관례를 따라 프로그램이 동작하도록 만드는 프로그래밍 스타일을 적극적으로 포용&lt;/li&gt;
&lt;li&gt;작성하는 코드 양에 비해 부가 정보가 많고, 일정한 패턴을 따르는 경우 관례를 부여해 명시적 설정을 최대한 배제 ~&amp;gt; 코드가 간략&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Transactional&lt;/code&gt; 대체 정책 예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중첩된 설정이 있는 경우 적용 우선순위를 직접 지명 (&lt;code&gt;(order=1)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;충돌을 방지하기 위해 4단계의 우선순위를 가진 대체 정책을 정해둠
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앞서 발전시켜왔던 사용자 DAO, 서비스 기능의 예제 코드를 스프링 3.1 DI 스타일로 수정해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.6.1 자바 코드를 이용한 빈 설정&lt;/h4&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;XML 제거 ~&amp;gt; 애노테이션 으로 수정&lt;/li&gt;
&lt;li&gt;Test 코드 수정하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 코드 기능을 테스트하는 UserTest 제외&lt;/li&gt;
&lt;li&gt;UserDaoTest, UserServiceTest 수정&lt;/li&gt;
&lt;/ul&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;pre class=&quot;less&quot;&gt;&lt;code&gt;@RunWith(SpringJunit4ClassRunner.class)
@ContextConfiguration(locations=&quot;/test-applicationContext.xml&quot;)
public class UserDaoTest {}

@RunWith(SpringJunit4ClassRunner.class)
@ContextConfiguration(classes=TestApplicationContext.class)
public class UserDaoTest {}&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;xml파일 참조에서 클래스 기반 참조로 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@ImportResource(&quot;/test-applicationContext.xml&quot;)
public class TestApplicationContext{}&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;DI 정보로 사용될 클래스 생성&lt;/li&gt;
&lt;li&gt;바로 XML을 제거하면 부담스러울 수 있기 때문에, &lt;code&gt;@ImportResource&lt;/code&gt;를 통해 주입하여 단계적으로 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;lt;context:anotation-config /&amp;gt; 제거
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;context:annotation-config&amp;gt;에 의해 등록되는 빈 후처리기가 &lt;code&gt;@PostConstruct&lt;/code&gt;와 같은 표준 애노테이션을 인식해서 자동으로 메소드를 수정해줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만, 이제 컨테이너가 참고하는 DI 정보 위치가 TestApplicationContext로 바뀌었으므로 불필요&lt;/li&gt;
&lt;li&gt;컨테이너가 직접 &lt;code&gt;@PostConstruct&lt;/code&gt; 애노테이션을 처리하는 빈 후처리기를 등록해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;lt;bean&amp;gt;의 전환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;bean&amp;gt;으로 정의된 DI 정보는 자바코드의 &lt;code&gt;@Bean&lt;/code&gt;이 붙은 메소드로 거의 1:1 매칭&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Bean
public DataSource dataSource() {
    SimpleDriverDataSource dataSource = new SimpleDataSource();

    dataSource.setDriverClass(Driver.class);
    dataSource.setUrl(&quot;jdbc:mysql://url~~&quot;);
    dataSource.setUsername(&quot;spring&quot;);
    dataSource.setPassword(&quot;book&quot;);

    return dataSource;
}&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;dataSource 빈은 UserDao 등에서 DataSource 타입의 프로퍼티를 통해 주입 받아 사용&lt;/li&gt;
&lt;li&gt;SimpleDriverDataSource 클래스는 DataSource의 한 가지 구현일 뿐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DI 원리에 따라 빈의 구현 클래스는 자유롭게 변경 가능&lt;/li&gt;
&lt;li&gt;하지만, dataSource() 메소드의 리턴 값을 SimpleDriverDataSource으로 하면, 참조하는 쪽에서 SimpleDriverDataSource 타입으로 주입 받을 위험이 있음
&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;DataSource 인터페이스를 통해 안전하게 관계를 맺도록 설정&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public PlatformTransactionManager transactionManager() {
    DataSourceTransactionManager tm = new DataSourceTransactionManager();
    tm.setDataSource(dataSource());
    return tm;
}&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;dataSource 빈을 참조해서 transactionManager의 프로퍼티에 주입&lt;/li&gt;
&lt;li&gt;위와 동일한 이유로 PlatformTransactionManager 인터페이스로 느슨하고 안전하게 관계를 맺음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//@Autowired SqlService sqlService; // 필드 주입은 권장되지 않음
@RequiredArgsConstructor
private final SqlService sqlService; // final로 생성자 주입이 권장됨

@Bean
public UserDao userDao() {
    UserDaoJdbc dao = new UserDaoJdbc();
    dao.setDataSource(dataSource());
    dao.setSqlSErvice(sqlService());
    return dao;
}

@Bean
public UserService userService() {
    UserServiceImpl service = new UserServiceImpl();
    service.setUserDao(userDao());
    service.setMailSender(mailSender());
    return service;
}

@Bean
public UserService testUserServive() {
    TestUserService testService = new TestUserService();
    testService.setUserDao(userDao());
    testService.setMailSender(mailSender());
    return testService;
}

@Bean
public MailSender mailSender() {
    return new DummyMailSender();
}&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;testUserService를 XML에서 userService와 프로퍼티 정의 부분이 동일해서 parent 정의를 사용하여 상속했는데, 프로퍼티 값을 모두 직접 넣어줘야함&lt;/li&gt;
&lt;li&gt;TestUserService 클래스는 public 접근 제한자로 설정하여 패키지가 달라도 접근할 수 있게 수정해줘야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;public static class TestUserService extends UserServiceImpl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;기존에는 UserServiceTest의 내부 스태틱 멤버 클래스로 정의했었음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XML에서 정의한 빈을 자바에서 참조하기 위해 &lt;del&gt;@Autowired를 사용&lt;/del&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;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public SqlService sqlService() {
    OxmSqlService sqlService = new OxmSqlService();
    sqlService.setUnmarshaller(unmarshaller());
    sqlService.setSqlRegistry(sqlRegistry());
    return sqlservice;
}

@Resource Database embeddedDatabase;

@Bean
public SqlRegistry sqlRegistry() {
    EmbeddedSqlRegistry sqlRegistry = new EmbeddedSqlRegistry();
    sqlRegistry.setDataSource(this.embeddedDatabase);
    return sqlRegistry;
}

@Bean
public Unmarshaller unmarshaller() {
    Jax2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setContextPath(&quot;springbook.user.sqlservice.jaxb&quot;);
    return marshaller;
}&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;embeddedDatabase 빈은 자바 코드로 변환하지 않았으니 @Resource를 통해 필드로 주입받아 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt; vs &lt;code&gt;@Resource&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Autowired
&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;Resource
&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;TestApplicationContext에 DataSource 타입의 dataSource 빈이 존재하므로 &lt;code&gt;@Resource&lt;/code&gt;를 사용&lt;/li&gt;
&lt;/ul&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;&amp;lt;jdbc:embedded-database&amp;gt;, &amp;lt;jdbc:script&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;jdbc:embedded-database&amp;gt;는 내장형 DB를 생성&lt;/li&gt;
&lt;li&gt;&amp;lt;jdbc:script&amp;gt;는 스크립트로 초기화한 뒤, DataSource 타입 DB의 커넥션 오브젝트를 빈으로 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Bean
public DataSource embeddedDatabase() {
    return new EmbeddedDatabaseBuilder()
        .setName(&quot;embeddedDatabase&quot;)
        .setType(HSQL)
        .addScript(&quot;classpath:springbook/user/.../~.sql&quot;)
        .build();
}&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;EmbeddedDatabaseBuilder를 통해 내장형 DB 종류와 초기화 스크립트를 지정하면, 위의 과정을 모두 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앞선 코드에서 &lt;code&gt;@Resource&lt;/code&gt;를 제거하고 sqlRegistry 빈에 프로퍼티를 embeddedDatabase 빈을 사용하도록 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public SqlRegistry sqlRegistry() {
    EmbeddedSqlRegistry sqlRegistry = new EmbeddedSqlRegistry();
    sqlRegistry.setDataSource(embeddedDatabase());
    return sqlRegistry;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;lt;tx:annotation-driven /&amp;gt; 태그 제거
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 AOP를 적용하려면 복잡하고 많은 빈이 동원됨
&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;li&gt;전용 태그를 사용하면 4가지 클래스를 빈으로 등록해줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;org.springframwork.aop.autoproxy.InfrastructureAdvisorAutoProxyCreator&lt;/li&gt;
&lt;li&gt;org.springframwork.transaction.annotation.AnnotationTransactionAttributeSource&lt;/li&gt;
&lt;li&gt;org.springframwork.transaction.interceptor.TransactionInterceptor&lt;/li&gt;
&lt;li&gt;org.springframwork.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 3.1부터는 &lt;code&gt;@EnableTransactionManagement&lt;/code&gt; 애노테이션을 사용해 위의 모든 것을 해결&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
public class TestApplicationContext {
    /*
     * DB 연결 및 트랜잭션
     */

    /*
     * 위에서 예시로 적었던 모든 빈 설정들
     */
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XML 설정을 1:1로 자바 코드로 전환하는 작업을 진행
&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;h4 data-ke-size=&quot;size20&quot;&gt;7.6.2 빈 스캐닝과 자동와이어링&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt;를 이용한 자동와이어링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserServiceImpl과 UserDaoJdbc 클래스에 &lt;code&gt;@Autowired&lt;/code&gt; 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&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;li&gt;최종 후보를 찾지 못한 경우에 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserDao 빈의 구현 클래스인 UserDaoJdbc는 dataSource와 sqlService 두 개의 빈에 의존&lt;/li&gt;
&lt;li&gt;dataSource 설정과 sqlService 프로퍼티에 &lt;code&gt;@Autowired&lt;/code&gt; 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class UserDaoJdbc implements UserDao {
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Autowired
    private SqlService sqlService;

    public void setSqlService(SqlService sqlService) {
        this.sqlService = sqlService;
    }
}&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;code&gt;@Autowired&lt;/code&gt;를 붙여 dataSource를 자동으로 주입&lt;/li&gt;
&lt;li&gt;DataSource 타입의 빈은 userDao가 사용하는 dataSource 빈, SQL 서비스용인 embeddedDatabase 빈 두 개
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dataSource 빈이 수정자 메소드와 이름이 동일하므로 dataSource를 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;sqlService가 자동 와이어링을 통해 주입되므로, 기존 userDao 빈의 userDao() 메소드는 빈 인스턴스만 생성하도록 수정하고, 기존에 Autowired로 SqlService를 주입하던 코드를 제거&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;del&gt;&lt;code&gt;@Autowired SqlService sqlService&lt;/code&gt;&lt;/del&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public UserDao userDao() {
    return new UserDaoJdbc();
}&lt;/code&gt;&lt;/pre&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;자바는 private 필드에 클래스 외부에서 값을 넣을 수 없게 되어있지만, 스프링은 리플렉션 API를 통해 제약조건을 우회해서 값을 주입&lt;/li&gt;
&lt;li&gt;수정자 메소드는 없어도 되지만, 다른 오브젝트를 주입해서 테스트해야하는 경우 수정자 메소드가 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt; 같은 자동와이어링은 적절히 사용하면 DI 관련 코드를 대폭 감소시켜 편리하지만, 빈 설정정보를 보고 다른 빈과 의존관계가 어떻게 맺어져있는지 한눈에 파악하기 힘듦&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Component&lt;/code&gt;를 이용한 자동 빈 등록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Component&lt;/code&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;code&gt;@Component&lt;/code&gt; 나 이를 메타 애노테이션으로 갖고 있는 애노테이션이 붙은 클래스가 자동 빈 등록 대상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestApplicationContext에 userDao() 메소드 제거, &lt;code&gt;@Autowired&lt;/code&gt; 적용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Autowired UserDao userDao;

@Bean
public UserService userService() {
    UserServiceImpl service = new UserServiceImpl();
    service.setUserDao(this.userDao);
    service.setMailSender(mailSender());
    return service;
}

@Bean
public UserService testUserService() {
    TestUserService testService = new TestUserService();
    testService.setUserDao(this.userDao);
    testService.setMailSender(mailSender());
    return testService;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;userDao() 삭제 후, userDao 빈이 등록될 방법이 없어서 테스트에 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserDaoJdbc 클래스에 &lt;code&gt;@Component&lt;/code&gt; 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
public class UserDaoJdbc implements UserDao {}&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestApplicationContext에 &lt;code&gt;@ComponentScan&lt;/code&gt; 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages=&quot;springbook.user&quot;)
public class TestApplicationContext {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Component&lt;/code&gt; 애노테이션이 달린 클래스를 스캔할 때, 프로젝트 내의 모든 클래스패스를 다 찾는 것은 부담이 큼
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 패키지 아래서만 찾도록 &lt;code&gt;@ComponentScan&lt;/code&gt;을 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위의 과정을 거쳐 다시 테스트가 통과되는데, 빈의 아이디가 userDaoJdbc로 바뀌었는데 왜 성공할까??
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈을 참조하는 테스트인 UserServiceTest나 DI 설정 클래스인 TestApplicationContext에서 모두 &lt;code&gt;@Autowired&lt;/code&gt;를 이용해 빈을 주입받기 때문&lt;/li&gt;
&lt;li&gt;아이디를 기준으로 주입할 빈을 찾지 않고 UserDao라는 타입으로 빈을 찾기 때문&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Component&lt;/code&gt;가 붙은 클래스 이름을 다른 빈 아이디로 사용하고 싶은 경우, 애노테이션 이름을 부여
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) &lt;code&gt;Component(&quot;userDao&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&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;여러 개의 애노테이션에 공통적인 속성을 부여하려면 메타 애노테이션을 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링은 &lt;code&gt;@Component&lt;/code&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;code&gt;@Transactional&lt;/code&gt;이 대표적인 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애노테이션이 빈 스캔을 통해 자동등록 대상으로 인식되게 하려면 애노테이션 정의에 &lt;code&gt;@Component&lt;/code&gt;를 메타 애노테이션을 붙여주면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
public @interface SnsConnector {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@SnsConnector
public class FacebookConnector {}&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;@SnsConnector 애노테이션을 부여하면, 자동 빈 등록 대상으로 만들고 Sns 커넥션과 관련된 부가 정보를 함께 담을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DAO 빈은 &lt;code&gt;@Repository&lt;/code&gt;를 사용하는 것을 권장하는데, &lt;code&gt;@Component&lt;/code&gt;를 메타 애노테이션으로 가지고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 작성한 UserDaoJdbc 코드도 &lt;code&gt;@Component&lt;/code&gt;에서 &lt;code&gt;@Repository&lt;/code&gt;로 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자동 빈 등록을 적용하는게 좋은 빈과 그렇지 않은 빈이 존재하기 때문에, 적절한 판단이 필요&lt;/li&gt;
&lt;li&gt;UserServiceImpl에 &lt;code&gt;@Component&lt;/code&gt;와 &lt;code&gt;@Autowired&lt;/code&gt;적용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Service(&quot;userService&quot;)
public class UserServiceImpl implements UserService {
    ...
    @Autowired
    private UserDao userDao;

    @Autowired
    private MailSender mailSender;
}&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;빈 자동등록을 했으니 TestApplicationContext의 userService() 메소드를 제거하는게 맞는데, 문제가 생김
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserService 타입의 빈이 userServiceImpl과 testUserService 두 개가 존재하기 때문인데, 주입할 빈을 결정하지 못하기 때문&lt;/li&gt;
&lt;li&gt;따라서, userService라는 아이디를 부여하여 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dataSource와 transactionManager 빈은 자동등록 기능을 적용할 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링이 제공해주는 클래스를 사용하기 때문에 소스코드에 &lt;code&gt;@Component&lt;/code&gt;나 &lt;code&gt;@Autowired&lt;/code&gt; 적용 불가&lt;/li&gt;
&lt;li&gt;dataSource 빈은 프로퍼티에 텍스트 값을 설정해줘야하기 때문에 더욱 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.6.3 컨텍스트 분리와 @Import&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지 작성한 정보는 테스트 DI 정보와 애플리케이션이 동작하는데 필요한 DI 정보가 혼재
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성격이 다른 DI 정보 분리하기&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;testUserService 빈은 테스트에서만 사용되고, 현재 작성한 mailSender 같은 경우에는 운영 중에 사용되면 안되기 때문에 분리&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class TestAppContext {    
    @Bean
    public UserService testUserService() {
        TestUserSerivce testService = new TestUserService();
        testService.setUserDao(this.userDao);
        testService.setMailSender(mailSender());
        return testService;
    }

    @Bean
    public MailSender mailSender() {
        return new DummyMailSender();
    }
}&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;테스트용 DI 설정 클래스인 TestAppContext를 만들어 빈 설정 애노테이션, 필드, 메소드를 옮김
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 TestApplicationContext는 ApplicationContext로 네이밍을 수정하고, 이와 혼동되지 않게 테스트용은 TestAppContext로 네이밍&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;testUserService 빈은 userDao와 mailSender 빈에 의존하는데, userDao 빈은 자동으로 등록되도록 &lt;code&gt;@Repository&lt;/code&gt;를 적용해서 &lt;code&gt;@Autowired&lt;/code&gt;로 빈을 주입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TestUserService 클래스가 UserServiceImpl을 상속했기 때문에, userDao 프로퍼티는 자동와이어링 대상
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 프로퍼티 설정 코드와 &lt;code&gt;@Autowired&lt;/code&gt; 필드를 제거하는 것이 더 깔끔&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class TestAppContext {
    @Bean
    public UserService testUserService() {
        return new TestUserService();
    }

    @Bean
    public MailSender mailSender() {
        return new DummyMailSender();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestUserService 클래스에 @Componnet를 붙이고 @ComponentScan을 이용해 자동 등록이 되게할 수 있지만, UserDaoServiceImpl과 UserServiceTest 등이 같은 패키지 아래 존재하므로 기준 패키지를 정하기 어려움
&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;운영 시스템은 AppContext만 참조, 테스트는 AppContext와 TestAppContext 두 개의 DI 정보를 함께 사용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestAppContext.class, AppContext.class})
public class UserDaoTest {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Import&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 서비스용 빈은 독립적인 모듈로 취급
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 애플리케이션에서도 사용될 수 있고, DAO에서는 sqlService 타입의 빈을 DI 받기만 하면 되지 구체적인 구현 방법을 알 필요가 없음&lt;/li&gt;
&lt;li&gt;독립적으로 개발되거나 변경될 가능성이 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 서비스와 관련된 빈 분리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Configuration
public class SqlServiceContext {
    // unmarshaller, registry를 설정하는 빈, sqlRegistry의 dataSource에 embeddedDbSqlRegistry를 설정하는 빈, unmarshaller 빈, embeddedDatabaseBuilder를 사용하는 DataSource 빈 등록 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 관련된 DI 설정 정보를 담은 클래스를 생성했으니, 운영용과 테스트용에 등록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppContext에 포함되는 보조 설정이므로, classes에 추가하기 보다 설정정보를 &lt;code&gt;@Import&lt;/code&gt;를 통해 AppContext에서 정보를 받아오는 것이 더 좋음&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages=&quot;springbook.user&quot;)
@Import(SqlServiceContext.class)
pubilc class AppContext {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.6.4 프로파일&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영용 mailsender
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaMail 기반의 메일 발송용 클래스 JavaMailSenderImpl 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MailSender mailSender() {
    JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
    mailSender.setHost(&quot;mail.mycompany.com&quot;);
    return mailSender;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserServiceTest 실행 시 문제 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppContext와 TestAppContext에 정의된 빈들이 함께 사용돼서, mailSender이 충돌&lt;/li&gt;
&lt;li&gt;빈 설정 정보를 읽는 순서에 따라 우선순위가 적용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;운영환경에서 반드시 필요하지만 테스트 실행 중에는 배제돼야하는 빈 설정을 별도의 설정 클래스를 만들어 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProductionAppContext&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class ProductionAppContext {
    @Bean
    public MailSender mailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(&quot;localhost&quot;);
        return mailSender;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;앞서 사용한 것처럼, AppContext에서 &lt;code&gt;@Import&lt;/code&gt;로 가져온다면, 테스트에서도 사용되는 문제가 그대로 발생&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Profile&lt;/code&gt;을 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Profile&lt;/code&gt;과 &lt;code&gt;@ActiveProfiles&lt;/code&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;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@Profile(&quot;test&quot;)
public class TestAppContext {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@Profile(&quot;production&quot;)
public class ProductionAppContext {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages=&quot;springbook.user&quot;)
@Import({SqlServiceContext.class, TestAppContext.class, ProductionAppContext.class}) 
public class AppContext {}&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;AppContext나 SqlServiceContext는 default 프로파일로 취급 ~&amp;gt; 항상 적용&lt;/li&gt;
&lt;li&gt;위와 같이 AppContext가 다 import 하고 있으므로, UserDaoTest, UserServiceTest의 @ContextConfiguration에서 AppContext.class만 설정해주면 된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@ContextConfiguration(classes=AppContext.class)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위의 설정을 하고 테스트를 돌리면 mailSender 빈 충돌이 발생
&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;ActiveProfile을 통해 활성 프로파일을 설정해주자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@ActiveProfiles(&quot;test&quot;)
@ContextConfiguration(classes=AppContext.class)
public class UserServiceTest {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 운영 환경은 production으로 지정해주어 사용&lt;/li&gt;
&lt;/ul&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;스프링 컨테이너는 BeanFactory라는 인터페이스를 구현&lt;/li&gt;
&lt;li&gt;DefaultListableBeanFactory는 BeanFactory를 구현한 클래스인데, 거의 대부분의 스프링 컨테이너가 이 클래스로 빈을 등록하고 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt;로 주입받아서 빈 이름과 클래스를 확인해보면 profile별로 configuration이 적용됨을 확인&lt;/li&gt;
&lt;/ul&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;전체 구성을 살펴보기가 번거로워짐
&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;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;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages=&quot;springbook.user&quot;)
@Import({SqlServiceContext.class}) 
public class AppContext {
    ...
    @Configuration
    @Profile(&quot;test&quot;)
    public static class TestAppContext {}

    @Configuration
    @Profile(&quot;production&quot;)
    public static class ProductionAppContext {}
}&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;ProductionAppContext와 TestAppContext 는 중첩 클래스로 만들었으므로 클래스 파일은 삭제&lt;/li&gt;
&lt;li&gt;중첩 클래스로 프로파일 설정 클래스를 포함했으므로, &lt;code&gt;@Import&lt;/code&gt;에 지정하지 않아도 적용&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.6.5 프로퍼티 소스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppContext에 테스트 환경에 종속되는 dataSource의 DB 연결정보가 남아있으므로 이를 분리해보자
&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;XML or properties 같은 텍스트 파일에 저장하는 것이 좋음
&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;&lt;code&gt;@PropertySource&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;#database.properties 파일
db.driverClass=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost/springboot?characterEncoding=UTF-8
db.username=spring
db.password=book&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages=&quot;springbook.user&quot;)
@Import({SqlServiceContext.class) 
@PropertySource(&quot;/database.properties&quot;)
public class AppContext {}&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;properties의 내용을 가져와 DB 연결정보를 프로퍼티에 주입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너가 프로퍼티 값을 가져오는 대상을 프로퍼티 소스(property source)라고 함&lt;/li&gt;
&lt;li&gt;환경 변수나 시스템 프로퍼티, 프로퍼티 파일이나 리소스 위치를 지정하는 등 다양한 프로퍼티 소스 존재&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@PropertySource&lt;/code&gt;를 통해 프로퍼티 소스 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt;를 통해 Environment 오브젝트를 주입받아 사용하는 방법이 예시에 있지만, Driver Class를 지정해줄 때 try - catch 블록을 활용해야하는 등 번거로움&lt;/li&gt;
&lt;li&gt;PropertySourcesPlaceholderConfigurer 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PropertySourcesPlaceholderConfigurer
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Value&lt;/code&gt; 애노테이션을 활용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Value(&quot;${db.driverClass}&quot;) class&amp;lt;? extends Driver&amp;gt; driverClass;
@Value(&quot;${db.url}&quot;) String url;
@Value(&quot;${db.username}&quot;) String username;
@Value(&quot;${db.password}&quot;) String password;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}&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;li&gt;빈 설정 메소드는 반드시 스태틱 메소드로 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Value&lt;/code&gt;로 가져온 네 개의 필드는 &lt;code&gt;@PropertySource&lt;/code&gt;로 지정한 파일에서 가져온 프로퍼티 값이 자동으로 주입됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dataSource 빈에서 값 사용하기&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Bean
public DataSource dataSource() {
    SimpleDriverDataSource ds = new SimpleDriverDataSource();
    ds.setDriverClass(this.driverClass);
    ds.setUrl(this.url);
    ds.setUsername(this.username);
    ds.setPassword(this.password);

    return ds;    
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.6.6 빈 설정의 재사용과 &lt;code&gt;@Enable*&lt;/code&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 서비스 빈은 서비스 인터페이스, 즉 API인 SqlService만 Dao 노출하면 됨
&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;SQL 서비스 구현 클래스는 애플리케이션의 다른 빈에 의존하지 않아서 독립적으로 패키징해서 배포 가능&lt;/li&gt;
&lt;li&gt;이미 분리가 되어있어서 &lt;code&gt;@Import(SqlServiceContext.class)&lt;/code&gt;를 통해 필요한 곳에서 SQL 서비스를 사용 가능&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;SQL 서비스를 재사용 가능한 독립 모듈로 만들기 위한 해결할 문제점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OxmlSqlService의 내부 클래스인 OxmlSqlReader&lt;/li&gt;
&lt;li&gt;sqlmap.xml 파일 위치를 지정하는 부분이 예제 코드의 UserDao 위치로 고정되어 있음&lt;/li&gt;
&lt;li&gt;sqlmap 프로퍼티의 디폴트 값을 UserDao 같은 사용자 예제에 종속되지 않게 수정하기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private Resource sqlmap = new ClassPathResource(&quot;/sqlmap.xml&quot;);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;default 값 이외에 빈 클래스 외부에서SQL 매핑 리소스를 설정하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Bean
public sqlService sqlService() {
    OxmSqlService sqlService = new OxmSqlService();
    ...
    sqlService.setSqlmap(new ClassPathResource(&quot;sqlmap.xml&quot;, UserDao.class));

    return sqlService;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;SQL 서비스 구현 클래스 내부 의존성이 제거됐지만, UserDao.class라는 애플리케이션 종속 정보가 남아있어서, 다른 애플리케이션에서 SqlServiceContext를 수정 없이 &lt;code&gt;@Import&lt;/code&gt;로 사용 불가&lt;/li&gt;
&lt;li&gt;의존성을 제거해서 SqlServiceContext까지 독립적인 모듈로 분리하자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sqlmap 리소스의 위치는 바뀔 일이 없으니 초기에 한 번만 지정하면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적인 DI 활용&lt;/li&gt;
&lt;li&gt;SQL과 같이 매번 달라지는 내용은 콜백 형태로 만들어서 템플릿/콜백 패턴을 쓰지만, 위의 경우는 기본 DI가 적절&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface SqlMapConfig {
    Resource getSqlMapResource();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class UserSqlMapConfig implements SqlMapConfig {
    @Override
    public Resource getSqlMapResource() {
        return new ClassPathResource(&quot;sqlmap.xml&quot;, UserDao.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
public class sqlServiceContext {
    @Autowired SqlMapConfig sqlMapConfig;

    @Bean
    public SqlService sqlService() {
        ...
        sqlService.setSqlmap(this.sqlMapConfig.getSqlMapResource());
        return sqlService;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class AppContext {
    ...
    @Bean
    public SqlMapConfig sqlMapConfig() {
        return new UserSqlMapConfig();
    }
}&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;SqlServiceContext가 변하지 않는 SqlMapConfig 인터페이스에만 의존하고, SqlMapConfig 구현 클래스는 빈으로 정의해 런타임 시 주입&lt;/li&gt;
&lt;li&gt;SqlMapConfig를 구현한 UserSqlMapConfig 클래스를 빈으로 등록하여 AppContext에서 빈을 생성&lt;/li&gt;
&lt;li&gt;SQL 매핑 파일 위치 변경에 영향을 받지 않게되면서, SqlServiceContext는 SqlMapConfig와 함께 SQL 서비스 모듈에 함께 패키징되어 수정 없이 재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AppContext에서 SqlMapConfig를 직접 구현하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 매핑 파일 리소스 위치도 애플리케이션 빈 설정 관련 정보인데, 새로운 클래스를 추가한 것이 복잡&lt;/li&gt;
&lt;li&gt;AppContext는 빈을 정의하고 DI 정보를 제공하는 설정용 클래스인 동시에 스스로도 빈이기 때문에, AppContext에서 직접 구현해도 무관
&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;pre class=&quot;java&quot;&gt;&lt;code&gt;public class AppContext implements sqlMapConfig {
    ...

    @Override
    public Resource getSqlMapResource() {
        return new ClassPathResource(&quot;sqlmap.xml&quot;, UserDao.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Enable*&lt;/code&gt; 애노테이션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Import&lt;/code&gt;를 통해 SqlServiceContext를 사용할 수 있지만, 조금 더 직관적인 의미가 있다면 더욱 좋음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Import&lt;/code&gt;애노테이션과 빈 설정 클래스 값을 메타 애노테이션으로 넣어 애노테이션을 생성하자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Import(value=SqlServiceContext.class)
public @interface EnableSqlService {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Import&lt;/code&gt;대신 &lt;code&gt;@EnableSqlService&lt;/code&gt;로 사용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(basePackages=&quot;springbook.user&quot;)
@EnableTransactionManagement
@EnableSqlService
@PropertySource(&quot;/database.properties&quot;)
public class AppContext implements SqlMapConfig {}&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;SQL 서비스를 사용한다는 의미가 더욱 명확&lt;/li&gt;
&lt;/ul&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;SQL 매핑 파일을 전달하게 했던 방식을 더욱 간결하게 만들 수 있음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@EnableSqlService(&quot;classpath:/springbook/user/sqlmap.xml&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>메타 애노테이션</category>
      <category>빈 스캐닝</category>
      <category>자동 와이어링</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/378</guid>
      <comments>https://zin0-0.tistory.com/378#entry378comment</comments>
      <pubDate>Mon, 30 Aug 2021 13:42:50 +0900</pubDate>
    </item>
    <item>
      <title>7장) 7.4 인터페이스 상속을 통한 안전한 기능확장 ~ 7.5 DI를 이용해 다양한 구현 방법 적용하기</title>
      <link>https://zin0-0.tistory.com/377</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;7장 스프링 핵심 기술의 응용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.4 인터페이스 상속을 통한 안전한 기능확장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션을 새로 시작하지 않고 특정 SQL의 내용만을 변경하고 싶다면 어떻게 해야 할지 생각해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.4.1 DI와 기능의 확장&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지 적용한 DI는 일종의 디자인 패턴 or 프로그래밍 모델이라는 관점에서 이해하는 것이 자연스러움
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링과 같은 DI 프레임워크를 적용하고 빈 설정파일로 애플리케이션을 구성했다고 해서 DI를 바르게 활용하고 있다고 볼 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DI의 장점은 DI에 적합한 오브젝트 설계가 요구됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI를 의식하는 설계
&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;SqlService 내부 기능을 적절한 책임과 역할에 따라 분리&lt;/li&gt;
&lt;li&gt;인터페이스 정의로 느슨하게 연결&lt;/li&gt;
&lt;li&gt;DI를 통해 유연하게 의존관계를 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI 덕분에 오브젝트들이 서로 세부적인 구현에 얽매이지 않고 유연한 의존관계로 독립적으로 발전&lt;/li&gt;
&lt;li&gt;DI 적용 조건
&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;DI는 런타임 시 의존 오브젝트를 다이내믹하게 연결해줘서 유연한 확장이 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI와 인터페이스 프로그래밍
&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;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;/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;오브젝트가 그 자체로 충분히 응집도가 높은 작은 단위로 설계됐더라도, 목적과 관심이 각기 다른 클라이언트가 있다면 인터페이스를 통해 이를 적절하게 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ex) B오브젝트가 B1과 B2라는 인터페이스를 구현하고 있고, A 오브젝트가 B1을 통해 B 오브젝트를 사용하고 있고있는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A는 B1에만 관심이 있는데 B2 인터페이스의 메소드까지 모두 노출되어 B 클래스에 직접 의존할 이유가 없음&lt;/li&gt;
&lt;li&gt;따라서 B1을 통해 B를 의존&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;h4 data-ke-size=&quot;size20&quot;&gt;7.4.2 인터페이스 상속&lt;/h4&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;/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;기존 클라이언트에 영향을 주지 않은 채로 오브젝트의 기능을 확장하거나 수정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또 다른 제 3의 클라이언트를 위한 인터페이스를 가질 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 작성한 코드를 보면 SqlRegistry의 구현 클래스인 MySqlRegistry의 오브젝트가 또 다른 제 3의 클라이언트를 위한 인터페이스를 가질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SqlRegistry에 SQL을 변경하는 기능을 넣어 확장하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BaseSqlService 클래스와 그 서브클래스가 존재 ~&amp;gt; SqlRegistry 인터페이스 수정은 바람직하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 조회 서비스인 BaseSqlService 입장에서 SQL 업데이트 기능을 이용하는 클라이언트가 될 이유가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기존의 SqlRegistry 인터페이스를 상속하고 메스드를 추가해서 새로운 인터페이스를 정의하기&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface UpdatableSqlRegistry extends SqlRegistry {
    public void updateSql(String key, String sql) throws SqlUpdateFailureException;

    public void updateSql(Map&amp;lt;String, String&amp;gt; sqlmap) throws SqlUpdateFailureException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;SQL 변경 요청을 담당하는 SQL 관리용 오브젝트가 있다고 하고, 클래스 이름을 SqlAdminService로 정해 UpdatableSqlRegistry라는 인터페이스를 통해 SQL 레지스트리 오브젝트에 접근하게 하자&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class SqlAdminService implements AdminEventListner {
    private UpdatableSqlRegistry updatableSqlRegistry;

    public void setUpdatableSqlRegistry(UpdatableSqlRegistry updatableSqlRegistry) {
        this.updatableSqlRegistry = updatableSqlRegistry;
    }

    public void updateEventListener(UpdateEvent event) {
        this.updatableSqlRegistry.updateSql(event.get(KEY_ID), event.get(SQL_ID));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.BaseSqlService&quot;&amp;gt;
    ...
    &amp;lt;property name=&quot;sqlRegistry&quot; ref=&quot;sqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;
&amp;lt;bean id=&quot;sqlRegistry&quot; class=&quot;springbook.user.sqlservice.MyUpdatableSqlRegistry&quot; /&amp;gt;

&amp;lt;bean id=&quot;sqlAdminService&quot; class=&quot;springbook.user.sqlservice.SqlAdminService&quot;&amp;gt;
    &amp;lt;property name=&quot;updatableSqlRegistry&quot; ref=&quot;sqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;BaseSqlService와 SqlAdminService는 동일한 오브젝트에 의존하지만, 각자의 관심과 필요에 따라 다른 인터페이스를 통해 접근
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스를 사용한 DI라 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.5 DI를 이용해 다양한 구현 방법 적용하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 중인 시스템에서 사용하는 정보를 실시간 변경하는 작업을 만들 때는, &lt;b&gt;동시성 문제&lt;/b&gt;를 가장 먼저 고려할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.5.1 ConcurrentHashMap을 이용한 수정 가능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Default로 사용하던 HashMapRegistry는 멀티스레드 환경에서 동시에 수정을 시도하거나 동시에 요청하는 경우 예상치 못한 결과가 발생할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티 스레드 환경에서는 Collections.synchronizedMap() 등을 이용해 외부에서 동기화 해줘야 함&lt;/li&gt;
&lt;li&gt;하지만, HashMap의 전 작업을 동기화하면 DAO의 요청이 많은 고성능 서비스에서는 성능 문제 존재&lt;/li&gt;
&lt;li&gt;동기화 해시 데이터 조작에 최적화된 ConcurrentHashMap을 사용 권장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 조작 시 전체 데이터에 락을 걸지 않고, 조회는 락을 아예 사용 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수정 가능 SQL 레지스트리 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class ConcurrentHashMapSqlRegistryTest {
    UpdatableSqlRegistry sqlRegistry;

    @BeforeEach
    public void setUp() {
        sqlRegistry = new ConcurrentHashMapSqlRegistry();
        sqlRegistry.registerSql(&quot;KEY1&quot;, &quot;SQL1&quot;);
        sqlRegistry.registerSql(&quot;KEY2&quot;, &quot;SQL2&quot;);
        sqlRegistry.registerSql(&quot;KEY3&quot;, &quot;SQL3&quot;);
    }

    @Test
    public void find() {
        checkFindResult(&quot;SQL1&quot;, &quot;SQL2&quot;, &quot;SQL3&quot;);
    }

    private void checkFindResult(String excepted1, String excepted2, String excepted3) {
        assertThat(sqlRegistry.findSql(&quot;KEY1&quot;), is(expected1));
        ...
    }

    @Test
    public void updateSingle() { // 하나의 SQL 수정 기능 검증
        sqlRegistry.updateSql(&quot;KEY2&quot;, &quot;Modified2&quot;);
        checkFindResult(&quot;SQL1&quot;, &quot;Modified2&quot;, &quot;SQL3&quot;);
    }

    @Test
    public void updateMulti() { // 동시에 여러개 SQL 수정 기능 검증
        Map&amp;lt;String, String&amp;gt; sqlmap = new HashMap&amp;lt;&amp;gt;(){{
            put(&quot;KEY1&quot;, &quot;Modified1&quot;);
            put(&quot;KEY3&quot;, &quot;Modified3&quot;);
        }};

        sqlRegistry.updateSql(sqlmap);
        checkFindResult(&quot;Modified1&quot;, &quot;SQL2&quot; ,&quot;Modified3&quot;);
    }

    @Test(expected = SqlNotFoundException.class)
    public void unknownKey() { // 주어진 키로 찾지 못하는 예외
        sqlRegistry.findSql(&quot;ASDioqwje&quot;);
    }

    @Test(expected=SqlUpdateFailureException.class)
    public void updateWithNotExistingKey() { // 존재하지 않는 키 SQL 변경 예외
        sqlRegistry.updateSql(&quot;SQL9999!@#$&quot;, &quot;Modified2&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수정 가능 SQL 레지스트리 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public class ConcurrentHashMapSqlRegistry implements UpdatableSqlRegistry {
    private Map&amp;lt;String, String&amp;gt; sqlMap = new ConcurrentHashMap&amp;lt;&amp;gt;();

    public String findSql(String key) throws SqlNotFOundException { ... }

    public void registerSql(String key, String sql) { ... }

    public void updateSql(String key, String sql) throws SqlUpdateFailureException {
        if (sqlMap.get(key) == null) {
            throw new SqlUpdateFailureException(&quot;not found sql from key : &quot; + key);
        }
        sqlMap.put(key, sql);
    }

    public void updateSql(Map&amp;lt;String, String&amp;gt; sqlmap) throws SqlUpdateFailureException {
        for(Map.Entry&amp;lt;String, String&amp;gt; entry : sqlmap.entrySet()) {
            updateSql(entry.getKey(), entry.getValue());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.OxmSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;unmarshaller&quot; ref=&quot;unmarshaller&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlRegistry&quot; ref=&quot;sqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;sqlRegistry&quot; class=&quot;springbook.user.sqlservice.updatable.ConcurrentHashMapSqlRegistry&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.5.2 내장형 데이터베이스를 이용한 SQL 레지스트리 만들기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내장형 DB를 이용해 SQL을 저장하고 수정하게 만들기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ConcurrentHashMap은 저장되는 데이터 양이 많아지고 잦은 조회와 변경이 일어날 때, 한계점이 존재&lt;/li&gt;
&lt;li&gt;데이터베이스는 인덱스를 이용한 최적화된 검색 지원, 동시에 많은 요청을 처리하면서 안정적인 변경 작업 가능&lt;/li&gt;
&lt;li&gt;내장형 DB는 IO로 인해 발생하는 부하가 적어 성능이 뛰어남
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리에 데이터를 저장하는 방법에 비해 효과적이고 안정적인 방법으로 CRUD 및 최적화 락킹, 격리수준, 트랜잭션 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 내장형 DB 지원 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 내장형 DB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Derby, HSQL, &lt;b&gt;H2&lt;/b&gt;를 가장 많이 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC 드라이버를 제공하고 표준 DB랑 호환 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내장형 DB를 위해 다른 서비스 추상화처럼 별도의 레이어와 인터페이스를 제공하지 않지만, 내장형 DB 빌더를 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL과 드라이버 등 초기화 기능&lt;/li&gt;
&lt;li&gt;초기화 쿼리 실행 기능&lt;/li&gt;
&lt;li&gt;모든 준비가 끝나면 내장형 DB에 대한 DataSource 오브젝트를 반환&lt;/li&gt;
&lt;li&gt;이후 DataSource를 통해 일반 DB처럼 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 안에서 직접 DB를 종료 요청이 가능해야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataSource 인터페이스를 상속해 shutdown() 메소드를 추가한 EmbeddedDatabase 인터페이스 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내장형 DB 빌더 학습 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 생성 쿼리를 담은 schema.sql, 초기화 데이터 생성 쿼리를 담은 data.sql이 준비되어있다고 가정&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;EmbeddedDatabase db;
SimpleJdbcTemplate template;

@BeforeEach
public void setUp() {
    db = new EmbeddedDatabaseBuilder()
        .setType(HSQL)
        .addScript(&quot;classpath:/springbook/learningtest/spring/embeddeddb/schema.sql&quot;)
        .addScript(&quot;classpath:/springbook/learningtest/spring/embeddeddb/data.sql&quot;)
        .build();
    template = new SimpleJdbcTemplate(db);
}

@AfterEach
public void tearDown() {
    db.shutdown();
}&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;테스트 메소드마다 EmbeddedDatabaseBuilder가 최종적으로 DataSource 를 상속한 EmbeddedDatabase 타입의 오브젝트를 리턴하여, template 사용에 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내장형 DB를 이용한 SqlRegistry 만들기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EmbeddedDatabaseBuilder는 직접 빈으로 등록한다고 바로 사용할 수 없음
&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;pre class=&quot;processing&quot;&gt;&lt;code&gt;public class EmbeddedDbSqlRegistry implements UpdatableSqlRegistry {
    SimpleJdbcTemplate jdbc;

    public void setDataSource(DataSource dataSource) {
        jdbc = new SImpleJdbcTemplate(dataSource);
    }

    public void registrySql(String key, String sql) {
        jdbc.update(&quot;insert into sqlmap(key_, sql_) values(?,?)&quot;, key, sql);
    }

    public String findSql(String key) throws SqlNotFoundException {
        try {
             return jdbc.queryForObject(&quot;select sql_ from sqlmap where key_ = ?&quot;, String.class, key);
        } catch (EmptyResultDataAcessException e) {
            throw new SqlNotFoundException(&quot;not found sql from key : &quot; + key);
        }
    }

    public void updateSql(String key, String sql) throws SqlUpdateFailureException {
        int affected = jdbc.update(&quot;update sqlmap set sql_ = ? where key_ = ?&quot;, sql, key);
        if (affected == 0) {
            throw new SqlUpdateFailureException(&quot;not found sql from key : &quot; + key);
        }
    }
    public void updateSql(Map&amp;lt;String, String sqlmap) throws SqlUpdateFailureException { ... }
}&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;내장형 DB는 EmbeddedDatabase 타입이라고 했는데, 위에서는 DataSource 타입의 오브젝트로 DI
&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;SQL 레지스트리는 JDBC를 이용해 DB에 접근할 수만 있으면 되기 때문에 DataSource 인터페이스가 적합
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UpdatableSqlRegistry 테스트 코드의 재사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스가 같은 클래스라도 구현 방식에 따라 검증 내용 / 테스트 방법이 달라질 수 있고, 의존 오브젝트 구성에 따라 mock이나 stub을 사용&lt;/li&gt;
&lt;li&gt;ConcurrentHashMapSqlRegistry는 의존 오브젝트가 아예 없고, EmbeddedDbSqlRegisry도 내장형 DataSource 빈을 의존하기는 하지만 테스트 대역으로 대체하기 힘듦&lt;/li&gt;
&lt;li&gt;UpdatableSqlRegistry 구현 클래스의 오브젝트 생성 부분을 분리하면, 나머지 테스트 코드가 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XML 설정을 통한 내장형 DB의 생성과 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jdbc 스키마의 전용 태그 사용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 네임스페이스 선언 --&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       ...
       xmlns=&quot;http://www.springframework.org/schema/jdbc&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                           http://www.springframework.org/schema/jdbc&quot;
       ...&quot;&quot;&amp;gt;&amp;lt;/beans&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 내장형 DB 등록 --&amp;gt;
&amp;lt;jdbc:embedded-database id=&quot;embeddedDatabase&quot; type=&quot;HSQL&quot;&amp;gt;
    &amp;lt;jdbc:script location=&quot;classpath:springbook/user/sqlservice/updatable/sqlRegistrySchema.sql&quot; /&amp;gt;
&amp;lt;/jdbc:embedded-database&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- embeddedDbSqlRegistry 클래스를 이용한 빈 등록 --&amp;gt;
&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.OxmSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;unmarshaller&quot; ref=&quot;unmarshaller&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlRegistry&quot; ref=&quot;sqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;sqlRegistry&quot; class=&quot;springbook.user.sqlservice.updatable.EmbeddedDbSqlRegistry&quot; &amp;gt;
    &amp;lt;property name=&quot;dataSource&quot; ref=&quot;embeddedDatabase&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.5.3 트랜잭션 적용&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 SQL을 수정할 때는 문제가 없지만 하나 이상의 SQL을 맵으로 전달받아 한 번에 수정해야하는 경우, 심각한 문제 발생
&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;중간에 예외 발생 ~&amp;gt; 수정 사항은 반영&lt;/li&gt;
&lt;li&gt;HashMap과 같은 컬렉션은 트랜잭션 개념을 적용하기 힘들지만, 내장형 DB는 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 경계가 DAO 밖에 있고 범위가 넓다면 AOP를 이용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 레지스트리라는 제한된 오브젝트 내에서 서비스에 특화된 경우는 트랜잭션 API를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다중 SQL 수정에 대한 트랜잭션 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class EmbeddedDbSqlRegistryTest extends AbstractUpdatableRegistryTest {
    ...
    @Test
    public void transactionalUpdate() {
        checkFind(&quot;SQL1&quot;, &quot;SQL2&quot;, &quot;SQL3&quot;);

        Map&amp;lt;String, String&amp;gt; sqlmap = new HashMap&amp;lt;&amp;gt;(){{
            put(&quot;KEY1&quot;, &quot;Modified&quot;);
            put(&quot;KEY9999!@#$&quot;, &quot;Modified999&quot;); // 존재하지 않는 키 ~&amp;gt; 실패
        }};

        try {
            sqlRegistry.updateSql(sqlmap);
            fail(); // 예외가 발생하지 않은 경우 실패
        } catch (SqlUpdateFailureException e) {}

        checkFind(&quot;SQL1&quot;, &quot;SQL2&quot;, &quot;SQL3&quot;); // 롤백이 되면 초기 값과 같음
    }
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class EmbeddedDbSqlRegistry extends AbstractUpdatableRegistryTest {
    SimpleJdbcTemplate jdbc;
    TransactionTemplate transactionTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbc = new SimpleJdbcTemplate(dataSource);
        transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
    }
    ...

    public void updateSql(final Map&amp;lt;String, String&amp;gt; sqlmap) throws SqlUpdateFailureException {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                for(Map.Entry&amp;lt;String, String&amp;gt; entry : sqlmap.entrySet()) {
                    updateSql(entry.getKey(), entry.getValue());
                }
            }
        });
    }
}&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;PlatformTransactionManager를 직접 사용하기보다 트랜잭션 적용 코드에 템플릿/콜백 패턴을 적용한 TransactionTemplate을 쓰는 것이 더욱 간결&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;여러 AOP를 통해 만들어지는 트랜잭션 프록시가 같은 트랜잭션 매니저를 공유해야 하기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EmbeddedDbSqlRegistry는 내부에서 직접 만들어 사용하는 것이 좋음
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>내장형 DB</category>
      <category>인터페이스 상속</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/377</guid>
      <comments>https://zin0-0.tistory.com/377#entry377comment</comments>
      <pubDate>Fri, 20 Aug 2021 14:09:32 +0900</pubDate>
    </item>
    <item>
      <title>7장) 7.3 서비스 추상화 적용</title>
      <link>https://zin0-0.tistory.com/376</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.3 서비스 추상화 적용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JaxbXmlSqlReader 개선 과제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JAXB 외에 다양한 XML과 자바오브젝트를 매핑하는 기술이 있음
&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;XML 파일을 좀 더 다양한 소스에서 가져올 수 있게 만든다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 작성한 내용은 UserDao 클래스와 같은 class path 안에서만 XML을 읽어올 수 있음&lt;/li&gt;
&lt;li&gt;임의의 class path나 파일 시스템 상의 절대위치 or HTTP 프로토콜을 통해 원격에서 가져오도록 확장하는 방법 생각&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.3.1 OXM 서비스 추상화&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OXM (Object-XML Mapping)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JAXB 외에 실전에서 자주 사용되는 &lt;b&gt;다양한&lt;/b&gt; XML과 자바오브젝트를 매핑하는 &lt;b&gt;기술(OXM)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Castor XML
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정파일 필요 X&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;특정 클래스가 어떤 클래스로부터 파생되었는지, 혹은 어떤 메소드가 구현되어 있는지, 객체에는 어떤 속성이 있는지에 대한 &lt;b&gt;상세한 정보를 런타임에 얻거나 조작하는 기술&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;JibX
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XmlBeans
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아파치 XML 프로젝트의 하나로 XML 정보셋을 효과적으로 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Xstream
&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;/ul&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;JAXB를 포함한 총 다섯가지의 OXM 프레임워크는 사용 목적이 동일하기 때문에 유사한 기능과 API를 제공&lt;/li&gt;
&lt;li&gt;기능이 같기 때문에 로우레벨의 구체적인 기술과 API에 종속되지 않고 추상화된 레이어와 API를 제공하여, 구현 기술에 대해 독립적인 코드를 작성할 수 있게 해주는 &lt;b&gt;서비스 추상화가 필요&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OXM 서비스 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링이 제공하는 OXM 추상화 서비스 인터페이스에는 자바오브젝트를 XML로 변환하는 Marshaller와 반대인 Unmarshaller가 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlReader는 Unmarshaller를 이용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Umarshaller {
    boolean supports(Class&amp;lt;?&amp;gt; clazz);

    Object unmarshal(Source source) throws IOException, XmlMappingException;
}
&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;Unmarshaller 인터페이스를 살펴보면 XML 파일에 대한 정보를 담은 Source 타입의 오브젝트를 제공받고, 설정에서 지정한 OXM 기술을 이용해 자바오브젝트 트리로 변환하고 그 루트 오브젝트를 리턴함&lt;/li&gt;
&lt;li&gt;OXM 기술에 따라 Unmarshaller 인터페이스를 구현한 클래스가 다섯가지 있고, 필요에 따라 추가 정보를 빈 프로퍼티로 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JAXB 구현 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 만든 JaxbTest 학습 테스트를 스프링 OXM 서비스 추상화 인터페이스를 이용하도록 수정&lt;/li&gt;
&lt;li&gt;Jaxb2Marshaller 클래스는 Unmarshaller와 Marshaller 인터페이스를 모두 구현하고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Jaxb2Marshaller 클래스를 빈으로 등록하고 바인딩 클래스 패키지 이름을 지정하는 contextPath 프로퍼티만 넣어주면 언마샬러로 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;beans xmlns=&quot;http://www.springframwork.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/benas
                           http://www.springframework.org/schema/benas/spring-beans-3.0xsd&quot;&amp;gt;
    &amp;lt;bean id=&quot;unmarshaller&quot; class=&quot;org.springframework.oxm.jaxb.Jaxb2Marshaller&quot;&amp;gt;
        &amp;lt;property name=&quot;contextPath&quot; value=&quot;springbook.user.sqlservice.jaxb&quot; /&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 서비스 추상화가 적용됐으므로 로우레벨의 JAXB API를 사용해서 컨텍스트를 만들어 언마샬러를 생성하는 등의 복잡한 코드 작성 필요 X
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상 인터페이스인 Unmarshaller의 unmarshal() 메소드를 호출하면 모든 작업을 Jaxb2Marshaller 빈이 알아서 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@ContextConfiguration
public class OxmTest {
    @Autowired Unmarshaller unmarshaller;

    @Test
    public void unmarshallSqlMap() throws XmlMappingException, IOException {
        SOurce xmlSource = new StreamSource(
        getClass().getResourceAsStream(&quot;sqlmap.xml&quot;));

        Sqlmap sqlmap = (Sqlmap) this.unmarshaller.unmarshal(xmlSource);

        List&amp;lt;SqlType&amp;gt; sqlList = sqlmap.getSql();
        assertThat(sqlList.size(), is(3));
        assertThat(sqlList.get(0).getKey(), is(&quot;add&quot;));
        ...
        assertThat(sqlList.get(2).getValue(), is(&quot;delete&quot;));
    }
}&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;OXM 추상화 계층을 이용하여 구체적인 기술에 의존하는 부분 없이 XML 설정에 의해 기술을 변경할 수 있게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Castor 구현 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Castor로 OXM 기술을 바꾸는 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE mapping PUBLIC &quot;-//EXOLAB/Castor Mapping DTD Version 1.0/EN&quot;
    &quot;http://castor.org/mapping.dtd&quot;&amp;gt;
&amp;lt;mapping&amp;gt;
    &amp;lt;class name=&quot;springbook.sqlservice.jaxb.Sqlmap&quot;&amp;gt;
        &amp;lt;map-to xml=&quot;sqlmap&quot; /&amp;gt;
        &amp;lt;filed name=&quot;sql&quot; type=&quot;springbook.sqlservice.jaxb.SqlType&quot; required=&quot;true&quot; collection=&quot;arraylist&quot;&amp;gt;
            &amp;lt;bind-xml name=&quot;sql&quot; node=&quot;element&quot; /&amp;gt;
        &amp;lt;/filed&amp;gt;
    &amp;lt;/class&amp;gt;
    &amp;lt;class name=&quot;springbook.sqlservice.jaxb.SqlType&quot;&amp;gt;
        &amp;lt;map-to xml=&quot;sql&quot; /&amp;gt;
        &amp;lt;field name=&quot;key&quot; type=&quot;string&quot; required=&quot;true&quot;&amp;gt;
            &amp;lt;bind-xml name=&quot;key&quot; node=&quot;attribute&quot; /&amp;gt;
        &amp;lt;/field&amp;gt;
        &amp;lt;field name=&quot;value&quot; type=&quot;string&quot; required=&quot;true&quot;&amp;gt;
            &amp;lt;bind-xml node=&quot;text&quot; /&amp;gt;
        &amp;lt;/field&amp;gt;
    &amp;lt;/class&amp;gt;
&amp;lt;/mapping&amp;gt;&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;key - value 값의 매핑 정보 설정 ~&amp;gt; mapping.xml로 이름 명명&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;unmarshaller&quot; class=&quot;org.springframework.oxm.CastorMarshaller&quot;&amp;gt;
    &amp;lt;property name=&quot;mappingLocation&quot; value=&quot;springbook/learningtest/spring/oxm/mapping.xml&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&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;언마샬러로 Castor용 구현 클래스로 변경하면서 매핑 설정을 제공하면 Castor로 OXM 기술을 바꿀 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서비스 추상화를 활용하여 로우레벨의 기술을 필요에 따라 변경해서 사용 ~&amp;gt; 애플리케이션 코드는 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.3.2 OXM 서비스 추상화 적용&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlService를 OXM 추상화 기능을 사용하여 만들자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlRegistry는 DI를 받지만, SqlReader는 스프링의 OXM 언마샬러를 사용하도록 고정&lt;/li&gt;
&lt;li&gt;OXM 기술에 의존적이라고해서 OXM 코드를 직접 가지고 있을 필요는 없음&lt;/li&gt;
&lt;li&gt;앞서 SqlReader와 SqlRegistry 두 전략을 유지하면서 SqlReader 구현 오브젝트에 대한 의존 관계를 고정시키기&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;BaseSqlService와 유사하게 SqlReader 타입의 의존 오브젝트를 사용하되 스태틱 멤버 클래스로 내장하고 자신만 사용하도록하는 OxmSqlService 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlRegistry는 가장 단순한 HashMapSqlRegisty를 default 의존 오브젝트로 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class OxmSqlService implements SqlService {
    private final OxmSqlReader oxmSqlReader = new OxmSqlReader();

    private class OxmSqlReader implements SqlReader {
        ...
    }
}&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;OxmSqlReader를 private으로 감추고, final로 선언하여 DI하거나 변경하지 못하도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OXM을 이용하는 서비스 구조로 최적화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링의 OXM 서비스 추상화를 사용하면 언마샬러를 빈으로 등록해야하는데, SqlService를 위해 등록할 빈이 계속 늘어나게 됨&lt;/li&gt;
&lt;li&gt;계속 발전시키기 위해서 가능한 한 분리하고 확장 가능하게 만들어야 하지만, 실제로 이를 적용해서 DAO를 개발하는 입장에서는 부담&lt;/li&gt;
&lt;li&gt;빈의 개수를 줄이고 설정을 단순하게 하는 방법에는 BaseSqlService를 확장해서 디폴트 설정을 두는 방법도 있지만, 디폴트로 내부에서 만드는 오브젝트의 프로퍼티를 외부에서 지정해주기 힘듦
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OXM 적용의 경우, 언마샬러를 비롯한 설정을 통해 DI 해줄게 많기 때문에 부적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하나의 빈 설정으로 SqlService와 SqlReader에 필요한 프로피터 설정이 가능하도록 해야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlService의 구현이 SqlReader의 구체적인 구현 클래스 정보를 알고, 자신의 프로퍼티를 통해 필요한 설정정보를 넘겨주고, 멤버 클래스로 소유하고 있는 강한 결합구조를 사용함&lt;/li&gt;
&lt;li&gt;OxmSqlReader는 OxmSqlService에 의해 만들어지기기 때문에, DI 받을 정보가 있다면 OxmSqlService의 공개된 프로퍼티를 통해 간접적으로 DI 받음&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class OxmSqlService implements SqlService {
    private final OxmSqlReader oxmSqlReader = new OxmSqlReader();

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.oxmSqlReader.setUnmarshaller(unmarshaller);
    }

    public void setSqlmapFile(String sqlmapFile) {
        this.oxmSqlReader.setSqlmapFile(sqlmapFile);
    }

    private class OxmSqlReader implenets SqlReader {
        private Unmarshaller unmarshaller;
        private String sqlmapFile;
        // ... setter methods
    }
}&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;앞서 UserDaoJdbc에서 JdbcTemplate을 만들 때, Datasource를 전달하는 방식과 비슷
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JdbcTemplate은 자체 독립 빈으로 만들 수도 있고 여러 DAO에서 사용 가능한 최상위 레벨 클래스지만, OxmSqlReader는 OxmSqlService에서만 사용하도록 제한한 멤버 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class OxmSqlService implements SqlService {
    private final OxmSqlReader oxmSqlReader = new OxmSqlReader();
    private SqlRegistry sqlRegistry = new HashMapSqlRegistry();

    public void setSqlRegistry(SqlRegistry sqlRegistry) {
        this.sqlRegistry = sqlRegistry;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.oxmSqlReader.setUnmarshaller(unmarshaller);
    }

    public void setSqlmapFile(String sqlmapFile) {
        this.oxmSqlReader.setSqlmapFile(sqlmapFile);
    }

    @PostConstruct
    public void loadSql() {
        this.oxmSqlReader.read(this.sqlRegistry);
    }

    public String getSql(String key) throws SqlRetrievalFailureException {
        try {
            return this.sqlRegistry.findSql(key);
        } catch (SqlNotFoundException e) {
            throw new SqlRetrievalFailureException(e);
        }
    }

    private class OxmSqlReader implenets SqlReader {
        private final static String DEFAULT_SQLMAP_FILE = &quot;sqlmap.xml&quot;;
        private Unmarshaller unmarshaller;
        private String sqlmapFile;

        public void setUnmarshaller(Unmarshaller unmarshaller) {
            this.unmarshaller = unmarshaller;
        }

        public void setSqlmapFile(String sqlmapFile) {
            this.sqlmapFile = sqlmapFile;
        }

        public void read(SqlRegistry sqlRegistry) {
            try {
                Source source = new StreamSource(UserDao.class.getResourceAsStream(this.sqlmapFile);
                Sqlmap sqlmap = (Sqlmal)this.unmarshaller.unmarshal(source);
                sqlmap.getSql().forEach(sql -&amp;gt; {
                    sqlRegistry.registerSql(sql.getKey(), sql.getValue());
                });
            } catch (IOException e) {
                throw new IllegalArgumentException(this.sqlmapFile + &quot;을 가져올 수 없습니다.&quot;, e);
            }
        }
    }
}&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;OXM을 적용했어도 빈 설정을 단순하게 유지할 수 있는 구조로 수정됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;unmarshaller 빈만 따로 설정해주면, SqlService와 OXM 언마샬러를 사용하는 SqlReader, SqlRgistry는 하나의 빈을 등록하는 것으로 충분&lt;/li&gt;
&lt;li&gt;SqlRegistry는 필요에 따라 다른 구현으로 교체 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.OxmSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;unmarshaller&quot; ref=&quot;unmarshaller&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;unmarshaller&quot; class=&quot;org.springframework.oxm.jaxb.Jaxb2Marshaller&quot;&amp;gt;
    &amp;lt;propery name=&quot;contextPath&quot; value=&quot;springbook.user.sqlService.jaxb&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&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;OxmSqlServic와 Jaxb2Marshaller를 사용하는 unmarshaller만 빈으로 등록하면, 프로퍼티를 내부적인 간접 DI를 통해 이용되므로 설정이 간단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위임을 이용한 BaseSqlService의 재사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;loadSql()과 getSql() 메소드가 BaseSqlService와 동일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇다고 BaseSqlService 코드를 재사용한다고 상속해서 OxmSqlService를 만들면 멤버 클래스로 통합시킨 OxmSqlReader를 생성하는 코드를 넣기 애매해짐&lt;/li&gt;
&lt;li&gt;중복을 제거하기 위해 loadSql()과 getSql()메소드를 추출해서 슈퍼클래스로 분리하는 방법도 있지만, 이 정도 코드로는 복잡한 계층구조로 만들기에도 부담스러움&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;loadSql()과 getSql() 구현 로직은 BaseSqlService에만 두고 OxmSqlService는 일종의 설정과 기본 구성을 변경해주기 위한 어댑터 같은 개념으로 BaseSqlService의 앞에 두는 설계가 가능&lt;/li&gt;
&lt;li&gt;OxmSqlService의 외형 틀을 유지한 채, SqlService의 기능 구현은 BaseSqlService로 위임&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;프록시처럼 많은 타깃에 적용하는게 아닌 특화된 서비스 한번만 사용하기 때문에, 유연한 DI는 포기하고 OxmSqlService와 BaseSqlService를 한 클래스로 묶기&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://i.ibb.co/TwNRs69/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;OxmSqlService는 OXM 기술에 특화된 SqlReader를 멤버로 내장, 필요한 설정을 한번에 지정할 수 있는 확장구조&lt;/li&gt;
&lt;li&gt;실제 SqlReader와 SqlService를 이용해 SqlService 기능을 구현하는 일은 내부의 BaseSqlService를 만들어서 위임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;public class OxmSqlService implenets SqlService {
    private final BaseSqlService baseSqlService = new BaseSqlService();
    ...

    @PostContruct
    public void loadSql() {
        this.baseSqlService.setSqlReader(this.oxmSqlReader);
        this.baseSqlService.setSqlRegistry(this.sqlRegistry);

        this.baseSqlService.loadSql(); // SQL 등록 초기화 위임
    }

    public String getSql(String key) throws SqlRetirevalFailureException {
        return this.baseSqlService.getSql(key);
    }
}&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;중복 코드가 제거되고 SqlReader와 SqlRegistry와 관련 로직에 변경점이 생기면 BaseSqlService만 수정해주면 되게끔 수정됨&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;h4 data-ke-size=&quot;size20&quot;&gt;7.3.3 리소스 추상화&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OxmSqlReader와 XmlSqlReader의 공통 문제점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 매핑 정보가 담긴 XML 파일 이름을 프로퍼티로 외부에서 지정할 수는 있지만, UserDao 클래스와 같은 classpath에 존재하는 파일로 제한됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;classpath 루트 등에 있는 XML 파일을 읽는 경우나 서버나 개발 시스템의 특정 디렉토리에 있는 파일을 읽어오지 못하는 등 문제점 존재&lt;/li&gt;
&lt;li&gt;URL 클래스를 사용해서 원격 리소스에 접근이 가능하지만, 클래스패스 안에 존재하는 리소스나 서블릿 컨텍스트의 리소스 등을 지정할 수 없고 존재 여부를 미리 확인할 수 없음&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;리소스 접근 API를 추상화한 Resource라는 추상화 인터페이스 사용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface Resource extends InputStreamSource {
    boolean exists();
    boolean isReadable();
    boolean isOpen();

    Url getURL() throws IOException;
    Url getURI() throws IOException;
    File getFile() throws IOException; // JDK의 URL, URI, FIle 형태로 전환 가능한 리소스에 사용

    Resource createRelative(String relativePath) throws IOException;

    long lastModified() throws IOException;
    String getFileName();
    String getDescription(); // 이상 3개의 메소드는 리소스에 대한 이름 및 부가정보 조회
}

public interface InputStreamSource {
    InputStream getInpuStream() throws IOException; // 모든 리소스를 InputStream 형태로 가져옴
}&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;Resource는 스프링에서 빈이 아닌 값으로 취급됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OXM이나 트랜잭션처럼 서비스를 제공해주는 것이 아닌 단순한 정보를 가진 값으로 지정&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈으로 등록하면 리소스 타입에 따라 각기 다른 Resource 인터페이스의 구현 클래스를 지정해주면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP로 가져올 리소스라면 HttpResource 같은 클래스를 빈의 클래스로 지정하여 사용하면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource는 빈으로 등록되지 않기 때문에 불가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;property의 value 애트리뷰트에 넣으려해도 value는 단순한 String 값만 가능하여 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ResourceLoader
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 안에 리소스 종류와 리소스의 위치를 함께 표현하여 Resource 오브젝트를 선언&lt;/li&gt;
&lt;li&gt;문자열로 정의된 리소스를 실제 Resource 타입 오브젝트로 변환해줌&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface ResourceLoader {
    Resource getResource(String location);
}&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;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;/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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접두어&lt;/th&gt;
&lt;th&gt;예&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file:&lt;/td&gt;
&lt;td&gt;file:/C:/temp/file.txt&lt;/td&gt;
&lt;td&gt;파일 시스템의 C:/temp 폴더에 있는 file.txt를 리소스로 만들어줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;classpath:&lt;/td&gt;
&lt;td&gt;classpath:file.txt&lt;/td&gt;
&lt;td&gt;클래스패스의 루트에 존재하는 file.txt 리소스에 접근하게 해줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;WEB-INF/test.dat&lt;/td&gt;
&lt;td&gt;ResourceLoader 구현에 따라 리소스 위치가 결정됨&lt;br /&gt;ServletResourceLoader라면 서블릿 컨텍스트의 루트를 기준으로 해석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;http:&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://www.myserver.com/test.dat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 프로토콜을 사용해 접근할 수 있는 웹상의 리소스를 지정&lt;br /&gt;&lt;code&gt;ftp:&lt;/code&gt; 또한 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;ResourceLoader의 대표적인 예&lt;/li&gt;
&lt;li&gt;ApplicationContext 인터페이스는 ResourceLoader 인터페이스를 상속&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;ex) 스프링 설정 정보가 담긴 XML 파일도 리소스 로더를 이용해 Resource 형태로 Load&lt;/li&gt;
&lt;li&gt;ex) 애플리케이션 컨텍스트가 외부에서 읽어오는 모든 정보는 리소스 로더를 사용하게 되어있음&lt;/li&gt;
&lt;li&gt;property 태그의 value 문자열로 된 리소스 정보 또한 Resource 오브젝트로 변환해서 프로퍼티에 주입할 때도애플리케이션 컨텍스트 자신이 리소스 로더로 변환과 로딩을 담당&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;li&gt;Resource를 이용해 XML파일 가져오기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OxmSqlService에 Resource 적용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 매핑정보가 담긴 파일을 다양한 위치에서 가져오도록 수정&lt;/li&gt;
&lt;li&gt;sqlmapFile 프로퍼티를 String 타입에서 Resource 타입으로 수정&lt;/li&gt;
&lt;li&gt;sqlmapFile 이름을 sqlmap으로 수정
&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;Resource 타입은 실제 소스가 어떤 것이든 getInputStream() 메소드로 스트림을 가져올 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SteramSource 클래스를 사용하여 OXM 언마샬러가 필요로 하는 Source 타입으로 만들어 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class OxmSqlService implenets SqlService {
    public void setSqlmap(Resource sqlmap) {
        this.oxmSqlReader.setSqlmap(sqlmap);
    }
    ...
    private class OxmSqlReader implements SqlReader {
        private Resource sqlmap = new ClassPathResource(&quot;sqlmap.xml&quot;, UserDao.class); // 디폴트 설정

        public void setSqlmap(Resource sqlmap) {
            this.sqlmap = sqlmap;
        }

        public void read(SqlRegistry sqlRegistry) {
            try {
                // 리소스 종류에 상관없이 스트링으로 가져올 수 있음
                Source source = new StreamSource(sqlmap.getInpuStream());
            } catch (IOException e) {
                throw new IllegalArgumentException(this.sqlmapFile + &quot;을 가져올 수 없습니다.&quot;, e);
            }
        }
    }
}
&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;Resource 오브젝트는 실제 리소스가 아닌, 리소스에 접근할 수 있는 추상화된 핸들러
&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;ClassPathResource를 사용하여 코드에서 클래스패스 리소스를 바로 지정(default 값 지정)
&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;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.OxmSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;unmarshaller&quot; ref=&quot;unmarshaller&quot; /&amp;gt;
    &amp;lt;!-- classpath를 사용한 리소스 접근 --&amp;gt;
    &amp;lt;property name=&quot;sqlmap&quot; value=&quot;classpath:springbook/user/dao/sqlmap.xml&quot; /&amp;gt;

    &amp;lt;!-- file:을 사용한 리소스 접근 --&amp;gt;
    &amp;lt;property name=&quot;sqlmap&quot; value=&quot;file:/opt/resources/sqlmap.xml&quot; /&amp;gt;

    &amp;lt;!-- http:을 사용한 리소스 접근 --&amp;gt;
    &amp;lt;property name=&quot;sqlmap&quot; value=&quot;http://www.epril.com/resources/sqlmap.xml&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;classpath:&lt;/code&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;code&gt;file:&lt;/code&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;code&gt;http://&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 프로토콜로 접근하여 웹 리소스 접근&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;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>OXM</category>
      <category>resourceLoader</category>
      <category>서비스 추상화</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/376</guid>
      <comments>https://zin0-0.tistory.com/376#entry376comment</comments>
      <pubDate>Sun, 8 Aug 2021 17:27:11 +0900</pubDate>
    </item>
    <item>
      <title>7장) 7.1 SQL과 DAO의 분리 ~ 7.2 인터페이스의 분리와 자기참조 빈</title>
      <link>https://zin0-0.tistory.com/375</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;7장 스프링 핵심 기술의 응용&lt;/h2&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;앞서 학습한 3대 핵심기술인 IoC/DI, 서비스 추상화, AOP를 Application 개발에 활용해서 새로운 기능을 만들어보고, 스프링의 개발철학과 추구하는 가치, 스프링 사용자에게 요구되는 게 무엇인지 알아보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.1 SQL과 DAO의 분리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAO에서 SQL 분리하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복적인 JDBC 작업 흐름은 템플릿, 트랜잭션과 예외처리 작업은 서비스 추상화와 AOP로 DAO로 부터 분리했다&lt;/li&gt;
&lt;li&gt;DAO는 데이터를 가져오고 조작하는 작업의 인터페이스 역할&lt;/li&gt;
&lt;li&gt;데이터 엑세스 로직이 바뀌지 않더라도 DB 테이블, 필드 이름과 SQL 문장의 변경이 일어나는 경우, 현재 구조에서는 DAO를 수정해야함
&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;h4 data-ke-size=&quot;size20&quot;&gt;7.1.1 XML 설정을 이용한 분리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개별 SQL 프로퍼티 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드 매핑을 위해 사용하는 userMapper도 SQL은 아니지만 필드 이름을 가지고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선, userMapper를 제외하고 순수한 SQL 문장만 작업&lt;/li&gt;
&lt;li&gt;기존에 작업했던&lt;code&gt;add()&lt;/code&gt; 메소드의 SQL을 외부로 빼내보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class UserDaoJdbc implemnets UserDao {
    private String sqlAdd;

    public void setSqlAdd(String sqlAdd) {
        this.sqlAdd = sqlAdd;
    }
}

/// UserService
public void add(User user) {
    this.jdbcTemplate.update(
        this.sqlAdd,
        user.getId(), //... data
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;userDao&quot; class=&quot;springbook.user.dao.UserDaoJdbc&quot;&amp;gt;
    &amp;lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlAdd&quot; value=&quot;insert into users(id, name, password, email, level, login, recommend) values(?,?,?,?,?,?,?)&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;sql 문장을 userDao 빈의 프로퍼티로 등록하고, DI 받아서 사용하도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스트링 값을 외부에서 DI해오기 때문에 SQL을 분리해서 관리할 수 있지만, 매번 새로운 SQL이 필요할 때마다 프로퍼티를 추가하고 DI 변수와 setter 메소드를 만들어야하는 단점이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 맵 프로퍼티 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL이 많이질수록 DAO에 DI용 프로퍼티를 추가하기 힘들어짐&lt;/li&gt;
&lt;li&gt;SQL을 하나의 컬레션으로 담아두는 방법을 이용해보자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class UserDaoJdbc implements UserDao {
    private Map&amp;lt;String, String&amp;gt; sqlMap;

    public void setSqlMap(Map&amp;lt;String, String&amp;gt; sqlMap) {
        this.sqlMap = sqlMap;
    }
}
// UserService
public void add(User user) {
    this.jdbcTemplate.update(
        this.sqlMap.get(&quot;add&quot;),
        // data
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;userDao&quot; class=&quot;springbook.user.dao.UserDaoJdbc&quot;&amp;gt;
    &amp;lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlMap&quot;&amp;gt;
        &amp;lt;map&amp;gt;
            &amp;lt;entry key=&quot;add&quot; value=&quot;nsert into users(id, name, password, email, level, login, recommend) values(?,?,?,?,?,?,?)&quot; /&amp;gt;
            &amp;lt;entry key=&quot;get&quot; value=&quot;select * from users where id = ?&quot; /&amp;gt;
        &amp;lt;/map&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Map은 하나 이상의 복잡한 정보를 담고 있어서, property의 value 애트리뷰트로만 정의할 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링이 제공하는 &amp;lt;map&amp;gt; 태그를 사용해서 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.1.2 SQL 제공 서비스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 만든 SQL 맵 프로퍼티 방식에서 여전히 문제점이 존재
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL과 DI 설정정보가 섞여있으면 보기에도 지저분하고 관리하기에도 좋지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAO의 일부인 SQL 문장을 Application 구성정보를 가진 설정정보와 함께 두는 것은 바람직하지 못함&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;싱글톤인 DAO의 인스턴스 변수에 접근해서 실시간으로 내용을 수정하기에 복잡하기도 하고, 맵 내용을 수정할 경우 동시성 문제를 일으킬 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DAO가 사용할 SQL을 제공해주는 기능을 독립시켜보자&lt;/li&gt;
&lt;li&gt;SQL 서비스 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL에 대한 키 값을 전달하면 그에 해당하는 SQL을 반환하도록 설계&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package springbook.user.sqlservice;

public interface SqlService {
    String getSql(String key) throws SqlRetrievalFailureException; // 런타임 예외로, 복구할 필요가 없으면 무시
}

// sql 조회 실패 예외
public class SqlRetrievalFailureException extends RuntimeException {
    public SqlRetrievalFailureException(String message) {
        super(message);
    }

    public SqlRetrievalFailureException(String message, Throwable cause) {
        super(message, cause); // 근본 원인이 되는 중첩 예외 저장
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public class UserDaoJdbc implements UserDao {
    private SqlService sqlService;

    public void setSqlService(SqlService sqlService) {
        this.sqlService = sqlService;
    }

    public User get(String id) {
        return this.jdbcTemplate.queryForObject(this.sqlService.getSql(&quot;userGet&quot;),
        new Object[] {id}, this.userMapper);
    }
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 설정을 사용하는 단순 SQL 서비스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map을 이용한 sqlService 구현체 구현 및 설정 수정&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class SimpleSqlService implements SqlService {
    private Map&amp;lt;String, String&amp;gt; sqlMap;

    public void setSqlMap(Map&amp;lt;String, String&amp;gt; sqlMap) {
        this.sqlMap = sqlMap;
    }

    public String getSql(String key) throws SqlRetrievalFailureException {
        Optional&amp;lt;String&amp;gt; sql = Optional.ofNullable(sqlMap.get(key));
        return sql.orElseThrow(() -&amp;gt; new SqlRetrievalFailureException(key + &quot;에 대한 SQL을 찾을 수 없습니다&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;userDao&quot; class=&quot;springboot.user.dao.UserDaoJdbc&quot;&amp;gt;
    &amp;lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlService&quot; ref=&quot;sqlService&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;
&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.simpleSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;sqlMap&quot;&amp;gt;
        &amp;lt;map&amp;gt;
            &amp;lt;entry key=&quot;add&quot; value=&quot;nsert into users(id, name, password, email, level, login, recommend) values(?,?,?,?,?,?,?)&quot; /&amp;gt;
            &amp;lt;entry key=&quot;get&quot; value=&quot;select * from users where id = ?&quot; /&amp;gt;
        &amp;lt;/map&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;앞선 설정과 큰 차이가 없어보이지만, UserDao를 포함한 모든 DAO는 이제 SQL을 어디에 저장해두고 가져오는지에 대해 전혀 신경쓸 필요가 없어짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시에 sqlService 빈에는 DAO에는 전혀 영향을 주지 않은 채, 다양한 방법으로 구현된 SqlService 타입 클래스를 적용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.2 인터페이스의 분리와 자기참조 빈&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlService 인터페이스 구현 방법에 대한 고민&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.2.1 XML 파일 매핑&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링의 XML 설정파일에서 &amp;lt;bean&amp;gt; 태그 안에 SQL 정보를 넣어놓고 활용하기보다, SQL을 저장해두는 전용 포맷을 가진 독립적인 파일을 이용하는 것이 더욱 바람직&lt;/li&gt;
&lt;li&gt;JAXB
&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;&lt;b&gt;JAVA 8까지 유지되었고, 9와 10에서 DEPRECATED, 11부터 더이상 지원 X&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;JAXB를 사용했던 이유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM과 같은 전통 XML API와 달리 XML 문서정보를 거의 동일한 구조의 오브젝트로 직접 매핑&lt;/li&gt;
&lt;li&gt;XML 문서의 구조를 정의한 스키마를 이용해서, 매핑할 오브젝트의 클래스까지 자동으로 만들어주는 컴파일러 제공&lt;/li&gt;
&lt;li&gt;스키마 컴파일러를 통해 자동생성된 오브젝트에는 매핑정보가 애노테이션으로 담겨있음&lt;/li&gt;
&lt;li&gt;JAXB API는 애노테이션에 담긴 정보를 이용해서 XML과 매핑된 오브젝트 트리 사이의 자동변환 작업을 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 맵을 위한 스키마 작성과 컴파일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키와 SQL 정보를 담은 &amp;lt;sql&amp;gt; , &amp;lt;sqlmap&amp;gt; 태그를 만들어보자&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;sqlmap&amp;gt;
    &amp;lt;sql key=&quot;userAdd&quot;&amp;gt;insert into users(...) ...&amp;lt;/sql&amp;gt;
    &amp;lt;sql key=&quot;userGet&quot;&amp;gt;select * from users ...&amp;lt;/sql&amp;gt;
&amp;lt;/sqlmap&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;schema xmlns=&quot;http://www.w3.org/2001/XMLSchema&quot;
        targetNamespace=&quot;http://www.epril.com/sqlmap&quot;
        xmlns:tns=&quot;http://www.epril.com/sqlmap&quot; elementFormDefault=&quot;qualified&quot;&amp;gt;

    &amp;lt;element name=&quot;sqlmap&quot;&amp;gt;
        &amp;lt;complexType&amp;gt;
            &amp;lt;sequence&amp;gt;
                &amp;lt;elemnet name=&quot;sql&quot; maxOccurs=&quot;unbounded&quot; type=&quot;tns:sqlType&quot; /&amp;gt;
                &amp;lt;!-- 필요한 개수만큼 &amp;lt;sql&amp;gt;을 포함할 수 있게하기 위한 maxOccurs=&quot;unbounded&quot; 설정 --&amp;gt;
            &amp;lt;/sequence&amp;gt;
        &amp;lt;/complexType&amp;gt;
    &amp;lt;/element&amp;gt;

    &amp;lt;!-- &amp;lt;sql&amp;gt;에 대한 정의 --&amp;gt;
    &amp;lt;complexType name=&quot;sqlType&quot;&amp;gt; 
        &amp;lt;simpleContent&amp;gt;
            &amp;lt;extension base=&quot;string&quot;&amp;gt;
                &amp;lt;attribute name=&quot;key&quot; use=&quot;required&quot; type=&quot;string&quot; /&amp;gt;
            &amp;lt;/extension&amp;gt;
        &amp;lt;/simpleContent&amp;gt;
    &amp;lt;/complexType&amp;gt;
&amp;lt;/schema&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;JAXB 컴파일러로 위의 스키마 파일을 컴파일해보기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;- `xjc -p springboo.user.sqlservice.jaxb sqlmap.xsd -d src`
  - 생성할 클래스 패키지, 변환한 스키마 파일, 생성된 파일이 저장될 위치 순서
  - 컴파일 이후 내부적으로 List를 통해 sql들을 담게되고, 변환 작업에서 참고할 정보를 애노테이션으로 담고있음
  - 또한, sql 내부에는 key와 value가 저장되어 required 설정이 되어있는 클래스 파일을 확인할 수 있음&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XML 문서를 읽어서 자바 오브젝트로 변환하는 것
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JAXB AIP의 사용법을 익히기 위한 학습 테스트&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class JaxbTest {
    @Test
    public void readSqlmap() throws JAXBException, IOException {
        String contextPath = Sqlmap.class.getPackage().getName();
        JAXBContext context = JAXBContext.newInstance(contextPath);

        Unmarshaller unmarshaller = context.createUnmarshaller();

        Sqlmap sqlmap = (Sqlmap) unmarshaller.unmarshal(getClass().getResourceAsStream(&quot;sqlmap.xml&quot;));
        List&amp;lt;SqlType&amp;gt; sqlList = sqlmap.getSql();

        assertThat(sqlList.size(), is(3));
        assertThat(sqlList.get(0).getKey(), is(&quot;add&quot;));
        assertThat(sqlList.get(0).getValue(), is(&quot;insert&quot;));
        assertThat(sqlList.get(1).getKey(), is(&quot;get&quot;));
        assertThat(sqlList.get(1).getValue(), is(&quot;select&quot;));
        assertThat(sqlList.get(2).getKey(), is(&quot;delete&quot;));
        assertThat(sqlList.get(2).getValue(), is(&quot;delete&quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.2.2 XML 파일을 이용하는 SQL 서비스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 맵 XML 파일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 &amp;lt;map&amp;gt;으로 만들어뒀던 SQL을 &amp;lt;sqlmap&amp;gt;, &amp;lt;sql&amp;gt; 태그를 사용하도록 sqlmap.xml로 생성하기&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;schema xmlns=&quot;http://www.epril.com/sqlmap&quot;
        xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
        xsi:schemaLocation=&quot;http://www.epril.com/sqlmap
                            http://www.epril.com/sqlmap/sqlmap.xsd&quot;&amp;gt;
    &amp;lt;sql key=&quot;userAdd&quot;&amp;gt;insert into users(...) ...&amp;lt;/sql&amp;gt;
    &amp;lt;sql key=&quot;userGet&quot;&amp;gt;select * from users ...&amp;lt;/sql&amp;gt;
&amp;lt;/schema&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XML SQL 서비스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 작업한 sqlmap.xml의 SQL을 가져와 DAO에 제공해주는 SqlService 인터페이스의 구현 클래스 구현하기
&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;b&gt;언제 JAXB를 사용해서 XML 문서를 가져올지에 대한 고민&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매번 XML 파일을 읽는 것은 비효율적, 특별한 이유가 없는 한 XML 파일은 한번만 읽도록 해야함&lt;/li&gt;
&lt;li&gt;XML 파일로 부터 읽은 내용을 어딘가에 저장해두고 DAO 요청이 올 때 마다 사용&lt;/li&gt;
&lt;li&gt;라이프사이클에 대한 이해가 부족하니, 먼저 생성자에 초기 작업을 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Sql 문장 조회 효율성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매번 검색을 위해 List 모두를 검사하는 방법은 비효율적&lt;/li&gt;
&lt;li&gt;Map 타입 오브젝트에 저장해두고 사용하도록 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성자에서 JAXB를 이용해 XML로 된 SQL 문서를 읽어들이고, 변환된 Sql 오브젝트를 맵으로 옮겨 저장해뒀다가, DAO의 요청에 따라 SQL을 찾아서 전달하는 방식으로 SqlService 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService {
    private Map&amp;lt;String, String&amp;gt; sqlMap;

    public XmlSqlService() {
        String contextPath = Sqlmap.class.getPackage().getName();
        try { 
            JAXBContext context = JAXBContext.newInstance(contextPath);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            InputStream is = UserDao.class.getResourceAsStream(&quot;sqlmap.xml&quot;); // UserDao와 같은 클래스패스
            Sqlmap unmarshalSqlmap = (Sqlmap) unmarshaller.unmarshal(is);

            sqlMap = unmarshalSqlmap.getSql().stream()
                .collect(Collectors.toMap(SqlType::getKey(), SqlType::getValue()));
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    public String getSql(String key) throws SqlRetrievalFailureException {
        Optional&amp;lt;String&amp;gt; sql = Optional.ofNullable(sqlMap.get(key));
        return sql.orElseThrow(() -&amp;gt; new SqlRetrievalFailureException(key + &quot;에 대한 SQL을 찾을 수 없습니다&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.XmlSqlService&quot;&amp;gt;
&amp;lt;/bean&amp;gt;&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;sqlService 설정 변경&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;h4 data-ke-size=&quot;size20&quot;&gt;7.2.3 빈의 초기화 작업&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XmlSqlService의 개선사항
&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;/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;코드의 로직과 여타 이유로 바뀔 가능성 있는 내용은 외부에서 DI로 설정해줄 수 있게 하는 것이 바람직&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void setSqlmapFile(String sqlmapFile) {
    this.sqlmapFile = sqlmapFile;    
}

public void loadSql() {
    String contextPath = Sqlmap.class.getPackage().getName();
    try {
        ...
        InputStream is = UserDao.class.getResourceAsStream(this.sqlmapFile);
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;초기화 메소드인 loadSql() 메소드는 언제 실행돼야하고, 어떻게 실행할까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XmlSqlService 오브젝트는 빈, 따라서 스프링이 제어권을 가지고 있음&lt;/li&gt;
&lt;li&gt;스프링은 빈 오브젝트를 생성하고 DI 작업을 수행해서 프로퍼티를 모두 주입한 뒤, 미리 지정한 초기화 메소드를 호출해주는 기능이 존재&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;&amp;lt;bean&amp;gt;태그를 이용해 하나씩 등록할 수도 있지만, &lt;b&gt;context 스키마의 annotation-config 태그&lt;/b&gt;로 편리하게 이용&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;beans ...
       xmlns:context=&quot;http://www.springframwork.org/schema/context&quot;
       xsi:schemaLocation=&quot;...
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd
                           &quot;&amp;gt;
    ... &amp;lt;!-- 트랜잭션 후처리기 등록 --&amp;gt;

    &amp;lt;context:annotation-config /&amp;gt; &amp;lt;!-- 코드의 애노테이션을 이용해서 부가적인 빈 설정 or 초기화 작업을 해주는 후처리기 등록 --&amp;gt;
&amp;lt;/beans&amp;gt;&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;code&gt;context:annotation-config&lt;/code&gt; 태그에 의해 등록되는 빈 후처리기는 빈 설정에 사용되는 애노테이션 제공하는데, 그 중 &lt;code&gt;@PostConstruct&lt;/code&gt; 애노테이션을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@PostConstruct&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JAVA 8까지 유지되었고, 9와 10에서 DEPRECATED, 11부터 더이상 지원 X&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;빈 오브젝트의 초기화 메소드를 지정하는데 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@PostConstruct&lt;/code&gt; 를 초기화 작업을 수행할 메소드에 부여해주면 스프링은 XmlSqlService 클래스로 등록된 빈의 오브젝트를 생성하고 DI 작업을 마친 후, &lt;b&gt;&lt;code&gt;@PostConstruct&lt;/code&gt;가 붙은 메소드를 자동으로 실행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService {
    ...
    @PostConstruct
    public void loadSql() { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.XmlSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;sqlmapFile&quot; value=&quot;sqlmap.xml&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&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;sqlmapFile 프로퍼티 value는 XML 파일의 클래스 패스로, UserDao 인터페이스의 패키지로부터 상대 위치를 지정 가능&lt;/li&gt;
&lt;li&gt;여기서는 UserDao와 같은 클래스패스에 있으므로 파일 이름만 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 컨테이너의 초기 작업 순서
&lt;ol 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;빈의 오브젝트를 생성&lt;/li&gt;
&lt;li&gt;프로퍼티에 의존 오브젝트 또는 값을 주입&lt;/li&gt;
&lt;li&gt;빈이나 태그로 등록된 후처리기를 동작시킨다.&lt;br /&gt;코드에 달린 애노테이션에 대한 부가작업 진행&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.2.4 변화를 위한 준비: 인터페이스 분리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 작성한 코드는 SQL을 가져오는 방법에 대해 특정 기술에 고정되어 있음
&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;XML 대신 다른 포맷 파일에서 SQL을 읽어오는 경우&lt;/li&gt;
&lt;li&gt;XML에서 SQL을 가져오는 방법은 변하지 않지만, 가져온 SQL 정보를 HashMap 타입 컬렉션이 아닌 다른 방식으로 저장해두고 이를 검색해서 가져오는 경우&lt;/li&gt;
&lt;li&gt;위의 두 경우 모두 코드를 고치거나 새로 만들어야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XmlSqlService가 변경되는 이유가 두 가지라면 단일 책임 원칙을 위반
&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;책임에 따른 인터페이스 정의
&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;SQL 정보를 외부의 리소스로부터 읽어오는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL이 담겨 있는 리소스가 어떤 것이든 상관없이 애플리케이션에서 활용 가능하도록 메모리에 읽어들이는 것은 하나의 책임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;읽어온 SQL을 보관해두고 있다가 필요할 때 제공해주는 것&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;서비스를 위해 한 번 가져온 SQL을 필요에 따라 수정할 수 있게 하는 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 운영 중, 서버를 재시작하거나 애플리케이션을 재설치하지 않고도 SQL을 긴급히 변경해야 하는 경우를 위한 경우&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DAO 관점에서는 SqlService라는 인터페이스를 구현한 오브젝트에만 의존&lt;/li&gt;
&lt;li&gt;SqlService의 구현 클래스가 변경 가능한 책임을 가진 SqlReader와 SqlRegistry 두 가지 타입의 오브젝트를 사용하도록 수정&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://i.ibb.co/m0sqWkg/image.png&quot; alt=&quot;&quot; /&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;SqlReader가 읽어오는 SQL 정보를 다시 SqlRegistry에 전달해서 등록하는데, 어떻게 전달할까??
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlService가 SqlReader에게 정보를 전달받은 뒤, SqlRegistry에 다시 전달해줄 필요가 없음&lt;/li&gt;
&lt;li&gt;정보를 전달하는 것이 전부라면, SqlService가 중간 과정에서 빠지는 방법도 생각 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlReader에게 SqlRegistry 전략을 제공해주어 SQL 정보를 SqlRegistry에 저장하는 것이 좋은 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SqlReader가 제공하는 메소드의 리턴 타입은 무엇으로 해야할까??
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 전달하던 Map 방식은 정보 전달만을 위해 일시적으로 Map 타입의 형식을 갖도록 만들어야 한다는 것이 불편&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SqlReader와 SqlRegistry는 각각 구현 방식을 독립적으로 유지하며 꼭 필요한 관계만 가지고 협력해서 일을 할 수 있는 구조로 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SqlReader가 사용할 SqlRegistry 오브젝트를 제공해주는 건 SqlService의 코드가 담당&lt;/li&gt;
&lt;li&gt;SqlRegistry가 일종의 콜백 오브젝트처럼 사용된다고 생각&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SqlRegistry 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface SqlRegistry {
    void registerSql(String key, String sql);

    String findSql(String key) throws SqlNotFoundException;
}&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;SQL을 등록하고 검색하는 두 가지 기능 정의&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SqlReader 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface SqlReader {
    void read(SqlRegistry sqlRegistry);
}&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;SqlReader는 SqlRegistry 오브젝트를 메소드 파라미터로 DI받아 읽어들인 SQL을 등록하는 데 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.2.5 자기참조 빈으로 시작하기&lt;/h4&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;SqlService의 구현 클래스는 SqlReader와 SqlRegistry 두 개의 프로퍼티를 DI 받는 구조로 구현&lt;/li&gt;
&lt;li&gt;모든 클래스가 인터페이스에 의존하고 있는데, 인터페이스에만 의존해야 스프링의 DI를 적용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DI를 적용하지 않아도, 자신이 사용하는 오브젝트 클래스가 어떤 것인지 알지 못하게 만드는 것이 좋음
&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;인터페이스 상속의 장점
&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;/li&gt;
&lt;/ul&gt;
&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;SqlReader와 SqlRegistry 두 개의 인터페이스 타입 오브젝트에 의존하는 구조로 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService {
    private SqlReader sqlReader;
    private SqlRegistry sqlRegistry;

    public void setSqlReader(SqlReader sqlReader) {
        this.sqlReader = sqlReader;
    }

    public void setSqlRegistry(SqlRegistry sqlRegistry) {
        this.sqlRegistry = sqlRegistry;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XmlSqlService 클래스가 SqlRegistry를 구현하도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService, SqlRegistry {
    private Map&amp;lt;String, String&amp;gt; sqlMap = new HashMap&amp;lt;&amp;gt;(); // sqlRegistry 구현의 일부로 외부에서 직접 접근 불가

    @Override
    public String findSql(String key) throws SqlNotFoundException {
        String sql = sqlMap.get(key);
        if (sql == null) {
            throw new SqlNotFoundException(&quot;Not Found Sql From &quot; + key);
        }
        return sql;
    }

    @Override
    public void registSql(String key, String sql) {
        sqlMap.put(key, sql);
        // HashMap 저장소를 사용하는 구체적인 구현 방법에서 독립되도록 인터페이스의 메소드로 접근하게 해줌
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;sqlMap은 SqlRegistry 구현의 일부이므로 SqlRegistry 구현 메소드가 아닌 메소드에서 직접 사용해서는 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;XmlSqlService 클래스가 SqlReader를 구현하도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService, SqlRegistry, SqlReader {
    ...
    private String sqlmapFile;
    public void setSqlmapFile(String sqlmapFile) { // sqlMapFile은 SqlReader 구현의 일부, SqlReader 구현 메소드를 통하지 않고는 접근 금지
        this.sqlmapFile = sqlmapFile;
    }

    @Override
    public void read(SqlRegistry sqlRegistry) { // loadSql()에 있던 코드를 SqlReader 메소드로
        String contextPath = Sqlmap.class.getPackage().getName();

        try {
            JAXBContext context = JAXBContext.newInstance(contextPath);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            InputStream is = UserDao.class.getResourceAsStream(sqlmapFile);
            sqlmap.getSql().forEach(sql -&amp;gt; sqlRegistry.registerSql(sql.getKey()), sql.getValue());
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }
}&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;어떻게 읽어오는지 SqlReader 메소드 뒤로 숨기고, 어떻게 저장해둘지 SqlRegistry 타입 오브젝트가 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@PostConstruct&lt;/code&gt;가 달린 빈 초기화 메소드와 SqlService 인터페이스에 선언된 getFinder()를 sqlReader와 sqlRegistry를 이용하도록 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class XmlSqlService implements SqlService, SqlRegistry, SqlReader {
    ...
    @PostConstruct
    public void loadSql() {
        this.sqlReader.read(this.sqlRegistry);
    }

    public String getSql(String key) throws SqlRetrievalFailureException {
        try {
            return this.sqlRegistry.findSql(key);
        } catch (SqlNotFoundException e) {
            throw new SqlRetrievalFailureException(e);
        }
    }
}&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;sqlReader와 sqlRegistry 두 전략을 이용하도록 재구성&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스는 하나뿐이고 빈도 하나만 등록할 것이지만, 세 개의 빈이 등록된 것처럼 SqlService 빈이 SqlRegistry와 SqlReader를 주입받도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.XmlSqlService&quot;&amp;gt;
    &amp;lt;!-- 프로퍼티는 수정자 메소드로 주입만 가능하면 자기 자신을 참조할 수 있다. --&amp;gt;      
    &amp;lt;property name=&quot;sqlReader&quot; ref=&quot;sqlService&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlRegistry&quot; ref=&quot;sqlService&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlmapFile&quot; ref=&quot;sqlmap.xml&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&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;위의 설정을 통해 sqlService를 구현한 메소드와 초기화 메소드는 외부에서 DI 된 오브젝트라고 생각하고 자신의 메소드에 접근&lt;/li&gt;
&lt;/ul&gt;
&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;b&gt;책임이 다르다면 클래스를 구분하고 각기 다른 오브젝트로 만드는 것이 자연스러움&lt;/b&gt;&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;h4 data-ke-size=&quot;size20&quot;&gt;7.2.6 디폴트 의존관계&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 인터페이스를 구현한 코드를 분리해두고 DI로 조합하기&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;자기참조 빈으로 만들었던 XmlSqlService 코드에서 의존 인터페이스와 구현 코드 제거&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class BaseSqlService implements SqlService {
    protected SqlReader sqlReader;
    protected SqlRegistry sqlRegistry;  // BaseSqlService는 상속을 통해 확장해서 사용하기에 적합, 서브 클래스에서 필요한 경우 접근할 수 있도록 protected로 선언

    public void setSqlReader(SqlReader sqlReader) { this.sqlReader = sqlReader; }
    public void setSqlRegistry(SqlRegistry sqlRegistry) { this.sqlRegistry = sqlRegistry; }

    @PostConstruct
    public void loadSql() {
        this.sqlReader.read(this.sqlRegistry);
    }

    public String getSql(String key) throws SqlRetrievalFailureException {
        try {
            return this.sqlRegistry.findSql(key);
        } catch (SqlNotFoundException e) {
            throw new SqlRetrievalFailureException(e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class HashMapSqlRegistry implements SqlRegistry {
    private Map&amp;lt;String, String&amp;gt; sqlMap = new HashMap&amp;lt;&amp;gt;();

    public String findSql(String key) throws SqlNotFoundException {
        String sql = sqlMap.get(key);

        if (sql == null) {
            throw new SqlNotFoundException(&quot;Not Found Sql From &quot; + key);
        }
        return sql;
    }

    public void registerSql(String key, String sql) {
        sqlMap.put(key, sql);
    }
}

public class JaxbXmlSqlReader implements SqlReader {
    private String sqlmapFile;

    public void setSqlmapFile(String sqlmapFile) {
        this.sqlmapFile = sqlmapFile;
    }

    public void read(SqlRegistry sqlRegistry) {
        ... // jaxb api로 sql을 읽어오는 코드
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.XmlSqlService&quot;&amp;gt;
    &amp;lt;property name=&quot;sqlReader&quot; ref=&quot;sqlReader&quot; /&amp;gt;
    &amp;lt;property name=&quot;sqlRegistry&quot; ref=&quot;sqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;sqlReader&quot; class=&quot;springbook.user.sqlservice.JaxbXmlSqlReader&quot;&amp;gt;
    &amp;lt;property name=&quot;sqlmapFile&quot; value=&quot;sqlmap.xml&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;sqlRegistry&quot; class=&quot;springbook.user.sqlservice.HashMapSqlRegistry&quot;&amp;gt;
&amp;lt;/bean&amp;gt;&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;/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;특정 의존 오브젝트가 대부분의 환경에서 거의 default라고 해도 좋을 만큼 기본적으로 사용될 가능성이 있다면, default 의존관계를 갖는 빈을 만드는 것을 고려
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디폴트 의존관계 = 외부에서 DI 받지 않는 경우, 기본적으로 자동 적용되는 의존관계&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public classDefaultSqlService extends BaseSqlService {
    public DefaultSqlService() {
        setSqlReader(new JaxbXmlSqlReader());
        setSqlRegistry(new HashMapSqlRegistry());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.DefaultSqlService&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;위 처럼 설정해도 테스트에는 실패&lt;/li&gt;
&lt;li&gt;DefaultSqlService 내부에서 생성하는 JaxbXmlSqlReader의 sqlmapFile 프로퍼티가 비어있기 때문&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;빈으로 등록되는 것은 DefaultSqlService 뿐&lt;/li&gt;
&lt;/ul&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;sqlmapFile을 DefaultSqlService의 프로퍼티로 지정하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JaxbXmlSqlReader는 디폴트 의존 오브젝트에 불과하기 때문에 부적절&lt;/li&gt;
&lt;li&gt;명시적인 설정이 없는 경우에 기본적으로 사용하겠다는 의미인데, 반드시 필요하지도 않은 sqlmapFile을 프로퍼티로 등록해두는 것은 바람직하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;sqlmapFile을 JaxbXmlSqlReader에 의해 기본적으로 사용될 만한 default 값을 가지도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 파일 이름을 매번 바꿀 필요가 없고, 관례적으로 사용할 만한 이름으로 default 값을 정해준다면, default sqlmapFile 이름도 갖게 있게 되므로 별다른 설정 없이 그대로 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class JaxbXmlSqlReader implements SqlReader {
    private static final String DEFAULT_SQLMAP_FILE = &quot;sqlmap.xml&quot;;

    private String sqlmapFile = DEFAULT_SQLMAP_FILE;

    public void setSqlmapFile(String sqlmapFile) {
        this.sqlmapFile = sqlmapFile;
    }
}&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;DI를 사용한다고 해서 항상 모든 프로퍼티 값을 설정에 넣고, 모든 의존 오브젝트를 빈으로 일일이 지정할 필요가 없음&lt;/li&gt;
&lt;li&gt;DefaultSqlService처럼 자주 사용되는 의존 오브젝트는 미리 지정한 디폴트 의존 오브젝트를 설정 없이도 사용할 수 있게 만드는 것은 좋은 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DefaultSqlService는 BaseSqlService를 상속
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DefaultSqlService는 BaseSqlService의 sqlReader와 sqlRegistry 프로퍼티를 그대로 갖고 있고, 원한다면 일부 or 모든 프로퍼티 변경 가능&lt;/li&gt;
&lt;li&gt;디폴트 의존 오브젝트 대신 사용하고 싶은 구현 오브젝트가 있다면 설정에 프로퍼티를 추가하면 되고, 설정하지 않은 부분은 디폴트가 사용됨&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;sqlService&quot; class=&quot;springbook.user.sqlservice.DefaultSqlService&quot;&amp;gt;
    &amp;lt;propery name=&quot;sqlRegistry&quot; ref=&quot;ultraSuperFastSqlRegistry&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Default 의존 오브젝트의 단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정을 통해 다른 구현 오브젝트를 사용하게 해도 DefaultSqlService는 생성자에서 일단 디폴트 의존 오브젝트를 다 만듦&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;ex) &lt;code&gt;@PostContructor&lt;/code&gt; 초기화 메소드에서 프로퍼티가 설정됐는지 확인하고, 없다면 default 오브젝트를 만드는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>디폴트 의존 오브젝트</category>
      <category>자기참조 빈</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/375</guid>
      <comments>https://zin0-0.tistory.com/375#entry375comment</comments>
      <pubDate>Fri, 6 Aug 2021 14:09:20 +0900</pubDate>
    </item>
    <item>
      <title>외부 통신 - (RestTemplate, AsyncRestTemplate, WebClient, HttpURLConnection, HttpClient ) 간략 정리</title>
      <link>https://zin0-0.tistory.com/374</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;RestTemplate&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 3.0 부터 지원&lt;/li&gt;
&lt;li&gt;Bolierplate code를 줄여줌&lt;/li&gt;
&lt;li&gt;RestAPI를 사용해야하는 경우 적합&lt;/li&gt;
&lt;li&gt;Multi-Thread &amp;amp; Blocking 방식&lt;/li&gt;
&lt;li&gt;JDK HttpURLConnection, Apache Http Components 등과 같이 기본 HTTP 클라이언트 라이브러리를 통해 템플릿 메서드 API를 제공하는 HTTP 요청을 수행하는 동기식 client&lt;/li&gt;
&lt;li&gt;RestTemplate은 HTTP 메소드에 대한 시나리오를 제공하고, exchange와 excute 메소드를 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring에서 5.0부터는 org.springframework.web.reactive.client를 사용하는 것을 권유
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최신 API와 동기, 비동기, 스트리밍 시나리오를 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AsyncRestTemplate&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 4.0 부터 지원 ~&amp;gt; 현재 deprecated&lt;/li&gt;
&lt;li&gt;비동기 클라이언트측 HTTP 액세스를 위한 스프링의 중앙 class&lt;/li&gt;
&lt;li&gt;RestTemplate과 유사하지만, 구체적인 결과는 ListenableFuture 로 래핑되어 반환&lt;/li&gt;
&lt;li&gt;getRestOperations() 메소드를 통해 동기식 RestTemplate을 노출시키고, Error Handler와 Message Converter를 RestTemplate과 공유&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebClient&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 5.0 부터 지원&lt;/li&gt;
&lt;li&gt;HTTP 요청을 수행하기 위한 Non-blocking 방식의 reactive 클라이언트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Non-blocking
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection은 유지되지만, 응답이 오기 전까지 다른 작업을 수행하다가 응답이 오면 callback을 수행하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Single-Thread &amp;amp; Non-blocking&lt;/li&gt;
&lt;li&gt;각 요청이 event loop에 Job으로 등록되고, event loop가 각 Job을 제공자에게 요청한 후, 결과를 기다리지 않고 다른 Job을 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;callback이 오면 결과를 요청자에게 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HttpURLConnection&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP별 기능을 지원하는 URLConnection&lt;/li&gt;
&lt;li&gt;각 HttpURLConnection 인스턴스는 단일 요청을 만드는데 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 서버에 대한 기본 네트워크 connection은 다른 인스턴스과 공유 가능&lt;/li&gt;
&lt;li&gt;요청 이후 HttpURLConnection의 InputStream이나 OutputStream의 close() 메소드를 호출하면, 인스턴스와 연결된 네트워크 리소스를 확보할 수 있지만, 영속적으로 공유된 connection에는 영향을 미치지 않음&lt;/li&gt;
&lt;li&gt;disconnect()를 호출하면, 영속적으로 공유된 connection이 놀고있는 소켓을 close시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;response 결과를 직접 stream으로 처리해야하는 등 개발 생산성이 다소 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HttpClient&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 11 이전에는 아파치에서 제공하는 라이브러리 사용&lt;/li&gt;
&lt;li&gt;Java 1부터 java.net 패키지에 http 관련 라이브러리가 추가됨&lt;/li&gt;
&lt;li&gt;Builder를 통해 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Builder는 각 client 별 상태(프로토콜 버전(HTTP/1.1 or HTTP/2), ridirect, 프록시, 인증자 등)를 구성할 수 있음&lt;/li&gt;
&lt;li&gt;build 이후에는 HttpClient는 immutable로 여러 요청을 보내는 데 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각 HttpRequest에 대해서 BodyHandler를 제공해야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BodyHandler&amp;lt;T&amp;gt;는 response body를 어떻게 핸들링할지 결정 ~&amp;gt; 응답 받을 형태를 관리&lt;/li&gt;
&lt;li&gt;HttpResponse를 받아 헤더, response code, body를 사용할 수 있음&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;send(HttpRequest, BodyHandler)&lt;/li&gt;
&lt;li&gt;sendAsync(HttpRequest, Bodyhander)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Synchronous Example&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-java&quot;&gt;HttpClient client = HttpClient.newBuilder()
    .version(Version.HTTP_1_1)
    .followRedirects(Redirect.NORMAL)
    .connectTimeout(Duration.ofSeconds(20))
    .proxy(ProxySelector.of(new InetSocketAddress(&quot;proxy.example.com&quot;, 80)))
    .authenticator(Authenticator.getDefault())
    .build();
HttpResponse&amp;lt;String&amp;gt; response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());  &lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Asynchronous Example&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(&quot;https://foo.com/&quot;))
    .timeout(Duration.ofMinutes(2))
    .header(&quot;Content-Type&quot;, &quot;application/json&quot;)
    .POST(BodyPublishers.ofFile(Paths.get(&quot;file.json&quot;)))
    .build();
client.sendAsync(request, BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);  &lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/AsyncRestTemplate.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/AsyncRestTemplate.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happycloud-lee.tistory.com/220&quot;&gt;https://happycloud-lee.tistory.com/220&lt;/a&gt; &amp;lt;- Spring WebClient에 대한 상세한 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://wonwoo.ml/index.php/post/2364&quot;&gt;http://wonwoo.ml/index.php/post/2364&lt;/a&gt; &amp;lt;- Spring WebClient 예제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://itforusblog.wordpress.com/2020/05/13/resttemplate-or-httpurlconnection/&quot;&gt;https://itforusblog.wordpress.com/2020/05/13/resttemplate-or-httpurlconnection/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html&quot;&gt;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://digitalbourgeois.tistory.com/56&quot;&gt;https://digitalbourgeois.tistory.com/56&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html&quot;&gt;https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lts0606.tistory.com/455&quot;&gt;https://lts0606.tistory.com/455&lt;/a&gt; &amp;lt;- HttpClient 기본 CRUD 및 multipart예제&lt;/p&gt;</description>
      <category>Java &amp;amp; Spring/기타</category>
      <category>AsyncRestTemplate</category>
      <category>HttpClient</category>
      <category>HttpUrlConnection</category>
      <category>resttemplate</category>
      <category>WebClient</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/374</guid>
      <comments>https://zin0-0.tistory.com/374#entry374comment</comments>
      <pubDate>Thu, 29 Jul 2021 17:11:53 +0900</pubDate>
    </item>
    <item>
      <title>6장) 6.6 트랜잭션 속성</title>
      <link>https://zin0-0.tistory.com/373</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6장 AOP&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.6 트랜잭션 속성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 학습했던 PlatformTransactionManager로 대표되는 스프링의 트랜잭션 추상화 적용 중, 트랜잭션 매니저에서 트랜잭션을 가져올 때 사용한 DefaultTransactionDefinition 오브젝트의 용도에 대해 알아보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6.6.1 트랜잭션 정의&lt;/h4&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;commit()&lt;/li&gt;
&lt;li&gt;rollback()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 밖에도 트랜잭션 동작방식을 제어할 수 있는 조건이 존재&lt;/li&gt;
&lt;li&gt;DefaultTransactionDefinition이 구현하고 있는 TransactionDefinition 인터페이스는 트랜잭션 동작 방식에 영향을 줄 수 있는 네 가지 속성을 정의하고 있음
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 진행 중인 트랜잭션이 있을 때 or 없을 때 어떻게 동작할 것인가를 결정하는 방식&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PROPRAGATION_REQUIRED&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;진행 중인 트랜잭션이 없으면 새로 시작하고, 이미 시작된 트랜잭션이 있으면 해당 트랜잭션에 참여&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DefaultTransactionDefinition&lt;/code&gt;의 트랜잭션 전파 속성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PROPRAGATION_REQUIRES_NEW&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;독자적으로 동작해서, 독립적인 트랜잭션이 보장돼야하는 코드에 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PROPRAGATION_NOT_SUPPORTED&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;진행 중인 트랜잭션이 있어도 무시
&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;포인트컷을 잘 만들어서 특정 메소드에 AOP 적용 대상이 되지 않게 하는 방법도 있지만, 상당히 복잡해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 매니저를 통해 트랜잭션을 시작하려고 할 때, getTransaction() 메소드를 사용하는 이유가 트랜잭션 전파 속성이 있기 때문
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getTransaction() 메소드가 항상 트랜잭션을 새로 시작하는 것이 아닌, 전파 속성에 따라 결정&lt;/li&gt;
&lt;/ul&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;모든 DB 트랜잭션은 격리수준을 가지고 있음&lt;/li&gt;
&lt;li&gt;DefaultTransactionDefinition에 설정된 격리수준은 ISOLATION_DEFAULT
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataSource의 Default 격리수준을 따름&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;DefaultTransactionDefinition의 기본 설정은 제한시간이 없음&lt;/li&gt;
&lt;li&gt;트랜잭션을 직접 시작할 수 있는 PROPRAGATION_REQUIRED, PROPRAGATION_REQUIRES_NEW와 함께 사용해야 유의미&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;read only로 설정
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TransactionDefinition 타입의 오브젝트를 사용하면, 네 가지 속성을 이용해 트랜잭션의 동작방식을 제어할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransactionDefinition 오브젝트를 생성하고 사용하는 코드는 트랜잭션 경계설정 기능을 가진 TransactionAdvice
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, 트랜잭션 정의를 바꾸고 싶다면 DefaultTransactionDefinition을 사용하는 대신, 외부에서 정의된 TransactionDefinition 오브젝트를 DI 받아서 사용하도록 설정&lt;/li&gt;
&lt;li&gt;TransactionDefinition 타입의 빈을 프로퍼티를 통해 원하는 속성을 지정할 수 있지만, TrasactionAdvice를 사용하는 모든 트랜잭션의 속성이 한꺼번에 바뀐다는 문제가 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6.6.2 트랜잭션 인터셉터와 트랜잭션 속성&lt;/h4&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TransactionInterceptor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 만들었던 TransactionAdvice를 다시 설계할 필요 없이, 스프링에서 트랜잭션 경계설정 어드바이스로 제공하는 TransactionInterceptor 사용&lt;/li&gt;
&lt;li&gt;트랜잭션 정의를 메소드 이름 패턴을 이용해서 다르게 지정&lt;/li&gt;
&lt;li&gt;TransactionInterceptor는 PlatformTransactionManager와 Properties 타입의 두 가지 프로퍼티를 가지고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Properties 타입은 transactionAttributes로, 트랜잭션 속성을 정의한 프로퍼티&lt;/li&gt;
&lt;li&gt;트랜잭션 속성은 TransactionDefinition의 네 가지 기본 항목에 rollback() 메소드를 하나 더 가지고 있는 TranscationAttribute 인터페이스로 정의됨&lt;/li&gt;
&lt;li&gt;rollback() 메소드는 어떤 예외가 발생하면 롤백을 할지 결정하는 메소드&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// 기존 TransactionAdvice 설정 코드
public Object invoke(MethodInvocation invocation) throws Throwable {
    TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); // 트랜잭션 정의를 통한 네가지 조건
    try {
        // ...
    } catch (RuntimeException e) { // 롤백 대상인 예외 종류
        this.transactionManager.rollback(status);
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 조건과 롤백 항목을 결합해서 트랜잭션 부가기능의 행동을 결정하는 TransactionAttribute 속성이 됨&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;비즈니스 로직상 예외의 Checked Exception을 던지는 경우에는 DB 트랜잭션은 커밋해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TransactionInterceptor가 제공하는 예외 처리 방식 두 가지
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransactionAttribute의 rollbackOn()이라는 속성은 기본 원칙과 다른 예외처리가 가능하도록 해줌&lt;/li&gt;
&lt;li&gt;TransactionInterceptor는 TransactionAttribute를 Properties라는 일종의 맵 타입 오브젝트로 전달받음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션을 사용하는 이유 ~&amp;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Properties 타입의 transactionAttributes 프로퍼티는 메소드 패턴과 트랜잭션 속성을 키와 값으로 갖는 컬렉션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN, -Exception1, +Exception2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;트랜잭션 속성은 위와 같은 문자열로 정의&lt;/li&gt;
&lt;li&gt;트랜잭션 전파 항목인 &lt;code&gt;PROPAGATION_NAME&lt;/code&gt;만 필수, 나머지는 생략 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생략 시, DefaultTransactionDefinition에 설정된 default 속성이 부여&lt;/li&gt;
&lt;li&gt;+-로 시작하는 Exception은 기본 원칙을 따르지 않는 예외를 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;beans xmlns=&quot;http://www.springfamework.org/schema/beans&quot;
       ...
       xmlns:tx=&quot;http://www.springframework.org/schema/tx&quot;
       xsi:schemaLocation=&quot;http://springframework.org/schema/beans
                           http://springframework.org/schema/tx
                           http://springframework.org/schema/tx/spring-tx-2.5.xsd&quot;
       ...

    &amp;lt;tx:advice id=&quot;transcationAdvice&quot; transaction-manager=&quot;transactionManager&quot;&amp;gt;
        &amp;lt;tx:attributes&amp;gt;
            &amp;lt;tx:method name=&quot;get*&quot; propagation=&quot;REQUIRED&quot; read-only=&quot;true&quot; timeout=&quot;30&quot; /&amp;gt;
            &amp;lt;tx:method name=&quot;upgrade*&quot; propagation=&quot;REQUIRED_NEW&quot; isolation=&quot;SERIALIZABLE&quot; /&amp;gt;
            &amp;lt;tx:method name=&quot;*&quot; propagation=&quot;REQUIRED&quot;/&amp;gt; &amp;lt;!-- default 값이 스키마에 정의되어 있어서, propagation이 REQUIRED라면 생략 가능 --&amp;gt;
        &amp;lt;/tx:attributes&amp;gt;
    &amp;lt;/tx:advice&amp;gt;
&amp;lt;/beans&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6.6.3 포인트컷과 트랜잭션 속성의 전용 전략&lt;/h4&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;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;UserService로 예를 들면, add() 메소드도 트랜잭션 적용 대상
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 정보를 DB에 추가하는 것 외에도 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;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;ex) &lt;code&gt;execution(**..*ServiceImpl.*(..))&lt;/code&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;/li&gt;
&lt;li&gt;ex) &lt;code&gt;execution(**..*Service.*(..))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메소드 시그니처를 사용한 execution() 방식의 포인트컷 표현식 대신 스프링의 bean() 표현식을 사용하는 방법도 존재
&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;/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;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;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 속성으로 설정했다가, 개발이 진행됨에 따라 단계적으로 속성을 추가하면서 개발
&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;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;tx:advice id=&quot;transactionAdvice&quot;&amp;gt;
    &amp;lt;tx:attributes&amp;gt;
        &amp;lt;tx:method name=&quot;get*&quot; read-only=&quot;true&quot; /&amp;gt;
        &amp;lt;tx:method name=&quot;*&quot;/&amp;gt;
    &amp;lt;/tx:attributes&amp;gt;
&amp;lt;/tx:advice&amp;gt;&lt;/code&gt;&lt;/pre&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;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;aop:config&amp;gt;
    &amp;lt;aop:advisor advice-ref=&quot;transactionAdvice&quot; pointcut=&quot;bean(*Service)&quot; /&amp;gt;
    &amp;lt;aop:advisor advice-ref=&quot;batchAdvice&quot; pointcut=&quot;execution(a.b.*BatchJob.*.(..))&quot; /&amp;gt;
&amp;lt;/aop:config&amp;gt;

&amp;lt;tx:advice id=&quot;transactionAdvice&quot;&amp;gt;
    &amp;lt;tx:attributes&amp;gt;...&amp;lt;/tx:attributes&amp;gt;
&amp;lt;/tx:advice&amp;gt;
&amp;lt;tx:advice id=&quot;batchAdvice&quot;&amp;gt;
    &amp;lt;tx:attributes&amp;gt;...&amp;lt;/tx:attributes&amp;gt;
&amp;lt;/tx:advice&amp;gt;&lt;/code&gt;&lt;/pre&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;li&gt;&lt;b&gt;프록시 AOP는 같은 타깃 오브젝트 내의 메소드를 호출할 때는 적용되지 않는다&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 방식의 AOP에서는 프록시를 통한 부가기능 적용은 클라이언트로부터 호출이 일어날 때만 가능
&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 API를 이용해 프록시 오브젝트에 대한 레퍼런스를 가져온 뒤, 같은 오브젝트의 메소드 호출도 프록시를 이용하도록 강제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순수한 비즈니스 로직에 스프링 API와 프록시 호출 코드가 공존하기 때문에 바람직하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AspectJ와 같은 타깃의 바이트코드를 직접 조작하는 방식의 AOP 적용
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.6.4 트랜잭션 속성 적용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 속성과 전략을 UserService에 적용하기&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;비즈니스 로직을 담은 서비스 계층에 트랜잭션 경계를 부여하는 것이 가장 적절&lt;/li&gt;
&lt;li&gt;테스트와 같은 특별한 경우가 아닌 경우, 다른 계층에서 DAO에 직접 접근하는 것은 차단하는 것이 바람직&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;DAO가 제공하는 주요 기능을 서비스 계층에 위임 메소드를 만들어야함&lt;/li&gt;
&lt;li&gt;DAO에 접근할 때는서비스 계층을 거치도록 설정&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;upgradeLevels()에만 트랜잭션이 적용되게 했던 기존 포인트컷 표현식을 모든 비즈니스 로직 서비스 빈에 적용되도록 수정&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;aop:config&amp;gt;
    &amp;lt;aop:advisor advice-ref=&quot;transactionAdvice&quot; pointcut=&quot;bean(*Service)&quot; /&amp;gt;
&amp;lt;/aop:config&amp;gt;&lt;/code&gt;&lt;/pre&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;위에서 설정한 &amp;lt;tx:attributes&amp;gt;로 지정한 트랜잭션 속성을 보면, get으로 시작하는 메소드에 read-only 옵션을 true로 설정했는데, 쓰기 작업이 허용되지 않는지 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;static class TestUserService extends UserServiceImpl {
    ...
    public List&amp;lt;User&amp;gt; getAll() {
        super.getAll().forEach(user -&amp;gt; super.update(user));
        return null;
    }
}&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;TransactionDataAccessResourceException 예외가 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링의 DataAccessResourceException의 한 종류로 일시적인 예외상황을 만났을 때 발생하는 예외
&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;update()에 의해 일어나는 DB 쓰기 작업은 원래 정상적으로 처리돼야하지만, 일시적인 제약조건 때문에 예외가 발생&lt;/li&gt;
&lt;li&gt;get메소드에 읽기전용 트랜잭션이 걸려있지 않다면 성공할 것이기 때문&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&lt;b&gt;READ UNCOMMITTED&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 트랜잭션의 변경내용이 COMMIT이나 ROLLBACK과 상관없이 다른 트랜잭션에서 보여진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DIRTY READ PROBLEM&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 트랜잭션이 특정 데이터 값을 변경하고 커밋하지 않았을 때, 다른 트랜잭션이 해당 데이터를 Read하는 경우, 뒤늦게 참여한 트랜잭션이 READ한 데이터 값은 앞선 트랜잭션의 변경 이후의 값&lt;/li&gt;
&lt;li&gt;하지만, commit을 하지 않는 경우, 두 트랜잭션의 데이터는 꼬이게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;READ COMMITTED&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Oracle의 default 격리수준&lt;/li&gt;
&lt;li&gt;어떤 트랜잭션의 변경 내용이 COMMIT 되어야만 다른 트랜잭션에서 조회할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NON-REPETABLE READ&lt;/b&gt; 부정합 문제 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 같은 값을 반환하지 않는다. (commit 전과 후에 read하면 값이 다름)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;REPEATABLE READ&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL의 default 격리수준&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;read에 부정합이 없음&lt;/li&gt;
&lt;li&gt;하지만 update 부정합과 &lt;b&gt;Phantom READ&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;부캠 루카스 수업 자료를 참고하자&lt;/li&gt;
&lt;li&gt;하지만, 극히 드물다 라고 하셨음 (mysql에서는 확인이 불가)&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;SERIALIZEABLE&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;읽기 작업에서도 공유 잠금을 하는데, 성능 저하가 발생
&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;30초 ? 1분? 정도 지나면 알아서 취소됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;읽기는 모든 트랜잭션 다 되지만 2번째로 시작한 트랜잭션부터는 수정이 불가능하다.
&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;필요한 상황이 거의 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 하기 위해서는 서비스 계층과 DAO를 분리해야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아키텍처를 단순하게 가져가면 서비스 계층과 DAO가 통합될 수 있지만 순수한 비즈니스 로직을 테스트할 수 없어진다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>토비 스프링</category>
      <category>트랜잭션 속성</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/373</guid>
      <comments>https://zin0-0.tistory.com/373#entry373comment</comments>
      <pubDate>Mon, 26 Jul 2021 14:27:54 +0900</pubDate>
    </item>
    <item>
      <title>6장) 6.5 스프링 AOP</title>
      <link>https://zin0-0.tistory.com/372</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6장 AOP&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.5.1 자동 프록시 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타깃 오브젝트마다 비슷한 내용의 ProxyFactoryBean 설정 정보 추가 부분이 남은 해결 과제&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;JDBC API를 사용하는 DAO 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전략 패턴과 DI를 적용해서 템플릿과 콜백, 클라이언트로 나누어 해결&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;다이내믹 프록시와 다이내믹 프록시 생성 팩토리 DI를 사용해서 런타임 코드 자동생성 기법으로 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;반복적인 ProxyFactoryBean 설정
&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;빈 후처리기를 이용한 자동 프록시 생성기
&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;BeanPostProcessor 인터페이스를 구현해서 만드는 빈 후처리기 존재&lt;/li&gt;
&lt;li&gt;DefaultAdvisorAutoProxyCreator 빈 후처리기 이용
&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;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;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;빈 오브젝트의 일부를 프록시로 포장하고 프록시를 빈으로 등록 ~&amp;gt; 자동 프록시 생성 빈 후처리기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DefaultAdvisorAutoProxyCreator 빈 후처리기 등록&lt;br /&gt;~&amp;gt; 빈 오브젝트를 만들 때마다 후처리기에 빈을 보냄&lt;br /&gt;~&amp;gt; 빈으로 등록된 모든 어드바이저 내의 포인트컷으로 프록시 적용 대상인지 확인&lt;br /&gt;~&amp;gt; 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 어드바이저를 연결&lt;br /&gt;~&amp;gt; 프록시가 생성되면 프록시 오브젝트를 컨테이너에 반환&lt;br /&gt;~&amp;gt; 컨테이너는 이 프록시 오브젝트를 빈으로 등록하고 사용&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public interface Pointcut {
    ClassFilter getClassFilter(); // 프록시 적용할 클래스인지 확인
    MethodMatcher getMethodMatcher(); // 어드바이스를 적용할 메소드인지 확인
}&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;프록시를 적용할 클래스인지 판단 ~&amp;gt; 어드바이스를 적용할 메소드인지 확인&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean에서는 클래스 레벨 필터가 필요 없었지만, 모든 빈에 대해 프록시 자동 적용 대상을 선별하는 빈 후처리기 &lt;b&gt;DefaultAdvisorAutoProxyCreator는 클래스와 메소드 선정 알고리즘 모두 가지고 있는 포인트컷이 필요&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;/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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void classNamePointcutAdvisor() {
    NameMatcherPointcut classMethodPointcut = new NameMatcherMethodPointcut() {
        public ClassFilter getClassFilter() {
            return new ClassFilter() {
                public boolean matches(Class&amp;lt;?&amp;gt; clazz) {
                    return clazz.getSimpleName().startsWith(&quot;HelloT&quot;); // HelloT로 시작하는 클래스를 선정
                }
            };
        }
    };
    classMethodPointcut.setMappedName(&quot;sayH*&quot;); // sayH로 시작하는 메소드 선정

    // Test
    checkAdviced(new HelloTarget(), classMethodPointcut, true);
    checkAdviced(new HelloWorld(), classMethodPointcut, false;)
}

private void checkAdviced(Object target, Pointcut pointcut, boolean adviced) {
    ProxyFactoryBean pfBean = new ProxyFactoryBean();
    pfBean.setTarget(target);
    pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
    Hello proxiedHello = (Hello) pfBean.getObject();

    if (adviced) {
        assetThat(proxiedHello.sayHello(&quot;Toby&quot;), is(&quot;HELLO TOBY&quot;));
        assetThat(proxiedHello.sayHi(&quot;Toby&quot;), is(&quot;HI TOBY&quot;));
    } else {
        assetThat(proxiedHello.sayHello(&quot;Toby&quot;), is(&quot;Hello Toby&quot;));
        assetThat(proxiedHello.sayHi(&quot;Toby&quot;), is(&quot;Hi Toby&quot;));
    }
}&lt;/code&gt;&lt;/pre&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;b&gt;어차피 어떤 메소드에도 부가기능이 적용되지 않는데, 굳이 프록시를 둘 이유가 없음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.5.2 DefaultAdvisorAutoProxyCreator의 적용&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;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
    public void setMappedClassName(String mappedClassName) {
        this.setClassFilter(new SimpleClassFilter(mappedClassName));
    }

    static class SimpleClassFilter implements ClassFilter {
        String mappedName;

        private SimpleClassFilter(String mappedName) {
            this.mappedName = mappedName;
        }

        @Override
        public boolean matches(Class&amp;lt;?&amp;gt; clazz) {
            return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName()); // 와일드카드 문자열 비교를 지원
        }
    }
}&lt;/code&gt;&lt;/pre&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;다른 빈에서 참조되거나 코드에서 빈 이름으로 조회될 필요가 없기 때문에, id 애트리뷰트는 생략하고 class만 입력하여 사용 가능&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;프로퍼티에 mapping될 클래스 이름과 메소드 이름의 패턴을 설정&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;li&gt;DefaultAdvisorAutoProxyCreator에 의해 자동수집되고, 프록시 대상 선정 과정에 참여하며, 자동 생성된 프록시에 다이내믹하게 DI돼서 동작하는 어드바이저가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean 제거와 서비스 빈의 원상복구
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간접적으로 사용되던 userServiceImpl 빈의 아이디를 userService로 재설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 이상 명시적인 프록시 팩토리 빈을 등록하지 않기 때문 ~&amp;gt; ProxyFactoryBean 타입 빈 삭제 가능&lt;/li&gt;
&lt;/ul&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;강제 예외 발생용 TestUserService 클래스를 빈으로 등록하기
&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;TestUserService가 UserServiceTest 내부에 정의된 스태틱 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스태틱 멤버 클래스를 설정에 등록할 때는, &lt;code&gt;$&lt;/code&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;클래스 이름을 TestUserServiceImpl로 수정&lt;/li&gt;
&lt;li&gt;users 리스트에서 예외를 발생시킬 기준 id를 고정 상수값으로 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;userService를 parent로 등록하고, @Autowired를 통해 testUserService를 DI 받도록 수정&lt;/li&gt;
&lt;/ul&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;트랜잭션이 커밋되는 경우 적용 여부를 확인하기 힘듦 ~&amp;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;/li&gt;
&lt;li&gt;포인트컷 빈의 클래스 이름 패턴을 변경해서 testUserService 빈에 트랜잭션이 적용되지 않게해서 확인&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;code&gt;getBean(&quot;userService&quot;)&lt;/code&gt;로 가져온 오브젝트의 타입은 TestUserService 타입이 아니라 JDK의 Proxy 타입&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Test
public void advisorAutoProxyCreator() {
    assertThat(testUserService, is(Proxy.class));
}&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;6.5.3 포인트컷 표현식을 이용한 포인트컷&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링의 리플렉션 API 사용
&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;/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;/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;li&gt;AspectJExpressionPointcut 클래스 사용
&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;AspectJ의 일부 문법을 확장해서 사용해서, AspectJ 포인트컷 표현식이라고도 함&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;pre class=&quot;java&quot;&gt;&lt;code&gt;package springbook.leaningtest.spring.pointcut;

public class Target implements TargetInterface {
    public void hello() {}
    public void hello(String a) {}
    public int minus(int a, int b) throws RuntimeException {return 0;}
    public int plus(int a, int b) {return 0;} // 이상 4개는 override한 메소드
    public void method() {} // 클래스 내부에서 자체 정의한 메소드
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&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;&lt;code&gt;execution()&lt;/code&gt;이 대표적으로 사용됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;execution([접근제한자 패턴] 타입패턴 [타입패턴.]이름패턴 (타입패턴 | *..*, ...) [throws 예외 패턴])&lt;/code&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void methodSignaturePointcut() throws SecurityException, NoSuchMethodException {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression(&quot;execution(public int springbook.learningtest.spring.pointcut.Target.minus(int,int) throws java.lang.RuntimeException)&quot;);

    // 

    .minus()
    assertThat(pointcut.getClassFilter().matches(Target.class) &amp;amp;&amp;amp; 
              pointcut.getMethodMatcher().matches(
              Target.class.getMethod(&quot;minus&quot;, int.class, int.class), null), is(true));

    // Target.plus()
    assertThat(pointcut.getClassFilter().matches(Target.class) &amp;amp;&amp;amp;
              pointcut.getMethodMatcher().matches(
              Target.class.getMethod(&quot;plus&quot;, int.class, int.class), null), is(false));
}&lt;/code&gt;&lt;/pre&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;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;code&gt;execution(int minus(int, int))&lt;/code&gt;와 같이 간략하게 표현 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리턴 타입 제한을 없애고 싶다면, int 대신 &lt;code&gt;*&lt;/code&gt; 와일드카드를 적용 가능&lt;/li&gt;
&lt;li&gt;파라미터 개수와 타입을 무시하려면, &lt;code&gt;(int, int)&lt;/code&gt; 부분을 &lt;code&gt;(...)&lt;/code&gt;로 적용 가능&lt;/li&gt;
&lt;li&gt;선정 조건을 다 없애고 모든 메소드를 다 허용하는 포인트 컷이라면 &lt;code&gt;execution(* *(..))&lt;/code&gt;과 같이 사용 가능&lt;/li&gt;
&lt;/ul&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void pointcutMatches(String expression, Boolean expected, Class&amp;lt;?&amp;gt; clazz, String methodName, Class&amp;lt;?&amp;gt;... args) throws Exception {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression(expression);

    assertThat(pointcut.getClassFilter().matches(clazz) &amp;amp;&amp;amp;
              pointcut.getMethodMatcher().matches(clazz.getMethod(methodName, args), null), is(expected));
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void targetClassPointcutMatches(String expression, boolean... expected) throws Exception{
    pointcutMatches(expression, expected[0], Target.class, &quot;hello&quot;);
        pointcutMatches(expression, expected[1], Target.class, &quot;hello&quot;, String.class);
        pointcutMatches(expression, expected[2], Target.class, &quot;plus&quot;, int.class, int.class);
        pointcutMatches(expression, expected[3], Target.class, &quot;minus&quot;, int.class, int.class);
        pointcutMatches(expression, expected[4], Target.class, &quot;method&quot;);
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Test
public void pointcut() throws Exception {
    targetClassPointcutMatches(&quot;execution(* *(..))&quot;, true, true, true, true, true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Target 클래스 대신 구현 대상인 interface를 선정하게되면, 해당 interface를 구현한 메소드에만 포인트 컷이 적용됨&lt;/li&gt;
&lt;/ul&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;execution
&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;bean
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@annotation(Transactional);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&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;앞서 만든 NameMatchclassMethodPointcut과 같이 직접 구현 클래스를 사용하지말고, 포인트컷 표현식을 사용해서 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AspectJExpressionPointcut으로 빈을 등록하고, 프로퍼티에 mapping될 클래스 이름과 메소드 이름의 패턴을 설정하도록 수정&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;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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.5.4 AOP란 무엇인가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserService에 트랜잭션 적용 과정 정리&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;서비스 추상화 기법을 적용&lt;/li&gt;
&lt;li&gt;구체적인 구현 내용을 담은 의존 오브젝트를 런타임 시에 다이내믹하게 DI&lt;/li&gt;
&lt;/ul&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;DI를 이용한 데코레이터 패턴 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 인터페이스와 DI를 통해 접근, 데코레이터 패턴으로 트랜잭션 부가기능 부여&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 기능을 부여하지 않아도 되는 메소드마저 프록시로서 위임기능이 필요 ~&amp;gt; 프록시 클래스를 일일이 만드는 작업의 부담 &amp;amp; 구현 코드 중복 문제가 존재&lt;/li&gt;
&lt;li&gt;프록시 클래스 없이도 프록시 오브젝트를 런타임 시에 만들어주도록 JDK 다이내믹 프록시 적용
&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;프록시 팩토리 빈을 이용해서 다이내믹 프록시 생성에 DI 도입
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;스프링 컨테이너의 빈 생성 후처리 기법 적용
&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;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;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;b&gt;다이내믹 프록시&lt;/b&gt;, IoC/DI 컨테이너 빈 생성 작업을 가로채서 빈 오브젝트를 프록시로 대체하는 &lt;b&gt;빈 후처리 기술&lt;/b&gt;, &lt;b&gt;데코레이터 패턴&lt;/b&gt;, &lt;b&gt;자동 프록시 생성&lt;/b&gt;, &lt;b&gt;포인트컷&lt;/b&gt; 등 복잡한 기술 요구&lt;/li&gt;
&lt;li&gt;위의 기술을 이용해서 TransactionAdvice라는 이름으로 모듈화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP: 애스펙트 지향 프로그래밍
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애스펙트(Aspect)
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞선 예시들의 어드바이저가 단순한 형태의 Aspect&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;li&gt;자신이 필요한 위치에 다이내믹하게 참여하지만, 설계 및 개발은 독립적인 관점으로 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애스펙트 지향 프로그래밍(Aspect Oriented Programming, &lt;b&gt;AOP&lt;/b&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션의 핵심적인 기능에서 부가기능을 분리해서 Aspect 모듈로 만들어서 설계 &amp;amp; 개발하는 방법&lt;/li&gt;
&lt;li&gt;OOP를 돕는 보조 기술&lt;/li&gt;
&lt;li&gt;애플리케이션을 특정한 관점을 기준으로 바라볼 수 있게 해준다는 의미에서 AOP를 관점 지향 프로그래밍이라고도 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.5.5 AOP 적용기술&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시를 이용한 AOP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시로 만들어 DI로 연결된 빈 사이에 적용해서 타깃 메소드 호출 과정에 참여하여 부가기능을 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 AOP는 자바의 기본 JDK와 스프링 컨테이너 외에 기술과 환경을 요구하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링 AOP의 어드바이스(부가기능)가 적용되는 대상은 오브젝트의 메소드이며, 프록시 방식이기에 메소드 호출 과정에서 부가기능을 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InvocationHandler의 작동 방식과 마찬가지로 어드바이스가 구현하는 MethodInterceptor 인터페이스가, 프록시로부터 요청을 받아서 타깃 오브젝트의 메소드를 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;바이트코드 생성과 조작을 통한 AOP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 방식이 아닌 AOP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AOP 기술의 원조, 가장 강력한 AOP 프레임워크인 AspectJ&lt;/li&gt;
&lt;li&gt;다이내믹 프록시 방식이 아닌, 타깃 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어주는 방법 사용&lt;/li&gt;
&lt;li&gt;컴파일된 타깃의 클래스 파일 자체를 수정하거나, JVM에 로딩되는 시점에 가로채서 바이트 코드를 조작&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;바이트코드를 조작해서 타깃 오브젝트를 직접 수정하면, 스프링과 같은 DI 컨테이너의 도움을 받지 않아도 AOP를 적용할 수 있기 때문
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너가 사용되지 않는 환경에서도 AOP를 손쉽게 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시 방식보다 강력하고 유연한 AOP 가능
&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;직접 바이트코드를 조작하면 오브젝트의 생성, 필드 값의 조회 &amp;amp; 조작, 스태틱 초기화 등 다양한 부가기능 부여 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고급 AOP 기술은 바이트코드 조작을 위해 JVM의 실행 옵션을 변경하거나, 별도의 바이트코드 컴파일러를 사용하거나, 특별한 클래스 로더를 사용하는 등 번거로움
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 AOP를 적용할 때는, 프록시 방식의 스프링 AOP로 충분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.5.6 AOP의 용어&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;부가기능을 부여할 대상&lt;/li&gt;
&lt;li&gt;핵심 기능을 담은 클래스 or 다른 부가기능을 제공하는 프록시 오브젝트&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;li&gt;오브젝트로 정의하거나 메소드 레벨에서 정의해서 사용&lt;/li&gt;
&lt;li&gt;MethodInterceptor처럼 메소드 호출 과정에 전반적으로 참여하는 것, 예외 발생 시에만 동작하는 등의 메소드 호출 과정의 일부만 동작하는 등 여러 종류가 존재&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;li&gt;스프링 프록시 AOP에서 메소드의 실행 단계&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;어드바이스를 적용할 조인 포인트를 선별하는 작업 or 기능을 정의한 모듈&lt;/li&gt;
&lt;li&gt;스프링 AOP의 조인 포인트는 메소드 실행 ~&amp;gt; 스프링의 포인트컷은 메소드 선정 기능&lt;/li&gt;
&lt;li&gt;excution으로 시작하고, 메소드 시그니처를 비교하는 방법을 주로 이용&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;li&gt;DI를 통해 타깃 대신 클라이언트에게 주입, 클라이언트의 메소드 호출을 대신 받아 타깃에 위임하면서 부가기능을 부여&lt;/li&gt;
&lt;li&gt;스프링은 프록시를 통해 AOP 지원&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;code&gt;포인트컷 + 어드바이스&lt;/code&gt; 쌍의 오브젝트&lt;/li&gt;
&lt;li&gt;어떤 부가기능(어드바이스)을 어디에(포인트컷) 전달할 것인지 알고있는 AOP의 가장 기본이 되는 모듈&lt;/li&gt;
&lt;li&gt;스프링 AOP에서만 사용되는 용어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 AOP에서는 사용 X&lt;/li&gt;
&lt;/ul&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;AOP의 기본 모듈&lt;/li&gt;
&lt;li&gt;한 개 이상의 포인트컷과 어드바이스 조합으로 만들어지며, 보통 싱글톤 형태의 오브젝트&lt;/li&gt;
&lt;li&gt;스프링의 어드바이저는 아주 단순한 Aspect&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AOP 네임스페이스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 AOP를 적용하기 위해 추가한 어드바이저, 포인트컷, 자동 프록시 생성기 같은 빈들은 애플리케이션 로직을 담은 빈과 달리, 스프링 컨테이너에 의해 자동으로 인식돼서 특별한 작업을 위해 사용됨&lt;/li&gt;
&lt;li&gt;스프링의 프록시 방식을 AOP에 적용하기 위한 빈 등록
&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;스프링의 DefaultAdvisorAutoProxyCreator 클래스를 빈으로 등록&lt;/li&gt;
&lt;li&gt;다른 빈을 DI하지 않고, 자신도 DI 되지 않으며 독립적으로 존재&lt;/li&gt;
&lt;li&gt;Application Context가 빈 오브젝트를 생성하는 과정에 빈 후처리기로 참여&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;/li&gt;
&lt;li&gt;TransactionAdvice는 AOP 관련 빈 중 유일하게 직접 구현한 클래스를 사용&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;스프링의 AspectJExpressionPointcut을 빈으로 등록하고, expression 프로퍼티에 포인트컷 표현식을 넣어 사용&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;스프링의 DefaultPointcutAdvisor 클래스를 빈으로 등록해서 사용&lt;/li&gt;
&lt;li&gt;어드바이스와 포인트컷을 프로퍼티로 참조하고, 자동 프록시 생성기에 의해 자동 검색되어 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AOP 네임스페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은 AOP와 관련된 태그를 정의해둔 aop 스키마를 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 네임스페이스를 지정해서 디폴트 네임스페이스의 &amp;lt;bean&amp;gt; 태그와 구분해서 사용&lt;/li&gt;
&lt;li&gt;설정 파일에 springframwork의 aop를 등록해서 사용&lt;/li&gt;
&lt;li&gt;&amp;lt;aop:config&amp;gt;, &amp;lt;aop:pointcut&amp;gt;, &amp;lt;aop:advisor&amp;gt; 세 태그를 정의하면 빈이 자동으로 등록&lt;/li&gt;
&lt;li&gt;transactionAdvice를 제외한 AOP 관련 빈들은 의미를 잘 드러내는 독립적인 전용 태그를 사용하도록 권장&lt;/li&gt;
&lt;/ul&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;AspectJ 포인트컷 표현식을 활용하는 포인트컷은 expression 프로퍼티를 설정해서 사용&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>AOP</category>
      <category>스프링</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/372</guid>
      <comments>https://zin0-0.tistory.com/372#entry372comment</comments>
      <pubDate>Fri, 23 Jul 2021 15:52:35 +0900</pubDate>
    </item>
    <item>
      <title>6장) 6.4 스프링의 프록시 팩토리 빈</title>
      <link>https://zin0-0.tistory.com/371</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6장 AOP&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.4 스프링의 프록시 팩토리 빈&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProxyFactoryBean
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 JDK에서 제공하는 다이나믹 프록시 외에도 프록시를 만들도록 지원하는 다양한 기술 존재
&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;/li&gt;
&lt;/ul&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;부가기능은 MethodInterceptor 인터페이스를 구현해서 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InvocationHandler의 invoke() 는 타깃 오브젝트 정보를 제공하지 않아서, 자체적으로 타깃을 알고 있어야함&lt;/li&gt;
&lt;li&gt;MethodInterceptor의 invoke()는 ProxyFactoryBean으로 부터 타깃 오브젝트 정보를 제공받음
&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;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public class DynamicProxyTest {
    @Test
    public void SimpleProxy() {
        Hello proxiedHello = (Hello)Proxy.newProxyInstance(
            getClass().getClassLoader(),
            new Class[] { Hello.class },
            new UppercaseHandler(new HelloTarget())
        );
        ...
    }

    @Test
    public void proxyFactoryBean() {
        ProxyFactoryBean pfBean = new ProxyFactoryBean();
        pfBean.setTarget(new HelloTarget());
        pfBean.addAdvice(new UppercaseAdvice());

        Hello proxiedHello = (Hello) pfBean.getObject();
        assertThat(proxiedHello.sayHello(&quot;Toby&quot;), is(&quot;HELLO TOBY&quot;));
        assertThat(proxiedHello.sayHi(&quot;Toby&quot;), is(&quot;Hi TOBY&quot;));
    }

    static class UppercaseAdvice implements MethodInterceptor {
        public Object invoke(MethodInterceptor invocation) throws Throwable {
            String ret = (String)invocation.proceed();
            return ret.toUpperCase();
        }
    }

    static interface Hello {
        String sayHello(String name);
        String sayHi(String name);
    }

    static class HelloTarget implements Hello {
        public String sayHello(String name) {
            return &quot;Hello &quot; + name;
        }

        public String sayHi(String name) {
            return &quot;Hi &quot; + name;
        }
    }
}&lt;/code&gt;&lt;/pre&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;MethodInvocation은 일종의 콜백 오브젝트로, proceed() 메소드를 실행해서 타깃 오브젝트 메소드를 내부적으로 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 추상화 기능인 ProxyFactoryBean을 사용하는 코드의 가장 큰 차이점이자 장점&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean은 작은 단위의 템플릿/콜백 구조를 응용해서 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;템플릿 역할인 MethodInvocation을 싱글톤으로 두고 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MethodInterceptor는 Advice 인터페이스를 상속하고있는 서브 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, ProxyFactoryBean의 addAdvice()로 여러 MethodInterceptor 추가 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProxyFactoryBean 하나만으로 여러 부가 기능을 제공하는 프록시를 만들 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부가기능을 담은 오브젝트를 스프링에서는 Advice라고 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean은 인터페이스 자동검출 기능을 사용해 타깃 오브젝트가 구현하고 있는 인터페이스 정보를 알아내서, 인터페이스를 모두 구현하는 프록시를 만들어 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스 중 일부만 프록시에 적용하기 원한다면, setInterfaces()를 통해 구현할 인터페이스 정보를 직접 제공&lt;/li&gt;
&lt;/ul&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;ProxyFactoryBean과 MethodInterceptor를 사용하는 방식에 메소드 선정 기능을 추가할 수 있을까???
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 적용 메소드 패턴은 프록시마다 다를 수 있기 때문에, 여러 프록시가 공유하는 MethodInterceptor에 특정 프록시에만 적용되는 패턴을 넣으면 문제 발생&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;부가기능(Advice)와 메소드 선정 알고리즘(Pointcut)을 활용한 유연한 구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;img src=&quot;https://i.ibb.co/4F0cNkY/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;포인트컷 : 메소드 선정 알고리즘 오브젝트&lt;br /&gt;어드바이스 : 부가기능 제공 오브젝트&lt;/li&gt;
&lt;li&gt;타깃 메소드를 직접 호출하는 것은 Invocation 콜백의 역할로, 전형적인 템플릿/콜백 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 요청 ~&amp;gt; 포인트컷에게 부가기능 부여할 메소드인지 확인 요청&lt;/li&gt;
&lt;li&gt;포인트컷 응답
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부가기능 부여할 메소드인 경우
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MethodInterceptor 어드바이스 호출&lt;/li&gt;
&lt;li&gt;기능 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;invocation 콜백을 통해 타깃 오브젝트 메소드 실행&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;프록시로부터 어드바이스와 포인트 컷을 독립시키고 DI를 사용&lt;/li&gt;
&lt;li&gt;여러 프록시가 공유, 부가기능 방식이나 메소드 선정 알고리즘이 바뀌면 구현 클래스만 바꿔 설정 ~&amp;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void pointAdvisor() {
    ProxyFactoryBean pfBean = new ProxyFactoryBean();
    pfBean.setTarget(new HelloTarget());

    NameMatchMethodPointCut pointcut = new NameMatchMethodPointCut();
    pointcut.setMappedName(&quot;sayH*&quot;);

    pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));

    Hello proxiedHello = (Hello)pfBean.getObject();

    //assertThat ..
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean에는 여러 어드바이스와 포인트 컷이 추가될 수 있기 때문에, 어떤 어드바이스에 어떤 포인트컷을 적용할지 조합을 만들어서 등록&lt;/li&gt;
&lt;li&gt;&lt;code&gt;어드바이저 = 포인트컷 + 어드바이스&lt;/code&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;ProxyFactoryBean 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다이나믹 프록시를 이용해 만든 TxProxyFactoryBean을 스프링의 ProxyFactoryBean을 이용하도록 수정&lt;/li&gt;
&lt;li&gt;TransactionAdvice
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부가기능을 담당하는 어드바이스를 MethodInterceptor라는 Advice 서브인터페이스를 구현해서 작성&lt;/li&gt;
&lt;li&gt;JDK 다이나믹 프록시 방식으로 만든 TransactionHandler의 코드에서 타깃과 메서드 선정 부분을 제거&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class TransactionAdvice implements MethodInterceptor {
    PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /* 
     * 타깃을 호출하는 기능을 가진 콜백 오브젝트를 프록시로부터 받는다.
     * 덕분에 어드바이스는 특정 타깃에 의존하지 않고 재사용이 가능하다.
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            /* 
             * 콜백을 호출해서 타깃의 메서드를 실행
             * 타깃 메서드 호출 전후로 필요한 부가기능을 넣을 수 있다.
             * 겨웅에 따라서 타깃이 아예 호출되지 않게 하거나 재시도를 위한 반복적인 호출도 가능하다.
             */
            Object ret = invocation.proceed();
            this.transactionManager.commit(status);
            return ret;

            /*
             * JDK 다이나믹 프록시가 제공하는 Method와는 달리 
             * 스프링의 MethodInvation을 통한 타깃 호출은 예외가 포장되지 않고 
             * 타깃에서 보낸 그대로 전달된다.
             */
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 XML 설정 파일
&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;어드바이저 빈 설정을 마치면 ProxyFactoryBean 설정에 어드바이저 빈을 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interceptorNames라는 propery에 등록&lt;/li&gt;
&lt;li&gt;advisor가 아닌 이유는 어드바이스와 어드바이즈러를 혼합해서 설정할 수 있도록 하기 위함&lt;/li&gt;
&lt;/ul&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
@DirtiesContext
public void upgradeAllOrNothing() {
    TestUserService testUserService = new TestUserService(users.get(3).getId());
    testUserService.setUserDao(userDao);
    testUserService.setMailSender(mailSender);

    ProxyFactoryBean txProxyFactoryBean = context.getBean(&quot;&amp;amp;userService&quot;, ProxyFactoryBean.class);
    txProxyFactoryBean.setTarget(testUserService);
    UserService txUserService = (UserService)txProxyFactoryBean.getObject();
}&lt;/code&gt;&lt;/pre&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;ProxyFactoryBean은 스프링의 DI, 템플릿/콜백 패턴, 서비스 추상화 등 기법이 모두 적용됨
&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;메소드 선정을 위한 포인트컷이 필요하면 이름 패턴만 지정해서 ProxyFactoryBean에 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>어드바이스</category>
      <category>토비 스프링</category>
      <category>포인트컷</category>
      <category>프록시 팩토리 빈</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/371</guid>
      <comments>https://zin0-0.tistory.com/371#entry371comment</comments>
      <pubDate>Fri, 16 Jul 2021 18:22:58 +0900</pubDate>
    </item>
    <item>
      <title>6장) 6.3 다이내믹 프록시와 팩토리 빈</title>
      <link>https://zin0-0.tistory.com/370</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6장 AOP&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.3 다이내믹 프록시와 팩토리 빈&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;프록시
&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;UserServiceTx, UserServiceImpl&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;핵심 기능(비즈니스 로직)을 담은 클래스를 부가 기능을 가진 트랜잭션 클래스에서 이용&lt;/li&gt;
&lt;li&gt;클라이언트가 핵심 기능을 가진 클래스를 직접 사용하지 않도록, 부가 기능을 담은 클래스가 핵심 기능인 것 처럼 위장 ~&amp;gt; 핵심 기능 클래스를 이용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제 대상인 것 처럼 위장해서 요청을 받아주는 대리자 역할&lt;/b&gt;을 한다고 해서 프록시라고 부름&lt;/li&gt;
&lt;li&gt;요청을 위임받아 실제 처리하는 오브젝트를 타깃 or 실체(real subject) 라고 부름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;클라이언트 --&amp;gt; 프록시 --&amp;gt; 타깃&lt;/code&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;/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;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;li&gt;프록시가 한 개로 제한되지 않고, 프록시가 타깃을 직접 사용하도록 고정시킬 필요 X
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 IO 패키지의 InputStream과 OutputStream 구현 클래스가 대표적인 예시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;InputStream is = new BufferedInputStream(new FileInputStream(&quot;a.txt&quot;));&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링의 DI를 이용하여 편하게 위임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;타깃 코드에 손대지 않고, 클라이언트가 호출하는 방법도 변경하지 않은 채로 새로운 기능을 추가할 때 유용한 방법&lt;/li&gt;
&lt;li&gt;&lt;code&gt;클라이언트 --&amp;gt; 여러 데코레이터(deco1 --&amp;gt; deco2) --&amp;gt; 타깃&lt;/code&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;b&gt;타깃에 대한 접근 방법을 제어&lt;/b&gt;하려는 목적을 가진 경우&lt;/li&gt;
&lt;li&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;프록시의 메소드를 통해 타깃을 사용하려고 시도할 때, 프록시가 타깃 오브젝트를 생성하고 요청을 위임&lt;/li&gt;
&lt;li&gt;많은 작업이 진행되는 경우, 생성을 최대한 늦춤으로써 장점을 가짐&lt;/li&gt;
&lt;/ul&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;수정 가능한 오브젝트를 읽기 전용으로만 동작하게 해야하는 경우, 오브젝트 프록시를 만들어서 read 이외에 예외를 발생하도록 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Collections의 unmodifiableCollection()을 통해 만들어지는 오브젝트가 대표 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;클라이언트 --&amp;gt; 접근 제어 프록시 --&amp;gt; 여러 데코레이터 --&amp;gt; 타깃&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다이내믹 프록시(Dynamic Proxy)
&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;일일이 프록시 클래스를 정의하지 않고 몇 가지 API를 이용해 프록시처럼 동작하는 오브젝트를 다이나믹하게 생성&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;타깃과 같은 메소드를 구현하고 있다가 메소드가 호출되면 타깃 오브젝트로 위임&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;타깃 인터페이스의 메소드가 추가되거나 변경될 때마다 함께 수정해야함&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;트랜잭션의 경우 DB 사용하는 로직에 적용될 확률이 높음&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;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;li&gt;자바의 모든 클래스는 그 자체의 구성정보를 담은 Class 타입의 오브젝트를 하나씩 가지고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스이름.class or 오브젝트의 getClass() 메소드로 호출 가능&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;code&gt;Method lengthMethod = String.class.getMethod(&quot;length&quot;);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;메소드 실행은 &lt;code&gt;invoke()&lt;/code&gt; 메소드를 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int length = lengthMethod.invoke(name);&lt;/code&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;img src=&quot;https://i.ibb.co/k2v2GBC/image.jpg&quot; alt=&quot;&quot; /&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;부가 기능은 프록시 오브젝트와 독립적으로 InvocationHandler를 구현한 오브젝트에 담음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;interface Hello {
    String sayHello(String name);
    String sayHi(String name);
    String sayThankYou(String name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class HelloTarget implements Hello {
    public String sayHello(String name) {
        return &quot;Hello &quot; + name;
    }
    public String sayHi(String name) {
        return &quot;Hi &quot; + name;
    }
    public String sayThankYou(String name) {
        return &quot;Thank you &quot; + name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class UppercaseHandler implements InvocationHandler {
    Hello target;

    public UppercaseHandler(Hello target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String ret = (String)method.invoke(target, args);
        return ret.toUpperCase();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;Hello proxiedHello = (Hello)Proxy.newProxyInstance(
    getClass().getClassLoader(), // 다이나믹 프록시 오브젝트는 Hello 인터페이스를 구현하고 있으므로 캐스팅 세이프
    new Class[] { Hello.class }, // dynamic proxy 클래스 로딩에 사용될 클래스 로더
    new UppercaseHandler(new HelloTarget()) // invocationHandler
);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;첫 번째 파라미터로 다이내믹 프록시가 정의되는 클래스 로더 지정&lt;/li&gt;
&lt;li&gt;두 번째 파라미터로 다이내믹 프록시가 구현해야 할 인터페이스 지정(한 번에 하나 이상의 인터페이스를 구현할 수 있기 때문에 배열을 사용)&lt;/li&gt;
&lt;li&gt;마지막 파라미터로 부가기능과 위임 관련 코드를 담고 있는 InvocationHandler 구현 오브젝트 제공&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;Hello 인터페이스의 메소드가 증가하는 경우, 다이내믹 프록시에 자동으로 포함되고, 부가 기능은 invoke() 메소드에서 처리됨&lt;/li&gt;
&lt;li&gt;타깃의 타입에 상관없이 InvocationHandler를 통해 적용 가능&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class UppercaseHandler implements InvocationHandler { // 확장
    Object target;
    private UppercaseHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = method.invoke(target, args);
        return ret instanceof String ? (String)ret.toUpperCase() : ret;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 메소드를 선별해서 부가기능을 적용하는 invoke
    Object ret = method.invoke(target, args);
    return ret instanceof String &amp;amp;&amp;amp; method.getName().startsWith(&quot;say&quot;) ?
        (String)ret.toUpperCase() : ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&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;앞서 작성했던 UserServiceTx를 다이내믹 프록시 방식으로 수정&lt;/li&gt;
&lt;li&gt;트랜잭션 InvocationHandler
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class TransactionHandler implements InvocationHandler {
    private Object target;
    private PlatformTransactionManager transactionManager;
    private String pattern;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return Method.getName().startsWith(pattern) ?
            invokeInTransaction(method, args) :
            method.invoke(target, args);
    }

    private Object invokeInTransaction(Method method, Object[] args) throws Throwable {
        TransactionStatus status = this.getTransaction(new DefaultTransactionManager());

        try {
            Object ret = method.invoke(target, args);
            this.transactionManager.commit(status);
            return ret;
        } catch (InvocationTargetException exception) {
            this.transactionmanager.rollback(status);
            throw exception.getTargetException();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;요청을 위임할 타깃을 DI로 제공받고, Object로 선언
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserServiceImpl 외에 트랜잭션 적용이 필요한 어떤 타깃 오브젝트에도 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserServiceTx와 마찬가지로 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 DI 받음&lt;/li&gt;
&lt;li&gt;타깃 오브젝트의 모든 메소드에 무조건 트랜잭션이 적용되지 않도록 트랜잭션을 적용할 메소드 이름의 패턴을 DI 받음&lt;/li&gt;
&lt;li&gt;InvocationHandler의 invoke() 메소드는 적용할 대상을 선별해서 트랜잭션을 적용하고, 아니라면 부가기능 없이 타깃 오브젝트의 메소드를 호출해서 결과를 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TransactionHandler와 다이내믹 프록시를 이용하는 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Test
public void upgradeAllOrNothing() throws Exception {
    ...
    TransactionHandler txHandler = new TransactionHandler();
    txHandler.setTarget(testUserService);
    txHandler.setTransactionManager(transactionManager);
    txhandler.setPattern(&quot;upgradeLevels&quot;);
    UserService txUserService = (UserService)Proxy.newProxyInstance(
        getClass().getClassLoader(),
        new Class[] { UserService.class },
        txHandler
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;TransactionHandler와 다이내믹 프록시를 스프링 DI를 통해 사용하도록 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은 내부적으로 리플렉션 API를 이용해서 빈 정의에 나오는 클래스 이름을 가지고 빈 오브젝트를 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다이내믹 프록시 오브젝트는 리플렉션을 통해 오브젝트 생성 X&lt;/li&gt;
&lt;li&gt;Proxy 클래스의 newProxyInstance() 스태틱 팩토리 메소드를 통해서만 생성 가능&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;FactoryBean 인터페이스를 구현&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public interface FactoryBean&amp;lt;T&amp;gt; {
    T getObject() throws Exception; // 빈 오브젝트를 생성해서 반환
    Class&amp;lt;? extends T&amp;gt; getObjectType(); // 생성되는 오브젝트 타입 반환
    bollean isSingleton(); // getObject()가 돌려주는 오브젝트가 항상 싱글톤인지 여부 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Message {
    String text;

    private Message(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public static Message newMessage(String text) {
        return new Message(text);
    }
}

public class MessageFactoryBean implements FactoryBean&amp;lt;Message&amp;gt; {
    String text;

    public void setText(String text) {
        this.text = text;
    }

    public Message getObject() throws Exception {
        return Message.newMessage(this.text);
    }

    public Class&amp;lt;? extends Message&amp;gt; getObjectType() {
        return Message.class;
    }

    public boolean isSingleton() {
        return false;
    }
}&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;스프링은 private 생성자를 가진 클래스를 빈으로 등록해주면 리플렉션을 이용해 오브젝트로 만들어줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리플렉션이 private 접근 규약을 위반할 수 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하지만, &lt;b&gt;스태틱 메소드를 통해 오브젝트가 만들어져야 하는 중요한 이유가 있을 것이기 때문에 강제로 생성하면 위험&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;FactoryBean 인터페이스를 구현한 클래스를 스프링 빈으로 만들어두면 getObject()라는 메소드가 생성해주는 오브젝트가 실제 빈의 오브젝트로 대치됨&lt;/li&gt;
&lt;li&gt;팩토리 빈이 만들어주는 빈 오브젝트가 아니라 팩토리 자체를 가져오고 싶은 경우, &lt;code&gt;&amp;amp;&lt;/code&gt;를 빈 이름 앞에 붙여주면 팩토리 빈 자체를 반환함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@Test
public void getFactoryBean() throws Exception {
    Object factory = context.getBean(&quot;&amp;amp;Message&quot;);
    assertThat(factory, is(MessageFactoryBean.class));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&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;img src=&quot;https://i.ibb.co/jZdL4Qd/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;팩토리 빈은 다이내믹 프록시가 위임할 타깃 오브젝트인 UserServiceImpl에 대한 레퍼런스를 프로퍼티를 통해 DI 받아둬야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransactionHandler에게 타깃 오브젝트를 전달해줘야 하기 때문&lt;/li&gt;
&lt;li&gt;TransactionHandler를 생성할 때 필요한 정보를 팩토리 빈의 프로퍼티로 설정해뒀다가 다이내믹 프록시를 만들면서 전달&lt;/li&gt;
&lt;/ul&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class TxProxyFactoryBean implements FactoryBean&amp;lt;Object&amp;gt; {
    Object target;
    PlatformTransactionManager transactionManager;
    String pattern; // 이상 3개는 TransactionHandler 생성시 필요
    Class&amp;lt;?&amp;gt; serviceInterface; // 다이내믹 프록시 생성시 필요

    // set properties

    public Object getObject() throws Exception {
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(target);
        txHandler.setTransactionManager(transactionManager);
        txHandler.setPattern(pattern);
        return Proxy.newProxyInstance(
            getClass().getClassLoader(),
            new Class[] { serviceInterface },
            txHandler
        );
    }

    public Class&amp;lt;?&amp;gt; getObjectType() {
        return serviceInterface;
    }

    public boolean isSingleton() {
        return false; // getObject()가 매번 같은 오브젝트를 리턴하지 않음
    }
}&lt;/code&gt;&lt;/pre&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;수동 DI를 통해 직접 다이내믹 프록시를 만들었던 코드에 팩토리 빈 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타깃 Object에 대한 레퍼런스를 TransactionHandler 오브젝트가 가지고 있으므로, factory의 getObject에 의한 DI가 아닌, FactoryBean을 직접 가져와서 프록시를 생성하도록 수정&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class UserServiceTest {
    @Test
    @DirtiesContext
    public void upgradeAllOrNothing() throws Exception {
        TestUserService testUserService = new TestUserService(users.get(3).getId());
        testUserService.setUserDao(userDao);
        testUserService.setMailSender(mailSender);

        TxProxyFactoryBean txProxyFactoryBean = context.getBean(&quot;&amp;amp;userService&quot;, TxProxyFactoryBean.class);
        txProxyFactoryBean.setTarget(testUserService);
        UserService txUserService = (UserService) txProxyFactoryBean.getObject();

        userDao.deleteAll();
        users.forEach(user -&amp;gt; userDao.add(user));

        try {
            txUserService.upgradeLevels();
            fail(&quot;TestUserServiceException expected&quot;);
        } catch (TestUserServiceException e) {

        }
        checkLevelUpgraded(users.get(1), false);
    }
}&lt;/code&gt;&lt;/pre&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserService 외에 트랜잭션 경계설정 기능을 부여해줄 필요가 있는 클래스가 있을 경우, 앞서 만들었던 TxProxyFactoryBean을 적용&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;프록시를 적용할 대상이 구현하고 있는 인터페이스 구현 프록시 클래스를 일일이 만들어야함&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;팩토리 빈 + DI를 통해 다양한 타깃 오브젝트 적용 및 중복코드 제거&lt;/li&gt;
&lt;/ul&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;/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;XML 설정 라인이 대폭 증가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 부분은 Spring Boot + Java Config로 인해 현재는 문제 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;타깃과 인터페이스만 다른 비슷한 설정 반복&lt;/li&gt;
&lt;li&gt;TransactionHandler 오브젝트가 프록시 팩토리 빈 개수만큼 만들어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>토비 스프링</category>
      <category>팩토리 빈</category>
      <category>프록시 팩토리 빈</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/370</guid>
      <comments>https://zin0-0.tistory.com/370#entry370comment</comments>
      <pubDate>Fri, 16 Jul 2021 18:06:05 +0900</pubDate>
    </item>
    <item>
      <title>HandlerMethodArgumentResolver</title>
      <link>https://zin0-0.tistory.com/369</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;HandlerMethodArgumentResolver&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;요청 파라미터를 수정하거나 클래스 파라미터를 조작, 공통으로 쓸 파라미터를 바인딩&lt;/li&gt;
&lt;li&gt;공통으로 수행할 작업을 수행한 후, Object를 반환해서 코드의 중복을 줄임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HandlerMethodArgumentResolver 인터페이스를 구현한 구현체를 WebMvcConfigurer 인터페이스 구현체의 addArgmuentResolver에 등록해서 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;supportsParameter와 resolveArgument 2개의 메서드를 오버라이딩하여 구현&lt;/li&gt;
&lt;li&gt;supportsParameter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;boolean 값으로 반환하는 메소드&lt;/li&gt;
&lt;li&gt;요청으로 들어온 MethodParameter가 resolveArgument 수행이 필요한 경우 true를, 아니라면 false를 리턴하도록 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;resolveArgument
&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;MethodParameter는 필수 파라미터, 나머지 파라미터들은 선택적&lt;/li&gt;
&lt;li&gt;메소드로 전달받은 파라미터 중 NativeWebRequest 타입의 인스턴스를 통해 요청으로 들어온 query parameter를 받아올 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Override
public Object resolveArgument(MethodParameter param, ModelAndViewContainer container, NativeWebRequest webRequest, WebDataBinderFactory factory) throws Exception {
    String name = webRequest.getParameter(&quot;name&quot;);
    ... // logic &amp;amp; return Object
}&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://jhkang-tech.tistory.com/49&quot;&gt;https://jhkang-tech.tistory.com/49&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://a1010100z.tistory.com/127&quot;&gt;https://a1010100z.tistory.com/127&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/기본 개념 정리</category>
      <category>HandlerMethodArgumentResolver</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/369</guid>
      <comments>https://zin0-0.tistory.com/369#entry369comment</comments>
      <pubDate>Tue, 13 Jul 2021 12:25:21 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot actuator</title>
      <link>https://zin0-0.tistory.com/368</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring boot actuator&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔드포인트로 HTTP나 JMX를 통해서 Application을 모니터링하고 관리하는 기능 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;health, Auditing, beans, ...&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;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&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;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;ID&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;auditevents&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;현재 실행중인 Applicaition의 audit Event를 보여준다.&lt;br /&gt;&lt;code&gt;AuditEventRepository&lt;/code&gt; bean이 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;beans&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;애플리케이션의 모든 Spring 빈 목록을 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;caches&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;사용 가능한 캐시를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;conditions&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Configuration과 Auto-Configuration의 매칭 여부와 이유를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;configprops&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;모든 &lt;code&gt;@ConfigurationProperties&lt;/code&gt;의 조합된 목록을 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;env&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Spring의 &lt;code&gt;ConfigurableEnvironment&lt;/code&gt;를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;flyway&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Flyway DB 마이그레이션이 적용된 모든 것을 보여준다.&lt;br /&gt;하나 이상의 Flyway bean이 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;health&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Application의 health 정보를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;httptrace&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;HTTP Trace 정보를 보여준다.&lt;br /&gt;&lt;code&gt;HttpTraceRepository&lt;/code&gt; bean이 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;info&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;임의의 application 정보를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;integrationgraph&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Spring의 Integration(통합) 그래프를 보여준다.&lt;br /&gt;&lt;code&gt;spring-integration-core&lt;/code&gt; 의존성 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;loggers&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Application의 로거에 대한 구성을 보여주고, 수정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;liquibase&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Liquibase DB 마이그레이션이 적용된 모든 것을 보여준다.&lt;br /&gt;하나 이상의 &lt;code&gt;Liquibase&lt;/code&gt; bean이 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;metrics&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;현재 Application에 대한 metrics 정보를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;mappings&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;@RequestMapping&lt;/code&gt; path들을 보여준다. (모든 API 주소)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;quartz&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Quartz Scheduler의 jobs의 정보를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;scheduledtasks&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Application의 작업 스케쥴을 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Allows retrieval and deletion of user sessions from a Spring Session-backed session store. Requires a Servlet-based web application using Spring Session.&lt;br /&gt;Spring Session-backed 세션 스토어에서 user 세션 검색 및 삭제를 허용. Spring Session을 사용하는 Servlet 기반 웹 Application이 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;shutdown&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Application을 정상적으로 종료. (Default는 비활성화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;startup&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;ApplicationStartup&lt;/code&gt;에서 수집한 시작 단계 데이터를 보여준다. &lt;br /&gt;&lt;code&gt;BufferingApplicationStartup&lt;/code&gt;로 구성된 &lt;code&gt;SpringApplication&lt;/code&gt; 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;threaddump&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;스레드 덤프 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 애플리케이션(Spring MVC, Spring WebFlux 또는 Jersey)인 경우 다음 엔드포인트를 추가 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;ID&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;heapdump&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;hprof&lt;/code&gt; 힙 덤프 파일을 리턴&lt;br /&gt;HotSpot JVM 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;jolokia&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;HTTP를 통해 JMX 빈을 보여준다(Jolokia가 classpath에 있으면, WebFlux에서는 사용 불가). &lt;br /&gt;&lt;code&gt;jolokia-core&lt;/code&gt; 의존성 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;logfile&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;로그 파일의 내용을 return(&lt;code&gt;logging.file.name&lt;/code&gt; or &lt;code&gt;logging.file.path&lt;/code&gt; ..) &lt;br /&gt;HTTP &lt;code&gt;Range&lt;/code&gt; header를 사용해서 로그 파일의 일부 내용을 검색할 수 있도록 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;prometheus&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Prometheus 서버에서 수집할 수 있는 metrices를 보여준다.&lt;br /&gt;&lt;code&gt;micrometer-registry-prometheus&lt;/code&gt; 의존성 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;shutdown을 제외한 모든 엔드포인트는 default로 enabled 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;enabled-by-default 속성을 통해 활성화 false 설정을 할 수 있고, 개별 endpoint마다 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비활성화된 엔드포인트는 Application 컨텍스트에서 완전하게 제거됨&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
    endpoints:
        enabled-by-default: false
    endpoint:
           info:
               enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엔드포인트에 대한 노출만 변경하려는 경우 include와 exclude를 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;management:
  endpoints:
    jmx:
      exposure:
        include: &quot;health,info&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: &quot;*&quot;
        exclude: &quot;env,beans&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 외에도 Cors 설정이나 TTL 등 여러 기능이 존재하니 필요에 따라 검색해서 사용하면 좋을 것 같음
&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;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html&quot;&gt;https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java &amp;amp; Spring/기본 개념 정리</category>
      <category>spring boot actuator</category>
      <category>모니터링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/368</guid>
      <comments>https://zin0-0.tistory.com/368#entry368comment</comments>
      <pubDate>Tue, 13 Jul 2021 11:29:33 +0900</pubDate>
    </item>
    <item>
      <title>6장) 6.1 트랜잭션 코드의 분리 ~ 6.2 고립된 단위 테스트</title>
      <link>https://zin0-0.tistory.com/367</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6장 AOP&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에 적용된 가장 인기있는 AOP 적용 대상은 선언적 트랜잭션 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.1 트랜잭션 코드의 분리&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;기존에 작성했던 UserService는 트랜잭션 경계설정 코드와 비즈니스 로직 코드 간에 서로 주고받는 정보가 없음
&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;upgradeLevels 메소드에서 시작된 트랜잭션 정보는 트랜잭션 동기화 방법을 통해 DAO가 알아서 활용&lt;/li&gt;
&lt;li&gt;완벽하게 독립된 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI를 이용한 클래스의 분리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DI 적용을 이용한 트랜잭션 분리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DI는 실제 사용할 오브젝트 클래스 정체를 감추고 인터페이스로 간접 접근하도록 사용
&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;UserService를 인터페이스로 만들고 기존 코드를 구현 클래스로 수정&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;pre class=&quot;xl&quot;&gt;&lt;code&gt;// 기존 구조
Client -----&amp;gt; UserService

// 수정할 구조
Client -----&amp;gt; UserService &amp;lt;---- UserServiceImpl&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;런타임 시 DI를 통해 적용하는 방법을 쓰는 이유는 일반적으로 구현 클래스를 바꾸며 사용하기 위함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번에 두 개의 UserService 인터페이스 구현 클래스를 동시에 사용한다면 ??
&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;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;Client ----&amp;gt; UserService &amp;lt;----------- UserServiceImpl
                              |
                              |
                              \------ UserServiceTx&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;UserServiceTx는 트랜잭션 경계설정 책임만 갖고, 비즈니스 로직을 담고있는 UserServiceImpl 구현체에게 실질적인 로직 처리 작업 위임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserService 인터페이스 도입
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface UserService {
    void add(User user);
    void upgradeLevels();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public class UserServiceImpl implements UserService {
    UserDao userDao;
    MailSender mailSender;

    public void upgradeLevels() {
        userDao.getAll()
            .filter(user -&amp;gt; canUpgradeLevel(user))
            .forEach(user -&amp;gt; upgradeLevel(user));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;upgradeLevelsInternal()로 분리했던 코드를 다시 원래대로 upgradeLevels에 추가&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;비즈니스 트랜잭션 처리를 담은 UserServiceTx 생성&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@AllArgsConstructor
public class UserServiceTx implements UserService {
    UserService userService;
    PlatformTransactionManager transactionManager;

    public void add(User user) {
        this.userService.add(user); // DI 받은 UserService에 기능 위임
    }

    public void upgradeLevels() {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            userService.upgradeLevels(); // DI 받은 UserService에 기능 위임
            this.transactionManager.commit(status);
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 적용을 위한 DI 설정
&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;Client ----&amp;gt; UserServiceTx ----&amp;gt; UserServiceImpl&lt;/li&gt;
&lt;/ul&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;@Autowired
&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;/li&gt;
&lt;li&gt;따라서, 기존 코드대로 UserService에 @Autowired를 걸어두면, 아이디가 userService인 빈이 주입됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserServiceTest는 두 개의 빈이 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock 객체로 만든 MailSender 구현체를 UserServiceImpl에 직접 DI 해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//UserServiceTest
@Autowired UserServiceTx;
@Autowired UserServiceImpl

@Test
public void upgradeLevels() throws Exception { // 메일링 검증
    ...
    MockMailSender mockMailSender = new MockMailSender();
    userServiceImpl.setMailSender(mockMailSender);
}

@Test
public void upgardeAllOrNothig() { // 트랜잭션 검증
    TestUserService testUserService = TestUserService(users.get(3).getId());
    ...

    UserServiceTx txUserService = new UserServiceTx();
    txUserService.setTranscationManager(transactionManager);
    txUserSerive.setUserService(testUserService);
    ...
}&lt;/code&gt;&lt;/pre&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;언제든 트랜잭션 도입이 가능&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.2 고립된 단위 테스트&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;테스트가 실패했을 때 원인을 찾기 쉬움&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;UserService의 경우, 세 가지 타입의 의존 오브젝트가 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserDao, MailSender, PlatformTransactionManager&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserServiceTest는 UserService는 비즈니스 로직을 테스트
&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;li&gt;환경이 달라지면 다른 테스트 결과 발생&lt;/li&gt;
&lt;li&gt;수행속도가 느리고, UserService의 책임이 아닌 문제점을 파악할 가능성이 큼&lt;/li&gt;
&lt;/ul&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;테스트 대역 사용(Test Double)
&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;테스트를 위한 UserServiceImpl 고립
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에 구현한 MockUserDao는 정상적으로 수행되도록 도와주는 스텁의 기능에 더불어 부가적인 검증 기능까지 가진 목 오브젝트로 생성
&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;MockUserDao, MockMailSender, UserServiceTx를 이용하여 고립시키기&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void upgradeLevels() throws Expcetion {
    userDao.deleteAll();
    users.forEach(user -&amp;gt; userDao.add(user)); // DB Test Data Set-Up

    MockMailSender mockMailSender = new MockMailSender();
    userServiceImpl.setMailSender(mockMailSender); // Mock Object DI

    userService.upgradeLevels();

    checkLevelUpgraded(user.get(0), false);
    checkLevelUpgraded(user.get(1), true);
    checkLevelUpgraded(user.get(2), false);
    checkLevelUpgraded(user.get(3), true);
    checkLevelUpgraded(user.get(4), false); // DB에 저장된 결과 확인

    List&amp;lt;String&amp;gt; request = mockMailSender.getRequests();
    assertThat(request.size(), is(2));
    assertThat(request.get(0), is(user.get(1).getEmail()));
    assertThat(request.get(1), is(user.get(3).getEmail())); // 결과 확인
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserDao 목 오브젝트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 UserDao와 DB까지 의존하고있는 테스트도 목 오브젝트를 만들어서 적용하기&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public void upgardeLevels() {
    userDao.getAll() // 스텁으로서 역할 필요
        .filter(user -&amp;gt; canUpgradeLevel(user))
        .forEach(user -&amp;gt; upgradeLevel(user));
}

protected void upgradeLevel(User user) {
    user.upgradeLevel();
    userDao.update(user); // 목 오브젝트로서 역할 필요
    sendUpgradeEmail();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RequiredContructor
static class MockUserDao implements UserDao {
    private final List&amp;lt;User&amp;gt; users;
    private List&amp;lt;User&amp;gt; updated = new ArrayList&amp;lt;&amp;gt;();

    public List&amp;lt;User&amp;gt; getUpdated() {
        return this.updated;
    }

    public List&amp;lt;User&amp;gt; getAll() {
        return this.users;
    }

    public void update(User user) {
        updated.add(user;)
    }

    public void add(User user) { throw new UnsupportedOperationException(); }
    public void deleteAll(User user) { throw new UnsupportedOperationException(); }
    public User get(String id) { throw new UnsupportedOperationException(); }
    public int getCount() { throw new UnsupportedOperationException(); }
}&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;사용하지 않을 메소드는 UnsupportedOperationException을 던져서 지원하지 않는 기능임을 명시해주기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null로 반환하거나 빈 메소드로 둬도 되지만, 안전한 사용성을 위해 위처럼 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void upgradeLevels() throws Exception {
    UserServiceImpl userServiceImpl = new UserServiceImpl();

    MockUserDao mockUserDao = new MockUserDao(this.users);
    userServiceImpl.setUserDao(mockUserDao);

    // mailSender 주입 및 upgradeLevels 실행

    List&amp;lt;User&amp;gt; updated = mockUserDao.getUpdated(); // 목 오브젝트 리턴
    assertThat(updated.size(), is(2));
    checkUserAndLevel(update.get(0), &quot;zin0&quot;, Level.SILVER);
    checkUserAndLevel(updated.get(1), &quot;jinyoung&quot;, Level.GOLD);

    // mailSender 검증
}

private void checkUserAndLevel(User updated, String expectedId, Level expectedLevel) {
    assertThat(updated.getId(), is(expectedId));
    assertThat(updated.getLevel(), is(expectedLevel));
}&lt;/code&gt;&lt;/pre&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;DB를 이용하는 테스트와 목 오브젝트를 이용하는 테스트 수행시간에는 큰 차이가 존재
&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;/ul&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;하나의 단위에 초점을 맞춘 테스트(클래스, 메소드 등이 단위가 됨)&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;/li&gt;
&lt;li&gt;외부의 리소스를 사용하는 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단위 테스트 vs 통합 테스트
&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;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;테스트 대역을 이용하도록 테스트를 구축&lt;/li&gt;
&lt;li&gt;외부 리소스를 사용해야만 하는 테스트는 통합 테스트로 구축&lt;/li&gt;
&lt;li&gt;DAO는 DB까지 연동하는 테스트로 만드는 것이 효과적
&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;li&gt;DAO 테스트로 충분히 검증하면, DAO를 이용하는 코드는 DAO 역할을 스텁 or 목 오브젝트로 대체 가능&lt;/li&gt;
&lt;/ul&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;직접 코드 레벨의 DI를 사용하면서 단위 테스트를 하는게 좋지만, 추상적인 레벨을 테스트하는 경우 스프링 테스트 컨텍스트 프레임워크를 이용하여 통합 테스트 작성&lt;/li&gt;
&lt;/ul&gt;
&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;번거로움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mockito 프레임워크
&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;/li&gt;
&lt;li&gt;리턴 값을 지정하거나 강제로 예외를 던지게 설정&lt;/li&gt;
&lt;li&gt;DI로 테스트 도중 사용되도록 설정&lt;/li&gt;
&lt;li&gt;특정 메소드가 호출됐는지, 어떤 값을 가지고 몇 번 호출됐는지 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
public void mockUpgradeLevels() throws Exception {
    UserServiceImpl userServiceImpl = new UserServiceImpl();

    UserDao mockUserDao = mock(UserDao.class);
    when(mockUserDao.getAll()).thenReturn(this.users);
    userServiceImpl
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>고립 테스트</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/367</guid>
      <comments>https://zin0-0.tistory.com/367#entry367comment</comments>
      <pubDate>Sun, 11 Jul 2021 17:32:40 +0900</pubDate>
    </item>
    <item>
      <title>5장) 5.3 서비스 추상화와 단일 책임 원칙 ~ 5.5 정리</title>
      <link>https://zin0-0.tistory.com/366</link>
      <description>&lt;h2&gt;5장 서비스 추상화&lt;/h2&gt;
&lt;h3&gt;5.3 서비스 추상화와 단일 책임 원칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;수직, 수평 계층구조와 의존관계&lt;ul&gt;
&lt;li&gt;기술과 서비스에 대해 추상화 기법 적용&lt;ul&gt;
&lt;li&gt;UserDao와 UserService가 각각 담당하는 코드의 기능적인 관심에 따라 분리, 독자적으로 확장이 가능하도록 작업 &lt;ul&gt;
&lt;li&gt;같은 계층에서 수평적인 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 추상화&lt;ul&gt;
&lt;li&gt;비즈니스 로직과 그 하위에서 동작하는 로우레벨의 트랜잭션 기술이라는 아예 다른 계층의 특성을 갖는 코드를 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단일 책임 원칙&lt;ul&gt;
&lt;li&gt;하나의 모듈은 한가지 책임을 가져야함&lt;ul&gt;
&lt;li&gt;== 하나의 모듈이 바뀌는 이유는 한 가지여야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserService 예시&lt;ul&gt;
&lt;li&gt;JDBC Connection 메소드를 직접 사용하는 트랜잭션 코드가 있던 경우&lt;ul&gt;
&lt;li&gt;두 가지의 책임을 가짐&lt;ul&gt;
&lt;li&gt;사용자 레벨을 어떻게 관리할 것인가&lt;/li&gt;
&lt;li&gt;트랜잭션을 어떻게 관리할 것인가&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&gt;
&lt;li&gt;사용자 관리 로직에 대해서만 관심&lt;/li&gt;
&lt;li&gt;트랜잭션에 대해서는 책임이 없음&lt;/li&gt;
&lt;li&gt;단일 책임 원칙을 지킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장점&lt;ul&gt;
&lt;li&gt;변경이 필요할 때 수정 대상이 명확&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스를 도입하고 DI로 연결해야 하며, 그 결과로 단일 책임 원칙과 개방 폐쇄 원칙도 잘 지키고, 모듈 간 결합도가 낮아서 서로의 변경이 영향 X, 같은 이유로 변경이 단일 책임에 집중되는 응집도 높은 코드를 작성하게 됨&lt;ul&gt;
&lt;li&gt;스프링 DI의 장점&lt;ul&gt;
&lt;li&gt;애플리케이션 로직의 종류에 따른 수평적인 구분이든, 로직과 기술의 수직적 구분이든 모두 결합도가 낮으며, 서로 영향을 주지 않고 자유롭게 확장될 수 있는 구조를 만들어 줌&lt;ul&gt;
&lt;li&gt;적절하게 책임과 관심이 다른 코드 분리&lt;/li&gt;
&lt;li&gt;서로 영향이 없도록 다양한 추상화 기법 도입&lt;/li&gt;
&lt;li&gt;애플리케이션 로직과 기술/환경 분리&lt;/li&gt;
&lt;li&gt;디자인 패턴 적용&lt;/li&gt;
&lt;li&gt;테스트하기 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 메일 서비스 추상화&lt;/h3&gt;
&lt;p&gt;레벨이 업그레이드되는 사용자에게 안내 메일을 발송해달라는 요구 사항이 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JavaMail을 이용한 메일 발송 기능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DB의 User 테이블에 email 필드 추가, User 클래스에 email 프로퍼티 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserDao의 userMapper와 insert(), update()에 email 필드 처리 코드 추가 및 테스트 코드 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JavaMail 발송&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;protected void upgradeLevel(User user) {
    user.upgradeLevel();
    userDao.update(user);
    sendUpgradeEamil(); // JavaMail을 이용한 이메일 전송 메소드
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SMTP 프로토콜을 지원하는 메일 전송 서버가 준비되었다면, 위의 코드는 정상적으로 작동&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JavaMail이 포함된 코드의 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;개발 중인 경우, 메일 서버가 준비되지 않으면 &lt;code&gt;MessagingException&lt;/code&gt;을 마주함&lt;ul&gt;
&lt;li&gt;메일 발송의 경우 부하가 크고 서버에 부담이 됨&lt;/li&gt;
&lt;li&gt;테스트 메일이 실제로 전송되는 경우가 생김&lt;/li&gt;
&lt;li&gt;SMTP로 메일 전송 요청을 받으면 정상이라고 테스트 가능&lt;ul&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;p&gt;테스트를 위한 서비스 추상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JavaMail을 이용한 테스트의 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaMail의 API는 위에서 제시한 테스트용 메일 서버를 사용하는 것이 불가능&lt;ul&gt;
&lt;li&gt;핵심 API가 인터페이스로 만들어져 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링이 제공하는 JavaMail에 대한 추상화 기능 이용&lt;ul&gt;
&lt;li&gt;기본적으로 JavaMail을 사용해 메일 발송 기능을 제공하는 JavaMailSenderImpl을 이용하면되지만, 테스트 시 메일 발송을 하지 않도록 하기 위해 인터페이스를 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;메일 발송 기능 추상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface MailSender {
    void send(SimpleMailMessage simpleMessage) throws MailException;
    void send(SimpleMailMessage[] simpleMessages) throws MailException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserService {
    ...
    private MailSender mailSender;

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    private void sendUpgradeEmail(User user) {
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo(user.getEmail());
        mailMessage.setFrom(&amp;quot;zin0@test.com&amp;quot;);
        mailMessage.setSubject(&amp;quot;Upgrade 안내&amp;quot;);
        mailMessage.setText(&amp;quot;사용자님의 등급이 &amp;quot; + user.getLevel().name() + &amp;quot;로 업그레이드 됐습니다.&amp;quot;);
        this.mailSender.send(mailMessage);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;자바 메일에서 처리하는 각종 예외 (AddressException, MessagingException, UnsupportedEncodingException) 를 MailException이라는 런타임 예외로 포장해서 던져줌&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트용 메일 발송 오브젝트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트용으로 MailSender 인터페이스를 구현&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class DummyMailSender implements MailSender {
    public void send(SimpleMailMessage mailMessage) throws MailException {}

    public void send(SimpleMailMessage[] mailMessages) throws MailException {}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;메일 발송 기능 자체에 대한 테스트는 MailSender에 대한 별도의 학습 테스트 or 메일 서버 설정 점검용 테스트를 통해 확인&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;pubilc class UserServiceTest {
    @Autowired
    MailSender mailSender;

    @Test
    public void upgradeAllOrNothing() throws Exception {
        ...
        testUserService.setMailSender(mailSender);
    }
}&lt;/code&gt;&lt;/pre&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;ul&gt;
&lt;li&gt;&lt;p&gt;테스트와 서비스 추상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서비스 추상화&lt;ul&gt;
&lt;li&gt;기능은 유사하나 사용 방법이 다른 로우레벨의 다양한 기술에 대해 추상 인터페이스와 일관성 있는 접근 방법을 제공해주는 것&lt;/li&gt;
&lt;li&gt;테스트를 어렵게 만드는 건전하지 않은 방식으로 설계뙨 API를 사용할 때도 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UserService와 같은 애플리케이션 계층의 코드는 아래 계층에 대한 관심이 없이 메일 발송을 요청한다는 기본 기능에 충실하게 작성&lt;ul&gt;
&lt;li&gt;비즈니스 로직이 바뀌지 않는한 수정할 필요가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메일 발송에 대한 트랜잭션 작업&lt;ul&gt;
&lt;li&gt;메일을 업그레이드할 사용자를 발견할 때마다 발송하지 않고 발송 대상을 별도의 목록에 저장&lt;ul&gt;
&lt;li&gt;메일 저장 리스트 등을 파라미터로 계속 가지고 다녀야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MailSender를 확장해서 메일 전송에 트랜잭션 개념을 적용&lt;ul&gt;
&lt;li&gt;MailSender를 확장한 클래스에 업그레이드 작업 이전에 새로운 메일 전송 작업 시작을 알려주고, 이 때 부터 send() 메소드를 호출해도 발송하지 않고 저장해둠&lt;/li&gt;
&lt;li&gt;업그레이드 작업이 끝나면 트랜잭션 기능을 가진 MailSender에 지금까지 저장된 메일을 모두 발송하고 예외가 발생하면 모두 취소&lt;/li&gt;
&lt;li&gt;서로 다른 종류의 작업을 분리해서 처리하기 때문에 더 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부의 리소스와 연동하는 대부분 작업은 추상화의 대상이 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트 대역&lt;/p&gt;
&lt;p&gt;테스트할 대상이 의존하고 있는 오브젝트를 DI를 통해 바꿔치기&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;의존 오브젝트 변경을 통한 테스트 방법&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 오브젝트가 사용하는 오브젝트를 DI에서는 의존 오브젝트라고 부름&lt;/li&gt;
&lt;li&gt;테스트 대상인 오브젝트가 의존 오브젝트를 가지고 있기 때문에 발생하는 여러 문제점이 존재&lt;ul&gt;
&lt;li&gt;간단한 오브젝트의 코드를 테스트하는데 거창한 작업이 뒤따르는 경우&lt;/li&gt;
&lt;li&gt;스프링 DI를 통해 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 대역의 종류와 특징&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트 대역(Test Double)&lt;ul&gt;
&lt;li&gt;테스트 환경을 만들어주기 위해, 테스트 대상이 되는 오브젝트의 기능에만 충실하게 수행하면서 빠르게, 자주 테스트를 실행할 수 있도록 사용하는 오브젝트&lt;ul&gt;
&lt;li&gt;MailSender 인터페이스를 구현한 것들, DataSource 등이 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 스텁&lt;ul&gt;
&lt;li&gt;대표적인 Test Double&lt;/li&gt;
&lt;li&gt;테스트 대상 오브젝트의 의존객체로서 존재하고, 코드가 정상적으로 수행할 수 있도록 도움&lt;/li&gt;
&lt;li&gt;테스트 시에 DI를 통해 테스트 스텁으로 변경&lt;ul&gt;
&lt;li&gt;DummyMailSender가 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;목 오브젝트(Mock Object)&lt;ul&gt;
&lt;li&gt;테스트 스텁이 결과를 돌려줘야하는 경우(리턴 값이 있는 메소드 이용)&lt;/li&gt;
&lt;li&gt;테스트 대상의 간접적인 출력 결과를 검증&lt;/li&gt;
&lt;li&gt;테스트 대상 오브젝트와 의존 오브젝트 사이에서 일어나는 일을 검증하도록 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;목 오브젝트를 이용한 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위 예시의 upgradeAllOrNothing() 테스트의 경우 메일 전송 여부에 관심 X&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DummyMailSender가 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사용자 레벨 업그레이드 결과를 확인하는 upgradeLevels() 테스트의 경우, 메일 전송 자체에 대한 검증 필요&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static class MockMailSender implements MailSender {
    private List&amp;lt;String&amp;gt; requests = new ArrayList&amp;lt;&amp;gt;();

    public List&amp;lt;String getRequests() {
        return this.requests;
    }

    public void send(SimpoleMailMessage mailMessage) throws MailException {
        requests.add(mailMessage.getTo()[0]);
    }
    public void send(SimpoleMailMessage[] mailMessages) throws MailException {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
@DirtiesContext
public void upgradeLevels() throws Exception {
    userDao.deleteAll();
    users.forEach(user -&amp;gt; userDao.add(user));

    MockMailSender mockMailSender = new MockMailSender();
    userService.setMailSender(mockMailSender);

    userService.upgradeLevels();

    checkLevelUpgraded(user.get(0), false);
    checkLevelUpgraded(user.get(1), true);
    checkLevelUpgraded(user.get(2), false);
    checkLevelUpgraded(user.get(3), true);
    checkLevelUpgraded(user.get(4), false);

    List&amp;lt;String&amp;gt; request = mockMailSender.getRequests();
    assertTaht(request.size(), is(2));
    assertTaht(request.get(0), is(users.get(1).getEmail()));
    assertTaht(request.get(1), is(users.get(3).getEmail()));
}&lt;/code&gt;&lt;/pre&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;h3&gt;5.5 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;비즈니스 로직과 데이터 액세스 로직은 깔끔하게 분리&lt;ul&gt;
&lt;li&gt;비즈니스 로직 코드는 내부적 책임과 역할에 따라 깔끔하게 메소드로 정리&lt;/li&gt;
&lt;li&gt;이를 위해 DAO의 기술 변화에 서비스 계층 코드가 영향이 없도록 인터페이스와 DI를 활용하여 결합도를 낮춰야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DAO를 사용하는 비즈니스 로직에는 단위 작업을 보장해주는 트랜잭션이 필요&lt;ul&gt;
&lt;li&gt;트랜잭션 경계설정&lt;ul&gt;
&lt;li&gt;트랜잭션의 시작과 끝을 지정하는 일&lt;/li&gt;
&lt;li&gt;주로 비즈니스 로직 안에서 일어남&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링이 제공하는 트랜잭션 동기화 기법을 활용&lt;/li&gt;
&lt;li&gt;트랜잭션 방법에 따라 비즈니스 로직 코드가 함께 변경되면 단일 책임 원칙에 위배&lt;ul&gt;
&lt;li&gt;DAO가 사용하는 특정 기술에 대한 강한 결합을 만들어냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서비스 추상화&lt;ul&gt;
&lt;li&gt;로우레벨의 트랜잭션 기술과 API의 변화에 상관없이 일관된 API를 가진 추상화 계층 도입&lt;/li&gt;
&lt;li&gt;테스트가 어려운 기술에 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 대역(Test Double)&lt;ul&gt;
&lt;li&gt;테스트 대상이 사용하는 의존 오브젝트를 대체하도록 만든 오브젝트&lt;/li&gt;
&lt;li&gt;테스트 스텁&lt;ul&gt;
&lt;li&gt;테스트 대상 오브젝트가 원활하게 동작할 수 있도록 도우면서 간접적인 정보를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;목 오브젝트&lt;ul&gt;
&lt;li&gt;테스트 대상으로부터 전달받은 정보를 검증할 수 있도록 설계된 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>DI</category>
      <category>객체 지향</category>
      <category>서비스 추상화</category>
      <category>트랜잭션</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/366</guid>
      <comments>https://zin0-0.tistory.com/366#entry366comment</comments>
      <pubDate>Fri, 2 Jul 2021 14:11:19 +0900</pubDate>
    </item>
    <item>
      <title>5장) 5.2 트랜잭션 서비스 추상화</title>
      <link>https://zin0-0.tistory.com/365</link>
      <description>&lt;h2&gt;5장 서비스 추상화&lt;/h2&gt;
&lt;h3&gt;5.2 트랜잭션 서비스 추상화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;트랜잭션&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;더 이상 나눌 수 없는 단위 작업&lt;/li&gt;
&lt;li&gt;트랜잭션 커밋&lt;ul&gt;
&lt;li&gt;모든 SQL 수행 작업이 다 성공적으로 마무리됐다고 DB에 알려줘서 작업을 확정시키는 작업&lt;/li&gt;
&lt;li&gt;변경 내용이 DB에 반영되도록 설정하는 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션 롤백&lt;ul&gt;
&lt;li&gt;SQL 수행 작업 중 뒤 차례의 수행에 문제가 발생한 경우에 앞에서 처리한 SQL 수행 작업도 취소시키는 작업&lt;/li&gt;
&lt;li&gt;DB에 변경 내용을 변경 이전으로 되돌리는 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;트랜잭션 경계 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;트랜잭션 경계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션이 시작되고 끝나는 위치&lt;/li&gt;
&lt;li&gt;transaction 시작 선언 이후, commit() or rollback() 으로 트랜잭션을 종료하는 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDBC 트랜잭션의 트랜잭션 경계 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 Connection을 사용하다가 닫는 사이에 일어남&lt;/li&gt;
&lt;li&gt;트랜잭션의 시작과 종료가 Connection 오브젝트를 통해 이루어짐&lt;/li&gt;
&lt;li&gt;자동 커밋 옵션을 false로 만들어주어 트랜잭션을 시작&lt;/li&gt;
&lt;li&gt;로컬 트랜잭션&lt;ul&gt;
&lt;li&gt;하나의 DB 커넥션 안에서 만들어지는 트랜잭션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserService와 UserDao의 트랜잭션 문제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일반적으로 트랜잭션은 커넥션보다 라이프 사이클이 짧음&lt;/li&gt;
&lt;li&gt;JdbcTemplate 메소드를 사용하는 UserDao는 메소드마다 하나씩 독립적인 트랜잭션으로 실행됨&lt;/li&gt;
&lt;li&gt;예시&lt;ul&gt;
&lt;li&gt;upgradeLevels() 메소드에서 세 번에 걸쳐 UserDao의 update()를 호출&lt;/li&gt;
&lt;li&gt;매번 새로운 DB 커넥션과 트랜잭션을 만들어 사용&lt;/li&gt;
&lt;li&gt;첫 번째 수행이 성공하고, 두 번째 호출 시점에서 오류가 발생해도, 첫 번째 커밋한 트랜잭션의 결과가 DB에 반영됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 엑세스 코드를 DAO로 만들어 분리해놓은 경우, DAO 메소드를 호출할 때마다 하나의 새로운 트랜잭션이 만들어지는 구조가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;비즈니스 로직 내의 트랜잭션 경계설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DAO 메소드 안으로 upgradeLevels() 메소드 내용을 옮기면 하나의 트랜잭션으로 관리가 됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하지만, 비즈니스 로직과 데이터 로직을 한데 묶는 좋지 않은 결과 초래&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;트랜잭션의 경계설정 작업을 UserService쪽으로 위임하는 방법으로 해결&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserService에 트랜잭션 시작과 종료를 담당하는 최소한의 코드만 가져오게 만들면, 책임이 다른 코드를 분리해둔 채로 트랜잭션 문제를 해결할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void upgradeLevels() throws Exception {
    (1) DB Connection 생성
    (2) 트랜잭션 시작
     try {
         (3) DAO 메소드 호출
         (4) 트랜잭션 커밋
     } catch (Exception exception) {
         (5) 트랜잭션 롤백
         throw exception;
     } finally {
         (6) DB Connection 종료
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Connection 오브젝트를 가지고 데이터 엑세스 작업을 진행하는 코드가 UserDao의 update 메소드 안에 있어야함&lt;/li&gt;
&lt;li&gt;DAO 메소드 호출마다 Connection 오브젝트를 마라메터로 전달해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserService 트랜잭션 경계설정의 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 코드를 수정하면 아래와 같은 문제가 발생&lt;ul&gt;
&lt;li&gt;리소스의 깔끔한 처리를 가능하게 했던 JdbcTemplate을 더 이상 활용할 수 없음&lt;/li&gt;
&lt;li&gt;DAO의 메소드와 비즈니스 로직을 담고 있는 UserService의 메소드에 Connection 파라메터가 추가돼야함&lt;/li&gt;
&lt;li&gt;Connection 파라메터가 UserDao 인터페이스 메소드에 추가되면 UserDao는 액세스 기술에 독립적일 수 없음&lt;ul&gt;
&lt;li&gt;UserDao 인터페이스가 바뀌고, 그에 따라 UserService도 함께 수정돼야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DAO 메소드에 Connection 파라메터를 받게 하면  테스트 코드에도 영향&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;p&gt;트랜잭션 동기화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링이 제공하는 기능을 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 동기화(Transaction Synchronization)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connection 파라메터 제거&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;upgradeLevels 메소드가 트랜잭션 경계 설정을 해야하기 때문에, 트랜잭션 시작과 종료를 관리&lt;/li&gt;
&lt;li&gt;트랜잭션 동기화를 사용&lt;ul&gt;
&lt;li&gt;UserService에서 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 호출되는 DAO의 메소드에서는 저장된 Connection을 가져다 사용하게 하는 방식&lt;/li&gt;
&lt;li&gt;UserService에서 Connection 생성 -&amp;gt; 트래잭션 시작 -&amp;gt; update 메소드 호출 -&amp;gt; JdbcTemplate 메소드에서 트랜잭션 동기화 저장소에 현재 시작된 트랜잭션 Connection 오브젝트 존재 확인 후 가져옴 -&amp;gt; 로직 진행 및 반복 -&amp;gt; 모든 작업이 정상적으로 종료되면 commit으로 트랜잭션 완료 / 예외 상황이라면 rollback으로 트랜잭션을 종료&lt;/li&gt;
&lt;li&gt;트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장하고 관리&lt;ul&gt;
&lt;li&gt;멀티 쓰레드 환경에서 충돌 우려 X&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;p&gt;트랜잭션 동기화 적용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//UserService
private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

public void upgradeLevels() throws Exception {
    TransactionSynchronizationManager.initSynchronization(); // 동기화
    Connection c = DataSourceUtils.getConnection(dataSource); // connection 생성 및 동기화
    c.setAutoCommit(false); // connection 생성 및 동기화

    try {
        userDao.getAll()
            .filter(user -&amp;gt; canUpgradeLevel(user))
            .forEach(user -&amp;gt; upgradeLevel(user));
        c.commit();
    } catch (Exception exception) {
        c.rollback();
        throw exception;
    } finally {
        DataSourceUtils.releaseConnection(c, dataSource); // connection close
        TransactionSynchronizationManager.unbindResource(this.dataSource); // 동기화 작업 종료
        TranscationSynchronizationManager.clearSynchronization(); // 동기화 정리
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;UserService에서 DB 커넥션을 직접 다룰 때 DataSource가 필요 ~&amp;gt; DI&lt;/li&gt;
&lt;li&gt;스프링 제공 트랜잭션 동기화 관리 클래스 ~&amp;gt; TransactionSynchronizationManager&lt;/li&gt;
&lt;li&gt;DataSourceUtils의 getConnection 메소드 ~&amp;gt; Connection 객체 생성 및 트랜잭션 동기화에 사용하도록 저장소에 바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JdbcTemplate 트랜잭션 동기화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JdbcTemplate은 Connection에 대해 영리하게 동작&lt;ul&gt;
&lt;li&gt;미리 생성돼서 트랜잭션 동기화 저장소에 등록된 DB 커넥션이나 트랜잭션이 없으면 직접 커넥션을 생성하고 트랜잭션을 시작 및 작업 진행&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;p&gt;트랜잭션 서비스 추상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;기술과 환경에 종속되는 트랜잭션 경계설정 코드&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DB 종류를 여러 개 이용하는 경우 트랜잭션 처리 코드를 담은 UserService에서 문제가 발생&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;글로벌 트랜잭션을 사용해서 트랜잭션을 관리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;자바는 JTA(JavaTransactionAPI)를 제공&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB와 메시징 서버를 제어하고 관리하는 각 리소스 매니저와 XA 프로토콜을 통해 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;InitialContext context = new InitialContext();
UserTransaction transaction = (UserTransaction)context.lookup(USER_TX_JNDI_NAME);

trasaction.begin();
Connection connection = dataSource.getConnection();

try {
    // Data Access Code
    taransaction.commit();
} catch (Exception exception) {
    transaction.rollback();
    throw exception;
} finally {
    connection.close();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;트랜잭션 처리 방법은 별로 달라진게 없지만, JDBC 로컬 트랜잭션을 JTA 글로벌 트랜잭션으로 바꾸는 경우 UserService를 수정해야하는 문제점이 존재&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 종류마다 트랜잭션 관리 코드가 다르기 때문에 계속해서 변경해야함&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;p&gt;트랜잭션 API의 의존관계 문제와 해결책&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserDao가 DAO 패턴을 사용해 구현 데이터 엑세스 기술을 유연하게 바꿔 사용하도록 했지만, UserService에 트랜잭션 경계 설정을 하게되면 다시 특정 데이터 엑세스 기술에 종속되는 구조가 존재&lt;/li&gt;
&lt;li&gt;여러 트랜잭션 경계 설정 기술 사용 방법에 공통점으로 추상화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링의 트랜잭션 서비스 추상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void upgradeLevels() {
    PlatformTranscationManager transactionManager = new DataSourceTransactionManager(dataSOurce);
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

    try {
        userDao.getAll()
            .filter(user -&amp;gt; canUpgradeLevel(user))
            .forEach(user -&amp;gt; upgradeLevel(user));
    } catch (RuntimeException e) {
        transactionManager.rollback(status);
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;스프링이 제공하는 트랜잭션 경계설정 추상 인터페이스 PlatformTransactionManger를 이용&lt;/li&gt;
&lt;li&gt;필요에 따라 트랜잭션 매니저가 DB 커넥션을 가져오는 작업도 같이 수행&lt;/li&gt;
&lt;li&gt;트랜잭션은 TransactionStatus 타입 변수에 저장되어 조작이 필요한 경우, PlatformTransactionManger 메소드의 파라미터로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;트랜잭션 기술 설정의 분리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JTA를 이용하는 글로벌 트랜잭션으로 변경&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PlatformTransactionManger 구현 클래스를 DataSourceTransactionManager -&amp;gt; JTATransactionManger로 바꿈&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;어떤 트랜잭션 매니저 구현 클래스를 사용할지 UserService 코드가 알고있는 것은 DI 원칙에 위배&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;외부에서 스프링 DI를 통해 제공받도록 수정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링에서 제공하는 PlatformTransactionManager는 싱글톤으로 사용이 가능&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@AllArgsContructor
public class UserService {
    ...
    private PlatformTransactionManager transactionManger;

    public void upgradeLevels() {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            userDao.getAll()
                .filter(user -&amp;gt; canUpgradeLevel(user))
                .forEach(user -&amp;gt; upgradeLevel(user));
            this.transactionManager.commit(status);
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;PlatformTransactionManager의 구현 클래스에 따라 주입을 다르게 해주면서 사용&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;h3&gt;정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션과 관련해서 spring boot에서는 &lt;code&gt;@Transactional&lt;/code&gt;을 제공해서, 위의 과정을 쉽게 이용 가능&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>5장</category>
      <category>토비 스프링</category>
      <category>트랜잭션</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/365</guid>
      <comments>https://zin0-0.tistory.com/365#entry365comment</comments>
      <pubDate>Sat, 26 Jun 2021 18:12:21 +0900</pubDate>
    </item>
    <item>
      <title>5장) 5.1 사용자 레벨 관리 기능 추가</title>
      <link>https://zin0-0.tistory.com/364</link>
      <description>&lt;h2&gt;5장 서비스 추상화&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;지금까지 만든 DAO에 트랜잭션을 적용하면서 스프링이 어떻게 성격이 비슷한 여러 종류의 기술을 추상화하고 일관된 방법으로 사용하도록 지원하는지 살펴보기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.1 사용자 레벨 관리 기능 추가&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;지금까지 만든 UserDao는 비즈니스 로직을 가지고 있지 않다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사용자 관리 기능을 넣어 활동 내역을 참고한 레벨 조정 기능을 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자 레벨 BASIC, SILVER, GOLD&lt;/li&gt;
&lt;li&gt;처음 가입 시, BASIC 등급, 활동에 따라 한 단계씩 업그레이드&lt;ul&gt;
&lt;li&gt;가입 후 50회 이상 로그인 ~&amp;gt; SILVER&lt;/li&gt;
&lt;li&gt;SILVER 레벨이면서 30번 이상 추천 ~&amp;gt; GOLD&lt;/li&gt;
&lt;li&gt;사용자 레벨은 일정한 주기를 가지고 일괄 진행&lt;ul&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;p&gt;필드 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;LEVEL 이늄&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;3 단계기 때문에 DB에 tinyInt로 간단하게 값을 넣어줌&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;level 타입이 int면 다른 종류의 정보를 넣는 실수를 해도 컴파일러가 체크해주지 못함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;숫자 타입을 직접 사용하기보다 자바5 이상에서 지원하는 ENUM을 이용해서 안전하고 편리하게 오브젝트로 관리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum Level {
    BASIC(1), SILVER(2), GOLD(3);

    private final int value;

    Level(int value) {
        this.value = value;
    }
    // set &amp;amp; get method
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User 클래스에 Level 타입의 변수와 int형 변수인 로그인 횟수와 추천 수를 추가해서 사용자 관리 기능을 대비&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDaoTest 수정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;새로운 기능을 추가하면서 우선, 테스트 픽스처로 만든 user 객체들에 새로 추가된 세 필드의 값을 추가&lt;/li&gt;
&lt;li&gt;생성자 파라메터에도 새로운 세 필드 추가&lt;/li&gt;
&lt;li&gt;오브젝트 필드 값이 모두 같은지 비교하는 checkSameUser() 메소드에 새로운 필드를 비교하는 코드를 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDaoJdbc 수정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add() 메소드의 SQL과 userMapper에 추가된 필드를 적용&lt;/li&gt;
&lt;li&gt;Level &lt;code&gt;ENUM&lt;/code&gt;은 오브젝트이므로 DB에 저장될 수 있는 SQL 값이 아님&lt;ul&gt;
&lt;li&gt;따라서, 미리 만들어둔 get 메소드를 통해 정수형 값으로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;조회하는 경우에는 DB의 정수형 값으로 부터 미리 만들어둔 get 메소드를 통해 Level ENUM 오브젝트로 받아서, set 메소드에 넣어줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사용자 수정 기능 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;수정 기능 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;픽스처 오브젝트를 하나 등록하고, 해당 오브젝트의 id를 제외한 필드 내용을 바꾼 후 update를 호출&lt;/li&gt;
&lt;li&gt;id로 조회한 값과 수정한 픽스처 오브젝트를 비교&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDao와 UserDaoJdbc 수정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;update 메소드 추가&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void update(User user) {
    this.jdbcTemplate.update(&amp;quot;update users set name = ?, password = ?, level = ?, login = ?, recommand = ? where id = ?&amp;quot;, user.getName(), user.getPassword(), user.getLevel().intValue(), user.getLogin(), user.getRecommend(), user.getId());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;수정 테스트 보완&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가장 많은 실수가 일어나는 곳이 SQL 문장이기 때문에 위의 수정 기능 테스트만으로는 Query문이 잘못 되었는지 파악하기 어려운 에러 존재&lt;ul&gt;
&lt;li&gt;update 문장에서 where 절을 빠뜨렸어도, 이 테스트는 통과&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보완하는 두 방법&lt;ul&gt;
&lt;li&gt;update()가 돌려주는 리턴 값 확인&lt;ul&gt;
&lt;li&gt;영향 받은 row의 수를 리턴&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserService.upgradeLevels()&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;사용자 관리 로직을 두기 위해 UserService 생성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;userDao 빈을 DI 받도록 설정&lt;ul&gt;
&lt;li&gt;DI 받기 위해서는 UserService도 빈으로 등록되어야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;upgradeLevels() 메소드&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void upgradeLevels() {
    userDao.getAll().forEach(user -&amp;gt; {
        Boolean changed = false;
        if (user.getLevel() == Level.BASIC &amp;amp;&amp;amp; user.getLogin() &amp;gt;= 50) {
            user.setLevel(Level.SILVER);
            changed = true;
        } else if (user.getLevel() == Level.SILVER &amp;amp;&amp;amp; user.getRecommand &amp;gt;= 30) {
            user.setLevel(Level.GOLD);
            changed = true;
        }
        if (changed) {
            userDao.update(user);
        }
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;upgradeLevels() 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자 레벨 세 가지, GOLD를 제외한 두 레벨은 업그레이드가 되는 경우와 아닌 경우를 살펴보면, 최소 다섯 경우를 테스트하는 코드를 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserService.add()&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;사용자 관리 비즈니스 로직에서 처음 가입하는 사용자가 BASIC으로 설정돼야 함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;생성 시, 레벨이 정해진 경우와 비어있는 경우를 고려&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void add(User user) {
    if (user.getLevel() == null) {
        user.setLevel(Level.BASIC);
    }
    userDao.add(user);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;레벨이 비어있는 경우와 정해진 경우에 대한 케이스를 만들어 테스트를 진행&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;코드 개선&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;간단한 비즈니스 로직을 테스트하는데 DAO와 DB까지 모두 동원되는 것이 부적절&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;코드 검토 사항&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;중복 부분이 있는가?&lt;/li&gt;
&lt;li&gt;이해하기 불편하지 않은가?&lt;/li&gt;
&lt;li&gt;자신의 역할에 맞게 짜여있는가?&lt;/li&gt;
&lt;li&gt;변경이 일어나면 어떤 사항들이 있고, 쉽게 대응할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;upgradeLevels() 메소드의 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for 루프 속에 if/elseif 블록이 가독성이 좋지 않음&lt;/li&gt;
&lt;li&gt;성격이 다른 로직이 한데 섞여있음&lt;ul&gt;
&lt;li&gt;레벨 파악 로직, 업그레이드 조건 로직, 다음 단계 파악 로직, 업그레이드 작업 로직, 임시 플래그 등&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;p&gt;upgradeLevels() 리팩토링&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void upgradeLevels() {
    userDao.getAll().filter(user -&amp;gt; canUpgradeLevel(user))
            .forEach(user -&amp;gt; upgradeLevel(user));
}

private boolean canUpgradeLevel(User user) {
    Level currentLevel = user.getLevel();
    switch (currentLevel) {
        case BASIC : 
            return user.getLogin() &amp;gt;= 50;
        case SILVER :
            return user.getRecommend() &amp;gt;= 30;
        case GOLD :
            return false;
        default : throw new IllegalArgumentException(&amp;quot;Unknown Level : &amp;quot; + currentLevel);
    }
}

private void upgradeLevel(User user) {
    if (user.getLevel() == Level.BASIC) {
        user.setLevel(Level.SILVER);
    } else if (user.getLevel() == Level.SILVER) {
        user.setLevel(Level.GOLD);
    }
    userDao.update(user);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;역할과 책임이 나누어졌지만, 여전히 upgradeLevel 메소드가 복잡함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다음 단계가 무엇인지 로직과 level 필드를 변경해주는 로직이 함께 있고 노골적으로 드러남&lt;/li&gt;
&lt;li&gt;예외 상황에 대한 처리가 없음&lt;/li&gt;
&lt;li&gt;레벨이 늘어나면 if 블록이 늘어나고, Level 이외의 필드를 update하면 조건이 길어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;upgradeLevel() 분리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;레벨의 순서와 다음 단계 레벨이 무엇인지 결정하는 일은 Level에게 위임&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum Level {
    GOLD(3, null), SILVER(2, GOLD), BASIC(1, SILVER);

    private final int value;
    private final int next;
    Level(int value, Level next) {
        this.value = value;
        this.next = next;
    }
    // get set 메소드
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User 내부 정보가 변경되는 것은 UserService보다 User 스스로 다루는게 적절&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// User Class
public void upgradeLevel() {
    Level nextLevel = this.level.nextLevel();
    if (nextLevel == null) {
        throw new IllegalStateException(this.level + &amp;quot; 업그레이드 불가&amp;quot;);
    }
    this.level = nextLevel;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;업그레이드 및 업그레이드가 불가한 상황에 대한 예외처리&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// User Service
private void upgradeLevel(User user) {
    user.upgradeLevel();
    userDao.update(user);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;if 문장이 들어있던 코드보다 간결하고 작업 내용이 명확해짐&lt;/li&gt;
&lt;li&gt;책임 분리가 깔끔하게 변경됨&lt;/li&gt;
&lt;li&gt;변경이 필요할 때 어디를 수정할지 명확해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;간단한 로직 메소드 테스트&lt;ul&gt;
&lt;li&gt;새로운 기능과 로직 추가 가능성을 고려해서 테스트를 작성하는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;upgradeLevel() 메소드 테스트 작성&lt;ul&gt;
&lt;li&gt;Level 이늄에 정의된 모든 레벨을 가져와서 User에 설정해두고, upgradeLevel()을 실행해서 다음 레벨로 바뀌는지 테스트&lt;/li&gt;
&lt;li&gt;다음 단계가 null인 경우는 제외(GOLD인 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserServiceTest 개선&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;기존 테스트에서는 checkLevel() 메소드를 호출할 때 일일이 다음 단계를 넣어줬지만, 이는 중복 코드&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;boolean타입의 upgraded flag를 파라메터로 전달받고, true인 경우 업그레이드 일어났는지 확인하고, false인 경우 업그레이드가 안일어났는지 확인하는 메소드를 생성해서 테스트&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;중복되는 숫자(상수)의 중복 값을 상수로 변경&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static final int MIN_LOGCOUNT_FOR_SILVER = 50;
public static final int MIN_RECCOMEND_FOR_GOLD = 30;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트코드 뿐만 아니라 애플리케이션 코드 모두 위의 상수로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;기준이 되는 값 등 애플리케이션에서 중요한 특정 값은 상수로 빼서 관리하는 것이 더욱 직관적이고, 수정에 용이&lt;/p&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;h3&gt;정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;테스트 코드의 중요성&lt;ul&gt;
&lt;li&gt;빠르게 실행 가능한 포괄적인 테스트를 만들어두면 기능의 추가 &amp;amp; 수정이 일어날 때, 빠르고 편리하고 비교적 안정적으로 작업할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유연한 정책 변경(DI 활용)&lt;ul&gt;
&lt;li&gt;위의 예시에서 이벤트 기간에 레벨 업그레이드 정책 변경을 유연하게 바꿔보기&lt;ul&gt;
&lt;li&gt;사용자 업그레이드 정책을 UserService에서 분리&lt;/li&gt;
&lt;li&gt;분리된 업그레이드 정책을 담은 오브젝트는 DI를 통해 UserService에 주입&lt;/li&gt;
&lt;li&gt;평상시 정책을 UserService에서 사용하다가, 이벤트 기간동안 이벤트 업그레이드 정책을 주입하면서 사용하면 편리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>5장</category>
      <category>Enum</category>
      <category>서비스 추상화</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/364</guid>
      <comments>https://zin0-0.tistory.com/364#entry364comment</comments>
      <pubDate>Sat, 26 Jun 2021 18:11:25 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch 기초 정리</title>
      <link>https://zin0-0.tistory.com/363</link>
      <description>&lt;h2&gt;Spring Batch&lt;/h2&gt;
&lt;h3&gt;Batch Application&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터에 대한 일괄 처리 작업을 사람 개입 없이 자동으로 지원해주는 Application&lt;ul&gt;
&lt;li&gt;Batch : 일괄처리&lt;/li&gt;
&lt;li&gt;Scheduler : 일정 시간마다 실행시키는 역할&lt;/li&gt;
&lt;li&gt;Quartz? Spring Batch?&lt;ul&gt;
&lt;li&gt;Quartz : Job Scheduling &lt;strong&gt;Library&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Quartz는 스케쥴러의 역할이지 Batch와 같이 대용량 데이터 배치 처리에 대한 기능 지원 X&lt;/li&gt;
&lt;li&gt;Quartz외에도 Spring Scheduler, Jenkins, Cron 등이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Batch : Bach &lt;strong&gt;Framework&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Spring Batch는 스케쥴링 프레임워크가 아님&lt;/li&gt;
&lt;li&gt;Batch는 Quartz의 스케쥴링 기능 지원 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보통 Quartz + Spring Batch로 사용하면서 데이터 일괄처리를 자동으로 진행&lt;ul&gt;
&lt;li&gt;요즘에는 Jenkins + Spring Batch로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;간단한 일정 주기 반복성 작업 ~&amp;gt; Spring Batch를 사용하지 않고 Scheduler만 사용해서 처리하는 것도 좋음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Batch Application&lt;/code&gt; VS &lt;code&gt;Web Application&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;Web : 실시간 처리 / 상대적인 속도 / QA 용이&lt;/li&gt;
&lt;li&gt;Batch: 후속 처리 / 절대적인 속도 / QA 복잡성 &amp;gt; 테스트코드 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;The Domain Language of Batch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Batch Stereotypes&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png&quot; alt=&quot;Domain Language of Batch&quot;&gt;&lt;/li&gt;
&lt;li&gt;Job&lt;ul&gt;
&lt;li&gt;배치 처리과정을 하나의 단위로 만들어 표현한 객체&lt;/li&gt;
&lt;li&gt;하나의 Job에는 1 : N으로 Step을 가짐&lt;/li&gt;
&lt;li&gt;Step 인스턴스의 컨테이너 역할&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-heirarchy.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobInstance&lt;ul&gt;
&lt;li&gt;Job이 실제 실행되는 단위&lt;/li&gt;
&lt;li&gt;JobInstance는 여러개의 JobExecution을 가질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobParameters&lt;ul&gt;
&lt;li&gt;배치 작업을 시작하는데 사용되는 파라미터를 보유&lt;/li&gt;
&lt;li&gt;식별자로 사용되거나 실행 중 참조될 수 있음&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-stereotypes-parameters.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobExecution&lt;ul&gt;
&lt;li&gt;JobInstance에 대한 한 번의 실행을 나타내는 객체&lt;/li&gt;
&lt;li&gt;JobExcution은 JobInstance, 배치 실행 상태, 시작 시간, 끝난 시간, 실패했을 때 메시지 등의 정보를 담고 있음&lt;/li&gt;
&lt;li&gt;실행이 성공적으로 완료되지 않는 한 지정된 실행에 해당하는 JobInstance는 완료 간주X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Step&lt;ul&gt;
&lt;li&gt;실질적인 배치 처리(모든 정보를 담고 있음)&lt;/li&gt;
&lt;li&gt;모든 &lt;strong&gt;Job에는 1개 이상의 Step이 존재해야함&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Step 안에 Tasklet 혹은 Reader &amp;amp; Processor &amp;amp; Writer 묶음이 존재&lt;/li&gt;
&lt;li&gt;배치 Job의 독립적이고 순차적인 단계를 캡슐화하는 도메인 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;StepExecution&lt;ul&gt;
&lt;li&gt;JobExecution과 유사하며, Step의 실제 정보를 담는 객체&lt;/li&gt;
&lt;li&gt;이전 step이 실패하면 다음 step 진행을 하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobRepository&lt;ul&gt;
&lt;li&gt;위의 모든 절차에 대한 지속적인 매커니즘&lt;/li&gt;
&lt;li&gt;JobRepository에서 JobExecution을 관리&lt;ul&gt;
&lt;li&gt;첫 Job이 실행되면 JobRepository로 부터 JobExecution을 가져와서 실행하는 동안 step 및 job execution을 JobRepository로 전달하며 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobLauncher, Job, Step에 대한 CRUD를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobLauncher&lt;ul&gt;
&lt;li&gt;JobParameters와 함께 실행되는 Job에 대한 간단한 인터페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tasklet&lt;ul&gt;
&lt;li&gt;Step 안에서 단일로 수행될 커스텀한 기능들을 선언할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemReader&lt;ul&gt;
&lt;li&gt;step에서 배치 데이터를 읽어오는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemWriter&lt;ul&gt;
&lt;li&gt;step의 결과인 배치 데이터를 저장하는 역할&lt;ul&gt;
&lt;li&gt;일반적으로 DB나 파일에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemProcessor&lt;ul&gt;
&lt;li&gt;배치 데이터를 가공 &amp;amp; 변환하는 역할 (필수 X)&lt;ul&gt;
&lt;li&gt;비즈니스 로직의 분리 ~&amp;gt; ItemWriter는 저장 수행, ItemProcessor는 로직 처리만 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tasklet || ItemReader + ItemWriter + ItemProcessor로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring Batch Job Flow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;next()&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;순차적으로 Step을 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.on()&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;캐치할 ExitStatus 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; 일 경우 모든 ExitStatus가 지정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;to()&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다음으로 이동할 Step 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;from()&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일종의 이벤트 리스너 역할&lt;/li&gt;
&lt;li&gt;상태값을 보고 일치하는 상태라면 to()에 포함된 step을 호출&lt;/li&gt;
&lt;li&gt;Step의 이벤트 캐치가 FAILED로 되어있는 상태에서 추가로 이벤트를 캐치하려면 from을 써야만 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;decide&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;지정된 ExitStatus외의 분기를 컨트롤할 경우, ExitStatus를 커스텀해야하기 때문에 번거로움&lt;/li&gt;
&lt;li&gt;Step들의 Flow 속에서 분기만 담당하는 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;StepJob 예시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepJobConfig {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job stepNextJob() {
        return jobBuilderFactory.get(&amp;quot;stepJob&amp;quot;)
            .start(step1())
            .next(step2())
            .next(conditionalJobStep1())
                .on(&amp;quot;FAILED&amp;quot;) // ExitStatus가 FAILED인 경우
                .to(conditionalJobStep3())
                .on(&amp;quot;*&amp;quot;)
                .end()
            .from(conditionalJobStep1())
                .on(&amp;quot;*&amp;quot;) // ExitStatus가 FAILED가 아닌 경우
                .to(conditionalJobStep2())
                .next(conditionalJobStep3())
                .end()
            .end()
            .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get(&amp;quot;step1&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; This is Step1&amp;quot;);
            }).build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFacotry.get(&amp;quot;step2&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; This is Step2&amp;quot;);
            }).build();
    }

    @Bean
    public Step conditionalJobStep1() {
        return stepBuilderFactory.get(&amp;quot;step1&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; This is stepNextConditionalJob Step1&amp;quot;);

                /**
                    ExitStatus를 FAILED로 지정한다.
                    해당 status를 보고 flow가 진행된다.
                    **/
                contribution.setExitStatus(ExitStatus.FAILED);

                return RepeatStatus.FINISHED;
            }).build();
    }

    @Bean
    public Step conditionalJobStep2() {
        return stepBuilderFactory.get(&amp;quot;conditionalJobStep2&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; This is stepNextConditionalJob Step2&amp;quot;);
                return RepeatStatus.FINISHED;
            }).build();
    }

    @Bean
    public Step conditionalJobStep3() {
        return stepBuilderFactory.get(&amp;quot;conditionalJobStep3&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; This is stepNextConditionalJob Step3&amp;quot;);
                return RepeatStatus.FINISHED;
            }).build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DecideJob Example&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
@RequiredArgsConstructor
public class DecideJobConfig {
    private final JobBuilderFactory jobBuilderfactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job deciderJob() {
        return jobBuilderFactory.get(&amp;quot;decideJob&amp;quot;)
            .start(startStep())
            .next(decider())
                .on(&amp;quot;ODD&amp;quot;)
                .to(oddStep())
            .from(decider())
                .on(&amp;quot;EVEN&amp;quot;)
                .to(evenStep())
            .end()
            .build();
    }

    @Bean
    public Step startStep() {
        return stepBuilderFactory.get(&amp;quot;startStep&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; Start!&amp;quot;);
                return RepeatStatus.FINISHED;
            }).build();
    }

    @Bean
    public Step evenStep() {
        return stepBuilderFactory.get(&amp;quot;evenStep&amp;quot;)
            .tasklet((contribution, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; 짝수입니다.&amp;quot;);
                return RepeatStatus.FINISHED;
            }).build();
    }

    @Bean
    public Step oddStep() {
        return stepBuilderFactory.get(&amp;quot;oddStep&amp;quot;)
            .tasklet((contirubtion, chunkContext) -&amp;gt; {
                log.info(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; 홀수입니다.&amp;quot;);
                return RepeatStatus.FINISHED;
            }).bulid();
    }

    @Bean
    public JobExcutionDecider decider() {
        return new OddDecider();
    }

    public static class OddDecider implements JobExecutionDecider {
        @Override
        public FlowExcutionStatus decide(JobExcecution jobExecution, StepExecution stepExecution) {
            Random rand = new Random();
            int randomNumber = rand.nextInt(50) + 1;
            log.info(&amp;quot;랜덤숫자: {}&amp;quot;, randomNumber);
            if (randomNumber % 2 == 0) {
                return new FlowExecutionStatus(&amp;quot;EVEN&amp;quot;);
            } else {
                return new FlowExecutionStatus(&amp;quot;ODD&amp;quot;);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Chunk-oriented Processing&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://1.bp.blogspot.com/-xx99Zk5YPH4/XvxEaMxqKcI/AAAAAAAAF-A/r8Or24XRRecVoSv5G9oPyZ1OAEpfGDDMgCNcBGAsYHQ/s1600/1.png&quot; alt=&quot;Chunk Oriented Processing&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Batch에서의 Chunk&lt;ul&gt;
&lt;li&gt;데이터 덩어리 == 작업할 때 각 커밋 사이에 처리되는 row 수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Batch는 Chunk 지향 처리&lt;ul&gt;
&lt;li&gt;데이터를 읽어 처리 &amp;amp; 가공 ~&amp;gt; Chunk 덩어리 생성 ~&amp;gt; Chunk 단위로 트랜잭션을 다룸&lt;/li&gt;
&lt;li&gt;Chunk 단위로 트랜잭션을 수행하기 때문에 실패한 경우에는 해당 Chunk 만큼만 롤백, 이전 커밋 트랜잭션 범위까지 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Chunk Oriented Processing Concepts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;어플리케이션과 데이터베이스 간에 데이터를 주고 받는 회수를 최소화 ~&amp;gt; 성능 향상&lt;/li&gt;
&lt;li&gt;PageSize와 ChunkSize는 의미하는 바가 다름&lt;ul&gt;
&lt;li&gt;Chunk Size&lt;ul&gt;
&lt;li&gt;한번에 처리될 트랜잭션 단위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Page Size&lt;ul&gt;
&lt;li&gt;한번에 조회할 Item 양&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EX) Page Size가 10이고 Chunk Size가 50인 경우&lt;ul&gt;
&lt;li&gt;Page 조회 5번이 일어나면, 1번의 트랜잭션이 발생 ~&amp;gt; Chunk 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Batch의 PagingItemReader에는 클래스 상단에 아래와 같은 주석이 존재&lt;ul&gt;
&lt;li&gt;Setting a fairly large page size and using a commit interval that matches the page size should provide better performance.&lt;br&gt;(상당히 큰 페이지 크기를 설정하고 페이지 크기와 일치하는 커밋 간격을 사용하면 성능이 향상됩니다.)ㅅ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성능 이외에도 JPA를 사용할 경우 영속성 컨텍스트가 깨지는 문제 존재&lt;ul&gt;
&lt;li&gt;특별한 이유가 없다면 PageSize와 ChinkSize를 일치시키는 것을 지향(&lt;a href=&quot;https://jojoldu.tistory.com/146&quot;&gt;링크&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;ItemRedaer &amp;amp; Processor &amp;amp; Writer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ItemReader&lt;ul&gt;
&lt;li&gt;Cursor 기반 ItemReader 구현체&lt;ul&gt;
&lt;li&gt;JdbcCursorItemReader&lt;/li&gt;
&lt;li&gt;HibernateCursorItemReader&lt;/li&gt;
&lt;li&gt;StoredProcedureItemReader&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Paging 기반 ItemReader 구현체&lt;ul&gt;
&lt;li&gt;JdbcPagingItemReader&lt;/li&gt;
&lt;li&gt;HibernatePagingItemReader&lt;/li&gt;
&lt;li&gt;JpaPagingItemReader&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JdbcCursorItemReader&lt;ul&gt;
&lt;li&gt;chunk&lt;ul&gt;
&lt;li&gt;&amp;lt;T, T&amp;gt; 제네릭에서 &lt;strong&gt;첫번째는 Reader에서 반환할 타입, 두번쨰는 Writer에 파라메터로 넘겨줄 타입&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;chunkSize로 인자값을 넣는 경우는 Reader &amp;amp; Writer가 묶일 Chunk 트랜잭션 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;fetchSize&lt;ul&gt;
&lt;li&gt;Database에서 한번에 가져올 데이터 양&lt;/li&gt;
&lt;li&gt;Paging은 실제 쿼리를 limit, offset을 이용해서 분할처리하지만, Cursor는 쿼리 분할 처리 없이 실행되거나 내부적으로 가져오는 데이터를 FetchSize만큼 가져와서 read()를 통해 하나씩 가져옴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dataSource&lt;ul&gt;
&lt;li&gt;Database에 접근하기 위해  사용할 DataSource 객체를 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;rowmapper&lt;ul&gt;
&lt;li&gt;쿼리 결과를 Java 인스턴스로 매핑하기 위한 Mapper&lt;/li&gt;
&lt;li&gt;커스텀하게 생성해서 사용할 수 있지만, 매번 Mapper 클래스를 생성해야해서 보편적으로는 Spring에서 공식적으로 지원하는 BeanPropertyRowMapper.class를 많이 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;sql&lt;ul&gt;
&lt;li&gt;Reader로 사용할 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;name&lt;ul&gt;
&lt;li&gt;reader의 이름 지정&lt;/li&gt;
&lt;li&gt;Bean 이름이 아닌 Spring Batch의 ExecutionContext에 저장될 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CursorItemReader&lt;ul&gt;
&lt;li&gt;Database와 SocketTimeout을 충분히 큰 값으로 설정&lt;/li&gt;
&lt;li&gt;Cursor는 하나의 Connection으로 Batch가 끝날 때까지 사용되기 때문에 Batch가 끝나기 전에 DB와 Application이 먼저 끊어질 수 있음&lt;ul&gt;
&lt;li&gt;Batch 수행 기간이 오래 걸리는 경우에는 PagingItemReader를 사용하는 것이 좋음&lt;/li&gt;
&lt;li&gt;Paging은 한 페이지를 읽을 때마다 Connection을 맺고 끝기 때문에 성능적으로 안정적&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;hr&gt;
&lt;h3&gt;JdbcCursorItemReader Example&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JdbcPagingItemReader&lt;ul&gt;
&lt;li&gt;Cursor와 사용 방식이 유사&lt;/li&gt;
&lt;li&gt;JdbcCursorItemReader를 사용할 때는 String 타입으로 쿼리를 생성하지만, PagingItemReader는 PagingQueryProvider를 통해 쿼리 생성&lt;/li&gt;
&lt;li&gt;Spring batch는 SqlPagingQueryProviderFactoryBean을 통해 DataSource 설정 값을 확인하고 db에 맞는 Provider를 선택하여 적절한 페이지 전략 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;JdbcPagingItemReader Example&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ItemWriter&lt;ul&gt;
&lt;li&gt;JdbcBatchItemWriter&lt;/li&gt;
&lt;li&gt;HibernateItemWriter&lt;/li&gt;
&lt;li&gt;JpaItemWrite&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JdbcBatchItemWriter&lt;ul&gt;
&lt;li&gt;columnMapped&lt;ul&gt;
&lt;li&gt;key, value 기반의 Insert SQL의 values를 매핑 (ex : Map&amp;lt;String, Object&amp;gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;beanMapped&lt;ul&gt;
&lt;li&gt;Pojo 기반의 Insert SQL의 values를 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;JdbcBatchItemWriter Example&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ItemProcessor&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;필수 X&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package org.springframework.batch.item;

public interface ItemProcessor&amp;lt;I, O&amp;gt; {
    O process(I item) throws Exception;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ItemReader에서 받을 데이터 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ItemWriter에 보낼 데이터 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;일반적으로 변환이나 필터 처리 용도로 사용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;ETC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;멱등성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연산을 여러번 적용해도 결과가 달라지지 않는 성질&lt;ul&gt;
&lt;li&gt;멱등성이 깨지는 경우 ~&amp;gt; 제어할 수 없는 코드를 직접 실행할 때&lt;ul&gt;
&lt;li&gt;ex) LocalDate.now() &amp;gt; 이전 데이터를 다시 수행해야할 경우, 해당 코드가 존재하면 문제의 여지 존재&lt;/li&gt;
&lt;li&gt;JobParameter로 입력받도록 처리&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;p&gt;무중단 배포&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readlink를 활용&lt;/li&gt;
&lt;li&gt;배포 젠킨스와 배치 젠킨스를 따로, nDeploy가 배포 젠킨스의 역할로 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Job 중복실행 방지 에러&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;A job instance already exists and is complete for parameters={requestDate=20180805}. If you want to run this job again, change the parameters.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;동일한 Job이 Job Parameter가 달라지면 그때마다 BATCH_JOB_INSTANCE가 생성&lt;/li&gt;
&lt;li&gt;동일한 Job Parameter는 여러개 존재 가능&lt;/li&gt;
&lt;li&gt;실패한 Job의 경우, 동일한 Job Parameter라도 동일 실행 가능&lt;/li&gt;
&lt;li&gt;Spring Batch는 동일한 Job Parameter로 성공한 기록이 있을 때만 재수행이 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ConditionalOnProperty&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;지정된 값으로 구성 등록정보가 있는 경우에만 Bean을 등록하여 사용, 특정 Bean만 활성화 ~&amp;gt; 성능 향상&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@Configuration
@RequiredArgsContructor
@ConditionalOnProperty(name = &amp;quot;spring.batch.job.names&amp;quot;, havingValue = &amp;quot;simpleJob&amp;quot;)
public class SimpleJobConfig {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFacotry stepBuilderFactory;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ConditionalOnProperty 테스트 수행 시, 테스틏 수행 속도 문제 발생&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링은 전체 테스트 수행시 Environment가 변경될 때마다 Spring Context 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 속도 문제가 존재 ~&amp;gt; ConditionalOnPropert 점검&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-batch/4.0.x/reference/html/index-single.html#spring-batch-intro&quot;&gt;https://docs.spring.io/spring-batch/4.0.x/reference/html/index-single.html#spring-batch-intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/&quot;&gt;https://jojoldu.tistory.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cheese10yun.github.io/spring-batch-basic/&quot;&gt;https://cheese10yun.github.io/spring-batch-basic/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/Spring Batch</category>
      <category>스프링 배치</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/363</guid>
      <comments>https://zin0-0.tistory.com/363#entry363comment</comments>
      <pubDate>Tue, 22 Jun 2021 17:44:11 +0900</pubDate>
    </item>
    <item>
      <title>4장) 4.2 예외 전환 ~ 4.3 정리</title>
      <link>https://zin0-0.tistory.com/362</link>
      <description>&lt;h2&gt;4장 예외&lt;/h2&gt;
&lt;h3&gt;4.2 예외 전환&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;예외 전환의 목적&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;런타임 예외로 포장&lt;ul&gt;
&lt;li&gt;불필요한 catch / throws 없애기&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;p&gt;JdbcTemplate의 DataAccessException이 런타임 예외로 SQLException을 포장&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대부분 복구가 불가능한 예외인 SQLException을 Application 레벨에서 신경쓰지 않고, 상세한 예외 정보 전달의 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDBC의 한계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DB 종류에 상관없이 사용할 수 있는 데이터 엑세스 코드를 작성하는 일이 쉽지 않음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;유연한 코드를 보장 못하는 두 가지&lt;ul&gt;
&lt;li&gt;비표준 SQL&lt;ul&gt;
&lt;li&gt;최적화 기법, 페이지 처리 등 비표준 SQL이 폭넓게 사용됨&lt;/li&gt;
&lt;li&gt;비표준 SQL은 DAO에 들어가게되고, 해당 DAO는 특정 DB에 종속적인 코드가 됨&lt;/li&gt;
&lt;li&gt;해결책&lt;ul&gt;
&lt;li&gt;DAO를 DB별로 만들어 사용하거나 SQL을 외부에서 독립시켜서 DB에 따라 변경해 사용할 수 있도록 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;호환성 없는 SQLException의 DB 에러 정보&lt;ul&gt;
&lt;li&gt;JDBC는 데이터 처리 중 발생하는 다양한 예외를 모두 SQLException 하나에 담음&lt;/li&gt;
&lt;li&gt;SQLException에서 getErrorCode()가져오는 &lt;strong&gt;에러 코드는 DB별로 에러 코드가 다름&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;그래서 SQLException은 DB 상태를 담은 SQL 상태정보를 부가적으로 제공하지만, JDBC 드라이버에서 SQLException을 담을 상태코드를 정확하게 만들어주지 않음&lt;ul&gt;
&lt;li&gt;SQL 상태코드를 믿고 결과를 파악하는 코드는 매우 위험&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;li&gt;&lt;p&gt;DB 에러 코드 매핑을 통한 전환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링은 DataAccessException 이라는 SQLException을 대체할 수 있는 런타임 예외 및 세분화된 다양한 예외 클래스를 제공&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DataAccessResourceFailureException, BadSqlGrammaException 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DB별 에러코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑정보를 만들고 이용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;bean id=&amp;quot;Oracle&amp;quot; class=&amp;quot;org.springframwork.jdbc.support.SQLErrorCodes&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;badSqlGrammarCodes&amp;quot;&amp;gt;
        &amp;lt;value&amp;gt;900,903,904,917,936,942,17706&amp;lt;/value&amp;gt;
    &amp;lt;/property&amp;gt;
    &amp;lt;property name=&amp;quot;invalidResultSetAccessCodes&amp;quot;&amp;gt;
        &amp;lt;value&amp;gt;17703&amp;lt;/value&amp;gt;
    &amp;lt;/property&amp;gt;
    ...
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;오라클 DB의 에러 코드 매핑 파일에 대한 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위와 같이 매핑 정보를 참고해서 적절한 예외 클래스를 선택하기 때문에, DB가 달라져도 같은 종류의 에러라면 동일한 예외를 받을 수 있게 코드를 작성할 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;만약, 직접 정의한 예외를 발생시키고 싶다면, try-catch 블록을 이용해서 catch &amp;amp; throw로 자신이 정의한 예외로 던져주는 방법을 이용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDK1.6, JDBC 4.0기준) SQLException의 서브 클래스들은 체크 예외기 때문에, 예외를 세분화하는 기준이 SQL 상태정보를 이용 하는 문제점이 존재&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DAO 인터페이스와 DataAccessException 계층 구조&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JDBC이외에 자바 데이터 엑세스 기술에서 발생하는 예외에도 적용&lt;/li&gt;
&lt;li&gt;DAO 인터페이스와 구현 분리&lt;ul&gt;
&lt;li&gt;데이터 엑세스 로직과 다른 코드의 성격 분리 &amp;amp; 분리된 DAO는 전략 패턴을 적용해 구현 방법을 변경해서 사용 가능하게 구성&lt;/li&gt;
&lt;li&gt;DAO의 사용 기술과 구현 코드는 전략 패턴과 DI를 통해 클라이언트에게 감출 수 있지만, 메소드 선언에 나타나는 예외정보가 문제&lt;/li&gt;
&lt;li&gt;인터페이스의 메소드 선언에 없는 예외를 구현 클래스에서 throws할 수 없음&lt;ul&gt;
&lt;li&gt;인터페이스에 throws를 추가&lt;/li&gt;
&lt;li&gt;하지만, JDBC가 아닌 다른 데이터 액세스 기술로 DAO를 구현하면 사용할 수 없는 코드&lt;/li&gt;
&lt;li&gt;가장 단순한 해결 방법은 throws Exception으로 선언하는 것이지만, 상당히 무책임함&lt;/li&gt;
&lt;li&gt;따라서, 런타임 예외를 이용해서 인터페이스에서 throws를 선언하지 않도록 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 액세스 예외 추상화와 DataAccessException 계층 구조&lt;ul&gt;
&lt;li&gt;스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화 ~&amp;gt; DataAccessException 계층구조 안에 정리&lt;ul&gt;
&lt;li&gt;공통적으로 나타나는 예외를 포함해서 데이터 액세스 기술에서 발생 가능한 대부분의 예외를 계층 구조로 분류&lt;/li&gt;
&lt;li&gt;템플릿 메소드나 DAO 메소드에서 직접 활용할 수 있는 예외도 정의되어 있음&lt;ul&gt;
&lt;li&gt;기대한 결과가 나오지 않은 예외상황에 대한 예외&lt;ul&gt;
&lt;li&gt;IncorrectResultSizeDataAccessException, EmptyResultDataAccessException 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 사용, 런타임 예외 전환과 함께 DataAccessException 예외 추상화를 적용하여, 데이터 액세스 기술과 구현 방법에 독립적인 DAO를 만들 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;기술에 독립적인 UserDao 만들기&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;인터페이스 적용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UserDao에서 DAO 기능을 사용하려는 클라이언트들이 필요한 것만 추출해서 인터페이스로 구현&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface UserDao {
    void add(User user);
    User get(String id);
    List&amp;lt;User&amp;gt; getAll();
    void deleteAll();
    int getCount();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;setDataSource() 메소드 같은 경우에는 구현 방법에 따라 변경될 수 있는 메소드이고, 클라이언트가 알고 있을 필요가 없기 때문에 제외&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 보완&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserDao 인스턴스 변수를 구현 클래스로 바꿀 필요가 없음&lt;ul&gt;
&lt;li&gt;@Autowired는 스프링 컨텍스트 내에서 정의된 빈 중 인스턴스 변수 주입이 가능한 타입의 빈을 찾아주기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 기술을 사용한 UserDao 구현 내용에 관심을 가지고 테스트한다면, 해당 구현 클래스로 타입을 사용해서 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataAccessException 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test(expected=DataAccessException.class)
public void duplicateKey() {
    dao.deleteAll();

    dao.add(user1);
    dao.add(user2); // 여기서 예외 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataAccessException 활용시 주의사항&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링을 활용하면 DB 종류와 데이터 액세스 기술에 상관없이 키 값이 중복되는 상황에서 동일한 예외가 발생하기로 기대&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하지만, DuplicateKeyException은 JDBC를 이용하는 경우에만 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataAccessException이 기술에 상관없이 어느 정도 추상화된 공통 예외로 변환해주지만, 근본적인 한계 때문에 사용에 주의&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;만약, 기술의 종류와 상관없이 동일한 예외를 얻고 싶다면, 직접 예외를 정의하고 상세한 예외 전환을 이용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DuplicateUserIdException이 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQLException을 DataAccessException으로 전환하는 보편적인 방법은 DB 에러코드를 이용하는 방법&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SQLErrorCodeSQLExceptionTranslator를 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;현재 연결된 DataSource를 필요로 함&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test
public void sqlExceptionTranslate() {
    dao.deleteAll();

    try {
        dao.add(user1);
        dao.add(user2);
    } catch (DuplicateKeyException ex) {
        SQLException sqlException = (SQLException)ex.getRootCause();
        SQLExceptionTranslator set = new SQLErrorCodeSQLExceptionTranslator(this.datasource);
        assertThat(set.translate(null, null, sqlException), is(DuplicateKeyException.class));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDBC 이외의 기술을 사용할 때, SQLException을 가져와서 직접 예외 전환하는 방법을 이용할 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SQLException을 그대로 두거나, 의미없는 RuntimeException으로 뭉뜽그려 던지는 대신 스프링의 DataAccessException 계층의 예외로 전환하는 방법을 염두하자&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;엔터프라이즈 애플리케이션에서 사용할 수 있는 바람직한 처리 방법 학습&lt;/li&gt;
&lt;li&gt;JDBC 예외의 단점과 스프링이 제공하는 효과적인 데이터 액세스 기술의 예외처리 전략과 기능 학습&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예외를 잡아서 아무런 조치를 취하지 않거나 의미없는 throws 선언을 남발하는 것은 위험&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;예외는 복구하거나 의도적으로 전달하거나 적절한 예외로 전환해야함&lt;/li&gt;
&lt;li&gt;예외 전환&lt;ul&gt;
&lt;li&gt;의미있는 예외로 변경 &lt;/li&gt;
&lt;li&gt;런타임 예외로 포장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복구할 수 없는 예외는 런타임 예외로 빠르게 전환&lt;ul&gt;
&lt;li&gt;SQLException이 대표 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 로직 예외는 체크 예외&lt;/li&gt;
&lt;li&gt;SQLException의 에러 코드는 DB에 종속 ~&amp;gt; 독립적인 예외로 전환할 필요 존재&lt;/li&gt;
&lt;li&gt;스프링은 DataAccessException이라는 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층 제공&lt;/li&gt;
&lt;li&gt;DAO를 데이터 액세스 기술에서 독립&lt;ul&gt;
&lt;li&gt;인터페이스 도입&lt;/li&gt;
&lt;li&gt;런타임 예외 전환&lt;/li&gt;
&lt;li&gt;기술에 독립적인 추상화된 예외로 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;낙관적인 락킹(Optimistic Locking)&lt;ul&gt;
&lt;li&gt;같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트할 때, 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>4장</category>
      <category>예외 전환</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/362</guid>
      <comments>https://zin0-0.tistory.com/362#entry362comment</comments>
      <pubDate>Mon, 21 Jun 2021 14:29:28 +0900</pubDate>
    </item>
    <item>
      <title>4장) 4.1 예외</title>
      <link>https://zin0-0.tistory.com/361</link>
      <description>&lt;h2&gt;4장 예외&lt;/h2&gt;
&lt;h3&gt;4.1 사라진 SQLException&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링의 JdbcTemplate을 적용하면서 throws SQLException 선언이 적용 후에 사라졌다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;초난감 예외처리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예외가 발생하면 catch 블록을 써서 잡는 것은 좋지만 아무 것도 하지 않고 넘어가는 것은 위험&lt;ul&gt;
&lt;li&gt;프로그램 실행 중 어디에서 오류가 있는지 모른채 무시하고 계속 진행하기 때문&lt;/li&gt;
&lt;li&gt;조치를 취할 방법이 없다면 잡지 말고 throws를 메소드 밖으로 던지고 자신을 호출한 코드에 예외처리 책임을 전가하는 것이 차라리 낫다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실행 창에 로그나 메시지로 남기는 경우 다른 수많은 로그나 메시지에 금방 묻힘&lt;/li&gt;
&lt;li&gt;예외 처리 시, 반드시 지켜야할 핵심 원칙&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;복구되든지 작업을 중단시키고 운영자 or 개발자에게 분명하게 통보돼야함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;무의미하고 무책임한 throws&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 예외를 무조건 던져버리는 선언에는 심각한 문제점이 존재&lt;ul&gt;
&lt;li&gt;의미 있는 정보를 얻을 수 없고, 적절한 처리를 통해 복구될 수 있는 예외 상황을 다룰 기회를 박탈당함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예외를 무시하는 방법이나, 무책임한 throws는 &lt;strong&gt;절대 지양&lt;/strong&gt;해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예외의 종류와 특징&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바의 예외 3가지&lt;ul&gt;
&lt;li&gt;Error, Exception, RuntimeException&lt;/li&gt;
&lt;li&gt;Error&lt;ul&gt;
&lt;li&gt;시스템에서 비정상적인 상황이 발생한 경우&lt;/li&gt;
&lt;li&gt;주로 자바 VM에서 발생, Application 코드에서 잡으려고 하면 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exception(CheckedException)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;애플리케이션 코드의 작업 중 예외 상황이 발생한 경우 사용&lt;/li&gt;
&lt;li&gt;Exception에는 Checked와 UnChecked가 존재&lt;/li&gt;
&lt;li&gt;CheckedException은 Exception의 서브 클래스면서 RuntimeException 클래스를 상속받지 않은 예외들&lt;/li&gt;
&lt;li&gt;예외를 어떤 식으로든 복구할 가능성이 있는 경우 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RuntimeException(UncheckedException)&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;RuntimeException을 상속한 클래스들&lt;/li&gt;
&lt;li&gt;명시적인 예외처리 강제 X&lt;/li&gt;
&lt;li&gt;catch로 잡거나 throws 하지 않아도 됨&lt;ul&gt;
&lt;li&gt;명시적으로 throws 선언해줘도 되지만, 무의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로그램의 오류가 있을 때 발생하도록 의도된 것들&lt;/li&gt;
&lt;li&gt;피할 수 있지만 부주의로 발생할 수 있는 경우를 위한 예외&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예외처리 방법&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전략&lt;ul&gt;
&lt;li&gt;예외 복구&lt;ul&gt;
&lt;li&gt;예외상황을 파악 ~&amp;gt; 문제 해결 ~&amp;gt; 정상 상태로 작동&lt;/li&gt;
&lt;li&gt;예외로 인해 기본작업 흐름이 불가능하다면 다른 작업 흐름으로 유도&lt;/li&gt;
&lt;li&gt;예를 들면, DB 서버에 접속할 때, 횟수에 제한을 두고 연결 재시도를 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예외처리 회피&lt;ul&gt;
&lt;li&gt;자신을 호출한 곳으로 throws 하거나 catch and throw(주로 로그 남기고 다시 던짐)&lt;/li&gt;
&lt;li&gt;JdbcTemplate으로 예를 들면, 콜백 오브젝트 메소드에서 SQLException을 템플릿으로 던짐&lt;ul&gt;
&lt;li&gt;SQLException을 처리하는 일이 콜백 오브젝트의 역할이 아니기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자신의 역할이 아닌 경우 throws 하지만, 역할 분담을 제대로 하고 있지 않고 무책임한 throws는 무책임한 회피&lt;/li&gt;
&lt;li&gt;위의 경우처럼 의도가 명확해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&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;li&gt;서비스 계층 오브젝트가 적절한 복구 작업을 시도 가능&lt;/li&gt;
&lt;li&gt;전환 예외에 원래 발생한 예외를 담어 중첩 예외(Nested Exception)으로 처리&lt;/li&gt;
&lt;li&gt;새로운 예외를 만들면서 생성자나 initCause() 메소드로 원인을 넣어줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예외를 포장&lt;ul&gt;
&lt;li&gt;새로운 예외를 만들고 원인이 되는 예외를 내부에 담아서 던지는 방식은 같음&lt;/li&gt;
&lt;li&gt;의미를 명확하게 하려는 목적이 아니라는 점이 다름&lt;/li&gt;
&lt;li&gt;주로 Checked Exception을 Runtime Exception으로 전환하는 경우에 사용&lt;/li&gt;
&lt;li&gt;의미 있는 예외거나 복구 가능한 예외가 아니라면 런타임 예외인 EJBException으로 포장해서 던지는 편이 좋음&lt;ul&gt;
&lt;li&gt;EJBException은 시스템 익셉션으로 인식하고 트랜잭션을 자동으로 롤백&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;애플리케이션 코드에서 로직상의 예외인 경우는 의도적으로 Checked Exception을 던지는 것이 좋음&lt;/strong&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;/li&gt;
&lt;li&gt;&lt;p&gt;예외처리 전략&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;런타임 예외의 보편화&lt;ul&gt;
&lt;li&gt;Checked Exception가 일반 예외, UnChecked는 시스템 장애나 프로그램 오류에 사용&lt;/li&gt;
&lt;li&gt;JavaEE에서는 서버 특정 계층에서 예외가 발생했을 때, 작업을 일시 중지하고 사용자와 바로 커뮤니케이션하면서 예외상황을 복구할 수 있는 방법이 없음&lt;/li&gt;
&lt;li&gt;예외상황을 미리 파악하고 예외가 발생하지 않도록 차단하는 것이 좋음&lt;/li&gt;
&lt;li&gt;대응이 불가능한 체크 예외는 런타임 예외로 전환해서 던지는게 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;add() 메소드의 예외 처리&lt;ul&gt;
&lt;li&gt;ID 중복의 경우 좀 더 의미 있는 예외인 DuplicatedUserIdException으로 전환해주고, 아니라면 SQLException을 그대로 던지는 예시를 구현&lt;ul&gt;
&lt;li&gt;SQLException은 복구 불가능하고 처리할 것이 없음&lt;ul&gt;
&lt;li&gt;런타임 예외로 포장해서 메소드 밖으로 던져서 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DuplicatedUserIdException을 잡아서 처리할 수 있다면 런타임 예외로 만드는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RuntimeException으로 처리하는 경우 예외 상황에 대한 충분한 고려가 필요&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 예외&lt;ul&gt;
&lt;li&gt;애플리케이션 자체 로직에서 의도적으로 발생해서 catch로 조치를 취하도록 요구하는 예외&lt;/li&gt;
&lt;li&gt;예시&lt;ul&gt;
&lt;li&gt;은행 계좌에서 출금하는 기능의 메소드&lt;ol&gt;
&lt;li&gt;예외 경우에 따라 각기 다른 종류의 리턴 값을 돌려주고, 메소드를 호출한 쪽에서 리턴 값을 확인&lt;br&gt;~&amp;gt; 리턴 값을 명확하게 코드화하고 잘 관리하지 않으면 복잡한 상황 발생&lt;br&gt;~&amp;gt; 조건문이 자주 등장해서 코드가 지저분해지고 흐름 파악이 어려움&lt;/li&gt;
&lt;li&gt;예외 상황에서 비즈니스적 의미를 띤 예외를 던짐&lt;br&gt;의도적으로 체크 예외를 만들어서 개발자가 잊지 않고 예외 상황에 대한 로직을 구현하도록 강제&lt;br&gt;~&amp;gt; 예외 정보를 함께 넣어 적절한 대응을 하도록 관리&lt;/li&gt;
&lt;/ol&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;p&gt;SQLException은 어떻게 됐나?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQLException은 코드 레벨에서 복구할 수 있는 예외가 아님&lt;ul&gt;
&lt;li&gt;예외가 발생했다는 사실을 빠르게 전달하는 것이 중요&lt;/li&gt;
&lt;li&gt;예외처리 전략을 적용해서 기계적인 throws를 지양&lt;/li&gt;
&lt;li&gt;빠르게 UncheckedException(RuntimeException)으로 전환하는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>checked exception</category>
      <category>runtime exception</category>
      <category>예외 처리</category>
      <category>토비</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/361</guid>
      <comments>https://zin0-0.tistory.com/361#entry361comment</comments>
      <pubDate>Thu, 17 Jun 2021 17:11:25 +0900</pubDate>
    </item>
    <item>
      <title>3장) 3.6 스프링의 JdbcTemplate~ 3.7 정리</title>
      <link>https://zin0-0.tistory.com/360</link>
      <description>&lt;h2&gt;3장 템플릿&lt;/h2&gt;
&lt;h3&gt;3.6 스프링의 JdbcTemplate&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스프링 제공 템플릿/콜백&lt;ul&gt;
&lt;li&gt;JdbcContext를 JdbcTemplate로 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;update() 메소드&lt;ul&gt;
&lt;li&gt;createPreapredStatement() 메소드와 대응되는 메소드로, SQL 문장만 파라메터 전달함으로써 사용&lt;/li&gt;
&lt;li&gt;add 메소드와 대응되는 콜백으로 사용하는 경우, 바인딩할 파라미터를 순서대로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;queryForInt() 메소드&lt;ul&gt;
&lt;li&gt;ResultSetExtractor는 PreparedStatement 쿼리를 실행해서 얻은 ResultSet을 전달받는 콜백&lt;ul&gt;
&lt;li&gt;콜백을 만들고 익명 내부 클래스를 복잡하게 설정&lt;/li&gt;
&lt;li&gt;JdbcTemplate에서 제공하는 queryForInt 메소드를 사용&lt;/li&gt;
&lt;li&gt;스프링에서 제공하는 클래스지만 DI 컨테이너를 필요로 하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;queryForObject() 메소드&lt;ul&gt;
&lt;li&gt;get() 메소드 수정&lt;ul&gt;
&lt;li&gt;ResultSet의 결과를 User 오브젝트를 만들어 프로퍼티에 대입&lt;/li&gt;
&lt;li&gt;RowMapper 콜백을 사용&lt;ul&gt;
&lt;li&gt;SQL 실행 결과가 로우 하나에 대응&lt;/li&gt;
&lt;li&gt;ResultSet의 첫 번째 로우에 RowMapper를 적용하도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;queryForObject 메소드는 쿼리를 실행하면 한 개의 로우만 얻을 것이라고 기대&lt;/li&gt;
&lt;li&gt;예외 상황에 대한 처리도 되어있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;query() 메소드&lt;ul&gt;
&lt;li&gt;결과가 없는 경우에 크기아 0인 List&lt;T&gt; 오브젝트를 반환&lt;/li&gt;
&lt;li&gt;제네릭 설정으로 쿼리 실행 결과를 담을 오브젝트를 설정&lt;/li&gt;
&lt;li&gt;테스트 코드 작성 시, 예외 상황에 대한 테스트가 중요&lt;ul&gt;
&lt;li&gt;크기가 0인 경우, 원하던 기대값이 아니라면 테스트 코드를 설정하는 것도 좋은 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;재사용 가능한 콜백의 분리&lt;ul&gt;
&lt;li&gt;DI를 위한 코드 정리&lt;ul&gt;
&lt;li&gt;DataSource 인스턴스 변수 제거&lt;ul&gt;
&lt;li&gt;직접 DI 해주기 위해 필요한 DataSource를 전달 받는 수정자 메소드는 유지&lt;/li&gt;
&lt;li&gt;JdbcTemplate을 스프링 빈으로 등록하는 방식을 사용하고 싶다면, setDataSource 대신 setJdbcTemplate으로 바꿔 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중복 제거&lt;ul&gt;
&lt;li&gt;get 메소드와 getAll 메소드에서 사용하는 RowMapper 내용이 같음&lt;ul&gt;
&lt;li&gt;다양한 조건으로 사용자를 조회하는 검색 기능이 추가될 것을 고려하여 확장을 위해 분리 및 재사용&lt;/li&gt;
&lt;li&gt;RowMapper 콜백 오브젝트는 상태 정보가 없기 때문에 싱글톤으로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;템플릿/콜백 패턴과 UserDao&lt;ul&gt;
&lt;li&gt;UserDao는 User 정보를 DB 조작 방법에 대한 핵심 로직만 있음 ~&amp;gt; 최적화&lt;ul&gt;
&lt;li&gt;응집도가 높다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JdbcTemplate은 JDBC API를 사용하는 방식, 리소스 반납, 예외 처리 DB 접근 등 관심&lt;/li&gt;
&lt;li&gt;따라서, 낮은 결합도를 유치하면서도 템플릿/콜백 구현에 대한 강한 결합을 가짐&lt;/li&gt;
&lt;li&gt;JdbcTemplate을 DAO 안에서 직접 만들어 사용하는게 스프링의 관례이지만, JdbcOperations 인터페이스를 통해 DI를 받아 사용하는 방법이 존재&lt;/li&gt;
&lt;li&gt;UserDao 개선 사항&lt;ul&gt;
&lt;li&gt;userMapper가 인스턴스 변수로 설정되어 있고, 변경되지 않는 프로퍼티의 성격을 지님&lt;ul&gt;
&lt;li&gt;DI용 프로퍼티로 수정&lt;/li&gt;
&lt;li&gt;User 프로퍼티와 DB 단에 수정이 일어나도 UserDao 코드 수정 없이 매핑 정보 수정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 문장을 외부 리소스에 담고 호출해서 사용&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;h3&gt;정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;안전한 리소스 반환, 객체지향 설계 원리, 디자인 패턴, DI 에 중점을 두고 학습했음&lt;/li&gt;
&lt;li&gt;예외 발생 가능성에 대해 try/catch/finally 블록으로 관리&lt;/li&gt;
&lt;li&gt;전략 패턴을 이용해서, 바뀌지 않는 부분은 컨텍스트로 / 바뀌는 부분은 전략으로 인터페이스를 통해 연결해서 사용&lt;/li&gt;
&lt;li&gt;클라이언트 메소드 안에 익명 내부 클래스로 사용&lt;/li&gt;
&lt;li&gt;컨텍스트 하나 이상의 클라이언트 오브젝트에서 사용된다면, 클래스를 분리해서 공유&lt;/li&gt;
&lt;li&gt;컨텍스트는 DI 받거나 직접 생성&lt;/li&gt;
&lt;li&gt;템플릿 / 콜백 패턴&lt;/li&gt;
&lt;li&gt;콜백의 코드에 일정 패턴이 반복 ~&amp;gt; 템플릿에 넣고 재활용&lt;/li&gt;
&lt;li&gt;템플릿과 콜백 사이에 주고받는 정보에 관심&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>jdbcTemplate</category>
      <category>템플릿 콜백 패턴</category>
      <category>토비</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/360</guid>
      <comments>https://zin0-0.tistory.com/360#entry360comment</comments>
      <pubDate>Thu, 17 Jun 2021 17:10:15 +0900</pubDate>
    </item>
    <item>
      <title>3장) 3.4 컨텍스트와 DI ~ 3.5 템플릿과 콜백</title>
      <link>https://zin0-0.tistory.com/359</link>
      <description>&lt;h2&gt;3장 템플릿&lt;/h2&gt;
&lt;h3&gt;3.4 컨텍스트와 DI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JdbcContext의 분리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전략 패턴 구조&lt;ul&gt;
&lt;li&gt;UserDao의 메소드가 클라이언트&lt;/li&gt;
&lt;li&gt;익명 내부 클래스로 만들어진 것이 개별적인 전략&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jdbcContextWithStatementStrategy&lt;/code&gt; 메소드가 Context&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JDBC의 일반적인 작업 흐름을 가지고 있는 컨텍스는 다른 DAO에서도 사용 가능&lt;br&gt;~&amp;gt; 분리 시켜보자&lt;/li&gt;
&lt;li&gt;클래스 분리&lt;ul&gt;
&lt;li&gt;JdbcContext가 DataSource에 의존 ~&amp;gt; DataSource 타입 빈을 DI 받게 변경&lt;ul&gt;
&lt;li&gt;생성자를 통해 DataSource 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빈 의존관계 변경&lt;ul&gt;
&lt;li&gt;클래스 분리로 인해 UserDao는 JdbcContext에 의존하고 있지만, JdbcContext는 구체 클래스&lt;/li&gt;
&lt;li&gt;스프링 DI는 인터페이스를 사이에 두고 의존 클래스를 바꿔 사용하는게 목적&lt;/li&gt;
&lt;li&gt;하지만, 이 경우 서비스 오브젝트로서 의미가 있을 뿐이고 구현 방법이 바뀔 가능성이 없기 때문에 인터페이스를 사용하지 않아도 괜찮다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;jdbcContext의 특별한 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;지금까지 적용했던 DI는 클래스 레벨에서 구체적인 의존관계가 만들어지지 않도록 인터페이스를 사용했지만, UserDao는 바로 JdbcContext 클래스를 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인터페이스를 사용하지 않았기 때문에 온전한 DI라고 볼 수는 없음&lt;/li&gt;
&lt;li&gt;하지만, DI의 기본을 따름&lt;ul&gt;
&lt;li&gt;객체의 생성과 관계설정에 대한 제어 권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC라는 개념을 포괄 &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링 빈으로 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JdbcContext를 UserDao와 DI 구조로 만들어야하는 이유&lt;ul&gt;
&lt;li&gt;JdbcContext가 스프링 컨테이너 싱글톤 레지스트리에서 관리되는 싱글톤 빈임&lt;ul&gt;
&lt;li&gt;그 자체로 변경되는 상태정보를 가지고 있지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JdbcContext가 DI를 통해 다른 빈에 의존하고 있음&lt;ul&gt;
&lt;li&gt;다른 빈을 DI 받기 위해서라도 스프링 빈으로 등록돼야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스프링에는 드물지만 이렇게 인터페이스를 사용하지 않는 클래스를 직접 의존하는 DI가 있음&lt;ul&gt;
&lt;li&gt;그 이유는, 책임과 목적을 따졌을 때, 강한 응집도를 가지고 있기 때문&lt;/li&gt;
&lt;li&gt;단, 클래스를 바로 사용하는 코드 구성을 DI에 적용하는 것은 가장 마지막에 고려해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;코드를 이용하는 수동 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UserDao 내부에서 직접 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JdbcContext를 스프링 빈으로 등록해서 사용했던 첫 번째 이유인 싱글톤으로 만드려는 것을 포기해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JdbcContext를 스프링 빈으로 등록하지 않았으므로 누군가 JdbcContext의 생성과 초기화를 책임져야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JdbcContext의 제어권은 UserDao가 갖는 것이 적당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JdbcContext를 스프링 빈으로 등록해서 사용했던 문제를 해결해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JdbcContext는 다른 빈을 인터페이스를 통해 간접적으로 의존 ~&amp;gt; 자신도 빈으로 등록되어 있었어야함&lt;/li&gt;
&lt;li&gt;JdbcContext에 대한 제어권을 갖고 생성과 관리를 담당하는 UserDao에게 DI 까지 맡기며 해결 가능&lt;/li&gt;
&lt;li&gt;UserDao가 임시로 DI 컨테이너처럼 동작하게 만들면 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;jdbcContext 빈을 제거한 설정파일만 보면 UserDao가 직접 DataSource를 의존하고 있는 것 같지만, 내부적으로는 JdbcContext를 통해 간접적으로 DataSource를 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빈 레벨에서는 userDao 빈이 dataSource 빈에 의존하고 있다고 말할 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserDao {
    // ...
    private JdbcContext jdbcContext;

    public void setDataSource(DataSource dataSource) {
        this.jdbcContext = new JdbcContext();
        this.jdbcContext.setDataSource(dataSource);
        this.dataSource = dataSource;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;인터페이스를 두지 않아도 될 만큼 긴밀한 관계를 갖는 DAO 클래스와 JdbcContext를 어색하게 따로 빈으로 분리하지 않고, 내부에서 직접 사용하면서도 다른 오브젝트에 대한 DI를 적용할 수 있다는 장점이 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최근 들어 수정자 주입을 하는 코드를 많이 보지는 못함 (개인 의견)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링 DI를 위한 빈 등록 vs 수동 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링 DI를 위한 빈 등록 &lt;ul&gt;
&lt;li&gt;장점) 의존관계가 설정파일에 명확하게 드러남&lt;/li&gt;
&lt;li&gt;단점) DI의 근본 원칙에 부합하지 않는 구체적 클래스와의 관계가 설정에 집적 노출됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수동 DI&lt;ul&gt;
&lt;li&gt;장점) 관계를 외부에 드러내지 않음&lt;/li&gt;
&lt;li&gt;단점) 싱글톤으로 만들 수 없고, DI를 위한 부가적인 코드가 필요함&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;h3&gt;3.5 템플릿과 콜백&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백 패턴&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전략 패턴의 기본 구조에 익명 내부 클래스를 활용한 방식&lt;/li&gt;
&lt;li&gt;전략 패턴의 컨텍스트 ~&amp;gt; 템플릿&lt;br&gt;익명 내부 클래스 오브젝트 ~&amp;gt; 콜백&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백의 동작 원리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;템플릿/콜백의 특징&lt;ul&gt;
&lt;li&gt;단일 메소드 인터페이스를 사용 ~&amp;gt; 전략 패턴은 여러 메소드를 가진 일반 인터페이스 사용 가능&lt;/li&gt;
&lt;li&gt;콜백은 일반적으로 하나의 메소드를 가진 인터페이스를 구현한 익명 내부 클래스로 만들어진다.&lt;/li&gt;
&lt;li&gt;클라이언트 역할&lt;ul&gt;
&lt;li&gt;템플릿 안에 실행될 로직을 담은 콜백 오브젝트를 생성, 참조할 정보 제공&lt;/li&gt;
&lt;li&gt;콜백은 템플릿 메소드를 호출할 때 파라미터로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;템플릿&lt;ul&gt;
&lt;li&gt;정해진 작업 흐름을 따라 작업 진행 ~&amp;gt; 콜백 오브젝트의 메소드 호출&lt;/li&gt;
&lt;li&gt;콜백은 클라이언트 메소드에 있는 정보와 템플릿의 참조정보를 이용해 작업 수행 ~&amp;gt; 다시 템플릿에게 결과를 리턴&lt;/li&gt;
&lt;li&gt;템플릿은 콜백이 돌려준 정보로 작업을 마저 수행 ~&amp;gt; 최종 결과를 클라이언트에 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI 방식의 전략 패턴 구조라고 생각하면 이해하기 수월&lt;ul&gt;
&lt;li&gt;일반적인 DI ~&amp;gt; 템플릿에 인스턴수 변수 생성 ~&amp;gt; 주입&lt;/li&gt;
&lt;li&gt;템플릿/콜백 패턴 ~&amp;gt; 매번 새롭게 오브젝트를 전달받음, 콜백 오브젝트가 자신을 생성한 클라이언트 메소드 내의 정보를 직접 참조&lt;/li&gt;
&lt;li&gt;전략 패턴과 DI 장점을 익명 내부 클래스 사용 전략과 결합한 활용법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JdbcContext에 적용된 템플릿/콜백&lt;ul&gt;
&lt;li&gt;UserDao, JdbcContext를 템플릿/콜백의 구조로 살펴보자&lt;ul&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;p&gt;편리한 콜백의 재활용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백의 장단점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;장점&lt;ul&gt;
&lt;li&gt;클라이언트 DAO의 메소드가 간결해짐&lt;/li&gt;
&lt;li&gt;최소한의 데이터 엑세스 로직만 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점&lt;ul&gt;
&lt;li&gt;DAO 메소드에서 매번 익명 내부 클래스를 사용 ~&amp;gt; 코드를 작성하고 읽기가 불편&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;콜백의 분리와 재활용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;deleteAll() 메소드의 내용을 통틀어 바뀔 수 있는 것은 오직 쿼리 뿐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파라미터로 SQL 문장을 받아 바꾸도록 설정하면 분리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void deleteAll() throws SQLException {
    executeSql(&amp;quot;delete from users&amp;quot;);
}

private void excuteSql(final String query) throws SQLException {
    this.jdbcContext.workWithStatementStrategy(
        new StatementStrategy() {
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                return c.prepareStatement(query);
            }
        }
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;바뀌지 않는 모든 부분을 빼내 executeSql() 메소드로 만들고, 바뀌는 부분인 SQL 문장만 파라미터로 받아 사용하게 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파라미터는 final로 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;콜백과 템플릿의 결합&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;excuteSql을 UserDao만 사용하기는 아까움 ~&amp;gt; 재활용 해보자&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;템플릿은 JdbcContext 클래스가 아니라 workWithStatementStrategy() 메소드 ~&amp;gt; JdbcContext 클래스로 콜백 생성과 템플릿 호출이 담긴 executeSql() 메소드를 옮겨도 문제 X&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class JdbcContext {
    //...
    public void excuteSql(final String query) throws SQLException {
        workWithStatementStrategy(
            new StatementStrategy() {
                public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                    return c.prepareStatement(query);
                }
            }
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;deleteAll은 &lt;code&gt;this.jdbcContext.executeSql(&amp;quot;delete from users&amp;quot;);&lt;/code&gt; 로 바꿔주기만 하면 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;수정으로 인해 JdbcContext 안에 클라이언트와 템플릿, 콜백이 모두 함께 공존하면서 동작하는 구조로 바뀜&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;성격이 다른 코드들은 가능한 한 분리하는 편이 낫지만, 이 경우는 하나의 목적을 가지고, 응집력이 강한 코드기 때문에 한 군데 모여있는게 유리&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백 응용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링은 기본적으로 OCP를 지키고, 전략 패턴과 DI를 바탕에 깔고 있으니, 언제든 확장해서 편리한 방법으로 사용 가능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;중복 코드를 분리할 방법을 생각해보는 습관을 기르자&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백 응용 예시&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Calculator라는 클래스를 만들어보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;초기에는 덧셈, 뺄셈 등과 같은 수식을 만든다고 가정해보자.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이후에, 곱셈 및 나눗셈 등이 추가될 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;수식의 공통점을 생각해보면 계산 값들이 있고, 해당 값을 저장할 초기 값(결과 값을 담을 변수), 수식 등이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface LineCallback {
    Integer doSomthingWithLine(String line, Integer value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public Integer lineReadTemplate(String filepath, LineCallback callback, int initVal) throws IOException {
    try (BuffredReader br = new BufferedReader(new FileReader(filepath));) {
        Integer res = initVal;
        String line = null;
        while(line = br.readLine() != null) {
            res = callback.doSomethingWithLine(line, res);
        }
        return res;
    } catch (IOException e) { //... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;익명 내부 클래스 때문에 라인 수가 많아보이지만 핵심 코드는 딱 한줄 뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public Integer calcSum(String filepath) throws IOException {
    LineCallback sumCallback = new LineCallback() {
        public Integer doSomethingWithLine(String line, Integer value) {
            return value + Integer.valueOf(line);
        }};
    return lineReadTemplate(filepath, sumCallback, 0);
}

public Integer calcMultiply(String filepath) throws IOException {
    LineCallback muliplyCallback = new LineCallback() {
        public Integer doSomethingWithLine(String line, Integer value) {
            return value * Integer.valueOf(line);
        }};
    return lineReadTemplate(filepath, muliplyCallback, 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위와 같이 재사용을 수월하게 할 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;코드의 특성이 바뀌는 경계를 잘 살피고, 인터페이스를 사용해 분리한다는 객체지향 원칙만 충실하면, 어렵지 않게 템플릿/콜백 패턴을 만들어 활용할 수 있음&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;제네릭스를 이용한 콜백 인터페이스&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;파일 각 라인의 문자를 모두 연결해서 하나의 스트링으로 돌려주는 기능을 추가한다고 생각해보자.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface LineCallback&amp;lt;T&amp;gt; {
    T doSomethingWithLine(String line, T value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public &amp;lt;T&amp;gt; T lineReadTemplate(String filepath, LineCallback&amp;lt;T&amp;gt; callback, T initVal) throws IOException{
    try (BuffredReader br = new BufferedReader(new FileReader(filepath));) {
        T res = initVal;
        String line = null;
        while((line = br.readLine()) != null) {
            res = callback.doSomethingWithLine(line, res);
        }
        return res;
    } catch (IOException e) { //... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public String concatenate(String filepath) throws IOException {
    LineCallBack&amp;lt;String&amp;gt; concatenateCallback = new LineCallback&amp;lt;String&amp;gt;() {
        public String doSomthingWithLine(String line, String value) {
            return value + line;
        }};
    return lineReadTemplate(filepath, concatenateallback, &amp;quot;&amp;quot;);
}

public Integer calcSum(String filepath) throws IOException {
    LineCallback&amp;lt;Integer&amp;gt; sumCallback = new LineCallback() {
         // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위와 같이 제네릭으로 인터페이스를 설정해주고, 콜백 함수에서 타입을 지정해주면 확장성을 고려하고, 코드 중복을 줄여줄 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;템플릿/콜백&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;템플릿&lt;ul&gt;
&lt;li&gt;어떤 목적을 위해 미리 만들어둔 모양이 있는 틀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;콜백&lt;ul&gt;
&lt;li&gt;다른 오브젝트 메소드에 전달되는 오브젝트&lt;/li&gt;
&lt;li&gt;펑셔널 오브젝트(functional object) 라고도 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>템플릿 콜백 패턴</category>
      <category>토비</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/359</guid>
      <comments>https://zin0-0.tistory.com/359#entry359comment</comments>
      <pubDate>Fri, 11 Jun 2021 16:44:42 +0900</pubDate>
    </item>
    <item>
      <title>3장) 3.1 다시 보는 초난감 DAO ~ 3.3 JDBC 전략 패턴의 최적화</title>
      <link>https://zin0-0.tistory.com/358</link>
      <description>&lt;h2&gt;3장 템플릿&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;개방 폐쇄 원칙&lt;ul&gt;
&lt;li&gt;확장에는 자유롭게 열려 있고 변경에는 굳게 닫혀 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 다시 보는 초난감 DAO&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;2장에서 Test와 관련해서 DB 연결과 관련된 여러 개선 작업을 진행했지만, 예외상황 처리 부분이 미흡하다. 이를 개선해보자.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예외처리 기능을 갖춘 DAO&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JDBC는 예외가 발생한 경우에도 리소스를 반드시 반환해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Connection Pool에서 리소스를 받아와 사용하기 때문에, 반환을 제대로 해주지 않은 리소스가 많아져서 connection pool에서 리소스가 부족하다는 치명적인 Exception이 발생할 수 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void deleteAll() throws SQLException {
    try (Connection c = dataSource.getConnection();
         PreparedStatement ps = c.prepareStatement(&amp;quot;delete from users&amp;quot;);) {
        // .. logic
    } catch (SQLException e) {
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDBC 조회 기능의 예외 처리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public int getCount() throws SQLException {
    try (Connection c = dataSource.getConnection();
         PreparedStatement ps = c.prepareStatement(&amp;quot;select count(*) from users&amp;quot;);
         ResultSet rs = ps.executeQuery();
         ) {
        // .. logic
    } catch (SQLException e) {
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;변하는 것과 변하지 않는 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JDBC try / catch / finally 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;복잡한 try / catch / finally 블록이 2중으로 중첩되어 나오고, 모든 메소드마다 반복&lt;ul&gt;
&lt;li&gt;copy &amp;amp; paste에 한계도 있고, 잘못된 코드가 발생하면 전체적으로 추적하기도 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;분리와 재사용을 위한 디자인 패턴 적용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;메소드 추출&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;변하는 부분을 메소드로 빼는 방법을 먼저 떠올릴 수 있다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;connection.prepareStatement(queryString)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자주 바뀌는 부분을 메소드로 독립시켜도 별 이득이 없음 (위의 예시 코드 참고)&lt;ul&gt;
&lt;li&gt;다른 곳에서 재사용할 수 있어야하는데, 분리시키고 남은 메소드가 재사용이 필요한 부분이고, 분리된 메소드가 DAO 로직마다 새롭게 만들어서 확장돼야하는 부분이 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;템플릿 메소드 패턴 적용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserDaoDeleteAll extends UserDao {
    protected PreparedStatment makeStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement(&amp;quot;delete from users&amp;quot;);
        return ps;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;접근에 제한이 많음&lt;/li&gt;
&lt;li&gt;DAO 로직마다 상속을 통해 새로운 클래스를 만들어야하는 불편함 존재&lt;/li&gt;
&lt;li&gt;컴파일 시점에 관계가 결정되어있음 ~&amp;gt; 유연성이 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;전략 패턴 적용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Context의 contextMethod()에서 일정 구조를 가지고 동작하다 특정 확장 기능은 Strategy 인터페이스를 통해 외부의 독립된 전략 클래스에 위임&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;deleteAll을 예로 들면, deleteAll 메소드가 contextMethod()에 해당&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 커넥션 가져오기&lt;/li&gt;
&lt;li&gt;PreparedStatement를 만들어줄 외부 기능 호출    &amp;lt;~ 전략 패턴의 전략&lt;/li&gt;
&lt;li&gt;전달받은 PreparedStatement 실행&lt;/li&gt;
&lt;li&gt;예외 발생 ~&amp;gt; 메소드 밖으로 던지기&lt;/li&gt;
&lt;li&gt;모든 경우에 만들어진 PreparedStatement와 Connection을 적절히 닫아주기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface StatementStrategy {
    PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class DeleteAllStatement implements StatementStrategy {
    public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareConnection(&amp;quot;delete from users&amp;quot;);
        return ps;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void deleteAll() throws SQLException {
    // ...
    try {
        c = dataSource.getConnection();
        StatementStrategy strategy = new DeleteAllStatement();
        ps = strategy.makePreparedStatement(c);
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;전략 패턴은 필요에 따라 컨텍스트는 그대로 유지되면서 전략을 바꿔 쓸 수 있어야 함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하지만, 컨텍스트가 StatementStragy 인터페이스 뿐만 아니라 특정 구현 클래스인 DeleteAllStatement를 직접 알고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;폐쇄 원칙(OCP의 폐쇄 원칙)에 들어맞지 않음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI 적용을 위한 Client / Context 분리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Context가 어떤 전략을 사용하게 할 것인지는 Client가 결정하는 것이 일반적&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;전략 오브젝트 생성과 컨텍스트로의 전달을 담당하는 책임을 분리하는 것이 ObjectFactory&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이를 일반화한 것이 의존관계 주입(DI)&lt;/li&gt;
&lt;li&gt;전략 패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;컨텍스트에 해당하는 부분을 별도의 메소드로 독립시켜보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    try (Connection c = dataSource.getConnection();
         PreparedStatement ps = stmt.makePreparedStatement(c);) {
        // .. logic
    } catch (SQLException e) {
        throw e;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;제공받은 전략 오브젝트는 PreparedStatement 생성이 필요한 시점에서 호출해서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;클라이언트에 해당하는 부분(deleteAll 메소드)을 살펴보자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;전략 오브젝트를 만들고 컨텍스트를 호출하는 책임이 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void deleteAll() throws SQLException {
    StatementStrategy st = new DeleteAllStatement(); // 전략 오브젝트 생성
    jdbcContextWithStatementStrategy(st); // 컨텍스트 호출, 전략 오브젝트 전달
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JDBC 전략 패턴의 최적화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;전략 클래스의 추가 정보&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터를 add의 경우에는 PreparedStatement에 등록할 정보를 set해줘야한다.&lt;br&gt;이런 경우에는 생성 정보를 생성자를 통해 전달해주도록 해주어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;전략과 클라이언트의 동거&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;생성자로 전달하는 방법보다 개선할 수 있는 방법을 찾아보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위에 만들어진 구조에 두 가지 개선사항이 있다.&lt;ul&gt;
&lt;li&gt;DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 점&lt;/li&gt;
&lt;li&gt;DAO 메소드에서 StatementStrategy에 전달할 부가적인 정보가 있는 경우, 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야한다는 점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;로컬 클래스&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;특정 메소드에서만 사용되는 것이라면 로컬 클래스로 만들 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void add(final User user) throws SQLException {
    class AddStatement implements StatementStrategy {
        User user;

        public AddStatement(User user) {
            this.user = user;
        }

        public PreparedStatement makePreparedStatement(Connection c) {
            // ..ps 선언 및 값 설정
            return ps;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;자신이 선언된 곳의 정보에 접근할 수 있다는 장점이 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다만, 내부 클래스에서 외부의 변수를 사용할 때는 외부 변수는 반드시 final로 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;익명 내부 클래스&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;이름을 갖지 않는 클래스로 클래스 선언과 오브젝트 생성이 결합된 형태&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;구현하는 인터페이스를 생성자처럼 이용해서 오브젝트로 만듦&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;StatementStrategy st = new StatementStrategy() {
    public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        // ..ps 선언 및 값 설정
        return ps;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;마이크로 DI&lt;ul&gt;
&lt;li&gt;DI의 가장 중요한 개념은 제 3자의 도움을 통해 두 오브젝트 사이에 유연한 관계가 설정되도록 만든다는 것&lt;/li&gt;
&lt;li&gt;DI의 장점을 단순화해서 IoC 컨테이너의 도움 없이 코드 내에서 적용한 경우를 마이크로 DI라고 함&lt;ul&gt;
&lt;li&gt;수동 DI라고도 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중첩 클래스 종류&lt;ul&gt;
&lt;li&gt;중첩 클래스(nested class)&lt;ul&gt;
&lt;li&gt;다른 클래스 내부에 정의되는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;종류&lt;ul&gt;
&lt;li&gt;스태틱 클래스(static class)&lt;ul&gt;
&lt;li&gt;독립적으로 오브젝트로 만들어질 수 있는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내부 클래스(inner class)&lt;ul&gt;
&lt;li&gt;자신이 정의된 클래스의 오브젝트 안에서만 만들어질 수 있는 클래스&lt;/li&gt;
&lt;li&gt;scope(범위)에 따라 세 분류로 구분됨&lt;ul&gt;
&lt;li&gt;멤버 내부 클래스&lt;ul&gt;
&lt;li&gt;멤버 변수처럼 오브젝트 레벨에 정의되는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로컬 클래스&lt;ul&gt;
&lt;li&gt;메소드 레벨에 정의되는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;익명 내부 클래스&lt;ul&gt;
&lt;li&gt;이름을 갖지 않는 클래스&lt;/li&gt;
&lt;li&gt;범위는 선언된 위치에 따라 다름&lt;/li&gt;
&lt;li&gt;클래스를 재사용할 필요가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/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;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>전략 패턴</category>
      <category>중첩 클래스</category>
      <category>토비</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/358</guid>
      <comments>https://zin0-0.tistory.com/358#entry358comment</comments>
      <pubDate>Fri, 11 Jun 2021 16:43:46 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 파일 편집 관련 명령어(grep, sed)</title>
      <link>https://zin0-0.tistory.com/357</link>
      <description>&lt;h3&gt;문자열 찾기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;grep&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;텍스트 검색 기능을 가진 명령어&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;grep [Options..] PATTERN [File...]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;옵션&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;명령어&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;-E&lt;/td&gt;
&lt;td&gt;PATTERN을 확장 정규 표현식(Extended RegEx)으로 해석.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-F&lt;/td&gt;
&lt;td&gt;PATTERN을 정규 표현식(RegEx)이 아닌 일반 문자열로 해석.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-G&lt;/td&gt;
&lt;td&gt;PATTERN을 기본 정규 표현식(Basic RegEx)으로 해석.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-P&lt;/td&gt;
&lt;td&gt;PATTERN을 Perl 정규 표현식(Perl RegEx)으로 해석.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-e&lt;/td&gt;
&lt;td&gt;매칭을 위한 PATTERN 전달.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-f&lt;/td&gt;
&lt;td&gt;파일에 기록된 내용을 PATTERN으로 사용.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-i&lt;/td&gt;
&lt;td&gt;대/소문자 무시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-v&lt;/td&gt;
&lt;td&gt;매칭되는 PATTERN이 존재하지 않는 라인 선택.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-w&lt;/td&gt;
&lt;td&gt;단어(word) 단위로 매칭.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-x&lt;/td&gt;
&lt;td&gt;라인(line) 단위로 매칭.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-z&lt;/td&gt;
&lt;td&gt;라인을 newline(\n)이 아닌 NULL(\0)로 구분.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-m&lt;/td&gt;
&lt;td&gt;최대 검색 결과 갯수 제한.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-b&lt;/td&gt;
&lt;td&gt;패턴이 매치된 각 라인(-o 사용 시 문자열)의 바이트 옵셋 출력.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-n&lt;/td&gt;
&lt;td&gt;검색 결과 출력 라인 앞에 라인 번호 출력.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-H&lt;/td&gt;
&lt;td&gt;검색 결과 출력 라인 앞에 파일 이름 표시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-h&lt;/td&gt;
&lt;td&gt;검색 결과 출력 시, 파일 이름 무시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-o&lt;/td&gt;
&lt;td&gt;매치되는 문자열만 표시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-q&lt;/td&gt;
&lt;td&gt;검색 결과 출력하지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-a&lt;/td&gt;
&lt;td&gt;바이너리 파일을 텍스트 파일처럼 처리.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-I&lt;/td&gt;
&lt;td&gt;바이너리 파일은 검사하지 않음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-d&lt;/td&gt;
&lt;td&gt;디렉토리 처리 방식 지정. (read, recurse, skip)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-D&lt;/td&gt;
&lt;td&gt;장치 파일 처리 방식 지정. (read, skip)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-r&lt;/td&gt;
&lt;td&gt;하위 디렉토리 탐색.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-R&lt;/td&gt;
&lt;td&gt;심볼릭 링크를 따라가며 모든 하위 디렉토리 탐색.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-L&lt;/td&gt;
&lt;td&gt;PATTERN이 존재하지 않는 파일 이름만 표시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-l&lt;/td&gt;
&lt;td&gt;패턴이 존재하는 파일 이름만 표시.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-c&lt;/td&gt;
&lt;td&gt;파일 당 패턴이 일치하는 라인의 갯수 출력.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;문자열 수정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;sed&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;텍스트 분해 &amp;amp; 변환 기능을 가진 명령어&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sed Options.. [명령어] [FILE...]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sed -i &amp;#39;s/찾을 문자열/바꿀 문자열 /g&amp;#39; [File...]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;옵션&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-n, --quiet, --silent&lt;ul&gt;
&lt;li&gt;suppress automatic printing of pattern space&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-e script, --expression=script&lt;ul&gt;
&lt;li&gt;add the script to the commands to be executed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-f script-file, --file=script-file&lt;ul&gt;
&lt;li&gt;add the contents of script-file to the commands to be executed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;--follow-symlinks&lt;ul&gt;
&lt;li&gt;follow symlinks when processing in place; hard links will still be broken.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-i[SUFFIX], --in-place[=SUFFIX]&lt;ul&gt;
&lt;li&gt;edit files in place (makes backup if extension supplied). The default operation mode is to break symbolic and hard links. This can be changed with --follow-symlinks and --copy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-c, --copy&lt;ul&gt;
&lt;li&gt;use copy instead of rename when shuffling files in -i mode. While this will avoid breaking links (symbolic or hard), the resulting editing operation is not atomic. This is rarely the desired mode; --follow-symlinks is usually enough, and it is both faster and more secure.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-l N, --line-length=N&lt;ul&gt;
&lt;li&gt;specify the desired line-wrap length for the `l&amp;#39; command&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;--posix&lt;ul&gt;
&lt;li&gt;disable all GNU extensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-r, --regexp-extended&lt;ul&gt;
&lt;li&gt;use extended regular expressions in the script.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-s, --separate&lt;ul&gt;
&lt;li&gt;consider files as separate rather than as a single continuous long stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;-u, --unbuffered&lt;ul&gt;
&lt;li&gt;load minimal amounts of data from the input files and flush the output buffers more often&lt;/li&gt;
&lt;li&gt;--help display this help and exit&lt;/li&gt;
&lt;li&gt;--version output version information and exit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;연산자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산자&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;p&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;출력&lt;/strong&gt;&lt;br /&gt;모든 줄을 출력하고, 패턴과 일치하는 줄을 한번 더 출력&lt;br&gt;기본 출력을 금지하고, 패턴과 일치하는 줄만 출력&lt;/td&gt;
&lt;td&gt;sed &amp;#39;/earth/p&amp;#39; myfile&lt;br&gt;sed -n &amp;#39;/earth/p&amp;#39; myfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;삭제&lt;/strong&gt;&lt;br /&gt;3번째 줄을 삭제하고 나머지 모든 줄을 출력&lt;br /&gt;3번째 ~ 마지막까지 삭제, 나머지 모든 줄 출력&lt;br /&gt;마지막 줄 삭제, 나머지 모든 줄 출력&lt;br /&gt;패턴과 일치하는 줄을 삭제, 나머지 모든 줄 출력&lt;/td&gt;
&lt;td&gt;sed &amp;#39;3d&amp;#39; myfile&lt;br /&gt;sed &amp;#39;3, $d&amp;#39; myfile&lt;br /&gt;sed &amp;#39;$d&amp;#39; myfile&lt;br /&gt;sed &amp;#39;/earth/d&amp;#39; myfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;치환&lt;/strong&gt;&lt;br /&gt;g 플래그는 전역을 의미&lt;br /&gt;&lt;br /&gt;west를 north로 치환&lt;br /&gt;west로 시작하는 줄을 north로 치환하고 그 줄만 출력&lt;/td&gt;
&lt;td&gt;sed &amp;#39;s/west/north/g&amp;#39; myfile&lt;br /&gt;sed -n &amp;#39;s/^west/north/g&amp;#39; myfile&lt;br /&gt;sed -n &amp;#39;s/west/north/gp&amp;#39; myfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  ​    &lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://recipes4dev.tistory.com/157&quot;&gt;https://recipes4dev.tistory.com/157&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hahwul.com/2019/02/10/multi-file-replace-string-with-grep-sed/&quot;&gt;https://www.hahwul.com/2019/02/10/multi-file-replace-string-with-grep-sed/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.computerhope.com/unix/used.htm&quot;&gt;https://www.computerhope.com/unix/used.htm&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;참고하면 좋을 글&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://board.theko.co.kr/bbs/board.php?bo_table=Command&amp;amp;wr_id=20&quot;&gt;http://board.theko.co.kr/bbs/board.php?bo_table=Command&amp;amp;wr_id=20&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Linux</category>
      <category>grep</category>
      <category>sed</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/357</guid>
      <comments>https://zin0-0.tistory.com/357#entry357comment</comments>
      <pubDate>Fri, 4 Jun 2021 14:01:40 +0900</pubDate>
    </item>
    <item>
      <title>2.4 스프링 테스트 적용 ~ 2.6 정리</title>
      <link>https://zin0-0.tistory.com/356</link>
      <description>&lt;h2&gt;2장 테스트&lt;/h2&gt;
&lt;h3&gt;2.4 스프링 테스트 적용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;앞서 작성한 테스트 코드에서 &lt;code&gt;@Before&lt;/code&gt;(Junit5는 &lt;code&gt;BeforeEach&lt;/code&gt;)로 인해 Application Context가 테스트 메소드 개수만큼 생성되는 문제점이 존재&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application Context 생성에는 많은 시간과 자원이 소모&lt;/li&gt;
&lt;li&gt;테스트 전체가 공유하는 오브젝트로 수정&lt;ul&gt;
&lt;li&gt;빈은 싱글톤으로 만들었기 때문에 무상태성&lt;/li&gt;
&lt;li&gt;UserDao 빈을 가져다 메소드를 사용한다고 해서 상태가 바뀌지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Before(Junit5 -&amp;gt; @BeforeEach)&lt;/code&gt;를 &lt;code&gt;@BeforeClass(Junit5 -&amp;gt; @BeforeAll)&lt;/code&gt;로 수정&lt;ul&gt;
&lt;li&gt;테스트 클래스 전체에 걸쳐 딱 한번만 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트를 위한 Application Context 관리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Junit을 이용하는 테스트 컨텍스트 프레임워크를 제공&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Autowired&lt;/code&gt;를 이용해서 빈 주입&lt;ul&gt;
&lt;li&gt;Autowired&lt;ul&gt;
&lt;li&gt;스프링의 DI에 사용되는 애노테이션&lt;/li&gt;
&lt;li&gt;변수 타입과 일치하는 컨텍스트 내의 빈을 찾아 주입&lt;/li&gt;
&lt;li&gt;같은 타입의 빈이 두 개 이상 있는 경우에는 타입만으로 결정할 수 없음&lt;ul&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;Test Class에는 &lt;code&gt;@Runwith&lt;/code&gt;와 &lt;code&gt;@ContextConfiguration(location=&amp;quot;path&amp;quot;)&lt;/code&gt; 를 붙임&lt;ul&gt;
&lt;li&gt;Runwith&lt;ul&gt;
&lt;li&gt;테스트 실행 방법을 확장 ~&amp;gt; JUnit이 테스트 진행 중, 테스트가 사용할 Application Context를 만들고 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ContextConfiguration&lt;ul&gt;
&lt;li&gt;Application Context의 설정 파일 위치를 지정&lt;/li&gt;
&lt;li&gt;테스트와 운영 DB를 공유하는 경우, 올바른 테스트 및 운영에 어려움이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 메소드의 컨텍스트 공유&lt;ul&gt;
&lt;li&gt;Junit 확장기능은 테스트 실행 전, 한 번만 Application Context를 만들어두고, 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해서 Application Context 자신을 테스트 오브젝트의 특정 필드에 주입(일종의 DI)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 클래스의 컨텍스트 공유&lt;ul&gt;
&lt;li&gt;스프링은 테스트 클래스 사이에서도 Application Context를 공유하게 해줌&lt;/li&gt;
&lt;li&gt;같은 설정파일 사용 ~&amp;gt; 단 한 개의 Application Context 생성 &amp;amp; 공유&lt;/li&gt;
&lt;li&gt;스프링 Application Context는 초기화할 때, 자기 자신도 빈으로 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI와 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;인터페이스를 두고 DI를 적용해야하는 이유&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;수정에 대한 비용 부담을 줄일 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;인터페이스를 두고 DI를 적용하면, 다른 차원의 서비스 기능 도입 가능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1장에서 학습했던, DB 커넥션 개수를 카운팅하는 부가기능이 예시&lt;/li&gt;
&lt;li&gt;새로운 기능을 넣거나 제거하기 위해 기존 코드는 전혀 수정할 필요가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;효율적인 테스트를 손쉽게 만들기 위해&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 코드에 의한 DI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB Connection과 관련된 Configuration을 수정할 때, 테스트 중 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Before(Junit5 -&amp;gt; @BeforeEach)&lt;/code&gt; 메소드에서 준비된 테스트용 DataSource 오브젝트를 생성하고 Application Context에서 가져온 Dao 오브젝트의 setDataSource를 통해 DI를 해줄 수 있음&lt;/li&gt;
&lt;li&gt;이 경우, &lt;code&gt;@DirtiesContext&lt;/code&gt;를 클래스에 붙여줘야함&lt;ul&gt;
&lt;li&gt;테스트 메소드에서 Application Context의 구성이나 상태를 변경한다는 것을 의미&lt;/li&gt;
&lt;li&gt;이 경우, Application Context의 공유를 허용하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Context 구성이나 상태를 테스트 내에서 변경하지 않는 것이 원칙이므로 사용에 주의&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;의존 관계를 강제로 변경 ~&amp;gt; Application Context는 테스트 중 하나만 만들어지고 모든 테스트에서 공유하기 때문&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;p&gt;테스트를 위한 별도의 DI 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트에서 사용될 DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법을 이용&lt;ul&gt;
&lt;li&gt;번거롭게 수동 DI하는 코드나 &lt;code&gt;@DirtiesContext&lt;/code&gt;가 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;컨테이너 없는 DI 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링 컨테이너를 이용해서 IoC방식으로 생성되고 DI 되도록 하는 대신, 테스트 코드에서 직접 오브젝트를 만들고 DI해서 사용해도 됨&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserDaoTest {
    UserDao dao; // @Autowired가 없음

    @BeforeEach
    public void setUp() {
        // ...
        dao = new UserDao();
        DataSource dataSource = new SingleConnectionDataSource(~);
        dao.setDataSource(dataSource);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;코드가 단순해지고 테스트시간 절약&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application Context가 만들어지는 번거로움이 없어짐&lt;/li&gt;
&lt;li&gt;매번 새로운 테스트 오브젝트를 만들기 때문에, 새로운 UserDao 오브젝트가 만들어지지만, Application Context에 비해 부담이 없음&lt;/li&gt;
&lt;li&gt;스프링 API에 의존하지 않고 자신의 관심에만 집중해서 깔끔하게 테스트 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI를 이용한 테스트 방법&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 &lt;strong&gt;우선적&lt;/strong&gt;으로 고려&lt;ul&gt;
&lt;li&gt;수행 속도가 가장 빠르고 간결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복잡한 의존관계를 갖고 있는 오브젝트 테스트 ~&amp;gt; 스프링 설정을 이용한 DI 방식&lt;/li&gt;
&lt;li&gt;예외적인 의존관계를 강제로 구성한 테스트 ~&amp;gt; 수동 DI 테스트&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@DirtiesContext&lt;/code&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;h3&gt;2.5 학습 테스트로 배우는 스프링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;학습 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;라이브러리 등을 테스트하는 경우&lt;/li&gt;
&lt;li&gt;기능을 제대로 이해하고 있는지, 사용 방법을 알고 있는지 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;학습 테스트의 장점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다양한 조건에 따른 기능을 손쉽게 확인 가능&lt;/li&gt;
&lt;li&gt;학습 테스트 코드를 개발 중에 참고할 수 있다.&lt;ul&gt;
&lt;li&gt;다양한 기능과 조건에 대한 테스트 코드를 개별적으로 만들고 남겨두면, 샘플 코드로 참고 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프레임워크나 제품을 업그레이드할 때 호환성 검증을 도움&lt;/li&gt;
&lt;li&gt;테스트 작성에 대한 좋은 훈련이 됨&lt;/li&gt;
&lt;li&gt;새로운 기술을 공부하는 과정이 즐거워짐&lt;ul&gt;
&lt;li&gt;당장 적용할 일부 기능의 사용법을 익히기 위해서만이 아니라, 새로운 프레임워크나 기술을 전반적으로 공부하는 과정에서도 유용&lt;/li&gt;
&lt;li&gt;참고할 수 있는 가장 좋은 소스는 스프링 자신에 대한 테스트코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;학습 테스트 예제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Runwith(SpringJunit4ClassRunner.class)
@ContextConfiguration
public class JunitTest {
    @Autowired ApplicationContext context;

    static Set&amp;lt;JunitTest&amp;gt; testObjects = new HashSet&amp;lt;&amp;gt;();
    static ApplicationContext contextObject = null;

    @Test
    public void test1() {
        assertThat(testObjects, not(hasItem(this)));
        testObjects.add(this);

        assertTrue(contextObject == null || contextObject == this.context);
        contextObject = this.context;
    }
    @Test
    public void test2() {
        assertThat(testObjects, not(hasItem(this)));
        testObjects.add(this);

        assertThat(contextObject,
                  either(is(nullValue())).or(is(this.contet)));
        contextObject = this.context;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;is()&lt;ul&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;p&gt;버그 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드에 오류가 있을 때, 그 오류를 가장 잘 드러내줄 수 있는 테스트&lt;/li&gt;
&lt;li&gt;필요성 및 장점&lt;ul&gt;
&lt;li&gt;테스트의 완성도를 높여줌&lt;ul&gt;
&lt;li&gt;불충분한 테스트를 보완&lt;/li&gt;
&lt;li&gt;비슷한 문제 발생 시, 쉽게 추적 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내용을 명확하게 분석해줌&lt;ul&gt;
&lt;li&gt;어떤 이유 때문에 발생했는지 효과적으로 분석 가능&lt;/li&gt;
&lt;li&gt;또한, 함께 발생하는 사이드이펙트를 함께 발견할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기술적인 문제를 해결하는데 도움이 됨&lt;ul&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;/ul&gt;
&lt;h3&gt;2.6 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;침투적 기술과 비침투적 기술&lt;ul&gt;
&lt;li&gt;침투적 기술&lt;ul&gt;
&lt;li&gt;기술을 적용했을 때, Application 코드에 기술 관련 API가 등장하거나, 특정 인터페이스나 클래스를 사용하도록 강제하는 기술&lt;/li&gt;
&lt;li&gt;해당 기술에 종속됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비침투적 기술&lt;ul&gt;
&lt;li&gt;Application 로직을 담은 코드에 영향을 주지 않고 적용 가능&lt;/li&gt;
&lt;li&gt;순수한 코드 유지&lt;/li&gt;
&lt;li&gt;스프링이 대표적인 비침투적 기술의 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동등분할&lt;ul&gt;
&lt;li&gt;같은 결과를 내는 값의 범위를 구분 ~&amp;gt; 대표 값으로 테스트하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;경계값 분석&lt;ul&gt;
&lt;li&gt;에러가 동등분할 범위의 경계에서 주로 많이 발생 ~&amp;gt; 경계 근처에 있는 값을 이용해 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 코드 작성&lt;ul&gt;
&lt;li&gt;자동화돼야 하고, 빠르게 실행할 수 있어야한다.&lt;/li&gt;
&lt;li&gt;항상 결과가 일관돼야한다.&lt;/li&gt;
&lt;li&gt;포괄적으로 작성해야한다. ~&amp;gt; 충분한 검증을 하지 않는 테스트는 없는 것 보다 나쁨&lt;/li&gt;
&lt;li&gt;테스트하기 쉬워야하며, 적절한 리팩토링이 필요하다&lt;/li&gt;
&lt;li&gt;오류가 발견될 경우에 대한 테스트를 만들어 두면 유용하다.&lt;ul&gt;
&lt;li&gt;이를 응용해서 TDD를 진행하면 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>DI</category>
      <category>테스트</category>
      <category>토비 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/356</guid>
      <comments>https://zin0-0.tistory.com/356#entry356comment</comments>
      <pubDate>Thu, 3 Jun 2021 14:32:41 +0900</pubDate>
    </item>
    <item>
      <title>2장) 2.3 개발자를 위한 테스팅 프레임워크 JUnit</title>
      <link>https://zin0-0.tistory.com/355</link>
      <description>&lt;h2&gt;2장 테스트&lt;/h2&gt;
&lt;h3&gt;2.3 개발자를 위한 테스팅 프레임워크 JUnit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링 프레임워크도 JUnit을 이용해 테스트를 통해 개발됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트 없이는 스프링도 의미 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;빌드 툴&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ANT, Maven, Gradle과 같은 빌드 툴과 스크립트를 사용한다면 ~&amp;gt; 빌드 툴에서 제공하는 Junit 플러그인이나 태스크를 이용해 JUnit 테스트를 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 결과의 일관성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 서버가 다운되거나 네트워크 장애 등 외부 상태에 따라 테스트가 성공하기도 실패하기도 한다면 좋지 못한 테스트이다.&lt;/li&gt;
&lt;li&gt;또한, 테스트를 마친 후, 수행하기 이전으로 DB의 상태를 돌려야 함&lt;/li&gt;
&lt;li&gt;동일한 결과를 보장하는 테스트&lt;ul&gt;
&lt;li&gt;예시의 테스트를 시작하기 전에 기존의 데이터를 항상 지우고 시작하게 설정하면, 해당 add를 하는 메소드에서, 항상 같은 결과 값을 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;포괄적인 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getCount 테스트를 조금 더 꼼꼼하게 수정하자&lt;ul&gt;
&lt;li&gt;3명의 각기 다른 유저 오브젝트를 생성해두고, deleteAll을 수행한 다음, 각 유저 오브젝트를 add 메소드로 등록하면서 getCount가 제대로 동작하는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;예외 조건 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;get() 테스트의 경우, 메소드에 전달된 id 값에 해당하는 사용자 정보가 없는 경우, 예외를 던지는 것이 바람직하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;@Test Annotation에 expected를 추가해두면, 보통의 테스트와 반대로 정상적인 테스트는 실패하고, expected에서 지정한 예외가 던져지면 테스트가 성공한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Test(expected=EmptyResultDataAccessException.class)
public void getUserFailure() throws SQLException {
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트가 이끄는 개발&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기능 설계를 위한 테스트&lt;ul&gt;
&lt;li&gt;Given, When, Then으로 구성하여 개발 흐름의 기능설계의 일부분을 테스트 코드가 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 주도 개발 (TDD)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트 코드를 먼저 작성 ~&amp;gt; 테스트를 성공하게 해주는 코드를 작성하는 개발 방식&lt;/li&gt;
&lt;li&gt;테스트를 작성하는 시간과 Application 코드를 작성하는 시간의 간격이 짧아짐&lt;ul&gt;
&lt;li&gt;이 주기를 최대한 짧게 가져가도록 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;머릿속에서 복잡하게 진행하던 작업을 실제 코드로 끄집어 내놓으면 TDD&lt;/li&gt;
&lt;li&gt;테스트 없이 한번에 너무 많은 코드를 작성하는 것을 지양한다.&lt;/li&gt;
&lt;li&gt;테스트를 만들고 자주 실행하면 개발이 지연되지 않을까 염려할 수 있겠지만, 그렇지 않다.&lt;ul&gt;
&lt;li&gt;테스트 덕분에 오류를 빨리 잡아낼 수 있어서 전체 개발 속도는 오히려 빨라짐&lt;/li&gt;
&lt;li&gt;서버에 올려서 테스트하고 코드 수정하는 과정이 생략되어 속도가 빨라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트 코드 개선&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리팩토링 대상은 애플리케이션 코드만이 아닌 테스트 코드도 포함된다.&lt;ul&gt;
&lt;li&gt;내부 구조 및 설계를 개선해서 좀 더 깔끔하고 이해하기 쉬우며, 변경이 용이한 코드를 만들 필요가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;getBean을 통해 context에서 bean을 주입받는 코드의 경우, 메소드 추출 리팩토링 방법 대신, JUnit의 &lt;code&gt;@Before&lt;/code&gt;를 이용해서 편하게 관리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JUnit 테스트 수행 방식&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.&lt;/li&gt;
&lt;li&gt;테스트 클래스의 오브젝트를 하나 만든다.&lt;/li&gt;
&lt;li&gt;@Before가 붙은 메소드가 있으면 실행&lt;/li&gt;
&lt;li&gt;@Test가 붙은 메소드 하나를 호출, 테스트 결과 저장&lt;/li&gt;
&lt;li&gt;@After 붙은 메소드 실행&lt;/li&gt;
&lt;li&gt;나머지 테스트 메소드에 대해 2~5 반복&lt;/li&gt;
&lt;li&gt;모든 테스트 결과를 종합해서 리턴&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;실제 과정은 더욱 복잡하지만, 간략하게 정리하면 위의 7단계를 거침&lt;/li&gt;
&lt;li&gt;테스트 메소드를 실행할 때마다 새로운 오브젝트를 만드는 이유는, 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위함&lt;ul&gt;
&lt;li&gt;따라서, 모든 테스트는 실행 순서에 상관 없이 독립적으로 항상 동일한 결과를 낼 수 있도록 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;픽스처(Fixture)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트를 수행하는 데 필요한 정보나 오브젝트&lt;ul&gt;
&lt;li&gt;일반적으로 여러 테스트에서 반복적으로 사용되기 때문에, @Before 메소드를 이용해 생성해두면 편리하다&lt;/li&gt;
&lt;li&gt;대표적인 예시는 dao&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;현재 버전에서는 Before는 deprecated ~&amp;gt; BeforeEach&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>TDD</category>
      <category>토비의 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/355</guid>
      <comments>https://zin0-0.tistory.com/355#entry355comment</comments>
      <pubDate>Tue, 1 Jun 2021 13:47:30 +0900</pubDate>
    </item>
    <item>
      <title>2장) 2.1 USERDAOTEST 다시보기 ~ 2.2 USERDAOTEST 개선</title>
      <link>https://zin0-0.tistory.com/354</link>
      <description>&lt;h2&gt;2장 테스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;객체지향 기술과 더불어 하나의 도구로 스프링이 강조하고 가치를 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.1 UserDaoTest 다시보기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트의 유용성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테스트란, 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인 ~&amp;gt; 확신&lt;ul&gt;
&lt;li&gt;결과가 원하는 대로 나오지 않는 경우 ~&amp;gt; 코드나 설계에 결함 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;웹을 통한 DAO 테스트 방법의 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문제점&lt;ul&gt;
&lt;li&gt;DAO 뿐만 아니라 서비스, 컨트롤러, 뷰 등 모든 레이어 기능을 만든 후, 테스트가 가능&lt;/li&gt;
&lt;li&gt;테스트에 실패하면, 어디에서 문제가 발생했는지 찾아야하는 수고가 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;단위 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트는 가능하면 작은 단위로 쪼개서 집중해서 할 수 있어야한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;관심이 다르다면, 테스트할 대상을 분리하고 집중해서 접근한다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;작은 단위 코드에 대한 테스트가 &lt;strong&gt;단위 테스트&lt;/strong&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정해진 범위나 단위가 정해지지는 않았지만, 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;통제할 수 없는 외부 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트에 DB가 사용되는 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DB 상태가 매번 달라지고, 테스트를 위해 DB를 특정 상태로 만들어줄 수 없는 경우&lt;/p&gt;
&lt;p&gt;~&amp;gt; 테스트로서 가치가 없음&lt;/p&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;p&gt;자동수행 테스트 코드&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;번거로운 작업이 없이 자주 반복할 수 있어야 한다&lt;ul&gt;
&lt;li&gt;운영 중인 코드를 수정하려고 하면 간단한 수정이라도, 전체 애플리케이션에 문제를 일으키지 않을까 하는 고민이 생긴다.&lt;/li&gt;
&lt;li&gt;이 때, 자동 수행 테스트를 통해, 최대한 빠르게 확인하고 성공하는지 확인해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;지속적인 개선 &amp;amp; 점진적인 개발을 위한 테스트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;앞서 초난감 DAO코드를 개선해나갔는데, DAO 기능에 문제가 없는지 검증하는 테스트를 만들어 두고, 자신을 가지고 코드를 개선해나갔다.&lt;/li&gt;
&lt;li&gt;기능을 만들고, 테스트로 검증 ~&amp;gt; 코드에 대한 확신을 갖는다.&lt;/li&gt;
&lt;li&gt;기능을 조금씩 추가하면서, 테스트도 함께 추가 ~&amp;gt; 점진적인 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDaoTest의 문제점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;수동 확인 작업의 번거로움&lt;ul&gt;
&lt;li&gt;현재 작성된 코드는 콘솔에 값만 출력해줄 뿐, 결과를 확인하는 일이 사람의 책임으로 되어있다.&lt;ul&gt;
&lt;li&gt;검증의 양이 많고 복잡해지면 실수할 가능성이 높아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실행 작업의 번거로움&lt;ul&gt;
&lt;li&gt;테스트 규모가 커질수록 main() 메소드를 호출해야하는 번거로움이 증가하고, 결과를 확인하고 문서화하는 작업이 개발만큼 큰 작업이 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 UserDaoTest 개선&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트 검증의 자동화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 테스트는 성공 / 실패 로 결과가 나뉜다.&lt;/li&gt;
&lt;li&gt;getName, getPassword의 값이 같은지 다른지 비교하여, if / else를 통해 테스트를 성공 / 실패 처리해주도록 바꾼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;테스트의 효율적인 수행과 결과 관리&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;main() 메소드를 이용한 테스트 작성 방법&lt;ul&gt;
&lt;li&gt;애플리케이션  규모가 커지고 테스트 개수가 많아지면 테스트 수행이 점점 부담&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JUnit&lt;ul&gt;
&lt;li&gt;자바로 단위 테스트를 만드는 것을 지원해주는 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JUnit 테스트로 전환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트 메소드의 조건&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public으로 선언되야 한다.&lt;/li&gt;
&lt;li&gt;@Test 애노테이션을 붙여야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class UserDaoTest {
    // ...
    @Test
    public void addAndGet() throws SQLException {
        ApplicationContext context = new ClassPathXmlApplicationContext(&amp;quot;applicationContext.xml&amp;quot;);

        UserDao dao = context.getBean(&amp;quot;userDao&amp;quot;, UserDao.class);
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;테스트 메소드 조건에 따라 바꾸어 주면 위와 같은 형태의 코드가 된다.&lt;/li&gt;
&lt;li&gt;@Test 애노테이션마다 하나의 단위 테스트가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;검증 코드 전환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위에서 if / else 처리했던 문장을 JUnit이 제공해주는 assertThat을 사용해서 변경&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;assertThat(user2.getName(), is(user.getName()));&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>junit</category>
      <category>테스트</category>
      <category>토비의 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/354</guid>
      <comments>https://zin0-0.tistory.com/354#entry354comment</comments>
      <pubDate>Thu, 27 May 2021 13:51:11 +0900</pubDate>
    </item>
    <item>
      <title>1장) 1.8 XML을 이용한 설정 ~ 1.9 정리</title>
      <link>https://zin0-0.tistory.com/353</link>
      <description>&lt;h2&gt;1장 오브젝트와 의존관계&lt;/h2&gt;
&lt;h3&gt;1.8 XML을 이용한 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;장점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;별도의 빌드 작업이 필요 없다&lt;/li&gt;
&lt;li&gt;환경이 달라져서 오브젝트의 관계가 바뀌는 경우, 빠르게 변경사항 반영 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;XML 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DI 정보가 담긴 XML 파일은 &lt;code&gt;&amp;lt;beans&amp;gt;&lt;/code&gt;를 루트 엘리먼트로 사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@Configuration과 @Bean이 붙은 자바 클래스로 만든 설정과 내용이 동일&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;@Bean의 DI 정보 3가지&lt;ul&gt;
&lt;li&gt;빈의 이름&lt;ul&gt;
&lt;li&gt;@Bean 메소드 이름 (getBean()에서 사용됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빈의 클래스&lt;ul&gt;
&lt;li&gt;빈 오브젝트를 어떤 클래스를 이용해서 만들지 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빈의 의존 오브젝트&lt;ul&gt;
&lt;li&gt;생성자나 수정자 메소드를 통해 의존 오브잭트를 주입&lt;/li&gt;
&lt;li&gt;의존 오브젝트는 하나 이상일 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;connecitonMaker() 전환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;자바 코드 설정 정보&lt;/th&gt;
&lt;th&gt;XML 설정 정보&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;빈 설정 파일&lt;/td&gt;
&lt;td&gt;@Configuration&lt;/td&gt;
&lt;td&gt;&amp;lt;beans&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈 이름&lt;/td&gt;
&lt;td&gt;@Bean methodName()&lt;/td&gt;
&lt;td&gt;&amp;lt;bean id =&amp;quot;methodName&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈 클래스&lt;/td&gt;
&lt;td&gt;return new BeanClass();&lt;/td&gt;
&lt;td&gt;class=&amp;quot;a.b.c... BeanClass&amp;quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;class 애트리뷰트 설정 -&amp;gt; 오브젝트를 만들 때 사용하는 클래스 이름(return 타입 지정 X&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean                                    // &amp;lt;bean
public ConnectionMaker 
connectionMaker() {                        // id=&amp;quot;connectionMaker&amp;quot;
    return new DConnectionMaker();        // class=&amp;quot;springbook...DConnectionMaker /&amp;gt;&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;userDao() 전환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링 개발자가 수정자 메소드를 사용해 의존관계를 주입하는 것을 선호하는 이유는 XML로 의존관계 정보를 만들 때 편리하기 때문&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;자바빈의 관례에 따라 수정자 메소드는 프로퍼티가 되고, 프로퍼티 이름은 메소드 이름에서 set을 제외한 나머지 부분을 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&amp;lt;property&amp;gt; 에는 name과 ref 두 개의 애트리뷰트가 있다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name&lt;ul&gt;
&lt;li&gt;프로퍼티의 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ref&lt;ul&gt;
&lt;li&gt;주입해줄 오브젝트의 빈 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code&gt;userDao.setConnectionMaker(connectionMaker());

&amp;lt;bean id=&amp;quot;userDao&amp;quot; class=&amp;quot;springbook.dao.UserDao&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;connectionMaker&amp;quot; ref=&amp;quot;connectionMaker&amp;quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;의존 관계 정보를 주입하는 코드 레벨을 xml로 변환하면 위와 같이 변경된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;XML의 의존관계 주입 정보&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&amp;lt;bean&amp;gt;으로 등록한 태그들을 &amp;lt;beans&amp;gt; 태그로 감싸주면 XML로의 전환 작업이 끝난다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;보통 프로퍼티 이름과 DI 되는 빈의 이름이 같은 경우가 많음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;바뀔 수 있는 클래스 이름보다는 대표적인 인터페이스 이름을 따르는 것이 자연스럽지만, 의미를 좀 더 잘 드러낼 수 있거나 같은 이름이 중복된다면 다르게 정해도 상관 없다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;beans&amp;gt;
    &amp;lt;bean id=&amp;quot;localDBConnectionMaker&amp;quot; class=&amp;quot;...LocalDBConnectionMaker&amp;quot; /&amp;gt;
    &amp;lt;bean id=&amp;quot;localDBConnectionMaker&amp;quot; class=&amp;quot;...LocalDBConnectionMaker&amp;quot; /&amp;gt;
    &amp;lt;bean id=&amp;quot;localDBConnectionMaker&amp;quot; class=&amp;quot;...LocalDBConnectionMaker&amp;quot; /&amp;gt;

    &amp;lt;bean id=&amp;quot;userDao&amp;quot; class=&amp;quot;springbook.dao.UserDao&amp;quot;&amp;gt;
        &amp;lt;property name=&amp;quot;connectionMaker&amp;quot; ref=&amp;quot;localDBConnectionMaker&amp;quot; /&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이름이 중복되는 경우 &amp;amp; DI하는게 여러 개인 경우 위와 같이 설정해두고, running 환경에 따라 바꿔서 사용하는 경우가 더러 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;XML을 이용하는 Application Context&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;XML에서 빈의 의존관계 정보를 이용하는 IoC/DI 작업에는 GenericXmlApplicationContext를 이용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성자 파라미터로 XML 파일의 클래스패스를 지정해주면 됨&lt;/li&gt;
&lt;li&gt;클래스패스는 루트부터 시작하므로 &lt;code&gt;/&lt;/code&gt;를 생략해도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ClassPathXmlApplicationContext&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;클래스패스가 루트부터 시작하기 때문에, 경로가 길어지는 경우 실수할 확률도 있고 귀찮다..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;같은 위치에 있는 클래스 파일을 인자로 넘기면, 알아서 class path를 찾아준다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;new GenericXmlApplicationContext(&amp;quot;springbook/user/dao/daoContext.xml&amp;quot;);
new ClassPathXmlApplicationContext(&amp;quot;daoContext.xml&amp;quot;, UserDao.class);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위의 방법으로 클래스를 지정할 경우가 아니라면 GenericXmlApplicationContext를 사용하는 것이 좋다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataSource 인터페이스로 변환&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;앞서 구현한 ConntionMaker처럼 일반적으로 DataSource를 구현해서 DB 커넥션을 제공하는 클래스를 만들 일은 거의 없다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이미 좋은 인터페이스 &amp;amp; 클래스가 제공되기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataSource 인터페이스에서 실제 관심을 가질 것은 getConntion() 메소드 하나 뿐&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDao를 DataSource를 이용하도록 수정해보자&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import javax.sql.DataSource;

public class UserDao {
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void add(User user) throws SQLException {
        Connection connection = dataSource.getConnection();
        // logic..
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataSrouce는 인터페이스기 때문에 구현 클래스가 필요한데, 테스트환경에서 간단히 사용할 수 있는 SimpleDriverDataSource를 사용하자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public DataSource dataSource() {
    SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

    datasource.setDriverClass(com.mysql.jdbc.Driver.class);
    dataSource.setUrl(&amp;quot;jdbc:mysql://localhost/springbook&amp;quot;);
    dataSource.setUserName(&amp;quot;spring&amp;quot;);
    dataSource.setPassword(&amp;quot;book&amp;quot;);

    return dataSource;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;위와 같이 제공되고 있는 인터페이스와 클래스를 이용해서 더욱 쉽고 빠르게 connection을 리턴을 빠르게 개발할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;프로퍼티 값의 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위의 코드를 XML로 변환할 때, 빈 등록을 해주면서 어떻게 오브젝트 레퍼런스가 아닌 단순 값을 주입할 수 있을까?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;value 애트리뷰트를 사용하면 쉽게 값이 주입된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;bean id=&amp;quot;dataSource&amp;quot; class=&amp;quot;org.springframework.jdbc.datasource.SimpleDriverDataSource&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;dirverClass&amp;quot; value=&amp;quot;com.mysql.jdbc.Driver&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;url&amp;quot; value=&amp;quot;jdbc:mysql://localhost/springbook&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;username&amp;quot; value=&amp;quot;spring&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;password&amp;quot; value=&amp;quot;book&amp;quot; /&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;여기서 driverClass 값을 주입하는데, 스트링 타입으로 설정해서 어떻게 주입되는지 궁금할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링이 프로퍼티의 값을 수정자 메소드의 파라미터 타입을 참고해서 적절한 형태로 변환해주기 때문에 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Class driverClass = Class.forName(&amp;quot;com.mysql.jdbc.Driver&amp;quot;);
dataSource.setDriverClass(driverClass);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&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;/ul&gt;
&lt;h3&gt;1.9 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;스프링이란 &lt;code&gt;어떻게 오브젝트가 설계뙤고, 만들어지고, 관계를 맺고 사용되는지에 관심을 같는 프레임워크&lt;/code&gt; 라는 사실이 가장 중요&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;어떻게 설계하고 분리하고, 개선하고, 의존관계를 가질지는 개발자의 몫이고, 스프링은 도구일 뿐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1장에서 진행한 내용 간략한 정리&lt;ul&gt;
&lt;li&gt;책임이 다른 코드 분리&lt;/li&gt;
&lt;li&gt;전략패턴&lt;ul&gt;
&lt;li&gt;바뀔 수 있는 쪽의 클래스를 인터페이스로 구현하고, 다른 클래스에서 인터페이스를 통해서만 접근하도록 설정 ~&amp;gt; 구현 클래스에 변경이 일어나도 인터페이스 및 다른 구현 클래스의 변경X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;개방 폐쇄 원칙&lt;ul&gt;
&lt;li&gt;자신의 책임 자체가 변경되는 경우 외에는 불필요한 변화가 발생하지 않게 하고, 자유롭게 확장 및 변경할 수 있도록 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;낮은 결합도 &amp;amp; 높은 응집도&lt;/li&gt;
&lt;li&gt;제어의 역전(IoC)&lt;ul&gt;
&lt;li&gt;IoC컨테이너로 넘겨서 오브젝트가 자신이 사용할 대상의 생성이나 선택에 관한 책임으로부터 자유롭게 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;싱글톤 레지스트리&lt;ul&gt;
&lt;li&gt;전통 싱글톤 패턴의 단점을 극복하며 장점을 살리도록 설계된 컨테이너를 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;의존관계 주입(DI)&lt;ul&gt;
&lt;li&gt;설계 시점과 코드에는 느슨한 의존관계 설정, 런타임시에 제3자인 DI 컨테이너를 통해 주입받아 의존관계를 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성자 주입과 수정자 주입&lt;/li&gt;
&lt;li&gt;XML 설정&lt;ul&gt;
&lt;li&gt;DI 설정정보와 의존 오브젝트가 아닌 일반 값을 외부에서 설정해서 런타인시에 주입하는 XML 설정 방법을 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DTD와 스키마&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;XML 문서는 미리 정해진 구조를 따라 작성됐는지 검사할 수 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;구조를 정의하는 방법에는 DTD와 스키마(schema)가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링은 기본 태그인 &amp;lt;beans&amp;gt;, &amp;lt;bean&amp;gt; 외에도 특별 목적의 태그를 사용하는 방법을 제공&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 태그들은 스키마(schema)에 정의되어 있기 때문에, 이런 기능을 사용하기 위해서는 DTD가 아닌 네임스페이스가 지원되는 스키마를 사용해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-xml-dtd&quot;&gt;&amp;lt;!DOCTYPE beans PUBLIC &amp;quot;-//SPRING//DTD BEAN 2.0//EN&amp;quot; 
    &amp;quot;http://www.springframework.org/dtd/spring-beans-2.0.dtd&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;beans xmlns=&amp;quot;http://www.springframework.org/schema/beans&amp;quot;
       xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLScheam-instance&amp;quot;
       xsi:schemaLocation=&amp;quot;http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;특별한 이유가 없다면 길더라도 DTD 보다는 스키마를 사용하는 것이 바람직 하다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>Configuration</category>
      <category>XML</category>
      <category>토비의 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/353</guid>
      <comments>https://zin0-0.tistory.com/353#entry353comment</comments>
      <pubDate>Thu, 27 May 2021 13:50:15 +0900</pubDate>
    </item>
    <item>
      <title>부스트캠프2020(5기) 후기</title>
      <link>https://zin0-0.tistory.com/352</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;세 줄 요약 &lt;/h3&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;li&gt;웹 개발에 대한 기본부터 깊은 부분까지 스스로 학습할 수 있는 좋은 기회가 됩니다&lt;/li&gt;
&lt;li&gt;개발에 대해 진심인 좋은 동료들과 네트워크 &amp;amp; 취직의 기회가 주어지는 1 + 2 입니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부스트캠프2020 챌린지 &lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://zin0-0.tistory.com/170?category=891836&quot;&gt;챌린지 합격 글 보러가기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zin0-0.tistory.com/247?category=891836&quot;&gt;챌린지 회고 &amp;amp; 멤버십 합격 글 보러가기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;너무 오랜 시간 지나기도 했고 그날의 기분과 느낌, 분위기는 위의 두 글에서 상세하게 작성되어 있다고 생각합니다 !!&lt;br /&gt;글을 작성하는 지금 시점(2021.05.23)에서 생각해보면, 개발에 기본이 되는 기초 지식을 학습하면서 코드레벨로 직접 구현해보는 재밌는 경험이었다고 생각합니다  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부스트캠프2020 멤버쉽 &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;학습 스프린트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 3개의 스프린트로 구성되어 있었고, 각 스프린트는 2주의 기간으로 진행되었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2주 x 3 =&amp;gt; 6 주&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기획서가 주어지고, 해당 기획서를 기반으로 구현해야할 프로젝트를 분석 &amp;amp; 설계를 거쳐 완성하는 교육 단계입니다.&lt;/li&gt;
&lt;li&gt;이 과정이 개인적으로는 부스트캠프 교육에서 &lt;b&gt;가장 중요한&lt;/b&gt; 단계라고 생각합니다.&lt;/li&gt;
&lt;li&gt;실무에서도 기획서를 바탕으로 설계 및 협업이 이루어지기 때문에, 기획서를 보고 전체적인 흐름을 짤 수 있는 능력을 기를 수 있는 과정입니다!&lt;/li&gt;
&lt;li&gt;기획서 뿐만이 아니라 참고할 학습 자료 및 키워드들이 함께 주어지기 때문에, 웹에 대한 지식이 부족한다고 해도 &lt;b&gt;체력과 열정만 있다면&lt;/b&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;이전 기수에서는 2개월 동안 하나의 그룹프로젝트를 진행했다고 하는데, 제가 이수한 5기인 부캠2020에서는 3주 / 5주 로 나뉜 두 번의 그룹 프로젝트를 진행했습니다.&lt;/li&gt;
&lt;li&gt;제 경험을 토대로 다시 생각해보면, 처음 3주 프로젝트는 자신이 어느정도 욕심이 있는지, 이 욕심을 모두 가져갈 수 없다면 어떤 부분을 포기해야하는지 돌아볼 수 있는 시간이라고 생각합니다.
&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; (변수명부터 시작해서 commit &amp;amp; pr 컨벤션 등 정말 신경쓸게 많습니다.) 느낄 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원활하게 커뮤니케이션&lt;/b&gt;하려면 상대방을 언제 배려해야하고 어떻게 자신의 의견을 주장해야하는지 배울 수 있는 기간이었습니다 !!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5주 프로젝트는 기업 지정과제 / 자유 과제를 선택해서 진행할 수 있었습니다.&lt;br /&gt;저는 기업 지정 과제를 선택해서 웨일 확장앱 API를 활용한 TODO 서비스 개발을 진행했습니다. (제 블로그에 올라와있는 CI &amp;amp; CD, TDD 글들은 이 프로젝트에서 경험하고 겪었던 내용을 토대로 작성되어있습니다.)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3주 동안의 경험을 토대로 현실적인 기술 스택 &amp;amp; 도전 과제를 설정할 수 있었고, 백로그 관리나 컨벤션 설정 등을 원할하게 가져갈 수 있었습니다.&lt;/li&gt;
&lt;li&gt;또한, 3주 프로젝트에서 완전품을 만들지 못해서 &lt;code&gt;내 포트폴리오에 올라갈 끝내주는 TODO 서비스를 만들겠다!!!&lt;/code&gt; 라는 욕심으로 참여했습니다.&lt;/li&gt;
&lt;li&gt;이 기간 동안 정말 많은 성장을 했습니다. CS 지식을 기반으로 내가 작성하는 코드는 어떻게 작동되고 어떻게 구성되어있는지 관심을 가장 많이 가졌고, 유지보수가 편리한 코드를 작성하려면 어떤 것들을 신경써야하는지, 팀원들과 가장 큰 시너지를 내려면 어떤 스탠스를 취해야하는지, 각자가 아닌 하나의 팀으로 개발을 하려면 어떻게 해야하는지 정말정말 많이 고민한 기간이었습니다.&lt;/li&gt;
&lt;li&gt;결론적으로 저희 팀에서 원했던 기대치의 90% 이상은 나온 서비스를 개발했습니다 !! (나만 그런거 아니지 얘들아..?)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3주 + 5주 = 8주의 팀 프로젝트 동안 개발 문화, 개발자로서의 태도, 좋은 코드를 작성하기 위한 고민 &amp;amp; 발전, 협업 능력, 커뮤니케이션 능력, 기획서 분석 등 정말 너무 많은 분야가 한번에 주입되고 성장할 수 있는 기간입니다 ! &lt;b&gt;이 기간동안 꼭 본인을 채찍질하면서 최대한 많은 것들을 흡수해가시길 바랍니다(인프라 지식과 기술 또한 덤입니다 ㅎㅎ)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;code&gt;나는, 우리 팀은 이런 개발자다&lt;/code&gt; 라는 것을 어필하는 자리입니다.&lt;/li&gt;
&lt;li&gt;부스트캠프 기간동안 가장 긴장이되고 간장 설렜던 순간입니다.&lt;br /&gt;많은 개발자 선배님들 앞에서 감히 주름잡으며&lt;br /&gt;나는 이런 고민을 하고 있는 신입입니다!! 더 성장하려면 어떻게 나아가야할까요?!&lt;br /&gt;와 같은 소통을 할 수 있는 &lt;b&gt;기회&lt;/b&gt;라고 생각합니다. (실제로 실무자들은 매우 바쁘신데, 국내에서 IT를 리딩하고 있는 선배님들과의 자리를 만들어 주신다니, 너무 과분하고 감사한 일이었다고 생각합니다)&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;Zoom으로 진행되었고, 세션과 파트를 나누어 각 팀들이 돌아가면서 발표를 진행했습니다.&lt;br /&gt;참여 기업의 관계자 분들이 마음에 드는 서비스 or 기술 스택을 보고 해당 zoom의 방으로 들어오시는 형태였습니다.&lt;br /&gt;각 팀마다 길지 않은 시간이 주어졌고, 짧게 서비스 소개와 기술 스택 소개, 지향점과 경험담을 공유하고 Q&amp;amp;A를 받는 식으로 진행했습니다. (다른 팀도 유사하게 진행했던 것 같습니다.)&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;네트워킹데이에 참여한 기업들의 zoom 회의실이 존재해서, 각 기업은 어떤 시간에 어떤 주제 혹은 설명회를 하겠다는 내용이 주어집니다. 발표는 기업들이 마음에 드는 팀을 픽하는 것이었다면, 이 때는 교육생들이 관심있는 기업들의 방으로 찾아가는 형태입니다.&lt;/li&gt;
&lt;li&gt;네이버, 카카오엔터를 시작으로 이름만 들어도 거의 아는 스타트업들이 대거 참여를 했었습니다. 많은 기업들이 있다보니까 모든 기업의 설명을 들을 수 없습니다.&lt;br /&gt;그래서 저는 팀원들과 같은 공간에서 네트워킹 데이를 진행했는데, 팀원들과 협의해서 서로 다른 방에 들어가 질문 사항 및 설명을 정리해서 공유했었습니다. (이 방법을 강력하게 추천드립니다!!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;부스트캠프 교육기간 동안, &lt;code&gt;이렇게까지 열심히한다고?&lt;/code&gt; 생각이 드는 분들이 정말 많았습니다. 아니 거의 전부라고 보셔도 될 것 같습니다. 기술적인 내용을 공유하기도 하고, 같은 시간을 다르게 쓰는 분들을 보면서 정말 큰 자극을 받았습니다.&lt;br /&gt;저는 전공자이지만 복수 전공을 하면서 기반 지식이 많이 부족했는데, 비전공자임에도 저보다 열심히 그리고 깊게 파고드는 분들도 많았고, 새로운 기술을 빠르게 도입하거나 기존의 기술을 관심깊게 깊이 파시는 분들도 많았습니다.&lt;br /&gt;이런 동료들 사이에서 나도 가만히 있을 수는 없다는 생각에 더욱 열심히 하게 됐습니다.&lt;br /&gt;그리고, 지금까지도 몇몇 동료들과 연락을 하며 기술적인 얘기를 가끔 하기도 합니다&lt;br /&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;br /&gt;교육 기간동안 나는 웹 개발을 왜 원하고, 어떤 부분에 더 관심이 있는지 명확해질 수 있었고, 부스트캠프에서 지향하는 &lt;code&gt;지속가능한 개발자&lt;/code&gt;가 되고싶다는 생각을 가지게 되었습니다.&lt;br /&gt;코드가 혹은 서비스가 정상적으로 돌아간다고해서 만족하지않고, &lt;code&gt;이건 어떤 원리로 작동하는거지?&lt;/code&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;br /&gt;사실 저도 교육 시작하기 전에는 이 부분이 가장 궁금했었습니다.&lt;br /&gt;결론부터 말씀드리면, 정말 많은 교육생들이 교육 연계 전형으로 주니어 개발자가 되었습니다 !&lt;br /&gt;하지만, 교육을 듣는 중반부터는 별로 중요하지 않다고 생각했는데, 그 이유는 위에 지향점이 취직이 아닌 성장하는 개발자로 변했기 때문이라고 생각합니다.&lt;br /&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;저 또한 부스트캠프의 은총(?)을 받아 연계 기업 중 하나인 NTS(엔테크서비스)에서 웹 풀스택 개발 직무로 커리어를 시작하게 되었습니다.&lt;br /&gt;여러 연계 전형에 지원을 하기도 했고, 개인적으로 지원한 기업들도 많았습니다.&lt;br /&gt;지금의 제 회사에서 저의 능력과 가능성을 가장 믿어주었기에 감사한 기회를 잡았습니다.&lt;br /&gt;또한, 제가 바라던 자바와 스프링을 중점으로 담당하는 서비스를 담당하게 되어 큰 만족감을 가지고 일을 하고 있습니다 :)&lt;/li&gt;
&lt;li&gt;너무 늦게 후기를 작성해서 빠진 부분도 많은 것 같습니다.&lt;br /&gt;하지만, 교육을 이수한지 어느정도 시간이 지났기 때문에, 객관적으로 교육의 구성을 전달하고 제가 느낀 부분을 전달하기에는 부족함이 없다고 생각합니다.&lt;br /&gt;혹여나 더 궁금한 점이 있으셔서 편하게 문의주시면, 확인하는대로 답변을 드리도록 하겠습니다!&lt;/li&gt;
&lt;li&gt;보기 힘든 아래의 텍스트들 말고, 위의 세 줄 요약만으로도 충분히 메리트가 넘친다고 생각합니다. 현재 부스트캠프를 고민하고 있는 웹 개발자 지망생이시라면 주저말고 도전하시라고 말씀드리고 싶습니다 !&lt;br /&gt;도전하면 성공확률이 50%이지만, 도전하지 않는다면 0%에 수렴하게 됩니다. (물론, 부스트캠프에 참여한다는 사실에 수렴하는 퍼센트입니다 ㅎㅎ 꼭, 여러 교육이 아니더라도 본인의 커리어에 대해 관심깊게 고민하고 낮은 순위의 이 글까지 보신 분이라면 충분히 높게 날아오르실 겁니다!)&lt;/li&gt;
&lt;li&gt;저 또한 교육 이전에는 막연히 백엔드와 웹 개발에 관심이 있었는데, 이 교육을 들으면서 방향성이 명확해졌습니다. 저처럼 고민하고 계신 분들은 꼭 하셨으면 좋겠다는 마지막 말씀을 드립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부스트캠프 지원&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부스트캠프 6기 (2021) 또한 웹 풀스택 &amp;amp; iOS 교육과정을 그대로 가져가네요.&lt;br /&gt;또한 6기 과정에서는 코틀린 기반의 Android 과정도 추가되었다니 더 다이나믹한 프로젝트들을 할 수 있을 것 같아요!!!&lt;br /&gt;개인적으로 더 재밌는 교육이 될 것 같습니다 ㅎㅎㅎ&lt;br /&gt;&lt;a href=&quot;https://boostcamp.connect.or.kr/guide_wm.html&quot;&gt;https://boostcamp.connect.or.kr/guide_wm.html&lt;/a&gt;&lt;br /&gt;관심 있으신 분들은 위의 링크로 가셔서 더욱 자세히 보시면 좋겠습니다 ㅎㅎ&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ETC</category>
      <category>5기</category>
      <category>부스트캠프</category>
      <category>부스트캠프2020</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/352</guid>
      <comments>https://zin0-0.tistory.com/352#entry352comment</comments>
      <pubDate>Sun, 23 May 2021 16:29:00 +0900</pubDate>
    </item>
    <item>
      <title>1장) 1.6 싱글톤 레지스트리와 오브젝트 스코프 ~ 1.7 의존관계 주입(IoC)</title>
      <link>https://zin0-0.tistory.com/351</link>
      <description>&lt;h2&gt;1장 오브젝트와 의존관계&lt;/h2&gt;
&lt;h3&gt;1.6 싱글톤 레지스트리와 오브젝트 스코프&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;스프링의 Application Context는 예시로 직접 만들었던 오브젝트 팩토리(DaoFactory)와 차이가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;오브젝트의 동일성과 동등성으로 말하면 이해하기 쉽다.&lt;/li&gt;
&lt;li&gt;DaoFactory는 호출할 때마다 새로운 오브젝트를 만들어서 반환한다. (다른 객체 -&amp;gt; 동등성)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스프링의 Application Context에&lt;/strong&gt; DaoFactory를 설정정보로 &lt;strong&gt;등록&lt;/strong&gt;(@Configuration)하고 getBean으로 &lt;strong&gt;빈을 호출하면 같은 주소값을 가진 오브젝트를 반환&lt;/strong&gt;한다. (같은 객체 -&amp;gt; 동일성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;싱글톤 레지스트리로서 Application Context&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Application Context는 IoC 컨테이너이자 싱글톤을 저장하고 관리하는 싱글톤 레지스트리&lt;/strong&gt;이기도 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링이 싱글톤으로 빈을 만든 이유&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바 엔터프라이즈 기술을 사용하는 서버 환경 때문&lt;ul&gt;
&lt;li&gt;스프링이 처음 설계됐을 때, 높은 성능이 요구되는 환경이었음&lt;/li&gt;
&lt;li&gt;다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이뤄진 경우가 대부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아무리 GC의 성능이 좋아졌어도, 매 요청마다 오브젝트를 새로 생성해주면 부하가 걸림&lt;/li&gt;
&lt;li&gt;그래서 자바 엔터프라이즈 분야는 서비스 오브젝트라는 개념을 일찍부터 사용&lt;ul&gt;
&lt;li&gt;서블릿이 가장 기본이 되는 서비스 오브젝트&lt;ul&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;p&gt;싱글톤 패턴의 한계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;싱글톤 구현 방법&lt;ul&gt;
&lt;li&gt;생성자를 private으로 만든다&lt;/li&gt;
&lt;li&gt;자신과 같은 타입의 스태틱 필드를 정의한다&lt;/li&gt;
&lt;li&gt;getInstance를 만들어 최초 호출되는 시점에서 한번만 오브젝트를 생성하고, 저장해둔 오브젝트를 넘겨준다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;한계&lt;ul&gt;
&lt;li&gt;private 생성자 떄문에 상속할 수 없음&lt;ul&gt;
&lt;li&gt;객체지향의 장점인 상속과 다형성을 적용할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트하기 힘들다&lt;ul&gt;
&lt;li&gt;테스트용 오브젝트로 대체하기 힘들다. (테스트 만드는데 지장있다는 것은 큰 단점)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하나만 만들어지는 것을 보장하지 못함&lt;ul&gt;
&lt;li&gt;자바 언어를 이용한 싱글톤 패턴은 서버 환경에서는 싱글톤이 꼭 보장되지 않음&lt;/li&gt;
&lt;li&gt;여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생김 ~&amp;gt; 싱글톤으로서 가치가 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전역 상태를 만들 수 있기 때문에 바람직하지 못함&lt;ul&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;p&gt;&lt;strong&gt;싱글톤 레지스트리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바의 기본 싱글톤 패턴 구현에 단점이 있어서 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 싱글톤 레지스트리를 제공&lt;/li&gt;
&lt;li&gt;생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘겨 관리. (오브젝트 생성에 관한 권한이 IoC기능을 제공하는 Application Context에 있음)&lt;/li&gt;
&lt;li&gt;테스트 환경에서 자유롭게 오브젝트를 만들거나 mock으로 대체 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;싱글톤과 오브젝트의 상태&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;멀티스레드 환경에서는 동시 접근 가능성이 있기 때문에 상태 관리에 주의를 기울여야함&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;싱글톤은 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으며 사용하게 해야함&lt;/li&gt;
&lt;li&gt;단순한 읽기 전용 값은 static final or final로 선언하는 편이 좋다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스프링 빈의 스코프&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빈 스코프&lt;ul&gt;
&lt;li&gt;스프링이 관리하는 오브젝트(빈)가 생성, 존재, 적용되는 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;싱글톤 스코프&lt;ul&gt;
&lt;li&gt;컨테이너 내에 하나의 오브젝트만 만들어서 제거하지 않는 한 계속 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로토타입 스코프&lt;ul&gt;
&lt;li&gt;컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 생성해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청 스코프&lt;ul&gt;
&lt;li&gt;HTTP 요청마다 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세션 스코프&lt;ul&gt;
&lt;li&gt;웹의 세션과 스코프가 유사&lt;ul&gt;
&lt;li&gt;최초의 요청을 발생시키고 브라우저를 닫을 때 까지 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bean을 정의할 때 session scope로 정의하면 브라우저가 서버에 최초의 요청을 보낼 때 Bean 객체가 주입&lt;ul&gt;
&lt;li&gt;주입만 되는 것이지 session 영역에 저장되지는 않는다.&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;h3&gt;1.7 의존관계 주입 IoC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;제어의 역전(IoC)과 의존관계 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IoC라는 용어가 매우 느슨하게 정의돼서 폭넓게 사용된다&lt;br&gt;~&amp;gt; 스프링을 IoC 컨테이너라고만 하면, 스프링이 제공하는 기능의 특징을 명확히 설명 못함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존관계 주입(Dependency Injection)&lt;/strong&gt;이라는 명칭이 더욱 명확해서 요즘은 DI로 많이 부른다.&lt;ul&gt;
&lt;li&gt;IoC 컨테이너라고 불리던 스프링을 DI 컨테이너라 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;런타임 의존 관계 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;의존관계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;항상 방향성을 부여해야함&lt;/strong&gt;(의존하는 관계까 있어야함) (A가 B에 의존한다 ~&amp;gt; A-&amp;gt;B)&lt;ul&gt;
&lt;li&gt;A가 B 메소드를 호출하여 사용하는 경우&lt;/li&gt;
&lt;li&gt;B가 변하면 A에 영향을 미친다는 의미&lt;/li&gt;
&lt;li&gt;반대로 B는 A에 의존하지 않기 때문에, A가 변화해도 B에 영향 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDao의 의존 관계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserDao -&amp;gt; ConnectionMaker(인터페이스)&lt;ul&gt;
&lt;li&gt;위의 인터페이스가 변화하면 당연히 UserDao도 변화해야겠지만, 구현체가 변해도 영향이 없음&lt;/li&gt;
&lt;li&gt;이런 느슨한 관계를 결합도가 낮다고 한다. &lt;/li&gt;
&lt;li&gt;SOLID 원칙 중 의존 역전 원칙과 관련&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위에서 설계에서 보이는 의존 관계 외에 런타임 시에 오브젝트 사이에서 만들어지는 관계가 있다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;런타임(오브젝트) 의존관계&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;프로그램 Running 후, UserDao 오브젝트가 만들어지고 의존관계를 맺는 대상(실제 사용대상인 오브젝트)을 의존 오브젝트라고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존관계 주입&lt;/strong&gt;은 &lt;strong&gt;의존 오브젝트와 사용할 주체(보통 클라이언트)인 오브젝트를 런타임 시에 연결해주는 작업&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;충족해야할 3 조건 &lt;ul&gt;
&lt;li&gt;클래스 모델이나 코드에는 &lt;strong&gt;런타임 시점의 의존관계가 드러나지 않음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;런타임 시점의 의존관계는 &lt;strong&gt;컨테이너나 팩토리 같은 제 3의 존재가 결정&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용할 오브젝트에 대한 레퍼런스를 외부에서 제공&lt;/strong&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;p&gt;UserDao의 의존관계 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 1
public UserDao() {
    connectionMaker = new DConnectionMaker();
}

///////////////////////////////////////////////////////
//2
public UserDao(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;1번의 경우 모델링 때의 의존관계를 UserDao과 결정하고 관리한다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;런타임 시 의존관계가 코드 속에 미리 결정됨 + 필요없는 관심&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2번의 경우 IoC 방식을 써서 제 3의 존재인 런타임 의존관계 결정 권한을 위임&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;따라서, DaoFactory는 DI컨테이너라고 부를 수 있음&lt;/li&gt;
&lt;li&gt;이렇게 생성자(메소드)를 통해 DI 컨테이너가 UserDao에게 주입해주는 것과 같다고 해서 &lt;strong&gt;의존관계 주입&lt;/strong&gt;이라고 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부에 넘기고,  수동적으로 주입받은 오브젝트를 사용한다는 점에서 IoC 개념에 잘 맞는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링 컨테이너의 IoC는 주로 의존관계 주입 or DI 라는데 초점이 맞춰져있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;의존관계 검색과 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;의존관계 검색(Dependency Lookup(DL))&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;외부로부터 주입이 아니라 스스로 검색을 사용&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;런타임 시 의존관계를 맺을 오브젝트를 결정 + 오브젝트의 생성 작업은 외부 컨테이너에게 IoC로 맡김&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;가져올 때는 메소드 주입이 아닌 &lt;strong&gt;스스로 컨테이너에게 요청하는 방법&lt;/strong&gt;을 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public UserDao() {
    DaoFactory daoFactory = new DaoFactory();
    this.connectionMaker = daoFactory.connectionMaker();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IoC 개념을 잘 따르며 혜택을 받지만, 스스로 IoC 컨테이너인 DaoFactory에게 요청하는 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링의 IoC 컨테이너인 Application Context는 getBean 메소드를 통해 의존관계 검색을 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;의존관계 주입 vs 의존관계 검색&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;의존관계 검색은 성격이 다른 오브젝트에 의존하게 되는 것이므로 바람직하지는 않음&lt;/p&gt;
&lt;p&gt;~&amp;gt; 대부분은 의존관계 주입(DI)을 사용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;언제 필요한 것인가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링 컨테이너에 담긴 오브젝트를 사용하려면 한 번은 의존관계 검색 방식을 사용해서 오브젝트를 가져와야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;의존관계 검색은 검색하는 오브젝트가 자신이 스프링 빈일 필요가 없다&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;의존관계 주입은 DI가 적용되려면 반드시 컨테이너가 만드는 빈 오브젝트여야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DI를 원하는 오브젝트는 먼저 자기 자신이 컨테이너가 관리하는 빈이 되어야함&lt;/strong&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;p&gt;의존관계 주입의 응용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기능 구현의 교환&lt;ul&gt;
&lt;li&gt;DI 방식을 사용하지 않고 로컬 DB 연결로 개발하다가 배포하는 상황을 떠올려보자...&lt;ul&gt;
&lt;li&gt;변경할 코드가 수백 수천 줄이 될거다...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DI 방식을 적용하면 배포 시점에도 DAO 클래스를 수정할 필요가 없어진다.&lt;ul&gt;
&lt;li&gt;connection 부분이 real server, beta server, alpha server, local server에 따라 바뀌겠지만 한줄이다&lt;ul&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;ul&gt;
&lt;li&gt;만약 DB 연결횟수를 카운팅한다면? DI로 충분&lt;/li&gt;
&lt;li&gt;UserDao로 예를 들어보자&lt;ul&gt;
&lt;li&gt;기존 UserDao -&amp;gt; DConnectionMaker의 의존 관계가 있음&lt;ul&gt;
&lt;li&gt;DConnectionMaker는 DB 연결 정보를 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB연결마다 카운트 기능을 포함한 CountingConnectionMaker 관계를 추가하고 ConnectionMaker의 코드를 살짝 수정하면&lt;/li&gt;
&lt;li&gt;UserDao -&amp;gt; CountingConnectionMaker -&amp;gt; DConnectionMaker 로 의존관계가 바뀐다. (이 때, CountingDaoFactory를 만들어서 DConnectionMaker와 관계 설정)&lt;/li&gt;
&lt;li&gt;DB 연결 시마다 CountingConnetionMaker가 열심히 카운팅 할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부가기능을 추가해서 런타임 의존관계를 새롭게 맺어주는데 용의하다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;메소드를 이용한 의존관계 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;수정자 메소드 주입&lt;ul&gt;
&lt;li&gt;setter&lt;/li&gt;
&lt;li&gt;외부에서 오브젝트 내부의 Attribute 값을 변경하려는 용도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일반 메소드 주입&lt;ul&gt;
&lt;li&gt;수정자 메소드처럼 set으로 시작해야하고, 한 번에 한 개의 파라미터만 가지는게 싫다!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;여러 파라미터를 갖는 일반 메소드를 DI 용으로 사용하자&lt;/code&gt;는 의미로 사용&lt;ul&gt;
&lt;li&gt;모든 파라미터를 받는 수정자를 만들 수 있지만 실수할 수 있기 때문에, 적절한 파라미터 단위로 끊어서 사용&lt;/li&gt;
&lt;li&gt;근데, 요즘에는 이런 경우 Builder를 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전통적으로 DI 중 가장 많이 사용&lt;/li&gt;
&lt;li&gt;네이밍이 아주 중요 (특별한 이름이 아니라면 setConnectionMaker와 같이 set을 붙이자)&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;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;의존관계 주입, 의존성 주입, 의존 오브젝트 주입&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dependency Injection ~&amp;gt; 의존성 주입, 의존(종속) 오브젝트 주입&lt;/li&gt;
&lt;li&gt;오브젝트의 레퍼런스가 전달될 뿐이다.&lt;/li&gt;
&lt;li&gt;오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 다른 오브젝트와 다이나믹하게 의존관계가 만들어지는 것이 DI의 핵심&lt;/li&gt;
&lt;li&gt;장점&lt;ul&gt;
&lt;li&gt;코드에는 런타임 클래스에 대한 의존관계가 나타나지 않음&lt;/li&gt;
&lt;li&gt;인터페이스를 통해 결합도가 낮은 코드 ~&amp;gt; 다른 책임을 가진 사용 의존 관계에 있는 대상이 변경되어도 영향X&lt;/li&gt;
&lt;li&gt;변경을 통한 다양한 확장방법에 자유롭다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI 받는다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주입해준다고 다 DI가 아니다.&lt;/li&gt;
&lt;li&gt;다이나믹하게 구현 클래스를 결정해서 제공받도록 Interface 타입의 파라미터를 통해 이뤄줘야함(메소드)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>스프링 스코프</category>
      <category>싱글톤 레지스트리</category>
      <category>의존관계 검색</category>
      <category>의존관계 주입</category>
      <category>토비의 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/351</guid>
      <comments>https://zin0-0.tistory.com/351#entry351comment</comments>
      <pubDate>Fri, 21 May 2021 10:38:47 +0900</pubDate>
    </item>
    <item>
      <title>1장) 1.3 DAO의 확장 ~ 1.5 스프링의 IoC</title>
      <link>https://zin0-0.tistory.com/350</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1장 오브젝트와 의존관계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 DAO의 확장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 오브젝트는 관심사가 바뀔 때마다 변경이 일어난다.&lt;/b&gt;&lt;/li&gt;
&lt;li&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;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class UserDao {
    private SimpleConnectionMaker;

    public UserDao() {
        simpleConnectionMaker = new SimpleConnectionMaker();
    }

    public void add(User user) throws ClassNotFoundException, SQLEXception {
        Connection c = simpleConnectionMaker.makeNewConnection();
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public class SimpleConnectionMaker {
    public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
        Class.forName(&quot;com.mysql.jdbc.Driver&quot;);
        Connection c = DriverManager.getConnection(&quot;&quot;, &quot;&quot;, &quot;&quot;);
        return c;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;코드를 확실하게 분리했지만, 기존 N사와 D사에 UserDao 클래스만 공급하고 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;첫째는 SimpleConnectionMaker의 메소드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection을 얻는 메소드의 네이밍을 통일해야한다. (다른 클래스에서 널리 이용하기 위함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;둘째는 DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고있어야 한다는 점(불필요한 관심)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserDao가 바뀔 수 있는 정보(DB 커넥션을 가져오는 클래스)에 대해 많이 알고 있기 때문에 UserDao를 수정해야하는 경우가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&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;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class DConectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        // D사의 Connection 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public class UserDao() {
    private ConnectionMaker connectionMaker;

    public UserDao() {
        connectionMaker = new DConnectionMaker();
    }
    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection c = connectionMaker.makeConnection();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;위의 코드로 인해 DB 접속 클래스를 다시 만든다고 해도 UserDao코드를 고칠일이 없어졌다.&lt;/li&gt;
&lt;li&gt;하지만, 여전히 DConnection 클래스의 생성자를 호출해서 오브젝트를 생성하는 코드가 남아있어서 확장에 자유롭지 못하다.&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;위의 코드를 독립적인 확장에 자유롭게 하기 위해서는, UserDao와 UserDao가 사용할 ConnectionMaker의 특정 &lt;b&gt;구현 클래스 사이의 관계를 설정해주는 것에 관한 관심을 분리&lt;/b&gt;해야한다.&lt;/li&gt;
&lt;li&gt;UserDao의 코든 코드는 ConnectionMaker 인터페이스 외에는 어떤 클래스와도 관계를 가져서는 안되게 해야, UserDao의 수정 없이 DB 커넥션 구현 클래스를 변경할 수 있다.&lt;/li&gt;
&lt;li&gt;외부에서 만든 오브젝트를 전달받기 위해 메소드 파라미터나 생성자 파라미터를 이용하자.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public UserDao(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;생성자를 통해 connectionMaker 오브젝트를 받아 ConnectionMaker의 구현에 관심이 사라졌다.&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;SOLID 5 원칙 (객체지향 설계 5원칙)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 책임 원칙(The Single Responsibility Principle =&amp;gt; SRP)
&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;개방 폐쇄 원칙(The Open Closed Priciple =&amp;gt; OCP)
&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;리스코프 치환 원칙(The Liskov Subsitution Principle =&amp;gt; LSP)
&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;인터페이스 분리 원칙(The Interface Segregation Principle =&amp;gt; ISP)
&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;의존관계 역전 원칙(The Dependency Inversion Principle =&amp;gt; DIP)
&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;높은 응집도와 낮은 결합도 (개방 폐쇄 원칙과 연관)
&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;b&gt;하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중&lt;/b&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;책임과 관심사가 다른 오브젝트 or 모듈과는 느슨하게 연결된 형태를 유지하는 것이 바람직하다.&lt;/li&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;/ul&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;/li&gt;
&lt;li&gt;자신의 &lt;b&gt;기능 맥락(Context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스로 통째로 외부로 분리&lt;/b&gt;시키고, 이를 구현한 &lt;b&gt;구체적인 알고리즘 클래스를 필요에 따라 바꿔 사용할 수 있게하는 디자인 패턴&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;위의 예시로 보면, UserDao가 전략 패턴의 Context에 해당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.4 제어의 역전(IoC)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UserDao와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 것과, 이 둘을 연결해서 사용하도록 관계를 맺어주는 관심을 예시에 추가해보자.&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;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public class DaoFactory() {
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }

    public accountDao() {
        return new AccountDao(connectionMaker());
    }

    private ConnectionMaker connectionMaker() {
        return new DConnectionMaker();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;UserDao와 ConnectionMaker는 데이터 로직과 기술 로직(실질적 로직)을 담당하고, DaoFactory는 Application의 오브젝트를 구성하고 관계를 정의하는 책임(설계도 역할)을 하게 된다.&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;제어의 역전 =&amp;gt; 프로그램의 제어 흐름 구조가 뒤바뀌는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 main() 메소드와 같이 프로그램 시작 지점에서 다음에 사용할 오브젝트 결정 ~&amp;gt; 생성 ~&amp;gt; 메소드 호출 ~&amp;gt; 메소드 내부에서 다음에 사용할 것을 결정 ~&amp;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;/li&gt;
&lt;li&gt;자신도 어떻게 만들어지고 어디서 사용되는지 알 수 없다.&lt;/li&gt;
&lt;li&gt;엔트리 포인트를 제외한 모든 오브젝트는 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.&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;h3 data-ke-size=&quot;size23&quot;&gt;1.5 스프링의 IoC&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;Bean Factory, Application Context&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;bean
&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;li&gt;스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;bean factory
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트&lt;/li&gt;
&lt;li&gt;빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 둘 때, bean factory 라는 명칭을 주로 활용(생성과 제어의 관점)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Application Context
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈 팩토리를 확장한 IoC 컨테이너&lt;/li&gt;
&lt;li&gt;애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 당당하는 IoC 엔진의 의미가 좀 더 부각됨(스프링이 지원하는 애플리케이션의 지원 기능을 포함한 의미)&lt;/li&gt;
&lt;li&gt;보통, bean factory와 Application Context를 같게보고 어디에 초점을 두고 말하는지에 따라 다르게 부를 때도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앞서 작성했던 DaoFactory를 스프링 빈 팩토리가 사용할 수 있는 설정정보로 만들어보자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration // application context or bean factory가 사용할 설정정보라는 표시
public class DaoFactory {
    @Bean // 오브젝트 생성을 담당하는 IoC용 메소드라는 표시
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }

    @Bean
    public ConnectionMaker connectionMaker() {
        return new DConnectionMaker();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Test 코드에서 이를 활용할 때는, AnnotationConfigApplicationContext를 이용해서 Application Context 객체를 생성하고 getBean 메소드를 통해 UserDao를 불러오면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Application Context의 동작 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ApplicationContext 인터페이스를 구현하는데, 빈 팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로 일종의 Bean Factory이다.&lt;/li&gt;
&lt;li&gt;DaoFactory는 DAO 오브젝트 생성과 DB 오브젝트와 관계를 맺어조는 제한적인 역할을 하는데, Application Context는 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계 설정을 담당한다.&lt;/li&gt;
&lt;li&gt;즉, 클라이언트의 요청이 들어오면 application context에서 bean factory 조회/호출/등록 등을 통해 관리하고, 오브젝트를 넘겨준다.&lt;/li&gt;
&lt;li&gt;Application Context의 장점
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;IoC를 적용하기 위해 사용하는 메타정보, IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용&lt;/li&gt;
&lt;li&gt;애플리케이션의 형상 정보 / 청사진&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨테이너 or IoC 컨테이너
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IoC방식으로 빈을 관리한다는 의미에서 Application Context나 빈 팩토리를 컨테이너 or IoC 컨테이너라고 한다.&lt;/li&gt;
&lt;li&gt;IoC 컨테이너는 주로 빈 팩토리 관점에서 이야기하는 것, 그냥 컨테이너 or 스프링 컨테이너라고 할 때는 애플리케이션 컨텍스트를 가리키는 것&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;IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>application context</category>
      <category>IoC</category>
      <category>토비의 스프링</category>
      <category>팩토리</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/350</guid>
      <comments>https://zin0-0.tistory.com/350#entry350comment</comments>
      <pubDate>Mon, 17 May 2021 15:03:04 +0900</pubDate>
    </item>
    <item>
      <title>1장 오브젝트와 의존관계 (1.1 초난감 DAO ~ 1.2 DAO의 분리)</title>
      <link>https://zin0-0.tistory.com/349</link>
      <description>&lt;h2&gt;1장 오브젝트와 의존관계&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;스프링의 핵심 철학 : 객체지향 프로그래밍&lt;ul&gt;
&lt;li&gt;객체의 라이프 사이클과 관계에 집중&lt;/li&gt;
&lt;li&gt;디자인 패턴, 리팩토링, 단위 테스트 등과 같은 설계와 지식을 요구&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 초난감 DAO&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;두 가지 이상의 관심을 포함하고 있거나, 중복되는 코드를 가지고 있는 경우 유지 / 보수에 어려움을 겪는다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;하기 예시 코드를 보면서 어떤 문제점이 있는지 생각해보자.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ... import statement
pubilc class UserDao {
    public void add(User user) throws ClassNotFoundException, SQLException {
        Class.forName(&amp;quot;com.mysql.jdbc.Driver&amp;quot;);
        Connection c = DriverManger.getConnection(&amp;quot;jdbc:mysql://localhost/springbook&amp;quot;, &amp;quot;spring&amp;quot;, &amp;quot;book&amp;quot;);
        PreparedStatement = c.preparedStatement(&amp;quot;insert into user(id, name, password), values(?, ?, ?);&amp;quot;);

        // ...insert logic
    }

    public void get(String id) throws ClassNotFoundException, SQLException {
        ClassNotFoundException, SQLException{
        Class.forName(&amp;quot;com.mysql.jdbc.Driver&amp;quot;);
        Connection c = DriverManger.getConnection(&amp;quot;jdbc:mysql://localhost/springbook&amp;quot;, &amp;quot;spring&amp;quot;, &amp;quot;book&amp;quot;);
        PreparedStatement = c.preparedStatement(&amp;quot;select id, name, password from user where id = ?;&amp;quot;);

            // ... get logic
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 DAO의 분리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위의 예시는 현재는 userDao 안에 두 개의 메소드만 존재하지만, 메소드가 확장될 가능성이 높고, 다른 Dao 클래스들이 추가될 가능성이 매우 높다. 만약, MySQL에서 Oracle로 변경한다면?? 메소드 개수만큼 변경해야할 것이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;변경이 일어날 때, 필요한 작업을 최소화 하기 위해서는 &lt;strong&gt;분리와 확장을 고려한 설계&lt;/strong&gt;가 필요&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;관심사의 분리라는 의미는 &lt;strong&gt;관심이 같은 것끼리 하나의 객체 또는 관련 객체로 모이게&lt;/strong&gt;하고, &lt;strong&gt;관심이 다른 것은 최대한 떨어뜨려 영향을 주지 않도록 분리&lt;/strong&gt;하는 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;위의 예시 코드를 살피면, 하나의 메소드에 세 가지 관심사항이 발견된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB와 연결을 위한 connection을 어떻게 가져오는가&lt;/li&gt;
&lt;li&gt;DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행&lt;/li&gt;
&lt;li&gt;작업이 종료된 이후 사용한 공유 리소스(Statement, Connection, ResultSet 등) 오브젝트를 닫아줘서 시스템에게 돌려주는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;먼저, 첫번째 관심을 메소드 추출로 리팩토링을 해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    public Connection getConnection() {
    Class.forName(&amp;quot;com.mysql.jdbc.Driver&amp;quot;);
        Connection c = DriverManger.getConnection(&amp;quot;jdbc:mysql://localhost/springbook&amp;quot;, &amp;quot;spring&amp;quot;, &amp;quot;book&amp;quot;);

    return c;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connection을 얻어오는 관심을 분리했을 뿐인데, Connection과 관련된 변화에 대응할 수 있는 코드가 되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;변화에 대응할 뿐만 아니라, 상속을 통해 변화에 자유로운 확장 코드로 해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public abstract class UserDao {
    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection c = getConnection();
        // ... logic
    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        Connection c = getConnection();
        // ... logic
    }

    public abstract Connection getConnection() throws ClassNotFundExecption, SQLException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;  public class NUserDao extends UserDao {
      public Connection getConnection() throws ClassNotFOundExpcetion, SQLException {
          // N 사 DB Connection 생성코드
      }
  }

  public class DUserDao extends UserDao {
      public Connection getConnection() throws ClassNotFOundExpcetion, SQLException {
          // D 사 DB Connection 생성코드
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;탬플릿 메소드 패턴(팩토리 메소드 패턴)&lt;/strong&gt;을 통해 UserDao에 대한 수정 없이, DB 연결 기능을 새롭게 정의한 클래스를 만들 수 있는 코드가 되었다. 즉, Connection 타입 인터페이스의 오브젝트를 이용할 수 있는 어떤 DB 연결에도 손쉽게 확장할 수 있게 수정되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDao는 getConnection 메소드에서 Connection 인터페이스 타입의 오브젝트를 반환한다는 것 외에는 관심을 두지 않음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UserDao를 상속받은 NUserDao와 DUserDao는 어떤 방법으로 Connection 객체를 만들어내고 기능을 제공할지에 관심을 둔다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;상속을 통해 관심이 다른 기능을 분리하고 필요에 따라 변화에 대응하도록 했지만, 여전히 단점이 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상속을 통한 상하위 클래스의 관계가 밀접하다.&lt;ul&gt;
&lt;li&gt;서브 클래스가 슈퍼클래스의 기능을 직접 사용할 수 있기때문에, 슈퍼클래스 내부의 변경이 있을 때는 모든 서브클래스를 함께 수정하거나 다시 개발해야할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위의 경우, DB Connection을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없다&lt;/li&gt;
&lt;li&gt;다음 장에서 클래스 분리를 통해 DAO 클래스들이 이용할 수 있는 DB Connection을 해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;용어 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바빈&lt;ul&gt;
&lt;li&gt;본래 비주얼 툴에서 조작 가능한 컴포넌트를 의미&lt;/li&gt;
&lt;li&gt;최근에는 디폴트 생성자와 프로퍼티를 지닌 오브젝트를 가리킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리팩토링&lt;ul&gt;
&lt;li&gt;기능에 영향을 주지 않으면서 코드의 구조만 변경하는 행위&lt;/li&gt;
&lt;li&gt;주로 견고함을 위해 내부 설계를 좋은 방향으로 최신화하면서 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메소드 추출&lt;ul&gt;
&lt;li&gt;공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 행위&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;탬플릿 메소드 패턴&lt;ul&gt;
&lt;li&gt;슈퍼클래스에 기본적인 로직의 흐름을 만들고, 기능의 일부를 추상 메소드 or Override가 가능한 protected 메소드 등으로 만든 뒤, 서브클래스에서 필요에 맞게 메소드를 구현해서 사용하도록 하는 디자인 패턴&lt;ul&gt;
&lt;li&gt;상속을 통해 슈퍼클래스의 기능을 확장&lt;/li&gt;
&lt;li&gt;변하지 않는 기능은 슈퍼클래스에 만들어두고, 자주 변경되어 확장할 기능은 서브클래스에서 만들도록 함&lt;ul&gt;
&lt;li&gt;서브 클래스에서 선택적으로 Override할 수 있도록 만든 메소드를 Hook 메소드라고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스프링에서 애용되는 디자인 패턴&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;팩토리 메소드 패턴&lt;ul&gt;
&lt;li&gt;서브클래스에서 &lt;strong&gt;구체적인 오브젝트 생성 방법을 결정&lt;/strong&gt;하게 하는 것&lt;/li&gt;
&lt;li&gt;주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할 지 슈퍼클래스는 알지 못함(관심 X)&lt;/li&gt;
&lt;li&gt;서브클래스에서 오브잭트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고, 이를 통해 나머지 로직을 완성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;슈퍼클래스의 기본 코드에서 독립시키는 방법&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;상속을 통해 기능을 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스프링에서 애용되는 디자인 패턴&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java &amp;amp; Spring/토비의 스프링 3.1</category>
      <category>관심사 분리</category>
      <category>토비의 스프링</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/349</guid>
      <comments>https://zin0-0.tistory.com/349#entry349comment</comments>
      <pubDate>Thu, 13 May 2021 17:45:00 +0900</pubDate>
    </item>
    <item>
      <title>MySQL 권한 설정</title>
      <link>https://zin0-0.tistory.com/348</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MySQL&amp;nbsp;권한&amp;nbsp;설정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&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;span style=&quot;color: #000000;&quot;&gt;5.x 버전까지는 테이블에 권한을 추가하면서 아이디를 생성해주는게 가능했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1619682688811&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GRANT ALL PRIVILEGES ON target_database.* to 'user_name'@'localhost' identified by 'user_pass';&lt;/code&gt;&lt;/pre&gt;
&lt;p&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;span style=&quot;color: #000000;&quot;&gt;하지만 8.x 버전으로 올라오면서, 유저를 먼저 생성해줘야 구문이 통과가 됐다. (위의 쿼리를 실행하면 quote( &lt;b&gt;' &lt;/b&gt;)가 조금 이상하게 찍힌 쿼리로 변경되어 실행됐다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1619682792097&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE USER 'user_name'@'localhost' identified by 'user_pass';
GRANT ALL PRIVILEGES ON target_database.* to 'user_name'@'localhost';&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;span style=&quot;color: #000000;&quot;&gt;이렇게 순차적으로 유저를 먼저 생성해주고, 권한부여 해주니 통과가 됐다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유에 대해서는 차차 알아보자&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SQL</category>
      <category>mysql 권한 부여</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/348</guid>
      <comments>https://zin0-0.tistory.com/348#entry348comment</comments>
      <pubDate>Thu, 29 Apr 2021 16:53:53 +0900</pubDate>
    </item>
    <item>
      <title>xml 설정과 Java Config 설정</title>
      <link>https://zin0-0.tistory.com/347</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 설정의 비교를 상세하게 나열하는 글이 아닙니다. 혹여나, 정확한 지식을 얻고자 하신분들께 죄송합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트를 하면서 느낀점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;span style=&quot;color: #000000;&quot;&gt;java config 설정을 읽어오도록 xml에 설정해줬는데, 404를 처리하기 위해 error page 정의 또한 xml에서 했더니, 모든 404 요청이 error page로 옮겨졌다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 진행하는 프로젝트는 일반 View Controller와 API Controller를 혼합한 서버의 형태인데, API Controller 에서는 못찾는 리소스에 대해서 JSON 형식으로 응답해주기를 바랬다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nohandlerfoundexception을 정의해서 구분해주고자 했으나, Bean으로 등록하는 방법과 xml에서 noHandlerException을 true로 설정해도 뱉어주지 않았다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 현재 문득 든 생각은, xml에서 정의한 error page의 사이클이 우선적으로 실행됐지 않았을까 한다. 이에 대한 명확한 해결책을 찾지 못하고, 임의의 RestController를 정의해서 API 하위 path들에 대해서 Exception을 날려주고, 예외를 핸들링하는 RestControllerAdvice에서 처리하도록 설정했는데 스프링 MVC의 사이클 이해가 부족했다고 생각한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px; color: #000000;&quot;&gt;위의 분리된 Exception 처리가 가능했던 이유는 error page를 정의한 사이클보다 controller에서 매핑되는 사이클이 빠르기 때문인데, xml 설정 대신 java config에서 에러 페이지 처리와 에러 API 요청 처리를 진행해주는 방식이 가능하지 않을까 싶다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이에 대해 시도해보고, 후에 후기를 남기자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java &amp;amp; Spring/기본 개념 정리</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/347</guid>
      <comments>https://zin0-0.tistory.com/347#entry347comment</comments>
      <pubDate>Fri, 9 Apr 2021 01:19:31 +0900</pubDate>
    </item>
    <item>
      <title>카카오 블라인드 2021) 합승 택시 요금</title>
      <link>https://zin0-0.tistory.com/346</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;합승 택시 요금&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1615108129917&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 합승 택시 요금&quot; data-og-description=&quot;6 4 6 2 [[4, 1, 10], [3, 5, 24], [5, 6, 2], [3, 1, 41], [5, 1, 24], [4, 6, 50], [2, 4, 66], [2, 3, 22], [1, 6, 25]] 82 7 3 4 1 [[5, 7, 9], [4, 6, 4], [3, 6, 1], [3, 2, 3], [2, 1, 6]] 14 6 4 5 6 [[2,6,6], [6,3,7], [4,6,7], [6,5,11], [2,5,12], [5,3,20], [2,4&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72413&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72413&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sgZDn/hyJtDfKeeP/9DvWfnEpD3CbeVogkrkw30/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/gouHL/hyJtHvGq9r/DURhEp2LhBhg30ZLwYavD1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/c7Vp2H/hyJtK0e1Jm/DKtM5U9770nT572Tw0KcYk/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72413&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sgZDn/hyJtDfKeeP/9DvWfnEpD3CbeVogkrkw30/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/gouHL/hyJtHvGq9r/DURhEp2LhBhg30ZLwYavD1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/c7Vp2H/hyJtK0e1Jm/DKtM5U9770nT572Tw0KcYk/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;코딩테스트 연습 - 합승 택시 요금&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;6 4 6 2 [[4, 1, 10], [3, 5, 24], [5, 6, 2], [3, 1, 41], [5, 1, 24], [4, 6, 50], [2, 4, 66], [2, 3, 22], [1, 6, 25]] 82 7 3 4 1 [[5, 7, 9], [4, 6, 4], [3, 6, 1], [3, 2, 3], [2, 1, 6]] 14 6 4 5 6 [[2,6,6], [6,3,7], [4,6,7], [6,5,11], [2,5,12], [5,3,20], [2,4&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;플로이드-와샬을 이용해서 쉽게 풀 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주어진 각 구간마다의 요금을 플로이드-와샬을 통해, 연결된 구간을 이동하는데 최소 비용을 저장해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이후, 문제에서 요구하는 답인, 출발지 s에서 시작해 k를 경유하여, 각각 a와 b 목적지로 가는 비용의 최소를 구해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;최근에 플로이드 와샬 문제를 많이 풀어서, 크게 어렵지 않게 풀 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size20&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615108118123&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Arrays;

class Solution {
    public int solution(int n, int s, int a, int b, int[][] fares) {
        int[][] map = initMap(n,fares);
        floydWarshal(n,map);
        return getMinCost(map,n, s, a, b);
    }
    
    private int getMinCost(int[][] map, int n, int s, int a, int b) {
        int answer = Integer.MAX_VALUE;
        for(int k=1; k&amp;lt;=n; k++) { // 문제에서 요구하는 답 = min(d[s][k] + d[k][a] + d[k][b]) (단, k = 1 ~ n)
          answer = Math.min(answer, map[s][k]+ map[k][a] + map[k][b]);
        }
        return answer;
    }
    
    private void floydWarshal(int n, int[][] map) {
        for(int k=1; k&amp;lt;=n; k++) {
          for(int i=1; i&amp;lt;=n; i++) {
            for(int j=1; j&amp;lt;=n; j++) {
              if(i!=j &amp;amp;&amp;amp; map[i][k] != Integer.MAX_VALUE &amp;amp;&amp;amp; map[k][j] != Integer.MAX_VALUE) {
                map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]);
              }
            }
          }
        }
    }
    
    private static int[][] initMap(int n, int[][] fares) {
        int[][] map = new int[n+1][n+1];
        for(int i=1; i&amp;lt;=n; i++) {
          Arrays.fill(map[i], Integer.MAX_VALUE);
          map[i][i] = 0;
        }

        for(int[] fare : fares) {
          map[fare[0]][fare[1]] = fare[2];
          map[fare[1]][fare[0]] = fare[2];
        }

        return map;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/프로그래머스 카카오</category>
      <category>자바</category>
      <category>카카오 2021</category>
      <category>플로이드 와샬</category>
      <category>합승 택시 요금</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/346</guid>
      <comments>https://zin0-0.tistory.com/346#entry346comment</comments>
      <pubDate>Sun, 7 Mar 2021 18:12:38 +0900</pubDate>
    </item>
    <item>
      <title>카카오 블라인드 2021) 순위 검색</title>
      <link>https://zin0-0.tistory.com/345</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72412&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;순위 검색&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1615107381017&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 순위 검색&quot; data-og-description=&quot;[&amp;quot;java backend junior pizza 150&amp;quot;,&amp;quot;python frontend senior chicken 210&amp;quot;,&amp;quot;python frontend senior chicken 150&amp;quot;,&amp;quot;cpp backend senior pizza 260&amp;quot;,&amp;quot;java backend junior chicken 80&amp;quot;,&amp;quot;python backend senior chicken 50&amp;quot;] [&amp;quot;java and backend and junior and pizza 100&amp;quot;,&amp;quot;pyt&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72412&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72412&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jocir/hyJtKTriA9/c6ZA01lgE1HgLIbXejB7tk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/EZmEz/hyJtLx1EmW/XziKkEQJkQV3B537xCF7wk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72412&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72412&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jocir/hyJtKTriA9/c6ZA01lgE1HgLIbXejB7tk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/EZmEz/hyJtLx1EmW/XziKkEQJkQV3B537xCF7wk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;코딩테스트 연습 - 순위 검색&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;[&quot;java backend junior pizza 150&quot;,&quot;python frontend senior chicken 210&quot;,&quot;python frontend senior chicken 150&quot;,&quot;cpp backend senior pizza 260&quot;,&quot;java backend junior chicken 80&quot;,&quot;python backend senior chicken 50&quot;] [&quot;java and backend and junior and pizza 100&quot;,&quot;pyt&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제도 여러 시도를 통해 풀었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음에 시도했던 것은, 문제의 조건에 따라 모두 파싱한 후, 값을 담을 class를 선언하여 인스턴스를 생성했었다. 이후, 각 조건들과 default인 조건에 따라 HashMap에 저장을 해주었는데, HashMap의 제네릭이 HashMap이고, 계속 타고 내려가다보니 로직이 복잡해졌었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;더 좋은 방법이 없을까 고민하다가, 다른 분의 풀이를 보니 default인 경우와 주어진 경우를 조합해서 key값으로 저장하는 방법이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제에서 주어진 조건을 split한 이후, default를 포함한 경우를 for문을 통해 조합해주며 key 값을 만들어주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때, 이중 반복문을 통해 값을 저장해주는데, 비트 연산을 통해 조건에 부합하는 key 값을 생성해서 넣어주었다. (default -는 공백으로 처리한다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 값을 조건에 따라 저장했으면, 해당 key에 들어있는 value를 오름차순으로 정렬해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정렬 이후에는 각 쿼리에 해당하는 지원자들의 List를 받아와서, 몇 명이 조건에 부합하는지 구해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구할 때는 카카오의 힌트인 lower bound를 사용해서, 가장 처음으로 나오는 합격 최저 점수의 인덱스를 전체 길이에서 빼주어 구했다. (list의 size - lower bound 결과 인덱스)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장할 때와, 소팅할 때, computeIfAbsent와 Entry를 사용해서 정렬할 수 있다는 것을 이 분의 풀이를 보고 내가 자바를 제대로 사용하지 못하고 있구나를 느꼈다.. 덕분에 자바에 대해 더 이해할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1615107343757&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Solution {
  final String DEFAULT = &quot;-&quot;, QUERY_REGEX = &quot; and &quot;, SPACE = &quot; &quot;, NOTHING = &quot;&quot;;
  final int CONDITION_LENGTH = 4;
  final int SCORE_INDEX = 4;

  public int[] solution(String[] info, String[] query) {
    Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; map = getInfoMap(info);
    int len = query.length;
    int[] answer = new int[len];

    for(int i=0; i&amp;lt;len; i++) {
      answer[i] = getCounts(map, query[i]);
    }

    return answer;
  }

  private Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; getInfoMap(String[] info) {
    HashMap&amp;lt;String,List&amp;lt;Integer&amp;gt;&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
    final int MAX_CONDITION = 1 &amp;lt;&amp;lt; CONDITION_LENGTH;
    for(String applicant : info) {
      String[] data = getCondition(applicant);
      int score = Integer.parseInt(data[SCORE_INDEX]);

      for(int i=0; i&amp;lt;MAX_CONDITION; i++) {
        StringBuilder sb = new StringBuilder();
        for(int j=0; j&amp;lt;CONDITION_LENGTH; j++) {
          if((i&amp;amp;(1 &amp;lt;&amp;lt;j)) &amp;gt;0) { // i: 1 ~&amp;gt; j: 0, i: 2 ~&amp;gt; j: 1, i: 3 ~&amp;gt; j:0,1 , i: 4 ~&amp;gt; j: 1,2 
            sb.append(data[j]); // =&amp;gt; 조합 가능한 모든 경우를 만들어 key로 생성
          }
        }
        String key = sb.toString();
        map.computeIfAbsent(key, mappingFunction -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(score);
      }
    }

    for(Map.Entry&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; entry : map.entrySet()) {
      entry.getValue().sort(null);
    }

    return map;
  }

  private int getCounts(Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; map, String query) {
    String[] queryCondition = getCondition(query.replaceAll(DEFAULT, NOTHING).replaceAll(QUERY_REGEX, SPACE));
    String key = getKey(queryCondition);
    int score = Integer.parseInt(queryCondition[SCORE_INDEX]);
    List&amp;lt;Integer&amp;gt; list = map.getOrDefault(key, new ArrayList&amp;lt;&amp;gt;());

    return list.size() - lowerBound(list, score);
  }

  private String[] getCondition(String condition) {
    return condition.split(SPACE);
  }

  private String getKey(String[] conditions) {
    StringJoiner sj = new StringJoiner(NOTHING);
    for(int i=0; i&amp;lt;CONDITION_LENGTH; i++) {
      sj.add(conditions[i]);
    }
    return sj.toString();
  }

  private int lowerBound(List list, int score) {
    int left =0, right = list.size();

    while(left &amp;lt; right) {
      int mid = (left + right) /2;
      if((int)list.get(mid) &amp;gt;= score) {
        right = mid;
      } else {
        left = mid+1;
      }
    }
    return left;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://girawhale.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;girawhale.tistory.com/94&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스 카카오</category>
      <category>HashMap</category>
      <category>lower bound</category>
      <category>비트 연산</category>
      <category>순위 검색</category>
      <category>자바</category>
      <category>카카오 2021</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/345</guid>
      <comments>https://zin0-0.tistory.com/345#entry345comment</comments>
      <pubDate>Sun, 7 Mar 2021 18:07:33 +0900</pubDate>
    </item>
    <item>
      <title>카카오 블라인드 2021) 메뉴 리뉴얼</title>
      <link>https://zin0-0.tistory.com/344</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 해석을 잘못해서 푸는데 오래걸렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;세트 메뉴의 갯수가 같을 때, 주문한 수가 가장 많은 세트로 답을 구해주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음에는 반대로 주문한 수가 많은 것들을 모아서, 세트 메뉴가 가장 많은 것을 답으로 리턴해주었다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이외에도, 주문받은 메뉴를 정수형 배열의 인덱스에 저장하며, 입력받은 course에 따라 리턴해주는 방법도 시도했었지만, 당연히 틀리는 방법이다. (문제 해석을 잘못해서 카운팅만 해주면 된다고 생각했었다.. ㅠ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각설하고, 여러 차례 시도했는데 계속 틀려서 카카오 해설 힌트를 보고 permutation을 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 때, 미리 메뉴를 정렬해서 하는 방법이 더욱 효율적이겠지만, 처음에 비트마스크로도 접근을 했었어서, 이 코드를 지우기 아까워서 재활용했다. (효율적인 방법이라고는 말할 수 없다. 비트마스크로 변환하고 문자열로 다시 변환하는 과정이 permuation만큼 동작하기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;왠만하면, 정렬을 통해 미리 메뉴 순서를 정렬해주는 방법을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;permutation이 끝나면, 해당 메뉴 조합을 리스트에 담아두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후, 이 리스트를 돌면서, 만들고자 하는 세트 메뉴의 비트마스크와 주어진 메뉴 주문서의 비트마스크의 and 연산을 통해, 유효한 경우가 몇 번인지 확인해서, 최소 주문 횟수인 2번 보다 크면, 해당 메뉴의 길이를 key로 세트 메뉴를 value로 HashMap에 저장해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 저장된 HashMap을 가지고, 각 메뉴들의 길이들 중 주문한 수가 가장 많은 메뉴들을 List에 담은 다음 배열에 담아 return 해주어 문제를 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;bitmask과정을 없애고 사전에 메뉴를 sorting하면 코드도 더 간결하고 쉽게 풀 수 있는 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비트마스크는 코드가 아까워서 연습용으로 적용시켰다고 생각한다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;문제 풀이의 핵심은 사전 정렬, permutation, HashMap의 활용 정도라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1615106579260&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

public class Solution {
  final char EXIST = '1';
  final int ASCII_UPPER_START = 'A';
  final int MIN_MENU =2;
  final int PERMUTATION_START_INDEX =0, DEFAULT_BITMASK =0, DEFAULT_COUNT =0;

  List&amp;lt;String&amp;gt; permutationList;
  Map&amp;lt;Integer, List&amp;lt;Menu&amp;gt;&amp;gt; answerMap;

  public String[] solution(String[] orders, int[] course) {
    initAnswerMap(course);
    permutationList = new ArrayList&amp;lt;&amp;gt;(); // 초기화
    for(String order : orders) {
      char[] orderArr = order.toCharArray();
      permutation(orderArr,PERMUTATION_START_INDEX, DEFAULT_BITMASK, DEFAULT_COUNT); // 메뉴 조합 생성
      setAnswerMap(orders, course); // permutation에 있는 메뉴 세트가 다른 order와 몇 개나 일치하는지 확인
      permutationList.clear();
    }

    return getAnswerArr(course);
  }

  private void initAnswerMap(int[] course) { // 여기가 잘못됨, 동길이일때, 가장 많이 주문된 것을 출력해야함 (가장 많은 것 중 가장 긴 길이가 아니라)
    answerMap = new HashMap&amp;lt;&amp;gt;();
    for(int courseNumber : course) {
      answerMap.put(courseNumber, new ArrayList&amp;lt;&amp;gt;());
    }
  }

  private void permutation(char[] order, int index, int bitMask, int count) {
    if(count &amp;gt;= MIN_MENU) {
      String menu = parseUpperString(bitMask);
      if(!permutationList.contains(menu)) {
        permutationList.add(menu);
      }
    }
    if(index == order.length) {
      return;
    }

    permutation(order, index+1, bitMask|(1&amp;lt;&amp;lt;order[index]-ASCII_UPPER_START), count+1);
    permutation(order, index+1, bitMask, count);
  }

  private void setAnswerMap(String[] orders, int[] course) {
    for(String menu : permutationList) { // 각 메뉴가 몇개나 일치하는지 카운팅
      int len = menu.length();
      if(!isCourse(len, course)) {
        continue;
      }

      int count =0;
      int origin = parseBitMask(menu);
      for(String order : orders) { // 다른 메뉴들이랑 비교
        int target = parseBitMask(order);
        if((origin&amp;amp;target) == origin) { // 되는 메뉴 카운팅
          count++;
        }
      }

      if(count &amp;gt;=MIN_MENU) {
        List&amp;lt;Menu&amp;gt; list = answerMap.get(len);
        list.add(new Menu(menu, count));
        answerMap.replace(len, list);
      }
    }
  }

  private String parseUpperString(int bitMask) {
    String menuBinary = Integer.toBinaryString(bitMask);
    int len = menuBinary.length();
    StringBuilder sb = new StringBuilder();
    for(int i=0; i&amp;lt;len; i++) {
      if(menuBinary.charAt(len-i-1) == EXIST) {
        sb.append(getChar(i));
      }
    }
    return sb.toString();
  }

  private boolean isCourse(int count, int[] course) {
    for(int courseNumber : course) {
      if(count == courseNumber) {
        return true;
      }
    }
    return false;
  }

  private int parseBitMask(String order) {
    char[] orderArr = order.toCharArray();
    int bitMask =0;
    for(char ch : orderArr) {
      bitMask |= (1 &amp;lt;&amp;lt; ch-ASCII_UPPER_START);
    }
    return bitMask;
  }

  private char getChar(int ascii) {
    return (char)(ascii+ASCII_UPPER_START);
  }


  private String[] getAnswerArr(int[] course) {
    List&amp;lt;String&amp;gt; answerList = new ArrayList&amp;lt;&amp;gt;();

    for(int courseNumber : course) {
      List&amp;lt;Menu&amp;gt; menuList = answerMap.get(courseNumber);
      Collections.sort(menuList);
      int prev = menuList.size() ==0 ? 0 : menuList.get(0).count;
      for(Menu menu : menuList) {
        if(prev != menu.count) {
          break;
        }
        if(!answerList.contains(menu.menus)) {
          answerList.add(menu.menus);
        }
      }
    }

    String[] answerArr = new String[answerList.size()];
    for(int i=0; i&amp;lt;answerList.size(); i++) {
      answerArr[i] = answerList.get(i);
    }
    Arrays.sort(answerArr);
    return answerArr;
  }

  private class Menu implements Comparable&amp;lt;Menu&amp;gt; {
    String menus;
    int count;

    public Menu(String menus, int count) {
      this.menus = menus;
      this.count = count;
    }

    @Override
    public int compareTo(Menu menu) {
      return menu.count - this.count;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/프로그래머스 카카오</category>
      <category>HashMap</category>
      <category>메뉴 리뉴얼</category>
      <category>비트마스크</category>
      <category>자바</category>
      <category>카카오 2021</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/344</guid>
      <comments>https://zin0-0.tistory.com/344#entry344comment</comments>
      <pubDate>Sun, 7 Mar 2021 17:54:51 +0900</pubDate>
    </item>
    <item>
      <title>카카오 블라인드 2021) 신규 아이디 추천</title>
      <link>https://zin0-0.tistory.com/343</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72410&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;신규&amp;nbsp;아이디&amp;nbsp;추천&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1615105912778&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 신규 아이디 추천&quot; data-og-description=&quot;카카오에 입사한 신입 개발자 네오는 &amp;quot;카카오계정개발팀&amp;quot;에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. &amp;quot;네오&amp;quot;에게 주어진 첫 업무는 새로 &quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72410&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72410&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bS3dcZ/hyJtAQFWko/04fsacJSGLhgyniI4eukN0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/b1lsnF/hyJtIBcx3m/MrjBgGoEhoRgDUHv34tT41/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/72410&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/72410&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bS3dcZ/hyJtAQFWko/04fsacJSGLhgyniI4eukN0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/b1lsnF/hyJtIBcx3m/MrjBgGoEhoRgDUHv34tT41/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;코딩테스트 연습 - 신규 아이디 추천&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;카카오에 입사한 신입 개발자 네오는 &quot;카카오계정개발팀&quot;에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. &quot;네오&quot;에게 주어진 첫 업무는 새로&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;문제 조건&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아이디의&amp;nbsp;길이는&amp;nbsp;3자&amp;nbsp;이상&amp;nbsp;15자&amp;nbsp;이하여야&amp;nbsp;합니다.&lt;br /&gt;아이디는&amp;nbsp;알파벳&amp;nbsp;소문자,&amp;nbsp;숫자,&amp;nbsp;빼기(-),&amp;nbsp;밑줄(_),&amp;nbsp;마침표(.)&amp;nbsp;문자만&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;단,&amp;nbsp;마침표(.)는&amp;nbsp;처음과&amp;nbsp;끝에&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;없으며&amp;nbsp;또한&amp;nbsp;연속으로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;없습니다.&lt;br /&gt;&lt;br /&gt;7단계의&amp;nbsp;순차적인&amp;nbsp;처리&amp;nbsp;과정을&amp;nbsp;통해&amp;nbsp;신규&amp;nbsp;유저가&amp;nbsp;입력한&amp;nbsp;아이디가&amp;nbsp;카카오&amp;nbsp;아이디&amp;nbsp;규칙에&amp;nbsp;맞는&amp;nbsp;지&amp;nbsp;검사하고&lt;br /&gt;규칙에&amp;nbsp;맞지&amp;nbsp;않은&amp;nbsp;경우&amp;nbsp;규칙에&amp;nbsp;맞는&amp;nbsp;새로운&amp;nbsp;아이디를&amp;nbsp;추천&lt;br /&gt;&lt;br /&gt;1단계&amp;nbsp;new_id의&amp;nbsp;모든&amp;nbsp;대문자를&amp;nbsp;대응되는&amp;nbsp;소문자로&amp;nbsp;치환합니다.&lt;br /&gt;2단계&amp;nbsp;new_id에서&amp;nbsp;알파벳&amp;nbsp;소문자,&amp;nbsp;숫자,&amp;nbsp;빼기(-),&amp;nbsp;밑줄(_),&amp;nbsp;마침표(.)를&amp;nbsp;제외한&amp;nbsp;모든&amp;nbsp;문자를&amp;nbsp;제거합니다.&lt;br /&gt;3단계&amp;nbsp;new_id에서&amp;nbsp;마침표(.)가&amp;nbsp;2번&amp;nbsp;이상&amp;nbsp;연속된&amp;nbsp;부분을&amp;nbsp;하나의&amp;nbsp;마침표(.)로&amp;nbsp;치환합니다.&lt;br /&gt;4단계&amp;nbsp;new_id에서&amp;nbsp;마침표(.)가&amp;nbsp;처음이나&amp;nbsp;끝에&amp;nbsp;위치한다면&amp;nbsp;제거합니다.&lt;br /&gt;5단계&amp;nbsp;new_id가&amp;nbsp;빈&amp;nbsp;문자열이라면,&amp;nbsp;new_id에&amp;nbsp;&quot;a&quot;를&amp;nbsp;대입합니다.&lt;br /&gt;6단계&amp;nbsp;new_id의&amp;nbsp;길이가&amp;nbsp;16자&amp;nbsp;이상이면,&amp;nbsp;new_id의&amp;nbsp;첫&amp;nbsp;15개의&amp;nbsp;문자를&amp;nbsp;제외한&amp;nbsp;나머지&amp;nbsp;문자들을&amp;nbsp;모두&amp;nbsp;제거합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;만약&amp;nbsp;제거&amp;nbsp;후&amp;nbsp;마침표(.)가&amp;nbsp;new_id의&amp;nbsp;끝에&amp;nbsp;위치한다면&amp;nbsp;끝에&amp;nbsp;위치한&amp;nbsp;마침표(.)&amp;nbsp;문자를&amp;nbsp;제거합니다.&lt;br /&gt;7단계&amp;nbsp;new_id의&amp;nbsp;길이가&amp;nbsp;2자&amp;nbsp;이하라면,&amp;nbsp;new_id의&amp;nbsp;마지막&amp;nbsp;문자를&amp;nbsp;new_id의&amp;nbsp;길이가&amp;nbsp;3이&amp;nbsp;될&amp;nbsp;때까지&amp;nbsp;반복해서&amp;nbsp;끝에&amp;nbsp;붙입니다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;풀이&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 번에 거쳐 문제를 풀었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫 번째 푼 방법은 문제의 조건들을 먼저 확인해서, 통과과 되면 그대로 아이디를 적용하고, 부합하지 않으면 조건에 따라 치환해서 문제를 풀었다. 하지만, 코드의 길이가 상당히 길어졌고, 효율적이지 못했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서, 두 번째로 푼 방법은 정규식을 최대한 활용하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사전 검증을 하지 않고, 주어진 7단계를 순차적으로 실행하는 정규식과 로직을 적용시켰다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1단계) String의 toLowwerCase 메소드를 통해, 대문자를 소문자로 치환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2단계)&amp;nbsp;[ 소문자, 숫자, -, _ , . ] 만 허용하기 때문에, 정규식을 작성해서 이에 맞지 않는 모든 문자를 replace&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3단계) . 이 두 번 이상 등장하면, 한개로 줄이는 조건에 따라, 정규식을 작성해서 replace&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4단계) 아이디의 시작과 끝에는 .이 올 수 없으므로, 이를 제거&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5단계) 치환되고 있는 새로운 아이디에 아무런 문자가 없으면, default인 a를 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6단계) 15 글자를 넘지 않도록 문자열을 잘라주고, 마지막 문자가 .이면 이를 제거&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7단계) 3글자 보다 작은 아이디면, 가장 마지막 문자를 3이 될 때까지 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 정규식은, 첫번째 방식으로 푼 이후, 다른 효율적인 풀이가 없을까 프로그래머스의 다른 분의 제출 코드를 보면서 작성했다. 이런 식으로 정규식을 사용해야하는구나라고 느꼈던 문제였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 방식 모두, 문제를 푸는데는 오래 걸리지는 않았지만, 문자열 문제는 정규식을 잘 활용하면 확실히 빠르고 효율적으로 풀 수 있다고 다시 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615105947205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RecommendNewId_72410 {
  static final String NOTHING_STATEMENT = &quot;&quot;, DEFAULT_STRING = &quot;a&quot;, SINGLE_DOT = &quot;.&quot;;
  static final int ZERO = 0, MAX_ID_LENGTH = 15, MIN_ID_LENGTH = 3;

  public static void main(String[] args) {
    String[] inputs = {&quot;...!@BaT#*..y.abcdefghijklm&quot;, &quot;z-+.^.&quot;, &quot;=.=&quot;, &quot;123_.def&quot;, &quot;abcdefghijklmn.p&quot;, &quot;ABCDEFGHIJKLMO*23&quot;};

    for(String newId : inputs) {
      System.out.println(solution(newId));
    }
  }

  private static String solution(String newId) {
    // 1단계
    String recommendId = newId.toLowerCase();
    // 2단계
    recommendId = filter(recommendId);
    // 3단계
    recommendId = getSingleDotString(recommendId);
    // 4단계
    recommendId = getRemoveDotCondition(recommendId);
    // 5단계
    if(recommendId.length() == ZERO) {
      recommendId = DEFAULT_STRING;
    }
    // 6단계
    if(recommendId.length() &amp;gt; MAX_ID_LENGTH) {
      recommendId = recommendId.substring(ZERO, MAX_ID_LENGTH).replaceAll(&quot;[.]$&quot;, NOTHING_STATEMENT);
    }
    // 7단계
    recommendId = getMinCondition(recommendId);
    return recommendId;
  }

  private static String filter(String id) {
    return id.replaceAll(&quot;[^a-z0-9._-]&quot;, NOTHING_STATEMENT);
  }

  private static String getSingleDotString(String id) {
    return id.replaceAll(&quot;[.]{2,}&quot;, SINGLE_DOT);
  }

  private static String getRemoveDotCondition(String id) {
    return id.replaceAll(&quot;^[.]|[.]$&quot;, NOTHING_STATEMENT);
  }

  private static String getMinCondition(String id) {
    StringBuilder sb = new StringBuilder(id);
    while(sb.length() &amp;lt;MIN_ID_LENGTH) {
      sb.append(sb.charAt(sb.length()-1));
    }
    return sb.toString();
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스 카카오</category>
      <category>신규 아이디 추천</category>
      <category>자바</category>
      <category>정규식</category>
      <category>카카오 2021</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/343</guid>
      <comments>https://zin0-0.tistory.com/343#entry343comment</comments>
      <pubDate>Sun, 7 Mar 2021 17:41:55 +0900</pubDate>
    </item>
    <item>
      <title>BOJ) 카드 게임</title>
      <link>https://zin0-0.tistory.com/342</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10835&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;카드 게임&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1615105228953&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;10835번: 카드게임&quot; data-og-description=&quot;첫 줄에는 한 더미의 카드의 개수를 나타내는 자연수 N(1 &amp;le; N &amp;le; 2,000)이 주어진다. 다음 줄에는 왼쪽 더미의 카드에 적힌 정수 A(1 &amp;le; A &amp;le; 2,000)가 카드 순서대로 N개 주어진다. 그 다음 줄에는 오&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/10835&quot; data-og-url=&quot;https://www.acmicpc.net/problem/10835&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eOy4I/hyJtMwVOoq/8vQxU5bEEXkE7lNPaomOG1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cUwEYM/hyJtyk9ymm/dA6dAodQXx1Ko600doyZ0k/img.png?width=586&amp;amp;height=600&amp;amp;face=0_0_586_600&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10835&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/10835&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eOy4I/hyJtMwVOoq/8vQxU5bEEXkE7lNPaomOG1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cUwEYM/hyJtyk9ymm/dA6dAodQXx1Ko600doyZ0k/img.png?width=586&amp;amp;height=600&amp;amp;face=0_0_586_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;10835번: 카드게임&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;첫 줄에는 한 더미의 카드의 개수를 나타내는 자연수 N(1 &amp;le; N &amp;le; 2,000)이 주어진다. 다음 줄에는 왼쪽 더미의 카드에 적힌 정수 A(1 &amp;le; A &amp;le; 2,000)가 카드 순서대로 N개 주어진다. 그 다음 줄에는 오&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;문제 조건&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 왼쪽 카드만 통에 버릴 수도 있고 왼쪽과 오른쪽 카드를 둘 다 통에 버릴 수도 있다. (점수 X)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 오른쪽 카드에 적힌 수 &amp;lt; 왼쪽 카드에 적힌 수 ~&amp;gt; 오른쪽 카드만 통에 버릴 수도 있다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp;오른쪽 카드만 버리는 경우에는 오른쪽 카드에 적힌 수만큼 점수를 얻는다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. (1)과 (2)의 규칙에 따라 게임을 진행하다가 어느 쪽 더미든 남은 카드가 없다면 게임이 끝나며 그때까지 얻은 점수의 합이 최종 점수가 된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size18&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;풀이&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제는 총 3번의 로직을 바꿔가며 풀었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) &lt;b&gt;왼쪽과 오른쪽의 카드 모두 정렬해서, 왼쪽 카드의 최댓 값보다 작은 오른쪽 카드의 수를 더하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 여기서 간과한 점은, 무작위로 카드를 내는 것이 아니라, 이미 셔플되어있는 덱에서 카드를 꺼내는 경우기 때문에, 성립할 수 없다는 것을 알았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) &lt;b&gt;투 포인터 활용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좌측의 카드를 역순으로 정렬하고있는 우선순위 큐를 선언하고, 오른쪽 카드가 큐에 담겨있는 최댓값보다 큰 경우, 두 수 모두 버리는 식으로 진행을 했다. 하지만, 이 방법 또한 항상 최대 스코어를 낼 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3)&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; DP로 Memoization을 하고, DFS로 탐색&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인적으로 이 방법으로는 문제를 접근한 적이 많지 않아서 많이 어려웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선, DP를 특정 값으로 초기화 해주었다. (0보다 미만인 수는 모두 유효하다고 생각한다. 어차피 모든 카드는 자연수기 때문에, 0 이상인 값이라면 DP에서 활용할 수 있는 유효한 수가 된다. 다만, 특정 변수를 선언해주기 보다 이미 내장되어있는 Integer의 MIN_VALUE를 사용했다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이후, 왼쪽 카드의 시작과 오른쪽 카드의 시작부터 값을 탐색했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카드를 버리는 조건에 따라, 왼쪽 카드가 더 큰 경우에는 오른쪽에 적혀있는 카드의 숫자를 더하며 오른쪽 카드만 버려주었다. 같거나 오른쪽 카드가 더 큰 경우에는, 왼쪽 카드만 버리는 경우와, 둘 다 버리는 카드 중 더 큰 점수를 갖는 값을 더해주며 값을 갱신했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때, 이미 DP 값이 변동이 되어있는 상태라면, 다시 로직을 돌 필요가 없기 때문에, 사전에 초기화한 Integer.MIN_VALUE인지 확인해주며, 반복을 줄여주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615105211499&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class CardGame_10835 {
  static int[][] dp;
  static int[] left, right;
  static int n;

  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    n = Integer.parseInt(br.readLine());
    StringTokenizer st = new StringTokenizer(br.readLine());
    left = getCards(st);
    st = new StringTokenizer(br.readLine());
    right = getCards(st);

    br.close();
    solution();
  }

  private static void solution() {
    initDP();
    int answer =getMaxScore(0,0);
    System.out.println(answer);
  }

  private static void initDP() {
    dp = new int[n+1][n+1];
    for(int i=0; i&amp;lt;=n; i++) {
      Arrays.fill(dp[i], Integer.MIN_VALUE);
    }
  }

  private static int getMaxScore(int leftIndex, int rightIndex) {
    if(leftIndex &amp;gt;= n || rightIndex &amp;gt;= n) { // end of game
      return 0;
    }
    if(dp[leftIndex][rightIndex] != Integer.MIN_VALUE) { // memoization
      return dp[leftIndex][rightIndex];
    }
    dp[leftIndex][rightIndex] =0; // init

    if(left[leftIndex] &amp;gt; right[rightIndex]) {
      dp[leftIndex][rightIndex] += right[rightIndex] + getMaxScore(leftIndex, rightIndex+1); // drop only right card
    } else {
      dp[leftIndex][rightIndex] += Math.max(getMaxScore(leftIndex+1, rightIndex), getMaxScore(leftIndex+1,rightIndex+1));
      // max(drop only left, drop both left and right)
    }
    return dp[leftIndex][rightIndex];
  }

  private static int[] getCards(StringTokenizer st) {
    int[] cards = new int[n];
    for(int i=0; i&amp;lt;n; i++) {
      cards[i] = Integer.parseInt(st.nextToken());
    }
    return cards;
  }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/백준</category>
      <category>10835</category>
      <category>dfs</category>
      <category>dp</category>
      <category>백준</category>
      <category>자바</category>
      <category>카드게임</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/342</guid>
      <comments>https://zin0-0.tistory.com/342#entry342comment</comments>
      <pubDate>Sun, 7 Mar 2021 17:30:22 +0900</pubDate>
    </item>
    <item>
      <title>BOJ) 계단 수 (1562 번)</title>
      <link>https://zin0-0.tistory.com/341</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1562&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;계단 수&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1615104785373&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;1562번: 계단 수&quot; data-og-description=&quot;첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1562&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1562&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHyaWe/hyJtKMFmwC/5ux4ItUu0k9CKF0pe9Uw6K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1562&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1562&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHyaWe/hyJtKMFmwC/5ux4ItUu0k9CKF0pe9Uw6K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;1562번: 계단 수&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;45656, 1210123 과 같이 각 자릿 수의 차이가 1씩 나는 수를 계단 수라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자릿 수를 의미하는 n이 주어질 때, 이러한 계단수가 몇 개 존재하는지 구하는 문제다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때, 모든 계단 수는 0으로 시작하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DP와 비트 마스크를 활용해서 푸는 문제로, 각 자릿수, 0 ~9의 수, 비트마스크를 저장할 3차원 정수형 배열 DP를 이용해서 풀었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선, n = 1인 경우, 0 ~ 9는 각각 자신 수 하나를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 비트마스크로 표현해주면서, n을 증가시켰다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0인 경우에는 0으로 시작할 수 없기 때문에, 0다음에 올 수 있는 수인 1만 dp에 추가시켜주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이외에 1 ~ 8은 각각 앞뒤로 숫자를 추가시켜 계단 수를 만들 수 있기 때문에, 다음 인덱스의 숫자도 함께 체크하면서 dp에 저장했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서, 9를 따로 체크해주지 않고 배열의 공간을 11로 잡아주어 out of index를 따로 체크하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 저장된 dp의 n 자릿 수에서, 0 ~ 9 까지의 공간을 탐색하며 값을 더해준다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때, 모든 숫자 0 ~ 9가 저장된 경우를 찾아야하기 때문에, 모두 저장되어 있는 비트마스크 (1 &amp;lt;&amp;lt; 10) -1 번 째 인덱스를 탐색하며 답을 구해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한, 문제의 조건에 &lt;span&gt;1,000,000,000으로 나눈 나머지를 출력하는 조건이 있기 때문에, DP를 저장할 때와 값을 구할 때 모두 MOD를 해주었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615104741070&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class StairNumber_1562 {
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());

    br.close();
    solution(n);
  }

  private static void solution(int n) {
    final int MAX_NUMBER =9, MOD = 1000000000, ALL_VISIT = (1&amp;lt;&amp;lt;10) -1; // 0 ~ 10 비트마스크
    int[][][] dp = new int[n+1][MAX_NUMBER+2][ALL_VISIT+1];
    for(int i=1; i&amp;lt;=MAX_NUMBER; i++) {
      dp[1][i][1 &amp;lt;&amp;lt; i] = 1;
    }

    for(int i=2; i&amp;lt;=n; i++) {
      for(int j=0; j&amp;lt;=MAX_NUMBER; j++) {
        for(int k=0; k&amp;lt;=ALL_VISIT; k++) {
          dp[i][j][k | 1 &amp;lt;&amp;lt; j] = (dp[i][j][k | 1 &amp;lt;&amp;lt; j] + dp[i-1][j+1][k]) % MOD;
          if(j !=0) { // 0인 경우 out of index 방지
            dp[i][j][k | (1 &amp;lt;&amp;lt; j)] = (dp[i][j][k | (1 &amp;lt;&amp;lt; j)] + dp[i - 1][j - 1][k]) % MOD;
          }
        }
      }
    }

    int answer =0;
    for(int i=0; i&amp;lt;=MAX_NUMBER; i++) {
      answer = (answer + dp[n][i][ALL_VISIT]) % MOD;
    }
    System.out.println(answer);
  }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/백준</category>
      <category>1562</category>
      <category>BOJ</category>
      <category>dp</category>
      <category>계단 수</category>
      <category>백준</category>
      <category>비트 마스크</category>
      <category>자바</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/341</guid>
      <comments>https://zin0-0.tistory.com/341#entry341comment</comments>
      <pubDate>Sun, 7 Mar 2021 17:19:19 +0900</pubDate>
    </item>
    <item>
      <title>BOJ) 사회망 서비스(SNS) (2533 번)</title>
      <link>https://zin0-0.tistory.com/340</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2533&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;사회망&amp;nbsp;서비스(SNS)&lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1614754973271&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;2533번: 사회망 서비스(SNS)&quot; data-og-description=&quot;첫 번째 줄에는 친구 관계 트리의 정점 개수 N이 주어진다. 단, 2 &amp;lt;= N &amp;lt;= 1,000,000이며, 각 정점은 1부터 N까지 일련번호로 표현된다. 두 번째 줄부터 N-1개의 줄에는 각 줄마다 친구 관계 트리의 에지 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/2533&quot; data-og-url=&quot;https://www.acmicpc.net/problem/2533&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNHMvj/hyJqUPMKqA/NHg3PxYkvbgyGhfNkbPTck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2533&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/2533&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNHMvj/hyJqUPMKqA/NHg3PxYkvbgyGhfNkbPTck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;2533번: 사회망 서비스(SNS)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;첫 번째 줄에는 친구 관계 트리의 정점 개수 N이 주어진다. 단, 2 &amp;lt;= N &amp;lt;= 1,000,000이며, 각 정점은 1부터 N까지 일련번호로 표현된다. 두 번째 줄부터 N-1개의 줄에는 각 줄마다 친구 관계 트리의 에지&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;효율적인 풀이 방법이 떠오르지 않아, 다른 분들이 푼 방법을 보고 이해했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저, 처음 생각했던 방식은 아래 코드의 로직과 크게 다르지는 않지만, 효율적이지는 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 점에서 시작해서, 본인이 얼리어답터일 경우, 몇명의 얼리어답터가 존재해야 문제의 조건을 만족시키는지 구하는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 메모이제이션을 하면 효율적으로 풀 수 있을 것 같아서, 이와 관련해서 생각하다가 실패했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 다른 분들의 풀이를 보니까 이해가 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자신이 얼리 어답터면 친구들이 얼리 어답터이든 레이트 어답터이든 상관 없이 내 친구들을 교화할 수 있고, 내 친구들이 교화하는 친구들의 수까지 저장해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면, 레이트 어답터면 친구들이 얼리어답터여야 문제의 조건이 성립하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 DFS 및 DP를 이용해서 구현하는 로직이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614755010442&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class SNS_2533 {
  static final int LENGTH =2, EARLY_ADAPTOR =1, LATE_ADAPTOR =0; // dp의 길이, 얼리 어답터 인덱스, 레이트 어답터 인덱스
  static int[][] dp; // 자신이 얼리 어답터일 때와 레이트 어답터일 때의 값을 저장
  static boolean[] visit; // 방문 체크
  static List&amp;lt;Integer&amp;gt;[] graph; // 연결 그래프

  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());
    init(n); // init graph, dp, visit

    for(int i=1; i&amp;lt;n; i++) { // set graph
      StringTokenizer st = new StringTokenizer(br.readLine());
      int left = Integer.parseInt(st.nextToken()), right = Integer.parseInt(st.nextToken());
      graph[left].add(right);
      graph[right].add(left);
    }

    br.close();
    solution();
  }

  private static void init(int n) {
    graph = new ArrayList[n+1];
    for(int i=1; i&amp;lt;=n; i++) {
      graph[i] = new ArrayList&amp;lt;&amp;gt;();
    }
    dp = new int[n+1][LENGTH];
    visit = new boolean[n+1];
  }

  private static void solution() {
    final int STARTING_INDEX =1;
    find(STARTING_INDEX);
    System.out.println(Math.min(dp[STARTING_INDEX][EARLY_ADAPTOR], dp[STARTING_INDEX][LATE_ADAPTOR]));
  }

  private static void find(int idx) {
    visit[idx] = true;
    dp[idx][LATE_ADAPTOR] = LATE_ADAPTOR;
    dp[idx][EARLY_ADAPTOR] = EARLY_ADAPTOR;
    for(int next : graph[idx]) {
      if(visit[next]) {
        continue;
      }
      find(next);
      dp[idx][LATE_ADAPTOR] += dp[next][EARLY_ADAPTOR]; // 자신이 레이트 어답터면, 친구들 모두 얼리 어답터.
      dp[idx][EARLY_ADAPTOR] += Math.min(dp[next][LATE_ADAPTOR], dp[next][EARLY_ADAPTOR]); // 자신이 얼리 어답터면 뭐든 OK
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://comyoung.tistory.com/41&quot;&gt;https://comyoung.tistory.com/41&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/백준</category>
      <category>2533</category>
      <category>dfs</category>
      <category>dp</category>
      <category>백준</category>
      <category>사회망 서비스</category>
      <category>자바</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/340</guid>
      <comments>https://zin0-0.tistory.com/340#entry340comment</comments>
      <pubDate>Wed, 3 Mar 2021 16:09:35 +0900</pubDate>
    </item>
    <item>
      <title>BOJ) 작업 (2056 번)</title>
      <link>https://zin0-0.tistory.com/339</link>
      <description>&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2056&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;작업&lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1614699573610&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;2056번: 작업&quot; data-og-description=&quot;수행해야 할 작업 N개 (3 &amp;le;&amp;nbsp;N &amp;le;&amp;nbsp;10000)가 있다. 각각의 작업마다 걸리는 시간(1 &amp;le;&amp;nbsp;시간 &amp;le; 100)이 정수로 주어진다. 몇몇 작업들 사이에는 선행 관계라는 게 있어서, 어떤 작업을 수행하기 위해 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/2056&quot; data-og-url=&quot;https://www.acmicpc.net/problem/2056&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/95h6r/hyJqVOoNw5/scM9RzNVuQpHncDOYayEd0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2056&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/2056&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/95h6r/hyJqVOoNw5/scM9RzNVuQpHncDOYayEd0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;2056번: 작업&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;수행해야 할 작업 N개 (3 &amp;le;&amp;nbsp;N &amp;le;&amp;nbsp;10000)가 있다. 각각의 작업마다 걸리는 시간(1 &amp;le;&amp;nbsp;시간 &amp;le; 100)이 정수로 주어진다. 몇몇 작업들 사이에는 선행 관계라는 게 있어서, 어떤 작업을 수행하기 위해&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업의 개수 N이 주어지고, 다음 N줄에 거쳐 각 작업의 수행 시간과 선행돼야하는 작업의 숫자가 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;K작업에서 선행되는 작업은 1 ~ K-1 사이의 작업이 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제는 DP와 위상정렬로 분류되어있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서, 위상정렬로 접근하려고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ArrayList에 각 선행되는 작업들을 넣어주고, 선행작업이 없는 작업들을 Queue에 넣어주어 순회했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 작업은 상관관계가 없으면 동시에 진행되니까, group을 넣어주고, depth를 함께 저장하면서 시간이 겹치지 않게 구하려고 했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 로직과 코드가 복잡해지고 올바르지 않다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제를 다시 봤는데, 친절하게도 힌트에 각 작업의 수행 시간이 적혀있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 든 생각이, 각 작업의 시작 시간을 저장해주면 쉽게 구할 수 있지 않을까? 였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 입력부터, 각 작업의 ( 선행 작업의 수행 시간 + 선행 작업의 대기 시간 ) 의 최댓값을 저장해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(그래야, 병렬로 진행되는 작업들을 선별해내 최소 시간에 진행할 수 있기 때문)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 저장하고 보니까 어렵게 접근할 필요도, 풀어갈 필요도 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제를 쉽게 접근하는 시야를 기르자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614699585364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Work_2056 {
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());
    int[] times = new int[n+1]; // 작업에 걸리는 시간
    int[] startingTime = new int[n+1]; // 선행 작업 이후 언제 시작하는지 저장

    for(int i=1; i&amp;lt;=n; i++) {
      StringTokenizer st = new StringTokenizer(br.readLine());
      times[i] = Integer.parseInt(st.nextToken());
      int m = Integer.parseInt(st.nextToken());
      while(m-- &amp;gt;0) {
        int parent = Integer.parseInt(st.nextToken());
        startingTime[i] = Math.max(startingTime[i], startingTime[parent] + times[parent]);
      }
    }

    br.close();
    solution(n, times, startingTime);
  }

  private static void solution(int n, int[] times, int[] startingTime) {
    int answer =0;
    for(int i=1; i&amp;lt;=n; i++) {
      answer = Math.max(answer, startingTime[i]+times[i]);
    }
    System.out.println(answer);
  }
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/백준</category>
      <category>2056</category>
      <category>dp</category>
      <category>java</category>
      <category>백준</category>
      <category>자바</category>
      <category>작업</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/339</guid>
      <comments>https://zin0-0.tistory.com/339#entry339comment</comments>
      <pubDate>Wed, 3 Mar 2021 00:45:23 +0900</pubDate>
    </item>
    <item>
      <title>자바 참조 유형 (Strong, Soft, Weak, Phantom Reference)</title>
      <link>https://zin0-0.tistory.com/338</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자바 참조 유형&lt;/b&gt;&lt;/h4&gt;
&lt;p&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;강한 참조(Strong Reference)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 new를 통해서 객체를 생성하게 되면 생기게 되는 참조.&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;강한 참조를 통해 참조되고 있는 객체는 가비지 컬렉션의 대상에서 제외된다.&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;소프트 참조(Soft Reference)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;java.lang.ref.SoftReference&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체를 참조하는 경우가 SoftReference 객체만 존재하면, GC의 대상이 됨&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로는 메모리 여유에 따라 GC의 대상 여부가 결정&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리 여유가 충분하면 GC가 수행되더라도 수거되지 않는다. &lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JVM의 메모리가 부족하다면(Out Of Memory에 가깝다면) 힙 영역에서 제거된다.&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;약한 참조(Weak Reference)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;java.lang.ref.WeakReference&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GC가 발생하면 무조건 수거됨&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GC의 실행주기와 수거 시점이 일치하며, 짧은 주기에 자주 사용되는 객체를 캐시할 때 유용&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;팬텀 참조(Phantom Reference)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;java.lang.ref.PhantomReference&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성자에서 무조건 ReferenceQueue를 받음&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GC가 실행되기 이전(finalize() 호출 후) PhantomReference는 객체 내부 참조를 phantomly reachable 객체로 만든 후, queue에 입력됨&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://lion-king.tistory.com/entry/Java-%EC%B0%B8%EC%A1%B0-%EC%9C%A0%ED%98%95-Strong-Reference-Soft-Reference-Weak-Reference-Phantom-References&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;lion-king.tistory.com/entry/Java-%EC%B0%B8%EC%A1%B0-%EC%9C%A0%ED%98%95-Strong-Reference-Soft-Reference-Weak-Reference-Phantom-References&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://ktko.tistory.com/entry/%EC%9E%90%EB%B0%94-%EA%B0%95%ED%95%9C%EC%B0%B8%EC%A1%B0Strong-Reference%EC%99%80-%EC%95%BD%ED%95%9C%EC%B0%B8%EC%A1%B0Weak-Reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ktko.tistory.com/entry/%EC%9E%90%EB%B0%94-%EA%B0%95%ED%95%9C%EC%B0%B8%EC%A1%B0Strong-Reference%EC%99%80-%EC%95%BD%ED%95%9C%EC%B0%B8%EC%A1%B0Weak-Reference&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java &amp;amp; Spring/자바</category>
      <category>java</category>
      <category>Strong Reference</category>
      <category>자바 참조 유형</category>
      <author>Zin0_0</author>
      <guid isPermaLink="true">https://zin0-0.tistory.com/338</guid>
      <comments>https://zin0-0.tistory.com/338#entry338comment</comments>
      <pubDate>Sun, 28 Feb 2021 22:54:30 +0900</pubDate>
    </item>
  </channel>
</rss>