<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>현재의 내가 미래의 나에게</title>
    <link>https://k9want.tistory.com/</link>
    <description>지금의 내용을 확실히 알고 있는 지금의 내가 잘 기억하지 못하고 있을 미래의 나에게</description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 17:08:58 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>k9want</managingEditor>
    <image>
      <title>현재의 내가 미래의 나에게</title>
      <url>https://tistory1.daumcdn.net/tistory/4786080/attach/b6032f18f8ee486ea1ec412b17284433</url>
      <link>https://k9want.tistory.com</link>
    </image>
    <item>
      <title>인터페이스와 추상 클래스 비교</title>
      <link>https://k9want.tistory.com/entry/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%B9%84%EA%B5%90</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실무 관점에서 본 인터페이스 vs. 추상 클래스 비교&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스와 추상 클래스는 객체지향 설계에서 핵심적인 개념이며, &lt;b&gt;실무에서는 상황에 따라 적절히 선택&lt;/b&gt;해야 합니다. 아래에서는 &lt;b&gt;언제 인터페이스를 사용하고, 언제 추상 클래스를 사용하는지&lt;/b&gt;, 그리고 &lt;b&gt;실제로 더 자주 사용하는 개념과 그 이유&lt;/b&gt;를 정리해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  인터페이스 vs. 추상 클래스 실무 비교&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 요소 인터페이스 (Interface) 추상 클래스 (Abstract Class)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상속 구조&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;다중 구현 가능 (클래스는 여러 개의 인터페이스를 구현 가능)&lt;/td&gt;
&lt;td&gt;단일 상속만 가능 (다른 클래스를 동시에 상속받을 수 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상태(State) 유지&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;불가능 (멤버 변수 선언 불가, Java 8 이후 default 메서드로 일부 구현 가능)&lt;/td&gt;
&lt;td&gt;가능 (멤버 변수 선언 및 상태 유지 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;기본 구현&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;기본적으로 없음, Java 8 이후 default 메서드 사용 가능&lt;/td&gt;
&lt;td&gt;일부 메서드를 구현 가능 (공통된 기능 제공)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;특정 기능을 강제하는 용도로 사용 (Comparable, Runnable)&lt;/td&gt;
&lt;td&gt;공통된 기능을 제공하면서 일부 메서드를 구현 강제 (Animal &amp;rarr; Dog, Cat)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;높은 확장성 (다양한 클래스에서 같은 동작을 구현 가능)&lt;/td&gt;
&lt;td&gt;공통 기능을 포함한 특정 계열의 클래스 설계에 적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;성능&lt;/b&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✅ 실무에서의 선택 기준&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 인터페이스 선택 추상 클래스 선택&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 140px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;상황&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인터페이스 선택&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;추상 클래스 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;여러 클래스가 &lt;b&gt;공통된 동작(기능)&lt;/b&gt;을 가져야 하나, 계층 구조가 아닐 때&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;여러 클래스가 공통된 &lt;b&gt;속성(필드)&lt;/b&gt;과 &lt;b&gt;행동(메서드)&lt;/b&gt;을 공유해야 할 때&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;특정 동작을 &lt;b&gt;강제&lt;/b&gt;하고, 다양한 구현이 필요할 때 (예: Comparable, Serializable)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;동일한 동작을 하는 다양한 클래스가 있고, &lt;b&gt;기본 구현을 제공해야 할 때&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;다중 상속이 필요한 경우 (여러 개의 기능을 조합)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;프레임워크나 라이브러리에서 기능을 확장할 때 (예: Spring의 BeanPostProcessor)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;특정 클래스를 기반으로 몇 개의 서브클래스를 만들고 싶을 때&lt;/td&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  실무에서 더 자주 사용하는 것은?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스가 실무에서 더 자주 사용됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;이유 1: 다중 상속 지원&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java는 클래스의 다중 상속을 지원하지 않지만, &lt;b&gt;인터페이스는 다중 구현 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;예를 들어, Runnable, Serializable, Cloneable과 같은 동작을 동시에 가질 수 있음&lt;/li&gt;
&lt;li&gt;Spring, JPA 등 주요 프레임워크에서도 인터페이스 기반 설계가 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;이유 2: 유연한 설계&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스를 사용하면 서로 다른 클래스에서도 &lt;b&gt;공통 기능을 강제&lt;/b&gt;할 수 있어 확장성이 뛰어남&lt;/li&gt;
&lt;li&gt;실무에서는 &lt;b&gt;비즈니스 로직이 확장될 가능성이 높기 때문에&lt;/b&gt; 인터페이스 중심의 설계가 선호됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;이유 3: SOLID 원칙 준수&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OCP(Open-Closed Principle, 개방-폐쇄 원칙)&lt;/b&gt;: 인터페이스를 사용하면 새로운 기능을 추가할 때 기존 클래스를 변경하지 않고 확장 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DIP(Dependency Inversion Principle, 의존 역전 원칙)&lt;/b&gt;: 고수준 모듈이 저수준 모듈에 직접 의존하지 않고, 인터페이스를 통해 의존성을 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  인터페이스 vs. 추상 클래스 장단점 정리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  인터페이스의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;1. 유연성과 확장성 (OCP: 개방-폐쇄 원칙 준수)&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;2. 다중 구현 가능 (다중 상속 대체)&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;3. 객체 간의 결합도를 낮춤 (DIP: 의존 역전 원칙 적용)&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  인터페이스의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;1. 구현 강제성 (불필요한 메서드 구현 발생)&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;/li&gt;
&lt;li&gt;만약 일부 클래스에서 필요하지 않은 메서드가 있을 경우, 빈 구현을 해야 하는 경우가 발생&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;해결 방법&lt;/b&gt;: 인터페이스를 분리하여 ISP(인터페이스 분리 원칙) 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;2. 코드 중복 가능성 (기본 구현이 없음)&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;b&gt;중복 코드가 발생&lt;/b&gt;할 수 있음&lt;/li&gt;
&lt;li&gt;(Java 8 이후 default 메서드를 지원하여 일부 해결 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  추상 클래스의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;1. 기본 동작 제공 (코드 재사용 가능)&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;2. 상태(필드) 유지 가능&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  추상 클래스의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;1. 단일 상속의 한계&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;/li&gt;
&lt;li&gt;만약 여러 개의 부모 클래스를 상속하고 싶다면, 추상 클래스를 사용할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;2. 확장성이 떨어짐 (OCP 원칙 위반)&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;/li&gt;
&lt;li&gt;인터페이스 기반 설계보다 유연성이 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;3. 구현 클래스 간 결합도가 높아짐&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  실무에서는 어떻게 선택해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 요소 인터페이스 추상 클래스&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 높음 (OCP 준수, 변경 용이)&lt;/td&gt;
&lt;td&gt;❌ 낮음 (추가 시 기존 클래스 수정 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드 재사용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;❌ 없음 (중복 가능성 존재)&lt;/td&gt;
&lt;td&gt;✅ 있음 (기본 구현 제공)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상태 유지&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;❌ 불가능&lt;/td&gt;
&lt;td&gt;✅ 가능 (멤버 변수 포함 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;다중 상속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 가능 (여러 인터페이스 구현 가능)&lt;/td&gt;
&lt;td&gt;❌ 불가능 (단일 상속 제한)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;결합도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 낮음 (느슨한 결합)&lt;/td&gt;
&lt;td&gt;❌ 높음 (변경 시 전체 영향)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구현 강제성&lt;/b&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  실무 예제: 인터페이스 vs. 추상 클래스 적용 사례&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  인터페이스 사용 예 (Spring에서 Repository 패턴 적용)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크에서는 &lt;b&gt;Repository 계층을 인터페이스로 설계&lt;/b&gt;하여, 다양한 데이터베이스 구현체를 쉽게 교체할 수 있도록 합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 인터페이스 정의 (다양한 DB 구현체를 만들 수 있음)
public interface UserRepository {
    User findById(Long id);
    void save(User user);
}

// MySQL 구현체
public class MySQLUserRepository implements UserRepository {
    @Override
    public User findById(Long id) {
        System.out.println(&quot;Fetching user from MySQL...&quot;);
        return new User(id, &quot;MySQL User&quot;);
    }

    @Override
    public void save(User user) {
        System.out.println(&quot;Saving user to MySQL...&quot;);
    }
}

// MongoDB 구현체
public class MongoUserRepository implements UserRepository {
    @Override
    public User findById(Long id) {
        System.out.println(&quot;Fetching user from MongoDB...&quot;);
        return new User(id, &quot;MongoDB User&quot;);
    }

    @Override
    public void save(User user) {
        System.out.println(&quot;Saving user to MongoDB...&quot;);
    }
}

// 서비스 계층 (인터페이스를 통해 종속성 주입)
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void processUser(Long id) {
        User user = userRepository.findById(id);
        System.out.println(&quot;Processing user: &quot; + user.getName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;MySQL, MongoDB 등의 구현을 쉽게 교체 가능&lt;/li&gt;
&lt;li&gt;확장성이 뛰어나며, 새로운 DB 지원이 필요할 경우 기존 코드를 수정하지 않고 확장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  추상 클래스 사용 예 (공통 동작을 가지는 동물 클래스)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;java
복사편집
// 추상 클래스: 공통 속성과 메서드 제공
abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void sleep() {
        System.out.println(name + &quot; is sleeping...&quot;);
    }

    // 추상 메서드 (각 동물이 다르게 구현해야 함)
    public abstract void makeSound();
}

// 하위 클래스
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + &quot; barks!&quot;);
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + &quot; meows!&quot;);
    }
}

// 실행 코드
public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog(&quot;Buddy&quot;);
        dog.sleep();
        dog.makeSound();

        Animal cat = new Cat(&quot;Kitty&quot;);
        cat.sleep();
        cat.makeSound();
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;Animal 클래스에서 &lt;b&gt;공통된 동작(sleep())을 제공&lt;/b&gt;하고, 하위 클래스에서 makeSound()를 반드시 구현하도록 강제&lt;/li&gt;
&lt;li&gt;공통 속성(name)을 관리할 수 있어 중복 코드 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&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;/li&gt;
&lt;li&gt;&lt;b&gt;다중 구현 가능하여 확장성 증가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spring, JPA, REST API 등 대부분의 프레임워크에서 인터페이스 기반 설계 적용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&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;/li&gt;
&lt;li&gt;&lt;b&gt;인터페이스만으로 해결하기 어려운 경우 보완적으로 활용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;즉, 기본적으로 인터페이스를 사용하고, 필요한 경우에만 추상 클래스를 활용하는 것이 실무에서 가장 효과적인 방식!&lt;/b&gt;  &lt;/p&gt;</description>
      <category>Java</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/149</guid>
      <comments>https://k9want.tistory.com/entry/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%B9%84%EA%B5%90#entry149comment</comments>
      <pubDate>Mon, 3 Feb 2025 23:34:53 +0900</pubDate>
    </item>
    <item>
      <title>인터페이스에 대한 생각 정리</title>
      <link>https://k9want.tistory.com/entry/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 인터페이스란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스(Interface)는 자바의 핵심 개념 중 하나로, 클래스가 구현해야 하는 메서드의 명세를 정의하는 추상적인 타입이다. 인터페이스는 다음과 같은 특징을 갖는다.&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;메서드의 시그니처 : 메서드의 이름과 그 메서드가 받을 파라미터의 종류 및 개수를 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 구현(multiple inheritance)&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;&lt;b&gt;구현 클래스의 계약(Contract) 역할&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 인터페이스의 주요 목적&lt;/h2&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;: SOLID 원칙 중 &lt;b&gt;ISP(Interface Segregation Principle, 인터페이스 분리 원칙)&lt;/b&gt; 및 **DIP(Dependency Inversion Principle, 의존 역전 원칙)**을 준수하는 데 필수적이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ISP : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않도록 해야한다는 원칙&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;큰 인터페이스는 다양한 기능을 제공하지만, 이를 구현해야 하는 클래스가 필요 없는 메서드까지 강제로 구현해야하는 상황이 발생 &amp;rarr; 클래스가 불필요한 코드까지 작성하게 하여, &lt;b&gt;유지 보수성&lt;/b&gt;을 떨어뜨리고 &lt;b&gt;확장성&lt;/b&gt;을 제한한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DIP : 고수준 모듈은 저수준 모듈에 의존해서는 안 되고, 둘 다 추상화에 의존해야 한다는 원칙 [추상화는 세부 사항에 의존해서는 안 되고, 세부 사항은 추상화에 의존해야 한다.]&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;이 DIP이다.&lt;/li&gt;
&lt;li&gt;DIP 적용하면 &lt;b&gt;테스트 코드 작성도 용이&lt;/b&gt;해진다. &amp;rarr; 실제 구현이 아닌 인터페이스를 기반으로 의존성을 주입하면, &lt;b&gt;테스트 시 Mock 객체를 활용할 수 있어 독립적인 단위 테스트가 가능&lt;/b&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;li&gt;&lt;b&gt;다형성(Polymorphism) 활용&lt;/b&gt;: 인터페이스를 통해 다양한 구현을 하나의 타입으로 다룰 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할(Role)과 책임(Responsibility) 분리 : 클래스의 역할(무엇을 하는가?)과 구현(어떻게 하는가?)을 분리할 수 있으며, 결합도를 낮추고 유연한 설계를 가능하게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본적인 사용 예시&lt;/h2&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface PaymentGateway {
    void processPayment(double amount);
}

public class CreditCardPayment implements PaymentGateway {
    @Override
    public void processPayment(double amount) {
        System.out.println(&quot;Processing credit card payment: &quot; + amount);
    }
}

public class PayPalPayment implements PaymentGateway {
    @Override
    public void processPayment(double amount) {
        System.out.println(&quot;Processing PayPal payment: &quot; + amount);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 인터페이스를 활용하면 &lt;b&gt;결합도를 낮추고&lt;/b&gt;, 새로운 결제 방식을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실무에서 인터페이스를 활용하는 방식&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 주입(DI, Dependency Injection)과 스프링 빈 관리&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;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class PaymentService {
    private final PaymentGateway paymentGateway;

    public PaymentService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public void executePayment(double amount) {
        paymentGateway.processPayment(amount);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전략 패턴(Strategy Pattern) 적용&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;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;/li&gt;
&lt;li&gt;실행 중에 &lt;b&gt;적절한 전략을 선택&lt;/b&gt;하여 사용할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OCP(개방-폐쇄 원칙) 준수&lt;/b&gt;: 새로운 전략을 추가해도 기존 코드를 수정할 필요 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DIP(의존 역전 원칙) 준수&lt;/b&gt;: 특정 구현체가 아닌 인터페이스에 의존&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 설계 시 인터페이스 중심 개발(Interface-Based Development, IBD)&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;REST API 설계 시 서비스 레이어에 인터페이스를 두어 구현체를 쉽게 변경할 수 있도록 해준다.&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;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구현체(Service Implementation)와 분리된 인터페이스&lt;/b&gt;를 통해 느슨한 결합(Loose Coupling)을 유지&lt;/li&gt;
&lt;li&gt;특정 기술(JPA, MyBatis, MongoDB 등)에 의존하지 않고 쉽게 교체 가능&lt;/li&gt;
&lt;li&gt;Mock 객체를 활용한 &lt;b&gt;단위 테스트 용이&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;SOLID 원칙 준수 (특히 &lt;b&gt;DIP(의존 역전 원칙), OCP(개방-폐쇄 원칙)&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;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 인터페이스의 단점은 무엇인가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코드 중복 가능성&lt;/b&gt;: 여러 구현체에서 동일한 로직이 필요할 경우, 인터페이스에서는 메서드 구현이 불가능하므로 중복된 코드가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경 시 영향도가 클 수 있음&lt;/b&gt;: 인터페이스 변경은 이를 구현한 모든 클래스에 영향을 미치므로 신중한 설계가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 인터페이스와 람다 표현식은 어떤 관계가 있는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 8부터 인터페이스는 람다 표현식과 함께 활용될 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, &lt;b&gt;함수형 인터페이스(Functional Interface)&lt;/b&gt;는 하나의 추상 메서드만 가지는 인터페이스로, @FunctionalInterface 어노테이션을 활용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

public class LambdaExample {
    public static void main(String[] args) {
        Calculator add = (a, b) -&amp;gt; a + b;
        System.out.println(add.calculate(5, 3));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다 표현식을 활용하면 코드가 간결해지고 유지보수성이 높아진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 인터페이스를 너무 많이 사용하면 발생하는 문제는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 과도하게 사용하면 &lt;b&gt;복잡성이 증가&lt;/b&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;적절한 수준에서 인터페이스를 설계하고 유지하는 것이 중요하다&lt;/b&gt;.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: 인터페이스의 중요성 강조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 인터페이스를 단순한 문법 요소가 아니라 &lt;b&gt;객체 지향 프로그래밍(OOP)과 SOLID 원칙을 준수하는 핵심 도구&lt;/b&gt;로 설명하며 마무리하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;인터페이스는 단순히 추상 메서드를 선언하는 것이 아니라, &lt;b&gt;설계의 유연성과 확장성을 극대화하는 수단이다&lt;/b&gt;. 실무에서는 이를 기반으로 &lt;b&gt;느슨한 결합(loose coupling)&lt;/b&gt;을 유지하며, 다양한 패턴과 원칙을 적용하여 유지보수성이 높은 코드를 작성하는 것이 중요하다고 생각한다.&quot;&lt;/p&gt;</description>
      <category>Java</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/148</guid>
      <comments>https://k9want.tistory.com/entry/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81-%EC%A0%95%EB%A6%AC#entry148comment</comments>
      <pubDate>Mon, 3 Feb 2025 23:21:19 +0900</pubDate>
    </item>
    <item>
      <title>[외주] 트래블록 프로젝트 리팩토링 회고</title>
      <link>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%ED%8A%B8%EB%9E%98%EB%B8%94%EB%A1%9D-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 세웠던 리팩토링 방향성에 대해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 리팩토링하면서 배운 점과 느꼈던 점에 대해 작성해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링을 통해 해당 프로젝트에서 얻고자 했던 것들이 몇 가지 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 리팩토링이 필요한가?&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링 방향성을 설정할 때, 가장 먼저 고민한 것은 왜 리팩토링이 필요한가?였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하면서 일관된 방식(레이어드 아키텍처)으로 코드를 구현하려고 노력했고,&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;Controller-Service-Repository&lt;/b&gt; 형태의 레이어드 아키텍처를 선택하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부끄럽게도 이번 외주 프로젝트를 진행하기 전에는 이것이 정답이라고 생각했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다고 레이어드 아키텍처가 잘못되었다고 비판하는 것도 아니고 틀렸다는 건 절대 아니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;후에 언급하겠지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;토이프로젝트의 경우 굳이 복잡성을 늘리면서까지 개발을 할 필요가 있을까?&lt;/b&gt;라는 의문이 들었기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 어떤 아키텍처가 정답이라는 건 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어드 아키텍처를 사용하면서&amp;nbsp;점차 JpaRepository에 강하게 의존하므로서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;트랜잭션 스크립트&amp;nbsp;&lt;/b&gt;형태의 서비스 코드가 보여졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로 인해 서비스 코드의 대다수가 JPA에 의존적인 코드가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;트랜잭션 스크립트&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;서비스 컴포넌트의 구현이 사실상 어떤 트랜잭션이 걸려있는 스크립트를 실행하는 것처럼 보일 때를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 스마트 서비스 라고 부를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 절차지향에 가깝기에 변경에 취약하고 확장에 취약하며 업무가 병렬 처리되기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 서비스에 대한 올바른 정의가 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토이프로젝트를 진행함에 있어, JPA 외에 다른 기술을 사용하지 않을 예정이었지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에 나는 이 부분이 크게 신경쓰였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 곧&amp;nbsp; 리팩토링의 방향성으로 이어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도메인 계층을 분리하여 비즈니스 로직을 보다 명확히 이동시키기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 서비스 계층에 많은 책임이 집중되면서 서비스 계층에 정의가 모호해졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해 조치가 필요함을 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 서비스 계층을 애플리케이션 계층과 도메인 계층으로 나눔으로써 이를 해소하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;추가로 테스트 코드 작성에 대한 막연한 두려움과 경험 부족으로 인해,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 테스트 코드를 작성하지 않고 단순히 API 개발에만 집중하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 단순, 포스트맨으로만 인수테스트를 진행할 뿐 더 이상의 테스트는 하지 않았다.. 지금와서 생각해보면 무섭다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 작성하고 정의할 때, 행위에 대해 먼저 생각하고 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그에 따라 상태를 결정하는 원칙을 나름대로 따랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 개발이 진행되면서 서비스 로직이 점점 비대해졌고, 그 결과 여러 레포지토리에 의존하는 서비스들이 생겨났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조는 테스트를 작성하는 데 어려움을 주었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 의존하고 있는 모든 레포지토리를 모킹하는 데 많은 복잡성이 따랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황에서 &quot;어떻게 하면 테스트를 더 용이하게 할 수 있을까?&quot;라는 고민을 하게 되었다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 조회용 서비스와 그 외에 쓰기용 서비스를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 &lt;b&gt;Reader&lt;/b&gt;와 &lt;b&gt;Writer&lt;/b&gt;로 나누어, 불필요한 의존성을 제거하는 방향으로 리팩토링 계획을 세웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어, 각 클래스를 살펴보면서 책임을 보다 명확히 정의하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 분리함과 동시에 필요시에는 추가로 서비스를 나눔으로써&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단일 책임 원칙(SRP)&lt;/b&gt;을 최우선으로 지키는 방향으로 리팩토링을 진행하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 서비스 로직이 간결해지고, 테스트 시에도 필요한 부분만 모킹하거나 대체할 수 있어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 더 용이해질 것으로 기대했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로, 이 리팩토링의 주된 목적은 &lt;b&gt;불필요한 의존성을 제거하고, 코드의 책임을 명확히 분리하여 유지보수성과 테스트 가능성을 향상시키는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리팩토링을 하면서 느낀 점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도메인 계층을 분리하여 비즈니스 로직을 보다 명확히 이동시키기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ndwb/btsJbMdXqQG/2Q5R6jTFr0oV5ywmW8WKSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ndwb/btsJbMdXqQG/2Q5R6jTFr0oV5ywmW8WKSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ndwb/btsJbMdXqQG/2Q5R6jTFr0oV5ywmW8WKSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ndwb%2FbtsJbMdXqQG%2F2Q5R6jTFr0oV5ywmW8WKSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;709&quot; height=&quot;532&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리하면서 얻을 수 있었던 장점을 나름대로 생각해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 책임의 명확한 분리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 계층은 사용자 요청을 처리하고, 도메인 계층과 협력하여 필요한 작업을 수행하는 역할을 담당하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 계층은 도메인 계층과 레포지토리가 협력할 수 있는 무대를 제공하며, 애플리케이션의 유스케이스(Use Case)를 구현하는 데 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 계층은 비즈니스 도메인을 표현하는 클래스들로, 비즈니스 문제를 해결하는 핵심 로직을 담도록&amp;nbsp; 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 객체들끼리만 협력하도록 함으로써, 비즈니스 로직이 외부의 변화에 영향을 받지 않도록 하고, 도메인의 순수성을 유지할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 복잡한 도메인을 다루지는 않았지만, 서비스 계층을 분리한 덕분에 코드가 더 단순하고 명확해졌음을 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;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;도메인 객체들이 외부 라이브러리나 인프라스트럭처 계층에 의존하지 않도록 했다.&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;size18&quot;&gt;3. 테스트 용이성 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;각 계층이 명확히 분리됨에 따라, 테스트도 훨씬 더 쉽게 수행할 수 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;도메인 계층은 순수한 비즈니스 로직만을 담고 있기 때문에 외부 의존성이 없고, 단위 테스트가 훨씬 간편해졌다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. 유지보수성 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 계층의 책임이 명확히 분리되어 있기 때문에, 특정 기능이나 로직을 찾고 수정하는 과정이 훨씬 간단해졌다. 이를 통해 시스템의 복잡성이 줄어들고, 유지보수가 용이해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나름대로 느꼈던 것과 생각한 장점들을 나열해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리함으로써,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;책임 분리&lt;/b&gt;와&amp;nbsp;&lt;b&gt;테스트 용이성&lt;/b&gt;, &lt;b&gt;도메인의 순수성 유지&lt;/b&gt;라는 여러 가지 장점을 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하지만! 오버엔지니어링이라는 생각이 들었다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 나열한 것처럼 장점만 있는 것은 아니었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 단점을 통해 스스로 깨달은 게 더 많았다...ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 서비스 계층을 애플리케이션 계층과 도메인 계층으로 분리하면서 몇 가지 단점을 끄적여보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 도메인 객체 변환의 추가 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 계층에서는 도메인 객체를 사용하기 위해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포지토리에서 반환되는 엔티티를 도메인 객체로 변환하는 작업을 신경 써야 했다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;2. 구조의 복잡성 증가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;애플리케이션 계층과 도메인 계층을 추가로 분리하면서, 전체 코드 구조가 복잡해졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;계층 간의 책임이 명확히 분리된다는 장점이 있지만, 그만큼 각 계층 간의 상호작용을 이해하고 관리하는 데 시간이 더 필요했다.&amp;nbsp;작은 프로젝트나 단순한 도메인을 다루는 경우에는 이 복잡성이 오히려 부담으로 작용할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;3. 오버엔지니어링의 우려&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리팩토링을 진행하면서 가장 크게 느낀 점은, 현재 프로젝트가 복잡한 도메인을 다루지 않기 때문에, 이 정도로 계층을 분리할 필요가 있었을까 하는 고민이었다. 결과적으로, 이번 리팩토링이 오버엔지니어링에 가까웠다는 생각이 들었다. 복잡성이 낮은 도메인과 작은 규모의 애플리케이션에서는 이러한 계층 분리가 오히려 불필요하다는 점을 직접 경험하게 되었다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배운 점&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 리팩토링을 통해 몇 가지 배운 점이 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서비스 계층에 대한 이해도가 한층 높아졌고, 어떻게 하면 도메인에 집중할 수 있는 코드를 작성할 수 있을지에 대한 해답을 찾을 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 복잡한 도메인을 다루는 경우에는 이런 식의 계층 분리가 유효한 해결책이 될 수 있다는 점도 배웠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국, 이번 리팩토링은 오버엔지니어링이었다고 판단되지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 복잡한 도메인 구조를 어떻게 다룰지에 대한 깊이 있는 이해와 소중한 경험을 얻었다는 점에서 큰 의미가 있었다. 이러한 경험을 바탕으로, 앞으로 더 나은 설계 결정을 내릴 수 있을 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;의존성 역전 원칙(DIP)을 적용하여, 의존성을 낮추고 테스트에 용이한 코드로 변경하기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KhvJu/btsJcy65SB0/kdNOplcsWCGffujz5lCyL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KhvJu/btsJcy65SB0/kdNOplcsWCGffujz5lCyL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KhvJu/btsJcy65SB0/kdNOplcsWCGffujz5lCyL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKhvJu%2FbtsJcy65SB0%2FkdNOplcsWCGffujz5lCyL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;369&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 의존성 역전 원칙(DIP)을 적용하여 얻은 장점과 단점을 정리해보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 장점은 앞서 리팩토링 방향성에 대해 언급하면서 나왔던 내용이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JPA에 대한 의존성 제거&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 역전 원칙(DIP)을 적용함으로써, 서비스(즉, 애플리케이션 계층)에서 JPA에 대한 직접적인 의존성을 제거할 수 있었다. 이를 통해 서비스 계층은 특정 기술 스택 (현재는 JPA)에 종속되지 않게 되었고, 코드의 유연성과 유지보수성이 향상되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;오버엔지니어링의 우려&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에서는 JPA가 아닌 다른 기술 스택을 사용할 계획이 없었기 때문에, 이러한 의존성 제거가 실질적인 필요성에 부합하지 않았다. 결과적으로, 이는 오버엔지니어링에 가까운 접근이 되었고, 추가적인 복잡성을 불필요하게 초래했다. 구조적으로 복잡성이 증가함에 따라, 작은 규모의 프로젝트인 지금의 프로젝트에서는 오히려 관리가 더 어려워졌다고 생각된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배운 점&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DIP를 적용하여 얻은 장점은 분명 존재하지만, 프로젝트의 실제 요구사항과 규모를 고려하지 않은 채 적용하면 오버엔지니어링의 위험이 있다는 점을 깨닫게 되었다. 이러한 경험을 통해, 앞으로는 기술적 선택이 실제 필요와 적절히 맞아떨어지는지를 더 신중히 고려해야 한다는 교훈을 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 이번 리팩토링을 통해 DIP에 대한 이해도가 크게 높아졌고, 이러한 구조에 대한 깊이 있는 고민과 실제 적용 경험을 쌓을 수 있었다. 만약 이 리팩토링을 하지 않았다면, DIP를 실제로 프로젝트에 어떻게 적용해야 하는지에 대한 감각을 익히기 어려웠을 것이다. 이 경험은 향후 더 복잡한 도메인을 다루거나 유연성이 요구되는 프로젝트에서 큰 도움이 될 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처를 변경하는 코드 구조의 리팩토링에 있어서 신중한 결정이 필요하다는 점을 이번 경험을 통해 더욱 깊이 깨닫게 되었다. 흔히들 &quot;아키텍처에는 상황에 따른 적절한 판단이 필요하다&quot;고 말하지만, 실제로 리팩토링을 진행하면서 이 말의 진정한 의미를 몸소 체감했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교적 간단한 프로젝트에서도 구조를 개선하는 데 많은 시간이 소요된다는 점을 실감했으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 아키텍처 변경이 단순히 기술적 선택에 그치는 것이 아니라, 프로젝트의 전체적인 방향성을 재검토하는 과정임을 깨닫게 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 내가 이를 직접 적용하면서 겪었던 시행착오도 없지 않았지만, 이러한 경험이 오히려 더 큰 배움으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링을 통해 코드 구조를 보는 관점이 넓어졌고, 어떤 아키텍처가 &quot;좋다&quot; 또는 &quot;나쁘다&quot;는 단순한 이분법적인 판단이 아닌, 상황에 맞는 적절한 선택이 중요하다는 점을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링의 방향성을 설정하는 과정에서도 많은 고민이 있었지만, 이를 실제로 적용하면서 매 순간 더 많은 고민이 필요했다. 그럼에도 불구하고 직접 리팩토링하고 코드를 개선해 나가는 과정에서 느낀 점이 훨씬 컸다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성을 줄이고, 각 클래스의 단일 책임 원칙을 지키는 방향으로 리팩토링을 진행하면서, 단순히 글이나 책, 강의를 통해 얻을 수 없는 실질적인 경험과 인사이트를 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이 블로그 글을 작성하는 시점이 리팩토링을 완료한 지 약 2주가 지난 시점이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 얻은 인사이트와 성장에 대한 자신감은 여전히 크게 남아 있다. 비록 장단점에 대해 나열하는 글이 되긴 했지만, 이번 리팩토링을 통해 얻은 경험이 나에게는 소중한 자산이 되었다고 자부할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글을 읽어주셔서 감사드리며, 만약 추가로 의견이 있으시다면 언제든지 댓글로 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다!&lt;/p&gt;</description>
      <category>외주</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/147</guid>
      <comments>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%ED%8A%B8%EB%9E%98%EB%B8%94%EB%A1%9D-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%9A%8C%EA%B3%A0#entry147comment</comments>
      <pubDate>Fri, 23 Aug 2024 02:16:39 +0900</pubDate>
    </item>
    <item>
      <title>[외주] 리팩토링 방향성</title>
      <link>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%EB%B0%A9%ED%96%A5%EC%84%B1</link>
      <description>&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일단 리팩토링을 하려고 하는 이유&lt;/h2&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;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 작성 시 의존성 관련해서 어려움을 겪고 있다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계에 대한 문제의 가능성이 있다고 알려주는 경우가 많다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 객체지향적이면서 SOLID한 코드로 이번에 리팩토링하려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리팩토링으로 얻고자 하는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체지향적 + SOLID한 코드를 만들어 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;유지보수가 수월하도록 하기 위함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 데이터에 의존적인 코드가 아닌 코드로 변경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 불필요한 의존성을 끊거나 약하게 함으로써 보다 테스트에 용이한 코드로 만들기 위해&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리팩토링 방향성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720547822762&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;리팩토링 구체화 | Notion&quot; data-og-description=&quot;일단 내가 리팩토링을 하는 이유&quot; data-og-host=&quot;foregoing-bit-2cc.notion.site&quot; data-og-source-url=&quot;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&quot; data-og-url=&quot;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckJcle/hyWzAo5ITN/hYF9ryipHgD6rPSzjxqxx0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050,https://scrap.kakaocdn.net/dn/ZweZC/hyWzwNKu5W/5r7X4Tm2cfKpXqzk1gGWC0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050&quot;&gt;&lt;a href=&quot;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://foregoing-bit-2cc.notion.site/09ee785d5f604b9fb60b1e5b5bfdfbed&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckJcle/hyWzAo5ITN/hYF9ryipHgD6rPSzjxqxx0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050,https://scrap.kakaocdn.net/dn/ZweZC/hyWzwNKu5W/5r7X4Tm2cfKpXqzk1gGWC0/img.png?width=2000&amp;amp;height=1050&amp;amp;face=0_0_2000_1050');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;리팩토링 구체화 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일단 내가 리팩토링을 하는 이유&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;foregoing-bit-2cc.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 리팩토링 방향은 해당 노션을 통해 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>외주</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/145</guid>
      <comments>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%EB%B0%A9%ED%96%A5%EC%84%B1#entry145comment</comments>
      <pubDate>Wed, 10 Jul 2024 02:57:32 +0900</pubDate>
    </item>
    <item>
      <title>[외주] 외주 같지 않은 외주 작업 - 제대로 된 스프링 프로젝트 경험하기 (feat. 그 동안의 나를 되돌아보기)</title>
      <link>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%EC%99%B8%EC%A3%BC-%EA%B0%99%EC%A7%80-%EC%95%8A%EC%9D%80-%EC%99%B8%EC%A3%BC-%EC%9E%91%EC%97%85-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%90%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%BD%ED%97%98%ED%95%98%EA%B8%B0-feat-%EA%B7%B8-%EB%8F%99%EC%95%88%EC%9D%98-%EB%82%98%EB%A5%BC-%EB%90%98%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;뜬금없이 뿜뿜해버렸따!&lt;/h2&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그동안의 나를 돌아보며,&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 블로그에 나에 대한 걸 적은 기억이 거의 없는 듯하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 지금까지 살아온? 나를 돌이켜보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 하다가 Semi 외주를 맡았는지까지 적어보려고 합니다.ㅎㅎ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;처음 개발을 하게 된 건 3학년 2학기 어느 날이었죠..&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;때는 대학교 3학년 2학기&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발 경험이 아예 없던 저는&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대외활동을 하고 싶어서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 동아리나 프로젝트에 &quot;나도 할래요&quot; 라면서 수많은 지원서를 냈었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러다가 최종 면접까지 합격했던&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서울시 산업진흥원?SBA에서 진행하는 개발자 양성 교육을 받는 기회가 왔고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;곧바로 시작했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 개발자를 1순위로 하였으나,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각치도 못했던 백엔드 과정을 들어야 한다는 소식에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;불안하고 막연하고 무섭기만 했으나,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 밤샘과 구글링 (이 때부터 왜 개발자는 구글이 없으면 살지 못하는가를 뼈저리게 느꼇다. 지금 챗GPT....)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;으로 개발이란 거에 아주 쪼금은 느낌 정도 잡았던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러고 협업 프로젝트를 하면서 잠깐이나마 서비스 출시까지 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;허나 4학년이 되고 어떤 걸 더 해야하는지 몰랐던 저는 졸업프로젝트만 하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;흐지부지 어영부영 공부 조금씩하면서 졸업을 했다.&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;본격적인 취업 스트레스... 허나 금방 탈출했다.&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;취업 스트레스는 4학년 2학기가 되면서 시작되었고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;졸업 전부터 이미 많은 곳에 지원서를 넣고 코테도 보고 면접도 보면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;많은 탈락과 쓴맛을 봐야했으나,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아주 고맙게도 나를 불러주는 곳은 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;채용연계형 인턴으로 6개월 인턴활동 후 정규직 채용을 하는 M사였고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바로 감사합니다. 하면서 &lt;b&gt;바로 가지는 않았다... ;;;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 회사기에 섣불리 판단하고 싶지 않았는데... &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나의 불안한 마음 끝내&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3일 뒤에 다니겠다는 의사를 표했고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그곳에서는 나를 좋게 봐주셨는지 기다리고 있었다면서 바로 ㅇㅋ해주었다.&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;스프링 너를 첨 본 순간 나의 머리는 하얘졌다.&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비록 졸업하기 전까지 Node.js 백엔드 개발자로 희망했으나,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;졸업 전 취업한 곳은 우연치 않게도 코프링이었고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 뒤로 인턴으로 6개월간 일하면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 다들 스프링 스프링 할까?&lt;/b&gt;라는 의문이 조금씩 이해해질 때쯤&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인턴 기간은 끝나게 되었고, 정규직 오퍼를 받았으나,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일하면서 공허한 느낌이 나에게는 너무나 크게 느껴져서였던걸까?!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공허함은 점차 미래의 불안함으로 느껴지고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이대로 가다가는 기초공사가 부실한 건물마냥&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어느 순간에 와르르 무너져 내릴 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스프링에 대한 기초적인 공부 및 객체지향적인 사고 부족, 자바언어와 코틀린 언어의 미숙함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 것들을 극복하면서 6개월간 주어진 업무를 해냈지만,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하면서도 단순 처리만 할 뿐&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정작 내가 왜 그런 생각을 통해 왜 이런 코드를 짰는지에 대한 확신이 많이 없었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(그럴 수 밖에 없었던게 잘 모르고 썼던 게 가장 큰 이유같다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 거절의사를 밝히고 퇴사를 했다.&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6개월 간의 경험&lt;/h2&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;처음에는 QA 담당으로 테스트 코드를 작성하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 기능목록, 기획 회의, DB 설계(ERD) 회의,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 명세서 등 많은 경험할 수 있게 해주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금와서 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채용연계형이고, 사내의 모든 개발자님들과 소통하고 협업하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 일할지 판단하는 걸 모든 분들이 정하기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인턴이라도 프로젝트에 참여하도록 하셨던 것 같다고 추측....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 대표님께서는 가끔 인턴 3인방(저 포함 2명의 한 달 먼저 입사한 동기들)들을&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불러서 많은 인사이트를 얻을 수 있도록 교육을 해주셨는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 개발에 관한 여러 인사이트들을 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 도메인에 대한 이해를 가장 많이 강조하셨었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에는 그렇게 도메인에 대한 이해가 중요할까? 싶었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴사 후 여러 책들을 읽어보면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인&lt;/b&gt;의 중요성을 깨달았다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그 때로 돌아간다면 사내 프로젝트 관련 도메인(feat.복잡한 물류체인 관련) 마스터하고 개발했을거에요.. 죄송합니다..)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇게 퇴사 후&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴사 후 몇 달동안 리프레시도 하고 &amp;lt;- 허허 한것도 없으면서 리프레시...ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 마음을 다잡고 초심으로 돌아가서 공부를 시작했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월간의 나름 현업에서 일하면서 스프링에 대해 공부해보고 싶었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS(ExpressJs)에서 Spring으로 기술스택을 바꾸기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 선택으로 다시 재취업이 늦어지는 걸수도 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 많은 걸 배웠기에 후회하지는 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교 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;오브젝트, 객체지향의 사실과 오해(토끼책), 개구리책, JPA 관련 책, 관련 여러 강의들 등등.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책과 강의 공부만 주먹구구식으로 했던 것 같다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 와서 돌이켜보면&amp;nbsp;&lt;/p&gt;
&lt;p 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;이러한 경험을 통해 나쁜 습관은 없애면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점차 내가 앞으로 개발자로서 성장하기에 좋은 습관만 남겨서 더 빨리 성장하는 계기가 된다고 믿고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로를 살아가면 된다! ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아!! 2023년도에 가장 아쉬웠던 건&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;나의 생각과는 조금은 다른 교육과정...(feat.하지만 생명연장은 가능)&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 시간은 2024년이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2024년도 여전히 혼자서 공부하고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 시간이 지나면서 조금은 나태해짐이 생겼고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;환경을 바꿔야 겠다는 생각이 많이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 초 아무래도 취준하면서 겪는 힘든 부분은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심적인 부분도 있지만, 금전적인 부분도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알바를 하고 싶지는 않고, 나의 미래를 위해 투자하는 시간을 가급적이면 놓고 싶지않아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 방법을 생각하다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F사 주관하는 국비교육을 신청했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 교육을 신청한 가장 큰 이유는 온라인 + 기업의 비즈니스 문제를 함께 해결하는 기회인 FINAL 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것만 바라보고 지원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 쓰고 있는 지금도 다달이 겨우겨우 30만원정도 주는 교육비로 생명을 연명중이다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더불어 해당 국비교육 특성상 개발을 처음하는 분들도 다수였기에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;템포가 달라서 나는 다름 템포로 진행하고자 하는 마음이 계속 컸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파이널까지는 기간이 오래 남았고,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마음은 초조해져갔기에&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;매주 한번 씩 인프런, Hola, 링커리어 등등 들어가면서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 경험을 계속 모색했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;우연치 않게 본 외주 공지&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러다가 문득&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코xx에서 FE 개발자 양성 과정 중에 FINAL 프로젝트를 위해&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드개발자와 디자이너를 뽑는 공지를 보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 프로젝트는 하지 않고 단순히 공부만 했기에...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 이제는 더 이상 뒤로 미루기!! 그만!!! 이라는 마음으로 지원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다도 내 스스로가 포트폴리오에도 넣고 싶은 프로젝트를 해야겠다는 마음도 겹쳤기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 지원하고 면접까지 보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 때는 스프링에 대한 경험이 없는 것이 조금은 흠이 되었으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3학년 때 어렴풋이나마 출시했던 서비스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이제와서 말하지만, 많이 부족했으나 현직 디자이너님의 캐리로 디스콰이엇 실시간 2등까지 올랐었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월간의 인턴 경험을 높이 사셨는지 통과해서 할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공지에서는 엄청 많은 걸 바라는 듯하면서도 아닌 듯해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 몰랐는데 이제와서 돌이켜보면 생각보다 많은 걸 바라지는 않았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 개발만 가능하다면 다 되었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇게 시작된 외주 같지 않은 외주 프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5명의 FE 교육생과 나 포함 2명의 백엔드 마지막으로 디자이너 한 분해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 8명이서 6주간 프로젝트를 기획부터 완료까지 하는 외주 같지 않은 외주프로젝트를 시작하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(나름 소소한 용돈벌이는 되었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획을 모두 다 같이 해야하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 최대한 FE 교육생들이 원하는 걸 들어주고 싶었기에 기획을 맡기고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 점검하는 방향으로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 다른 한분과 같이&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD 구축과 배포 서버를 구축을 우선으로 진행했고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 동안 기획이 fix되는 걸 기다렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 계속 기획이 변경되었기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 기획회의에 참여하고 좀 더 프로젝트를 이해하는데에 집중했다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋았을텐데 라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 개발자에게 중요한 건 해당 도메인에 대한 이해인데...이를 당시엔 조금 망각했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그랬는지 마지막 주에는 이 선택에 대한 아쉬움과&amp;nbsp; FE 분들께 미안함 마음이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3주간의 기획과 3주간의 개발&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 3주만에 겨우겨우 기획은 fix되었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 사이에 나와 다른 백엔드분은 여러 컨벤션과 방향성을 다 잡은 상태였기에 바로 개발에 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 문서화에 대한 중요성을 알기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 개발하기 보다는 추후 테스트 작성도 고려하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 명세서와&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어를 어떻게 하면 좋을지 모르겠으나. API Design?! 세부 명세서?! 기능명세서?라고 해야하나&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 우선시 적고 작업했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 추후 프론트분들에게 상당히 많은 도움이 되었다기에 뿌듯했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 이 글을 쓰고 난 후 진행할 리팩토링 후에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행할 테스트에서도 많은 도움이 될 거라고 확신한다. ㅎㅎ (다행, 기특)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 도메인은 그렇게 복잡하지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 생각하면 여행 계획 커뮤니티라고 할 수 있는 기획이 덮여있는 CRUD 구현?!이라고 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 이 프로젝트뿐만 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서 REST API를 보내는 프로젝트를 진행할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(feat. 예매, 동시성 이슈가 있을 만한 프로젝트 혹은 기능을 혼자서 개발해보고 겪을 필요가 있다고 판단되기에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 API명세서를 보면 40개의 API가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨알 모자이크 허허 혹시 몰라서?! ㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1837&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m4pv0/btsIt6QbOta/KGnUZW9JQfcb6eDRkB44Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m4pv0/btsIt6QbOta/KGnUZW9JQfcb6eDRkB44Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m4pv0/btsIt6QbOta/KGnUZW9JQfcb6eDRkB44Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm4pv0%2FbtsIt6QbOta%2FKGnUZW9JQfcb6eDRkB44Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1837&quot; height=&quot;733&quot; data-origin-width=&quot;1837&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트분들이 백엔드와 같이 협업해본 경험이 없기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 작성했고, 이를 바탕으로 의논하고 설명해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 프론트분들이 이해하는데 많은 도움이 되었다고 한다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬웠던 건 프론트분들이 다들 바쁘셔서 문서 최신화를 조금은 신경쓰지 않았던 점인데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 일했을 때 최신화를 자주 하라고 다들 그러셨는지 알 것 같다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하면서 테이블 구조를 JOIN 테이블로 바꾼 것과&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N+1을 막기 위해 FETCH JOIN을 사용하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 FETCH JOIN 사용 시점을 미리 땡겨서 메서드가 시작할 때 한 번에 조회하도록 하는 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 관련된 부분에 많은 신경을 썼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 확실한 지표나 수치가 없어서 나중에 서비스화된다면 어떻게 될까 라는 의문들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 성능, 부하 테스트를 하는 듯 하다. (아직은 둘을 경험하지 않았기에 일단은 같이 적는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 필요성을 느끼기엔 적합했던 프로젝트였다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 좀더 자세히 설명하면 단순하게 테이블을 select할 때와 join해서 한번에 긁어올때 어떤게 좋을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 이는 직접 지표를 통해 알고 싶었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3주간의 일정도 끝나고 개발은 완료했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 정산까지.. (그 와중에... 10% 세그므...ㅠㅠ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하지만 시작은 지금부터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로젝트가 끝나고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 일정에 쫓겨서 포스트맨으로 밖에 테스트하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 가장 중요한 테스트를 작성하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 구현에만 급급한 나머지 코드들도 많은 걸 놓친게 보였다.&lt;br /&gt;(유지보수 관련 좋지 않은 코드의 냄새가 풀풀 난다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 끝내고 일주일 간&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바/스프링 개발자를 위한 실용주의 프로그래밍&lt;/b&gt; 이라는 책을 다 읽고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3장 테스트는 노션에 요약하지 않았지만, 다른 부분까지는 다시 한번 읽으면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하고 프로젝트에 적용할 만한 걸 간추렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 읽었던 책들의 내용이 저 책에 녹아있는 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 부족했던 무언가를 긁어주는 느낌이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽으면서 너무 좋아서 피식 웃었던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 책을 왜 이제야 읽을까 하면서...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무튼 그래서 지금부터가 시작이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에는 개발하고 끝!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이랬지만, 이 또한 나의 나쁜 습관 중에 하나라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 꼭 회사의 취업을 위해서가 아닌 회사에서 최소 1인분은 하는 개발자가 되기 위해서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 끝내는게 아니란걸 잘 알기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 리팩토링부터 테스트 더 나아가 앞서 필요성을 느꼈던 성능테스트, 부하테스트까지 해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 점차 디벨롭할 예정이며 그 사이에 생기는 여러 일들을 블로그에 이렇게 주저리주저리 적어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞부분을 너무 많이 적느라 정작 중요했던 외주 관련 내용이 많이 안 써진듯 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게나마 있었던 일들을 적었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 긴글 읽어주셔서 감사합니다. ㅎㅎ&lt;/p&gt;</description>
      <category>외주</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/144</guid>
      <comments>https://k9want.tistory.com/entry/%EC%99%B8%EC%A3%BC-%EC%99%B8%EC%A3%BC-%EA%B0%99%EC%A7%80-%EC%95%8A%EC%9D%80-%EC%99%B8%EC%A3%BC-%EC%9E%91%EC%97%85-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%90%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%BD%ED%97%98%ED%95%98%EA%B8%B0-feat-%EA%B7%B8-%EB%8F%99%EC%95%88%EC%9D%98-%EB%82%98%EB%A5%BC-%EB%90%98%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0#entry144comment</comments>
      <pubDate>Wed, 10 Jul 2024 01:28:23 +0900</pubDate>
    </item>
    <item>
      <title>[강의메모] 스프링 DB 2편 -데이터 접근 활용 기술 - ch10. 스프링 트랜잭션 전파 1 - 기본</title>
      <link>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch10-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-1-%EA%B8%B0%EB%B3%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;(복습) 스프링 트랜잭션 전파 - 트랜잭션 (각각) 두 번 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 각각 따로 사용되는 경우 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 트랜잭션이 완전히 끝나고 나서 다음 트랜잭션을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1714217315311&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void double_commit() {
    log.info(&quot;트랜잭션1 시작&quot;);
    TransactionStatus tx1 = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;트랜잭션1 커밋&quot;);
    txManager.commit(tx1);
	
    log.info(&quot;트랜잭션2 시작&quot;);
    TransactionStatus tx2 = txManager.getTransaction(newDefaultTransactionAttribute());
    log.info(&quot;트랜잭션2 커밋&quot;);
    txManager.commit(tx2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDoS4/btsGZWWctbY/sge77wRGjiWuTpd3U0uuRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDoS4/btsGZWWctbY/sge77wRGjiWuTpd3U0uuRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDoS4/btsGZWWctbY/sge77wRGjiWuTpd3U0uuRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDoS4%2FbtsGZWWctbY%2Fsge77wRGjiWuTpd3U0uuRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1143&quot; height=&quot;506&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를&amp;nbsp;보면&amp;nbsp;트랜잭션1과&amp;nbsp;트랜잭션2가&amp;nbsp;같은&amp;nbsp;conn0&amp;nbsp;커넥션을&amp;nbsp;사용중이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 중간에 커넥션 풀 때문에 그런 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션1은 conn0 커넥션을 모두 사용하고 커넥션 풀에 반납까지 완료했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 트랜잭션2가 conn0 를 커넥션&amp;nbsp;풀에서&amp;nbsp;획득한&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 둘은 완전히 다른 커넥션으로 인지하는 것이 맞다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HikariProxyConnection@XXXX wrapping conn0:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히카리 커넥션 풀에서 커넥션을 획득하면 실제 커넥션을 그대로 반환하는 것이 아니라&lt;b&gt; 내부 관리를 위해 히카리 프록시&amp;nbsp;커넥션이라는&amp;nbsp;객체를&amp;nbsp;생성해서&amp;nbsp;반환한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 내부에는 실제 커넥션이 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체의 주소를 확인하면 커넥션&amp;nbsp;풀에서&amp;nbsp;획득한&amp;nbsp;커넥션을&amp;nbsp;구분할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션이 각자 관리된다. (커밋, 롤백)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl7U8N/btsGYTlWnvw/efYxDqdMqL2s854HlR3bK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl7U8N/btsGYTlWnvw/efYxDqdMqL2s854HlR3bK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl7U8N/btsGYTlWnvw/efYxDqdMqL2s854HlR3bK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl7U8N%2FbtsGYTlWnvw%2FefYxDqdMqL2s854HlR3bK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;823&quot; height=&quot;539&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vXmrR/btsGYmWlPzh/R2E8Mz1FNzbXNwmo2GS9C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vXmrR/btsGYmWlPzh/R2E8Mz1FNzbXNwmo2GS9C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vXmrR/btsGYmWlPzh/R2E8Mz1FNzbXNwmo2GS9C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvXmrR%2FbtsGYmWlPzh%2FR2E8Mz1FNzbXNwmo2GS9C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;560&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 각각 수행되기에 사용되는 DB 커넥션도 각각 다르다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이런 경우에는 트랜잭션을 각자 관리하기 때문에 전체 트랜잭션을 묶을 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(지금부터 시작) 스프링 트랜잭션 전파 - 기본옵션 (REQUIRED)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 각각 사용하는 것이 아니라, 트랜잭션이 이미 진행중인데, 여기에 추가로 트랜잭션을 수행하면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 트랜잭션과 별도의 트랜잭션을 진행해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 기존 트랜잭션을 그대로 이어 받아서 트랜잭션을 수행해야&amp;nbsp;할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이런&amp;nbsp;경우&amp;nbsp;어떻게&amp;nbsp;동작할지&amp;nbsp;결정하는&amp;nbsp;것을&amp;nbsp;트랜잭션&amp;nbsp;전파(propagation)라&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(예제) 외부 트랜잭션이 수행중인데, 내부 트랜잭션이 추가로 수행됨&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biDoLW/btsGZzUvWlm/NfWfbEIjdyCqrVpADlVL5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biDoLW/btsGZzUvWlm/NfWfbEIjdyCqrVpADlVL5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biDoLW/btsGZzUvWlm/NfWfbEIjdyCqrVpADlVL5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiDoLW%2FbtsGZzUvWlm%2FNfWfbEIjdyCqrVpADlVL5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;844&quot; height=&quot;178&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 트랜잭션이 수행중이고, 아직 끝나지 않았는데, 내부 트랜잭션이 수행된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부 트랜잭션&lt;/b&gt;이라고 이름 붙인 것은 &lt;b&gt;둘 중 상대적으로 밖&lt;/b&gt;에 있기 때문에 외부 트랜잭션이라 한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;처음 시작된 트랜잭션&lt;/b&gt;&lt;/span&gt;으로 이해하면 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 트랜잭션&lt;/b&gt;은 외&lt;b&gt;부에 트랜잭션이 수행되고 있는 도중에 호출&lt;/b&gt;되기 때문에 마치 내부에 있는 것 처럼 보여서 내부&amp;nbsp;트랜잭션이라&amp;nbsp;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lByCV/btsGZBdHhND/ZFScTnjrb0fp6xjbaOYEP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lByCV/btsGZBdHhND/ZFScTnjrb0fp6xjbaOYEP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lByCV/btsGZBdHhND/ZFScTnjrb0fp6xjbaOYEP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlByCV%2FbtsGZBdHhND%2FZFScTnjrb0fp6xjbaOYEP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;314&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에서 이 경우 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션을 만들어준다.&lt;/li&gt;
&lt;li&gt;내부 트랜잭션이 외부&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;/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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX3LLn/btsGZmVql23/wOMczLammZFgdeAxIAGKWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX3LLn/btsGZmVql23/wOMczLammZFgdeAxIAGKWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX3LLn/btsGZmVql23/wOMczLammZFgdeAxIAGKWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX3LLn%2FbtsGZmVql23%2FwOMczLammZFgdeAxIAGKWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;310&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링은&amp;nbsp;이해를&amp;nbsp;돕기&amp;nbsp;위해&amp;nbsp;논리&amp;nbsp;트랜잭션과&amp;nbsp;물리&amp;nbsp;트랜잭션이라는&amp;nbsp;개념을&amp;nbsp;나눈다.&lt;/li&gt;
&lt;li&gt;논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;물리&amp;nbsp;트랜잭션&lt;/b&gt;은&amp;nbsp;우리가&amp;nbsp;이해하는&amp;nbsp;실제&amp;nbsp;데이터베이스에&amp;nbsp;적용되는&amp;nbsp;트랜잭션을&amp;nbsp;뜻한다.&amp;nbsp;&lt;br /&gt;실제 커넥션을 통해서 트랜잭션을 시작(setAutoCommit(false)) 하고, &lt;b&gt;실제 커넥션을 통해서 커밋, 롤백하는 단위&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;논리&amp;nbsp;트랜잭션&lt;/b&gt;은&amp;nbsp;&lt;b&gt;트랜잭션&amp;nbsp;매니저를&amp;nbsp;통해&amp;nbsp;트랜잭션을&amp;nbsp;사용하는&amp;nbsp;단위&lt;/b&gt;이다. &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;경우에&amp;nbsp;나타난다.&lt;/li&gt;
&lt;li&gt;단순히 &lt;b&gt;트랜잭션이 하나인 경우 둘을 구분하지는 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;왜 논리, 물리 트랜잭션을 나누어 설명하는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;트랜잭션이 사용중일 때 또 다른 트랜잭션이 내부에 사용되면 여러가지 복잡한 상황이 발생한다. 이때 논리 트랜잭션 개념을&amp;nbsp;도입하면&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;단순한&amp;nbsp;원칙을&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모든&amp;nbsp;논리&amp;nbsp;트랜잭션이&amp;nbsp;커밋되어야&amp;nbsp;물리&amp;nbsp;트랜잭션이&amp;nbsp;커밋된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하나의&amp;nbsp;논리&amp;nbsp;트랜잭션이라도&amp;nbsp;롤백되면&amp;nbsp;물리&amp;nbsp;트랜잭션은&amp;nbsp;롤백된다.&lt;/b&gt;&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;모든 트랜잭션 매니저를 커밋해야 물리 트랜잭션이 커밋된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/scaLS/btsG0VvE2Oh/VksytnEackQLtxOAK7iTq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/scaLS/btsG0VvE2Oh/VksytnEackQLtxOAK7iTq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/scaLS/btsG0VvE2Oh/VksytnEackQLtxOAK7iTq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FscaLS%2FbtsG0VvE2Oh%2FVksytnEackQLtxOAK7iTq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;210&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 트랜잭션 매니저라도&amp;nbsp;롤백하면&amp;nbsp;물리&amp;nbsp;트랜잭션은&amp;nbsp;롤백된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doinqf/btsGZVQxncD/y13d6IKUt1KKZEQInNEL80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doinqf/btsGZVQxncD/y13d6IKUt1KKZEQInNEL80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doinqf/btsGZVQxncD/y13d6IKUt1KKZEQInNEL80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdoinqf%2FbtsGZVQxncD%2Fy13d6IKUt1KKZEQInNEL80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;214&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0t82T/btsGYR2FfJh/TVquZMK5JNqnlBwyiUSLDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0t82T/btsGYR2FfJh/TVquZMK5JNqnlBwyiUSLDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0t82T/btsGYR2FfJh/TVquZMK5JNqnlBwyiUSLDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0t82T%2FbtsGYR2FfJh%2FTVquZMK5JNqnlBwyiUSLDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;207&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링 트랜잭션 전파 - 전파 (외부,내부[물리,논리] 트랜잭션이 전부 커밋일 경우)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpql4Q/btsGYTlWKBy/hUZxIlFRb4UZrcwkCty5Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpql4Q/btsGYTlWKBy/hUZxIlFRb4UZrcwkCty5Vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpql4Q/btsGYTlWKBy/hUZxIlFRb4UZrcwkCty5Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdpql4Q%2FbtsGYTlWKBy%2FhUZxIlFRb4UZrcwkCty5Vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;286&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1714219692114&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void inner_commit() {
    log.info(&quot;외부 트랜잭션 시작&quot;);
    TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;outer.isNewTransaction()={}&quot;, outer.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 시작&quot;);
    TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;inner.isNewTransaction()={}&quot;, inner.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 커밋&quot;);
    txManager.commit(inner);
    
    log.info(&quot;외부 트랜잭션 커밋&quot;);
    txManager.commit(outer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 나온 트랜잭션 1,2 코드와 다른 부분은 처음 트랜잭션을 commit하기 전에 다른 트랜잭션이 시작한다는 점이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FQad9/btsG1eu606z/i1Gf3JhAV1lTM264LoPzA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FQad9/btsG1eu606z/i1Gf3JhAV1lTM264LoPzA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FQad9/btsG1eu606z/i1Gf3JhAV1lTM264LoPzA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFQad9%2FbtsG1eu606z%2Fi1Gf3JhAV1lTM264LoPzA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;497&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 트랜잭션을 시작할 때 Participating in existing transaction 이라는 메시지를 확인할 수 있다.&amp;nbsp;이&amp;nbsp;메시지는&amp;nbsp;내부&amp;nbsp;트랜잭션이&amp;nbsp;기존에&amp;nbsp;존재하는&amp;nbsp;외부&amp;nbsp;트랜잭션에&amp;nbsp;참여한다는&amp;nbsp;뜻이다.&lt;/li&gt;
&lt;li&gt;실행 결과를 보면 외부 트랜잭션을 시작하거나 커밋할 때는 DB 커넥션을 통한 물리 트랜잭션을 시작(manual commit )하고, DB 커넥션을 통해 커밋 하는 것을 확인할 수 있다. 그런데 내부 트랜잭션을 시작하거나 커밋할 때는&amp;nbsp;DB&amp;nbsp;커넥션을&amp;nbsp;통해&amp;nbsp;커밋하는&amp;nbsp;로그를&amp;nbsp;전혀&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;없다.&lt;/li&gt;
&lt;li&gt;정리하면&amp;nbsp;외부&amp;nbsp;트랜잭션만&amp;nbsp;물리&amp;nbsp;트랜잭션을&amp;nbsp;시작하고,&amp;nbsp;커밋한다.&lt;/li&gt;
&lt;li&gt;만약 내부 트랜잭션이 실제 물리 트랜잭션을 커밋하면 트랜잭션이 끝나버리기 때문에, 트랜잭션을 처음 시작한 외부&amp;nbsp;트랜잭션까지&amp;nbsp;이어갈&amp;nbsp;수&amp;nbsp;없다.&amp;nbsp;따라서&amp;nbsp;내부&amp;nbsp;트랜잭션은&amp;nbsp;DB&amp;nbsp;커넥션을&amp;nbsp;통한&amp;nbsp;물리&amp;nbsp;트랜잭션을&amp;nbsp;커밋하면&amp;nbsp;안된다.&lt;/li&gt;
&lt;li&gt;스프링은&amp;nbsp;이렇게&amp;nbsp;여러&amp;nbsp;트랜잭션이&amp;nbsp;함께&amp;nbsp;사용되는&amp;nbsp;경우,&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;처음&amp;nbsp;트랜잭션을&amp;nbsp;시작한&amp;nbsp;외부&amp;nbsp;트랜잭션이&amp;nbsp;실제&amp;nbsp;물리&amp;nbsp;트랜&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;잭션을&amp;nbsp;관리하도록&amp;nbsp;한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;이를&amp;nbsp;통해&amp;nbsp;트랜잭션&amp;nbsp;중복&amp;nbsp;커밋&amp;nbsp;문제를&amp;nbsp;해결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션 전파 동작 흐름(이미지)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvyCpC/btsGY2XaKsQ/BAWZas6ocNLFDR5ZuAiB81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvyCpC/btsGY2XaKsQ/BAWZas6ocNLFDR5ZuAiB81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvyCpC/btsGY2XaKsQ/BAWZas6ocNLFDR5ZuAiB81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvyCpC%2FbtsGY2XaKsQ%2FBAWZas6ocNLFDR5ZuAiB81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;407&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;요청 흐름 - 외부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. txManager.getTransaction() 를 호출해서 외부 트랜잭션을 시작한다. &lt;br /&gt;2. 트랜잭션 매니저는 데이터소스를 통해 커넥션을 생성한다. &lt;br /&gt;3. 생성한 커넥션을 수동 커밋 모드( setAutoCommit(false) )로 설정한다. - 물리 트랜잭션 시작 &lt;br /&gt;4. 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다. &lt;br /&gt;5. 트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus 에 담아서 반환하는데, 여기에 신규 트랜잭션의 여부가&lt;br /&gt;담겨 있다. isNewTransaction 를 통해 신규 트랜잭션 여부를 확인할 수 있다. 트랜잭션을 처음 시작했으므로 신규 트랜잭션이다.( true ) &lt;br /&gt;6. 로직1이 사용되고, 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 트랜잭션이 적용된 커넥션을 획득해서&amp;nbsp;사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;요청 흐름 - 내부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. txManager.getTransaction() 를 호출해서 &lt;b&gt;내부 트랜잭션을 시작&lt;/b&gt;한다. &lt;br /&gt;8. 트랜잭션 매니저는 트랜잭션 동기화 매니저를 통해서 &lt;b&gt;기존 트랜잭션이 존재하는지 확인&lt;/b&gt;한다. &lt;br /&gt;9. 기&lt;b&gt;존 트랜잭션이 존재하므로 기존 트랜잭션에 참여한다.&lt;/b&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;/li&gt;
&lt;li&gt;따라서 이미 물리 트랜잭션이 진행중이므로 그냥 두면 이후 로직이 기존에 시작된 트랜잭션을 자연스럽게 사용하게 되는 것이다.&lt;/li&gt;
&lt;li&gt;이후 로직은 자연스럽게 트랜잭션 동기화 매니저에 보관된 기존 커넥션을 사용하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 트랜잭션 매니저는 트&lt;b&gt;랜잭션을 생성한 결과를 TransactionStatus 에 담아서 반환&lt;/b&gt;하는데, 여기엔 &lt;b&gt;isNewTransaction를 통해 신규 트랜잭션 여부를 확인할 수 있다.&lt;/b&gt; 여기서는 기존 트랜잭션에 참여했기때문에 신규 트랜잭션이 아니다. ( false ) &lt;br /&gt;11. 로직2가 사용되고, &lt;b&gt;커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 외부 트랜잭션이 보관한 커넥션을 획득&lt;/b&gt;해서 &lt;b&gt;사용&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTvivY/btsGYO6akLc/GKKter6z0a50crP0qrGsr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTvivY/btsGYO6akLc/GKKter6z0a50crP0qrGsr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTvivY/btsGYO6akLc/GKKter6z0a50crP0qrGsr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTvivY%2FbtsGYO6akLc%2FGKKter6z0a50crP0qrGsr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;402&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;응답 흐름 - 내부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 로직2가 끝나고 트랜잭션 매니저를 통해 &lt;b&gt;내부 트랜잭션을 커밋&lt;/b&gt;한다. &lt;br /&gt;13. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;트랜잭션 매니저는 커밋 시점에 신규 트랜잭션 여부에 따라 다르게 동작한다.&lt;/b&gt; &lt;/span&gt;&lt;b&gt;이 경우 신규 트랜잭션이 아니기 때문에 실제 커밋을 호출하지 않는다.&lt;/b&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;왜냐하면&lt;/b&gt; &lt;b&gt;실제 커넥션에 커밋이나 롤백을 호출하면 물리 트랜잭션이 끝나버린다. 아직 트랜잭션이 끝난 것이 아니기 때문에 실제 커밋을 호출하면 안된다. 물리 트랜잭션은 외부 트랜잭션을 종료할 때 까지 이어져야한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;응답 흐름 - 외부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. 로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋한다. &lt;br /&gt;15. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;트랜잭션 매니저는 커밋 시점에 신규 트랜잭션 여부에 따라 다르게 동작한다.&lt;/b&gt;&lt;/span&gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;외부 트랜잭션은 신규 트랜잭션이다.&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;따라서&amp;nbsp;DB&amp;nbsp;커넥션에&amp;nbsp;실제&amp;nbsp;커밋을&amp;nbsp;호출한다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;16. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;트랜잭션 매니저에 커밋하는 것이 논리적인 커밋이라면, 실제 커넥션에 커밋하는 것을 물리 커밋이라 할 수&lt;/b&gt; &lt;b&gt;있다.&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;실제&amp;nbsp;데이터베이스에&amp;nbsp;커밋이&amp;nbsp;반영되고,&amp;nbsp;물리&amp;nbsp;트랜잭션도&amp;nbsp;끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 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;핵심은 트랜잭션 매니저에 커밋을 호출한다고해서 항상 실제 커넥션에 물리 커밋이 발생하지는 않는다는&amp;nbsp;점이다.&lt;/li&gt;
&lt;li&gt;신규 트랜잭션인 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다. 신규 트랜잭션이 아니면 실제물리 커넥션을 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 내부에서 추가로 사용되면, 트랜잭션 매니저를 통해 논리 트랜잭션을 관리하고, 모든 논리 트랜잭션이&amp;nbsp;커밋되면&amp;nbsp;물리&amp;nbsp;트랜잭션이&amp;nbsp;커밋된다고&amp;nbsp;이해하면&amp;nbsp;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링 트랜잭션 전파 - 외부 롤백(내부 트랜잭션은 커밋되는데, 외부 트랜잭션이 롤백되는 경우)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgxFnU/btsGZwRcftG/nxpsJLIhTBxZUUU82NrkhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgxFnU/btsGZwRcftG/nxpsJLIhTBxZUUU82NrkhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgxFnU/btsGZwRcftG/nxpsJLIhTBxZUUU82NrkhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgxFnU%2FbtsGZwRcftG%2FnxpsJLIhTBxZUUU82NrkhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;758&quot; height=&quot;272&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리&amp;nbsp;트랜잭션이&amp;nbsp;하나라도&amp;nbsp;롤백되면&amp;nbsp;전체&amp;nbsp;물리&amp;nbsp;트랜잭션은&amp;nbsp;롤백된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&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;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714227732205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void outer_rollback() {
    log.info(&quot;외부 트랜잭션 시작&quot;);
    TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;outer.isNewTransaction()={}&quot;, outer.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 시작&quot;);
    TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;inner.isNewTransaction()={}&quot;, inner.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 커밋&quot;);
    txManager.commit(inner);
    
    log.info(&quot;외부 트랜잭션 롤백&quot;);
    txManager.rollback(outer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2oUn1/btsG0GyCo81/KYXlKmN0xh97GDeauC9ZN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2oUn1/btsG0GyCo81/KYXlKmN0xh97GDeauC9ZN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2oUn1/btsG0GyCo81/KYXlKmN0xh97GDeauC9ZN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2oUn1%2FbtsG0GyCo81%2FKYXlKmN0xh97GDeauC9ZN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;396&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 트랜잭션이 물리 트랜잭션을 시작하고 롤백하는 것을 확인할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;내부 트랜잭션은 앞서 배운대로 직접 물리 트랜잭션에 관여하지 않는다.&lt;/li&gt;
&lt;li&gt;결과적으로 외부 트랜잭션에서 시작한 물리 트랜잭션의 범위가 내부 트랜잭션까지 사용된다. 이후 외부 트랜잭션이&amp;nbsp;롤백되면서&amp;nbsp;전체&amp;nbsp;내용은&amp;nbsp;모두&amp;nbsp;롤백된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스프링 트랜잭션 전파 - 외부 롤백 흐름 (이미지)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mXfto/btsGY1EbjcE/hXk68LZkUCziV3oV6GHkw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mXfto/btsGY1EbjcE/hXk68LZkUCziV3oV6GHkw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mXfto/btsGY1EbjcE/hXk68LZkUCziV3oV6GHkw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmXfto%2FbtsGY1EbjcE%2FhXk68LZkUCziV3oV6GHkw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;403&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(중요) 스프링 트랜잭션 전파 - 내부 롤백(내부 트랜잭션은 롤백인데, 외부 트랜잭션은 커밋되는 경우) UnexpectedRollbackException 발생&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUgQNf/btsGZvrdbdx/O3W5bf11x3s7eDKhV4fIEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUgQNf/btsGZvrdbdx/O3W5bf11x3s7eDKhV4fIEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUgQNf/btsGZvrdbdx/O3W5bf11x3s7eDKhV4fIEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUgQNf%2FbtsGZvrdbdx%2FO3W5bf11x3s7eDKhV4fIEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;275&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1714228281735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void inner_rollback() {
    log.info(&quot;외부 트랜잭션 시작&quot;);
    TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;outer.isNewTransaction()={}&quot;, outer.isNewTransaction());

    log.info(&quot;내부 트랜잭션 시작&quot;);
    TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;inner.isNewTransaction()={}&quot;, inner.isNewTransaction());

    log.info(&quot;내부 트랜잭션 롤백&quot;);
    txManager.rollback(inner);

    log.info(&quot;외부 트랜잭션 커밋 - 예외 발생&quot;);
    Assertions.assertThatThrownBy(() -&amp;gt; txManager.commit(outer))
         .isInstanceOf(UnexpectedRollbackException.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 외부 트랜잭션을 커밋할 때 UnexpectedRollbackException.class 이 발생하는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tpyB0/btsGYnufd0g/NHyBik52GNE0w7Lrad0hK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tpyB0/btsGYnufd0g/NHyBik52GNE0w7Lrad0hK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tpyB0/btsGYnufd0g/NHyBik52GNE0w7Lrad0hK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtpyB0%2FbtsGYnufd0g%2FNHyBik52GNE0w7Lrad0hK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;475&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;내부&amp;nbsp;트랜잭션&amp;nbsp;시작&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;Participating in existing transaction&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기존&amp;nbsp;트랜잭션에&amp;nbsp;참여한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;내부&amp;nbsp;트랜잭션&amp;nbsp;롤백&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Participating transaction failed - marking existing transaction as&amp;nbsp;rollback-only&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;내부 트랜잭션을 롤백하면 실제 물리 트랜잭션은 롤백하지 않는다. 대신에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기존 트랜잭션을 롤백 전용으로&amp;nbsp;표시&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;외부 트랜잭션 커밋&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Global&amp;nbsp;transaction&amp;nbsp;is&amp;nbsp;marked&amp;nbsp;as&amp;nbsp;rollback-only&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;&lt;b&gt;커밋을 호출했지만, 전체 트랜잭션이 &lt;span style=&quot;color: #ee2323;&quot;&gt;롤백 전용으로 표시되어 있다. 따라서 물리 트랜잭션을 롤백&lt;/span&gt;한다. 그러고는 &lt;span style=&quot;color: #ee2323;&quot;&gt;커밋했지만 예기치 못한 롤백이 일어났다는 예외를 터트린다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스프링 트랜잭션 전파 - 내부 롤백 (이미지)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQSNzi/btsGY5M92qw/QNKGs5c09S1U0sItyW25OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQSNzi/btsGY5M92qw/QNKGs5c09S1U0sItyW25OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQSNzi/btsGY5M92qw/QNKGs5c09S1U0sItyW25OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQSNzi%2FbtsGY5M92qw%2FQNKGs5c09S1U0sItyW25OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;406&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;응답 흐름 - 내부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백한다. (로직2에 문제가 있어서 롤백한다고 가&amp;nbsp; &lt;br /&gt;정한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 트랜잭션 매니저는 롤백 시점에 신규 트랜잭션 여부에 따라 다르게 동작한다. 이 경우 &lt;b&gt;신규 트랜잭션이 아니기 때문에 실제 롤백을 호출하지 않는다.&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;3. 내부 트랜잭션은 물리 트랜잭션을 롤백하지 않는 대신에 트랜잭션 동기화 매니저에rollbackOnly=true 라는 표시를 해둔다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;응답 흐름 - 외부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 트랜잭션 매니저는 커밋 시점에 신규 트랜잭션 여부에 따라 다르게 동작한다. 외부 트랜잭션은 신규 트랜잭션이다. 따라서 DB 커넥션에 실제 커밋을 호출해야 한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이때 먼저 트랜잭션 동기화 매니저에 롤백 전용&amp;nbsp;( rollbackOnly=true ) 표시가 있는지 확인한다. 롤백 전용 표시가 있으면 물리 트랜잭션을 커밋하는&amp;nbsp;것이&amp;nbsp;아니라&amp;nbsp;롤백한다.&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;6. 실제 데이터베이스에 롤백이 반영되고, 물리 트랜잭션도 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;7. 트랜잭션 매니저에 커밋을 호출한 개발자 입장에서는 분명 커밋을 기대했는데 롤백 전용 표시로 인해 실제로는 롤백이 되어버렸다. ( 스프링은 이 경우 UnexpectedRollbackException 런타임 예외를 던진다. )&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은&amp;nbsp;조용히&amp;nbsp;넘어갈&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;문제가&amp;nbsp;아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 입장에서는 커밋을 호출했지만 롤백이 되었다는 것은 분명하게&amp;nbsp;알려주어야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 고객은 주문이 성공했다고 생각했는데, 실제로는 롤백이 되어서 주문이 생성되지 않은 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은&amp;nbsp;이&amp;nbsp;경우&amp;nbsp;UnexpectedRollbackException&amp;nbsp;런타임&amp;nbsp;예외를&amp;nbsp;던진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 커밋을 시도했지&amp;nbsp;만,&amp;nbsp;기대하지&amp;nbsp;않은&amp;nbsp;롤백이&amp;nbsp;발생했다는&amp;nbsp;것을&amp;nbsp;명확하게&amp;nbsp;알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인&lt;/b&gt;한다. 롤&lt;b&gt;백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백하고,&amp;nbsp;UnexpectedRollbackException&amp;nbsp;예외를&amp;nbsp;던진다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링 트랜잭션 전파 - REQUIRES_NEW (외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 사용)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 각각 별도의 물리 트랜잭션을 사용하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 커밋과 롤백도&amp;nbsp;각각&amp;nbsp;별도로&amp;nbsp;이루어지게&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 내부 트랜잭션에 문제가 발생해서 롤백해도, 외부 트랜잭션에는 영향을 주지 않는다. 반대로 외부 트랜잭션에&amp;nbsp;문제가 발생해도 내부 트랜잭션에 영향을 주지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTREq/btsGYPDNSRg/oTnquvi4rpn2ztGyOqNmCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTREq/btsGYPDNSRg/oTnquvi4rpn2ztGyOqNmCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTREq/btsGYPDNSRg/oTnquvi4rpn2ztGyOqNmCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTREq%2FbtsGYPDNSRg%2FoTnquvi4rpn2ztGyOqNmCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;362&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 물리 트랜잭션을 분리하려면 내부 트랜잭션을 시작할 때 REQUIRES_NEW 옵션을 사용하면 된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;외부 트랜잭션과 내부 트랜잭션이 각각 별도의 물리 트랜잭션을 가진다.&lt;/li&gt;
&lt;li&gt;별도의 물리 트랜잭션을 가진다는 뜻은 DB 커넥션을 따로 사용한다는 뜻이다.&lt;/li&gt;
&lt;li&gt;이 경우 내부 트랜잭션이 롤백되면서 로직 2가 롤백되어도 로직 1에서 저장한 데이터에는 영향을 주지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;최종적으로&amp;nbsp;로직2는&amp;nbsp;롤백되고,&amp;nbsp;로직1은&amp;nbsp;커밋된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1714229961951&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void inner_rollback_requires_new() {
    log.info(&quot;외부 트랜잭션 시작&quot;);
    TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    log.info(&quot;outer.isNewTransaction()={}&quot;, outer.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 시작&quot;);
    DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
    definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    TransactionStatus inner = txManager.getTransaction(definition);
    log.info(&quot;inner.isNewTransaction()={}&quot;, inner.isNewTransaction());
    
    log.info(&quot;내부 트랜잭션 롤백&quot;);
    txManager.rollback(inner); // 롤백
    
    log.info(&quot;외부 트랜잭션 커밋&quot;);
    txManager.commit(outer); // 커밋
}&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;내부 트랜잭션을 시작할 때 전파 옵션인 propagationBehavior 에 PROPAGATION_REQUIRES_NEW 옵션을 주었다.&lt;/li&gt;
&lt;li&gt;이 전파 옵션을 사용하면 내부 트랜잭션을 시작할 때 기존 트랜잭션에 참여하는 것이 아니라 새로운 물리 트랜잭션을&amp;nbsp;만들어서&amp;nbsp;시작하게&amp;nbsp;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUdPhL/btsGZyuxdbv/kqPLkMpwOgajpUCUCBsuZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUdPhL/btsGZyuxdbv/kqPLkMpwOgajpUCUCBsuZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUdPhL/btsGZyuxdbv/kqPLkMpwOgajpUCUCBsuZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUdPhL%2FbtsGZyuxdbv%2FkqPLkMpwOgajpUCUCBsuZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1274&quot; height=&quot;535&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;스프링 트랜잭션 전파 - REQUIRES_NEW(이미지)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVvYC/btsG1fnfD4y/A3IdASBnqpoCCRAIwmt8Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVvYC/btsG1fnfD4y/A3IdASBnqpoCCRAIwmt8Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVvYC/btsG1fnfD4y/A3IdASBnqpoCCRAIwmt8Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzVvYC%2FbtsG1fnfD4y%2FA3IdASBnqpoCCRAIwmt8Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;422&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;요청 흐름 - 외부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. txManager.getTransaction() 를 호출해서 외부 트랜잭션을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 트랜잭션 매니저는 데이터소스를 통해 커넥션을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 생성한 커넥션을 수동 커밋 모드( setAutoCommit(false) )로 설정한다. - &lt;b&gt;물리 트랜잭션 시작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus 에 담아서 반환하는데, 여기에 신규&amp;nbsp;트랜잭션의 여부가 담겨 있다. isNewTransaction 를 통해 신규 트랜잭션 여부를 확인할 수 있다. 트랜잭션을 처음 시작했으므로 신규 트랜잭션이다.( true )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 로직1이 사용되고, 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 트랜잭션이 적용된 커넥션을 획득&amp;nbsp; &lt;br /&gt;해서&amp;nbsp;사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;요청&amp;nbsp;흐름&amp;nbsp;-&amp;nbsp;내부&amp;nbsp;트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. REQUIRES_NEW 옵션과 함께 txManager.getTransaction() 를 호출해서 내부 트랜잭션을 시작한다. 트랜잭션 매니저는 REQUIRES_NEW 옵션을 확인하고, 기존 트랜잭션에 참여하는 것이 아니라 새로운 트랜잭션을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 트랜잭션 매니저는 데이터소스를 통해 커넥션을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 생성한 커넥션을 수동 커밋 모드( setAutoCommit(false) )로 설정한다. - &lt;b&gt;물리 트랜잭션 시작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10.&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다.&lt;/b&gt; &lt;b&gt;이때 con1 은 잠시 보류되고, 지금부터는 con2 가 사용된다. (내부 트랜잭션을 완료할 때 까지 con2가 사용된다.) (보류되고 내부 트랜잭션으로 흐름이 넘어간다는게 중요!)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 트랜잭션 매니저는 신규 트랜잭션의 생성한 결과를 반환한다. isNewTransaction == true&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 로직2가 사용되고, 커넥션이 필요한 경우 트랜잭션 동기화 매니저에 있는 con2 커넥션을 획득해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPcLhq/btsGZ0YyVwK/tH37XAyt90oN9Kan9xt7P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPcLhq/btsGZ0YyVwK/tH37XAyt90oN9Kan9xt7P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPcLhq/btsGZ0YyVwK/tH37XAyt90oN9Kan9xt7P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPcLhq%2FbtsGZ0YyVwK%2FtH37XAyt90oN9Kan9xt7P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;415&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;응답 흐름 - 내부 트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백한다. (로직2에 문제가 있어서 롤백한다고 가정한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 트랜잭션 매니저는 롤백 시점에 신규 트랜잭션 여부에 따라 다르게 동작한다. 현&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;재 내부 트랜잭션은 신규 트랜잭션이다. 따라서 실제 롤백을 호출한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;내부 트랜잭션이 con2 물리 트랜잭션을 롤백한다. 트랜잭션이 종료되고, con2 는 종료되거나, 커넥션 풀에 반납된다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이후에 con1 의 보류가 끝나고, 다시 con1 을 사용한다. (보류가 끝난다는 부분이 중요!)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;응답&amp;nbsp;흐름&amp;nbsp;-&amp;nbsp;외부&amp;nbsp;트랜잭션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 외부 트랜잭션에 커밋을 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 외부 트랜잭션은 신규 트랜잭션이기 때문에 물리 트랜잭션을 커밋한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이때 rollbackOnly 설정을 체크한다. rollbackOnly 설정이 없으므로 커밋한다. (다른 물리 트랜잭션이기에)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 본인이 만든 con1 커넥션을 통해 물리 트랜잭션을 커밋한다. 트랜잭션이&amp;nbsp;종료되고,&amp;nbsp;con1&amp;nbsp;은&amp;nbsp;종료되거나,&amp;nbsp;커넥션&amp;nbsp;풀에&amp;nbsp;반납된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRES_NEW 옵션을 사용하면 물리 트랜잭션이 명확하게 분리된다.&lt;/li&gt;
&lt;li&gt;REQUIRES_NEW&amp;nbsp;를&amp;nbsp;사용하면&amp;nbsp;데이터베이스&amp;nbsp;커넥션이&amp;nbsp;동시에&amp;nbsp;2개&amp;nbsp;사용된다는&amp;nbsp;점을&amp;nbsp;주의해야&amp;nbsp;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;REQUIRED vs REQUIRES_NEW&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;REQUIRED&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;많이&amp;nbsp;사용하는&amp;nbsp;기본&amp;nbsp;설정이다.&amp;nbsp;기존&amp;nbsp;트랜잭션이&amp;nbsp;없으면&amp;nbsp;생성하고,&amp;nbsp;있으면&amp;nbsp;참여한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랜잭션이&amp;nbsp;필수&lt;/b&gt;라는&amp;nbsp;의미로&amp;nbsp;이해하면&amp;nbsp;된다.&amp;nbsp;(&lt;b&gt;필수이기&amp;nbsp;때문에&amp;nbsp;없으면&amp;nbsp;만들고,&amp;nbsp;있으면&amp;nbsp;참여한다&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;기존&amp;nbsp;트랜잭션&amp;nbsp;없음:&amp;nbsp;새로운&amp;nbsp;트랜잭션을&amp;nbsp;생성한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기존&amp;nbsp;트랜잭션&amp;nbsp;있음:&amp;nbsp;기존&amp;nbsp;트랜잭션에&amp;nbsp;참여한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;REQUIRES_NEW&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;항상&amp;nbsp;새로운&amp;nbsp;트랜잭션을&amp;nbsp;생성한다. &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;기존&amp;nbsp;트랜잭션&amp;nbsp;없음:&amp;nbsp;새로운&amp;nbsp;트랜잭션을&amp;nbsp;생성한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기존&amp;nbsp;트랜잭션&amp;nbsp;있음:&amp;nbsp;새로운&amp;nbsp;트랜잭션을&amp;nbsp;생성한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>강의메모</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/142</guid>
      <comments>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch10-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-1-%EA%B8%B0%EB%B3%B8#entry142comment</comments>
      <pubDate>Sat, 27 Apr 2024 21:13:58 +0900</pubDate>
    </item>
    <item>
      <title>[강의메모] 스프링 DB 2편 -데이터 접근 활용 기술 - ch9. 스프링 트랜잭션 이해(feat. 트랜잭션 AOP 사용시 주의 사항)</title>
      <link>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch9-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4feat-%EC%A4%91%EC%9A%94%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-AOP-%EC%A3%BC%EC%9D%98-%EC%82%AC%ED%95%AD</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션&amp;nbsp;적용&amp;nbsp;확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 트랜잭션 관련 코드가 눈에 보이지 않고, AOP를 기반으로 동작하기 때문에, 실제 트랜잭션이 적용되고&amp;nbsp;있는지&amp;nbsp;아닌지를&amp;nbsp;확인하기가&amp;nbsp;어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인하는 방법을 알아보자&lt;/p&gt;
&lt;pre id=&quot;code_1714152359658&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@SpringBootTest
public class TxBasicTest {

    @Autowired
    BasicService basicService;

    @Test
    void proxyCheck() {
        //BasicService$$EnhancerBySpringCGLIB...
        log.info(&quot;aop class={}&quot;, basicService.getClass());
        assertThat(AopUtils.isAopProxy(basicService)).isTrue();
    }

    @Test
    void txTest() {
        basicService.tx();
        basicService.nonTx();
    }

    @TestConfiguration
    static class TxApplyBasicConfig {

        @Bean
        BasicService basicService() {
            return new BasicService();
        }
    }

    @Slf4j
    static class BasicService {

        @Transactional
        public void tx() {
            log.info(&quot;call tx&quot;);
            boolean txActive =
                TransactionSynchronizationManager.isActualTransactionActive();
            log.info(&quot;tx active={}&quot;, txActive);
        }

        public void nonTx() {
            log.info(&quot;call nonTx&quot;);
            boolean txActive =
                TransactionSynchronizationManager.isActualTransactionActive();
            log.info(&quot;tx active={}&quot;, txActive);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;proxyCheck()&amp;nbsp;-&amp;nbsp;실행&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;AopUtils.isAopProxy()&lt;/b&gt;&amp;nbsp;:&amp;nbsp;선언적&amp;nbsp;트랜잭션&amp;nbsp;방식에서&amp;nbsp;스프링&amp;nbsp;트랜잭션은&amp;nbsp;AOP를&amp;nbsp;기반으로&amp;nbsp;동작한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Transactional&lt;/b&gt;을 메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이 되고,&lt;b&gt; 결과적으로&amp;nbsp;실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다.&lt;/b&gt; 그리고 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;주입을 받을 때도 실제 객체&amp;nbsp;대신에&amp;nbsp;프록시&amp;nbsp;객체가&amp;nbsp;주입된다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클래스&amp;nbsp;이름을&amp;nbsp;출력해보면&amp;nbsp;basicService$$EnhancerBySpringCGLIB...&amp;nbsp;라고&amp;nbsp;프록시&amp;nbsp;클래스의&amp;nbsp;이름이&amp;nbsp; &lt;br /&gt;출력되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너에 트랜잭션 프록시 등록&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W7CJI/btsGY8o9T6d/8crFOg2KOs0Z4DwzPml8R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W7CJI/btsGY8o9T6d/8crFOg2KOs0Z4DwzPml8R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W7CJI/btsGY8o9T6d/8crFOg2KOs0Z4DwzPml8R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW7CJI%2FbtsGY8o9T6d%2F8crFOg2KOs0Z4DwzPml8R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;837&quot; height=&quot;380&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;@Transactional&amp;nbsp;애노테이션이&amp;nbsp;특정&amp;nbsp;클래스나&amp;nbsp;메서드에&amp;nbsp;하나라도&amp;nbsp;있으면&lt;/b&gt;&amp;nbsp;트랜잭션&amp;nbsp;AOP는&amp;nbsp;프록시를&amp;nbsp;만들어서&amp;nbsp;스프링&amp;nbsp;컨테이너에&amp;nbsp;&lt;b&gt;등록&lt;/b&gt;한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 basicService &lt;b&gt;객체 대신에 프록시&lt;/b&gt;인 basicService$$CGLIB 를 스프링 빈에 &lt;b&gt;등록&lt;/b&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;프록시는 내부에 실제&lt;/b&gt; basicService 를 &lt;b&gt;참조&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;핵심은&amp;nbsp;실제&amp;nbsp;객체&amp;nbsp;대신에&amp;nbsp;프록시가&amp;nbsp;스프링&amp;nbsp;컨테이너에&amp;nbsp;등록&lt;/b&gt;되었다는&amp;nbsp;점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;프록시는&amp;nbsp;BasicService&amp;nbsp;를&amp;nbsp;상속해서&amp;nbsp;만들어지기&amp;nbsp;때문에&amp;nbsp;다형성을&amp;nbsp;활용할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그 추가&lt;/h3&gt;
&lt;pre id=&quot;code_1714152918927&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.level.org.springframework.transaction.interceptor=TRACE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;로그를&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TransactionSynchronizationManager.isActualTransactionActive()&lt;/h3&gt;
&lt;pre id=&quot;code_1714153050533&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public void tx() {
   log.info(&quot;call tx&quot;);
    boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
  log.info(&quot;tx active={}&quot;, txActive);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재&amp;nbsp;쓰레드에&amp;nbsp;트랜잭션이&amp;nbsp;적용되어&amp;nbsp;있는지&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 true 면 트랜잭션이 적용되어 있는것이다.&amp;nbsp;트랜잭션의&amp;nbsp;적용&amp;nbsp;여부를&amp;nbsp;가장&amp;nbsp;확실하게&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 적용 위치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서&amp;nbsp;&lt;b&gt;우선순위는&amp;nbsp;항상&amp;nbsp;더&amp;nbsp;구체적이고&amp;nbsp;자세한&amp;nbsp;것이&amp;nbsp;높은&amp;nbsp;우선순위를&amp;nbsp;가진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메서드 &amp;gt; 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터페이스를 구현한 클래스 &amp;gt; 인터페이스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의&amp;nbsp;@Transactional&amp;nbsp;은&amp;nbsp;다음&amp;nbsp;두&amp;nbsp;가지&amp;nbsp;규칙이&amp;nbsp;있다. &lt;br /&gt;1.&amp;nbsp;&amp;nbsp;&amp;nbsp;우선순위 규칙 (클래스보다 메서드가 더 구체적이므로 우선순위)&lt;br /&gt;2.&amp;nbsp;&amp;nbsp;&amp;nbsp;클래스에 적용하면 메서드는 자동 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TransactionSynchronizationManager.isCurrentTransactionReadOnly&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재&amp;nbsp;트랜잭션에&amp;nbsp;적용된&amp;nbsp;readOnly&amp;nbsp;옵션의&amp;nbsp;값을&amp;nbsp;반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;readOnly=false&amp;nbsp;는&amp;nbsp;기본&amp;nbsp;옵션이기&amp;nbsp;때문에&amp;nbsp;보통&amp;nbsp;생략한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;@Transactional&amp;nbsp;==&amp;nbsp;@Transactional(readOnly=false)&amp;nbsp;와&amp;nbsp;같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스에&amp;nbsp;@Transactional&amp;nbsp;적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&amp;nbsp;&amp;nbsp;클래스의&amp;nbsp;메서드&amp;nbsp;(우선순위가&amp;nbsp;가장&amp;nbsp;높다.) &lt;br /&gt;2.&amp;nbsp;&amp;nbsp;&amp;nbsp;클래스의&amp;nbsp;타입 &lt;br /&gt;3.&amp;nbsp;&amp;nbsp;&amp;nbsp;인터페이스의&amp;nbsp;메서드 &lt;br /&gt;4.&amp;nbsp;&amp;nbsp;&amp;nbsp;인터페이스의&amp;nbsp;타입&amp;nbsp;(우선순위가&amp;nbsp;가장&amp;nbsp;낮다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&amp;nbsp;인터페이스에&amp;nbsp;@Transactional&amp;nbsp;사용하는&amp;nbsp;것은&amp;nbsp;스프링&amp;nbsp;공식&amp;nbsp;메뉴얼에서&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;권장하지&amp;nbsp;않는&amp;nbsp;방법&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP를 적&amp;nbsp;용하는&amp;nbsp;방식에&amp;nbsp;따라서&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;인터페이스에&amp;nbsp;애노테이션을&amp;nbsp;두면&amp;nbsp;AOP가&amp;nbsp;적용이&amp;nbsp;되지&amp;nbsp;않는&amp;nbsp;경우도&amp;nbsp;있기&amp;nbsp;때문&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;가급적 구체 클래스에 @Transactional 을 사용하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(매우 중요)트랜잭션 AOP 주의 사항 - 프록시 내부 호출&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;앞서 언급했듯이 @Transactional 을 적용하면 프록시 객체가 요청을 먼저 받아서 트랜잭션을 처리하고, 실제 객체를 호출해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서&amp;nbsp;트랜잭션을&amp;nbsp;적용하려면&amp;nbsp;항상&amp;nbsp;프록시를&amp;nbsp;통해서&amp;nbsp;대상&amp;nbsp;객체(Target)을&amp;nbsp;호출해야&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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;AOP가&amp;nbsp;적용되지&amp;nbsp;않고,&amp;nbsp;트랜잭션도&amp;nbsp;적용되지&amp;nbsp;않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접&amp;nbsp;호출하는&amp;nbsp;문제가&amp;nbsp;발생한다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1714153639074&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
static class CallService {

    public void external() {
        log.info(&quot;call external&quot;);
        printTxInfo();
        internal();
    }

    @Transactional
    public void internal() {
        log.info(&quot;call internal&quot;);
        printTxInfo();
    }

    private void printTxInfo() {
        boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
        log.info(&quot;tx active={}&quot;, txActive);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CallService&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;external() 은 트랜잭션이 없다.&lt;/li&gt;
&lt;li&gt;internal()&amp;nbsp;은&amp;nbsp;@Transactional&amp;nbsp;을&amp;nbsp;통해&amp;nbsp;트랜잭션을&amp;nbsp;적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 이 하나라도 있으면 트랜잭션 프록시 객체가 만들어진다. 그리고 callService 빈을 주입 받으면&amp;nbsp;트랜잭션&amp;nbsp;프록시&amp;nbsp;객체가&amp;nbsp;대신&amp;nbsp;주입된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;callService.internal(); (문제 없음)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pFLm3/btsGY9PctrT/qnl250XYNoH6I2kfmZkcF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pFLm3/btsGY9PctrT/qnl250XYNoH6I2kfmZkcF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pFLm3/btsGY9PctrT/qnl250XYNoH6I2kfmZkcF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpFLm3%2FbtsGY9PctrT%2Fqnl250XYNoH6I2kfmZkcF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;831&quot; height=&quot;280&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;b&gt;callService.external(); (이게 문제임)&lt;/b&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;external()&amp;nbsp;은&amp;nbsp;@Transactional&amp;nbsp;애노테이션이&amp;nbsp;없다.&amp;nbsp;따라서&amp;nbsp;트랜잭션&amp;nbsp;없이&amp;nbsp;시작한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 내부에서&amp;nbsp;@Transactional&amp;nbsp;이&amp;nbsp;있는&amp;nbsp;internal()&amp;nbsp;을&amp;nbsp;호출하는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbcEbF/btsGX1ki9o1/H1fw71ijC1xc9ZyInANeK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbcEbF/btsGX1ki9o1/H1fw71ijC1xc9ZyInANeK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbcEbF/btsGX1ki9o1/H1fw71ijC1xc9ZyInANeK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbcEbF%2FbtsGX1ki9o1%2FH1fw71ijC1xc9ZyInANeK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;815&quot; height=&quot;283&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;external() 메서든 @Transactional이 없기에 트랜잭션 프록시는 트랜잭션을 적용하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 callService 객체의 인스턴스의 external()을 호출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 external() 은 내부에서 internal() 메서드를 호출한다. &amp;lt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;여기서 문제가 발생한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;문제 원인&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바&amp;nbsp;언어에서&lt;b&gt;&amp;nbsp;메서드&amp;nbsp;앞에&amp;nbsp;별도의&amp;nbsp;참조가&amp;nbsp;없으면&amp;nbsp;this&amp;nbsp;라는&amp;nbsp;뜻으로&amp;nbsp;자기&amp;nbsp;자신의&amp;nbsp;인스턴스를&amp;nbsp;가리킨다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과적으로&amp;nbsp;자기&amp;nbsp;자신의&amp;nbsp;내부&amp;nbsp;메서드를&amp;nbsp;호출하는&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;b&gt;this.internal()&amp;nbsp;&lt;/b&gt;&lt;/span&gt;이&amp;nbsp;되는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; this 는 자기 자신을 가리키므로,&amp;nbsp;실제&amp;nbsp;대상&amp;nbsp;객체(&amp;nbsp;target&amp;nbsp;)의&amp;nbsp;인스턴스&lt;/b&gt;&lt;/span&gt;를&amp;nbsp;뜻한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과적으로&amp;nbsp;이러한&amp;nbsp;내부&amp;nbsp;호출은&amp;nbsp;프록시를&amp;nbsp;거치지&amp;nbsp;않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 트랜잭션을&amp;nbsp;적용할&amp;nbsp;수&amp;nbsp;없다.&amp;nbsp;결과적으로&amp;nbsp;target&amp;nbsp;에&amp;nbsp;있는&amp;nbsp;internal()&amp;nbsp;을&amp;nbsp;직접&amp;nbsp;호출하게&amp;nbsp;된&amp;nbsp;것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;프록시 방식의 AOP 한계&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;@Transactional&amp;nbsp;를&amp;nbsp;사용하는&amp;nbsp;트랜잭션&amp;nbsp;AOP는&amp;nbsp;프록시를&amp;nbsp;사용한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;프록시를 사용하면 메서드 내부 호출에 프록시를&amp;nbsp;적용할&amp;nbsp;수&amp;nbsp;없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;해결 방법 - 단순한 방법 : 별도의 클래스로 분리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해결할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;단순한&amp;nbsp;방법은&amp;nbsp;내부&amp;nbsp;호출을&amp;nbsp;피하기&amp;nbsp;위해&amp;nbsp;internal()&amp;nbsp;메서드를&amp;nbsp;&lt;b&gt;별도의&amp;nbsp;클래스로&amp;nbsp;분리&lt;/b&gt;하는&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TZfNW/btsGYguTiWF/420FrQKFa6WP1lruNf7nbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TZfNW/btsGYguTiWF/420FrQKFa6WP1lruNf7nbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TZfNW/btsGYguTiWF/420FrQKFa6WP1lruNf7nbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTZfNW%2FbtsGYguTiWF%2F420FrQKFa6WP1lruNf7nbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;342&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 다른 방법이 있지만, 실무에선 주로 별도의 클래스로 분리하는 방법을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(중요) &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 AOP 주의 사항 - &lt;/span&gt;public&amp;nbsp;메서드만&amp;nbsp;트랜잭션&amp;nbsp;적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링의&amp;nbsp;트랜잭션&amp;nbsp;AOP&amp;nbsp;기능은&amp;nbsp;&lt;b&gt;public&amp;nbsp;메서드에만&amp;nbsp;&lt;/b&gt;트랜잭션을&amp;nbsp;적용하도록&amp;nbsp;기본&amp;nbsp;설정이&amp;nbsp;되어있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서 그렇게 정했다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이유는 알고 가자 (만약 public 외에 다른 데에도 가능하다면?)&lt;/h4&gt;
&lt;pre id=&quot;code_1714154455838&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public class Hello { 
  public method1(); 
  method2(): 
  protected method3(); 
  private method4();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;클래스&amp;nbsp;레벨에&amp;nbsp;트랜잭션을&amp;nbsp;적용하면&amp;nbsp;모든&amp;nbsp;메서드에&amp;nbsp;트랜잭션이&amp;nbsp;걸릴&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 트랜잭션을 의도하지&amp;nbsp;않는&amp;nbsp;곳&amp;nbsp;까지&amp;nbsp;트랜잭션이&amp;nbsp;과도하게&amp;nbsp;적용된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 주로 비즈니스 로직의 시작점에 걸기 때문에 대부분 외부에&amp;nbsp;열어준&amp;nbsp;곳을&amp;nbsp;시작점으로&amp;nbsp;사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런&amp;nbsp;이유로&amp;nbsp;public&amp;nbsp;메서드에만&amp;nbsp;트랜잭션을&amp;nbsp;적용하도록&amp;nbsp;설정되어&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;참고로&amp;nbsp;public&amp;nbsp;이&amp;nbsp;아닌곳에&amp;nbsp;@Transactional&amp;nbsp;이&amp;nbsp;붙어&amp;nbsp;있으면&amp;nbsp;예외가&amp;nbsp;발생하지는&amp;nbsp;않고,&amp;nbsp;트랜잭션&amp;nbsp;적용만&amp;nbsp;무시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(중요) &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 AOP 주의 사항 - 초기화 시점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링&amp;nbsp;초기화&amp;nbsp;시점에는&amp;nbsp;트랜잭션&amp;nbsp;AOP가&amp;nbsp;적용되지&amp;nbsp;않을&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화&amp;nbsp;코드(예:&amp;nbsp;@PostConstruct&amp;nbsp;)와&amp;nbsp;@Transactional&amp;nbsp;을&amp;nbsp;함께&amp;nbsp;사용하면&amp;nbsp;트랜잭션이&amp;nbsp;적용되지&amp;nbsp;않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1714154620660&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostConstruct
@Transactional
public void initV1() {
    log.info(&quot;Hello init @PostConstruct&quot;); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 초기화&amp;nbsp;코드가&amp;nbsp;먼저&amp;nbsp;호출되고,&amp;nbsp;그&amp;nbsp;다음에&amp;nbsp;트랜잭션&amp;nbsp;AOP가&amp;nbsp;적용되기&amp;nbsp;때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 초기화 시점에는 해당&amp;nbsp;메서드에서&amp;nbsp;트랜잭션을&amp;nbsp;획득할&amp;nbsp;수&amp;nbsp;없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 방법 - ApplicatonReadyEvent 를 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;확실한&amp;nbsp;대안은&amp;nbsp;ApplicationReadyEvent&amp;nbsp;이벤트를&amp;nbsp;사용하는&amp;nbsp;것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1714154717637&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EventListener(value = ApplicationReadyEvent.class) 
@Transactional
public void init2() {
    log.info(&quot;Hello init ApplicationReadyEvent&quot;); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이벤트는 트랜잭션 AOP를 포함한 스프링이 컨테이너가 완전히 생성되고 난 다음에 이벤트가 붙은 메서드를 호출해준다.&amp;nbsp;따라서&amp;nbsp;init2()&amp;nbsp;는&amp;nbsp;트랜잭션이&amp;nbsp;적용된&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션 옵션 소개&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링&amp;nbsp;트랜잭션은&amp;nbsp;다양한&amp;nbsp;옵션을&amp;nbsp;제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714154927843&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public @interface Transactional {
    
    @AliasFor(&quot;transactionManager&quot;)
    String value() default &quot;&quot;;
    @AliasFor(&quot;value&quot;)
    String transactionManager() default &quot;&quot;;
    
    String[] label() default {};
    Propagation propagation() default Propagation.REQUIRED;
    Isolation isolation() default Isolation.DEFAULT;
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    String timeoutString() default &quot;&quot;;
    boolean readOnly() default false;
    
    Class&amp;lt;? extends Throwable&amp;gt;[] rollbackFor() default {};   
    String[] rollbackForClassName() default {};   
    
    Class&amp;lt;? extends Throwable&amp;gt;[] noRollbackFor() default {}; 
    String[] noRollbackForClassName() default {};

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;value, transactionManager - 기본으로 등록된 걸 사용해서 보통 생략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을&amp;nbsp;사용하려면&amp;nbsp;먼저&amp;nbsp;스프링&amp;nbsp;빈에&amp;nbsp;등록된&amp;nbsp;어떤&amp;nbsp;트랜잭션&amp;nbsp;매니저를&amp;nbsp;사용할지&amp;nbsp;알아야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 코드로 직접&amp;nbsp;트랜잭션을&amp;nbsp;사용할&amp;nbsp;때&amp;nbsp;분명&amp;nbsp;트랜잭션&amp;nbsp;매니저를&amp;nbsp;주입&amp;nbsp;받아서&amp;nbsp;사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 에서도 트랜잭션 프록시가&amp;nbsp;사용할&amp;nbsp;트랜잭션&amp;nbsp;매니저를&amp;nbsp;지정해주어야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사용할 트랜잭션 매니저를 지정할 때는 value , transactionManager 둘 중 하나에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 매니저의 스프링 빈의&amp;nbsp;이름을&amp;nbsp;적어주면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;이&amp;nbsp;값을&amp;nbsp;생략하면&amp;nbsp;기본으로&amp;nbsp;등록된&amp;nbsp;트랜잭션&amp;nbsp;매니저를&amp;nbsp;사용하기&amp;nbsp;때문에&amp;nbsp;대부분&amp;nbsp;생략한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사용하는 트랜잭션 매니저가&amp;nbsp;둘&amp;nbsp;이상이라면&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;트랜잭션&amp;nbsp;매니저의&amp;nbsp;이름을&amp;nbsp;지정해서&amp;nbsp;구분하면&amp;nbsp;된다.&lt;/p&gt;
&lt;pre id=&quot;code_1714155116020&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TxService {

    @Transactional(&quot;memberTxManager&quot;) 
public void member() {...}

    @Transactional(&quot;orderTxManager&quot;) 
public void order() {...}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;rollbackFor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외&amp;nbsp;발생시&amp;nbsp;스프링&amp;nbsp;트랜잭션의&amp;nbsp;기본&amp;nbsp;정책은&amp;nbsp;다음과&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;언체크 예외인 RuntimeException , Error 와 그 하위 예외가 발생하면 롤백한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;체크&amp;nbsp;예외인&amp;nbsp;Exception&amp;nbsp;과&amp;nbsp;그&amp;nbsp;하위&amp;nbsp;예외들은&amp;nbsp;커밋한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&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;/p&gt;
&lt;pre id=&quot;code_1714155201522&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional(rollbackFor = Exception.class)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;지정하면&amp;nbsp;체크&amp;nbsp;예외인&amp;nbsp;Exception&amp;nbsp;이&amp;nbsp;발생해도&amp;nbsp;롤백하게&amp;nbsp;된다.&amp;nbsp;(하위&amp;nbsp;예외들도&amp;nbsp;대상에&amp;nbsp;포함된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;rollbackForClassName&amp;nbsp;도&amp;nbsp;있는데,&amp;nbsp;rollbackFor&amp;nbsp;는&amp;nbsp;예외&amp;nbsp;클래스를&amp;nbsp;직접&amp;nbsp;지정하고, &lt;br /&gt;rollbackForClassName&amp;nbsp;는&amp;nbsp;예외&amp;nbsp;이름을&amp;nbsp;문자로&amp;nbsp;넣으면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;noRollbackFor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollbackFor&amp;nbsp;와&amp;nbsp;반대이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;propagation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션&amp;nbsp;전파에&amp;nbsp;대한&amp;nbsp;옵션이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;isolation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션&amp;nbsp;격리&amp;nbsp;수준을&amp;nbsp;지정할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 값은 데이터베이스에서 설정한 트랜잭션 격리 수준을 사용하는 DEFAULT이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 데이터베이스에서 설정한 기준을 따른다. 애플리케이션 개발자가 트랜잭션 격리 수준을 직접 지정하는 경우는&amp;nbsp;드물다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;DEFAULT&amp;nbsp;:&amp;nbsp;데이터베이스에서&amp;nbsp;설정한&amp;nbsp;격리&amp;nbsp;수준을&amp;nbsp;따른다.&amp;nbsp; &lt;br /&gt;READ_UNCOMMITTED&amp;nbsp;:&amp;nbsp;커밋되지&amp;nbsp;않은&amp;nbsp;읽기 &lt;br /&gt;READ_COMMITTED&amp;nbsp;:&amp;nbsp;커밋된&amp;nbsp;읽기&amp;nbsp; &lt;br /&gt;REPEATABLE_READ&amp;nbsp;:&amp;nbsp;반복&amp;nbsp;가능한&amp;nbsp;읽기&amp;nbsp; &lt;br /&gt;SERIALIZABLE&amp;nbsp;:&amp;nbsp;직렬화&amp;nbsp;가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;timeout&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에&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;timeoutString&amp;nbsp;도&amp;nbsp;있는데,&amp;nbsp;숫자&amp;nbsp;대신&amp;nbsp;문자&amp;nbsp;값으로&amp;nbsp;지정할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;label&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션&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;일반적으로&amp;nbsp;사용하지&amp;nbsp;않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;readOnly&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은&amp;nbsp;기본적으로&amp;nbsp;읽기&amp;nbsp;쓰기가&amp;nbsp;모두&amp;nbsp;가능한&amp;nbsp;트랜잭션이&amp;nbsp;생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;readOnly=true 옵션을 사용하면 읽기 전용 트랜잭션이 생성된다. 이 경우 등록, 수정, 삭제가 안되고 읽기 기능만&amp;nbsp;작동한다.&amp;nbsp;(드라이버나&amp;nbsp;데이터베이스에&amp;nbsp;따라&amp;nbsp;정상&amp;nbsp;동작하지&amp;nbsp;않는&amp;nbsp;경우도&amp;nbsp;있다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 readOnly 옵션을 사용하면 읽기에서&amp;nbsp;다양한&amp;nbsp;성능&amp;nbsp;최적화가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;JdbcTemplate은 읽기 전용 트랜잭션 안에서 변경 기능을 실행하면 예외를 던진다.&lt;/li&gt;
&lt;li&gt;JPA(하이버네이트)는 &lt;b&gt;읽기 전용 트랜잭션의 경우 커밋 시점에 플러시를 호출하지 않는다.&lt;/b&gt; 읽기 전용이니 변경에 사용되는 플러시를 호출할 필요가 없다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추가로 변경이 필요 없으니 변경 감지를 위한 스냅샷 객체도&amp;nbsp;생성하지&amp;nbsp;않는다.&amp;nbsp;이렇게&amp;nbsp;JPA에서는&amp;nbsp;다양한&amp;nbsp;최적화가&amp;nbsp;발생한다.&lt;/b&gt;&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예외와&amp;nbsp;트랜잭션&amp;nbsp;커밋,&amp;nbsp;롤백&amp;nbsp;-&amp;nbsp;기본&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외가 발생했는데, 내부에서 예외를 처리하지 못하고, 트랜잭션 범위( @Transactional가 적용된 AOP ) 밖으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외를&amp;nbsp;던지면&amp;nbsp;어떻게&amp;nbsp;될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8Btzl/btsGYo0EKuV/WUGI3aM0QVZY62BSbLcxZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8Btzl/btsGYo0EKuV/WUGI3aM0QVZY62BSbLcxZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8Btzl/btsGYo0EKuV/WUGI3aM0QVZY62BSbLcxZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8Btzl%2FbtsGYo0EKuV%2FWUGI3aM0QVZY62BSbLcxZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;220&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외&amp;nbsp;발생시&amp;nbsp;스프링&amp;nbsp;트랜잭션&amp;nbsp;AOP는&amp;nbsp;예외의&amp;nbsp;종류에&amp;nbsp;따라&amp;nbsp;트랜잭션을&amp;nbsp;커밋하거나&amp;nbsp;롤백한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;언체크 예외&lt;/b&gt;인 RuntimeException , Error 와 그 하위 예외가 발생하면&lt;b&gt; 트랜잭션을 롤백&lt;/b&gt;한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체크 예외&lt;/b&gt;인 Exception 과 그 하위 예외가 발생하면&lt;b&gt; 트랜잭션을 커밋&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;물론&amp;nbsp;정상&amp;nbsp;응답(리턴)하면&amp;nbsp;트랜잭션을&amp;nbsp;커밋한다.&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;pre id=&quot;code_1714155795382&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG

#JPA 
loglogging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG 
logging.level.org.hibernate.resource.transaction=DEBUG&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA를 사용하므로 트랜잭션 매니저로 JpaTransactionManager 가 실행되고, 여기의 로그를 출력하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예외와&amp;nbsp;트랜잭션&amp;nbsp;커밋,&amp;nbsp;롤백&amp;nbsp;-&amp;nbsp;활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은&amp;nbsp;왜&amp;nbsp;체크&amp;nbsp;예외는&amp;nbsp;커밋하고,&amp;nbsp;언체크(런타임)&amp;nbsp;예외는&amp;nbsp;롤백할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스프링 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;런타임(언체크) 예외는 복구 불가능한 예외로 가정한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체크 예외: 비즈니스 의미가 있을 때 사용 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;lt;- 필수는 아님 (rollbackFor 옵션으로 체크예외도 롤백가능) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;언체크&amp;nbsp;예외:&amp;nbsp;복구&amp;nbsp;불가능한&amp;nbsp;예외&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;런타임 예외는 항상 롤백&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체크 예외의 경우 rollbackFor 옵션을 사용해서 비즈니스 상황에 따라서 커밋과&amp;nbsp;롤백을&amp;nbsp;선택&lt;/b&gt;하면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그 - JPA 실행하는 SQL 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1714156559975&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.level.org.hibernate.SQL=DEBUG&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고자료 및 출처&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&lt;/a&gt;&lt;/p&gt;</description>
      <category>강의메모</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/141</guid>
      <comments>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch9-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%9D%B4%ED%95%B4feat-%EC%A4%91%EC%9A%94%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-AOP-%EC%A3%BC%EC%9D%98-%EC%82%AC%ED%95%AD#entry141comment</comments>
      <pubDate>Sat, 27 Apr 2024 03:36:49 +0900</pubDate>
    </item>
    <item>
      <title>[강의메모] 스프링 DB 2편 -데이터 접근 활용 기술 - ch3. 테스트 (feat. 임베디드 모드 쓰고 싶다면? build.gradle에 우선 h2 설정부터!!)</title>
      <link>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch3-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;@SpringBootTest&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@SpringBootTest는 @SpringBootApplicatoin를 찾아서 설정으로 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 - 데이터베이스 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬에서 사용하는 애플리케이션 서버와 테스트에서 같은 데이터베이스를 사용하고 있으니 테스트에서 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런&amp;nbsp;문제를&amp;nbsp;해결하려면&amp;nbsp;테스트를&amp;nbsp;다른&amp;nbsp;환경과&amp;nbsp;철저하게&amp;nbsp;분리해야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;간단한&amp;nbsp;방법은&amp;nbsp;테스트&amp;nbsp;전용&amp;nbsp;데이터베이스를&amp;nbsp;별도로&amp;nbsp;운영하는&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트에서&amp;nbsp;매우&amp;nbsp;중요한&amp;nbsp;원칙은&amp;nbsp;다음과&amp;nbsp;같다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;테스트는 다른 테스트와 격리해야 한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;테스트는&amp;nbsp;반복해서&amp;nbsp;실행할&amp;nbsp;수&amp;nbsp;있어야&amp;nbsp;한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DELETE SQL을 사용하면 되는 거 아냐?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;테스트가 끝날 때 마다 추가한 데이터에 DELETE SQL 을 사용해도 되겠지만, 이 방법도 궁극적인 해결책은 아니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 테스트 과정에서 데이터를 이미 추가했는데, 테스트가 실행되는 도중에 예외가 발생하거나 애플리케이션이 종료되어&amp;nbsp;버려서&amp;nbsp;테스트&amp;nbsp;종료&amp;nbsp;시점에&amp;nbsp;DELETE&amp;nbsp;SQL&amp;nbsp;을&amp;nbsp;호출하지&amp;nbsp;못할&amp;nbsp;수&amp;nbsp;도&amp;nbsp;있다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면&amp;nbsp;결국&amp;nbsp;데이터가&amp;nbsp;남아있게&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리에겐 데이터 롤백이 가능한 트랜잭션이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&amp;nbsp;-&amp;nbsp;데이터&amp;nbsp;롤백&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션과&amp;nbsp;롤백&amp;nbsp;전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가&amp;nbsp;끝나고&amp;nbsp;나서&amp;nbsp;트랜잭션을&amp;nbsp;강제로&amp;nbsp;롤백해버리면&amp;nbsp;데이터가&amp;nbsp;깔끔하게&amp;nbsp;제거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 커밋하지&amp;nbsp;않았기&amp;nbsp;때문에&amp;nbsp;데이터베이스에&amp;nbsp;해당&amp;nbsp;데이터가&amp;nbsp;반영되지&amp;nbsp;않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트에 직접 트랜잭션 추가&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1714147505977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
class ItemRepositoryTest {
    @Autowired
    ItemRepository itemRepository;
    
    //트랜잭션 관련 코드
    @Autowired
    PlatformTransactionManager transactionManager;
    TransactionStatus status;

    @BeforeEach
    void beforeEach() {
    //트랜잭션 시작
        status = transactionManager.getTransaction(new
            DefaultTransactionDefinition());
    }

    @AfterEach
    void afterEach() {
    //MemoryItemRepository 의 경우 제한적으로 사용
        if (itemRepository instanceof MemoryItemRepository) {
            ((MemoryItemRepository) itemRepository).clearStore();
        }
        //트랜잭션 롤백
        transactionManager.rollback(status);
    }
//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션&amp;nbsp;관리자는&amp;nbsp;PlatformTransactionManager&amp;nbsp;를&amp;nbsp;주입&amp;nbsp;받아서&amp;nbsp;사용하면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 스프링 부트는&amp;nbsp;자동으로&amp;nbsp;적절한&amp;nbsp;트랜잭션&amp;nbsp;매니저를&amp;nbsp;스프링&amp;nbsp;빈으로&amp;nbsp;등록해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 - @Transactional (이것만 쓰면 됨)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 테스트 데이터 초기화를 위해 트랜잭션을 적용하고 롤백하는 방식을 @Transactional 애노테이션 하나로&amp;nbsp;깔끔하게&amp;nbsp;해결해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1714147635450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
@SpringBootTest
class ItemRepositoryTest {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Transactional 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이&amp;nbsp;제공하는&amp;nbsp;@Transactional&amp;nbsp;애노테이션은&amp;nbsp;로직이&amp;nbsp;성공적으로&amp;nbsp;수행되면&amp;nbsp;커밋하도록&amp;nbsp;동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;@Transactional&amp;nbsp;애노테이션을&amp;nbsp;테스트에서&amp;nbsp;사용하면&amp;nbsp;아주&amp;nbsp;특별하게&amp;nbsp;동작한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;@Transactional이 테스트에 있으면 스프링은 테스트를 트랜잭션 안에서 실행하고, 테스트가 끝나면 트랜잭션을&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;자동으로&amp;nbsp;롤백시켜&amp;nbsp;버린다!&lt;/b&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;span style=&quot;color: #000000;&quot;&gt;@Transactional이&amp;nbsp;적용된&amp;nbsp;테스트&amp;nbsp;동작&amp;nbsp;방식&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 테스트에서 시작하기 때문에 서비스, 리포지토리에 있는 @Transactional 도 테스트에서&amp;nbsp;시작한 트랜잭션에 참여한다. (트랜잭션 전파)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;강제로 커밋하기 @Commit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional 을 테스트에서 사용하면 테스트가 끝나면 바로 롤백되기 때문에 테스트 과정에서 저장한 모든 데이터가&amp;nbsp;사라진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 이렇게 되어야 하지만, 정말 가끔은 데이터베이스에 데이터가 잘 보관되었는지 최종 결과를 눈으로&amp;nbsp;확인하고&amp;nbsp;싶을&amp;nbsp;때도&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴&amp;nbsp;때는&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;@Commit&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;@Rollback(value&amp;nbsp;=&amp;nbsp;false)&amp;nbsp;를&amp;nbsp;사용해도&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1714148062771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Commit
@Transactional 
@SpringBootTest
class ItemRepositoryTest {}&lt;/code&gt;&lt;/pre&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;/li&gt;
&lt;li&gt;테스트&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;커밋&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;롤백되어&amp;nbsp;버린&amp;nbsp; &lt;br /&gt;다.)&lt;/li&gt;
&lt;li&gt;트랜잭션&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;장점이&amp;nbsp; &lt;br /&gt;있다.&lt;/li&gt;
&lt;li&gt;@Transactional 덕분에 아주 편리하게 다음 원칙을 지킬수 있게 되었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트는 다른 테스트와 격리해야 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테스트는&amp;nbsp;반복해서&amp;nbsp;실행할&amp;nbsp;수&amp;nbsp;있어야&amp;nbsp;한다.&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;테스트 - 임베디드 모드 DB (build.gradle - h2 설정부터!!)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build.gradle에 일단 h2를 사용한다고부터 명시&lt;/h3&gt;
&lt;pre id=&quot;code_1714150736075&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;testRuntimeOnly 'com.h2database:h2:2.1.214'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vAc3e/btsGYMNLKhw/7RjzGKtF1vDRKD6KkOlJz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vAc3e/btsGYMNLKhw/7RjzGKtF1vDRKD6KkOlJz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vAc3e/btsGYMNLKhw/7RjzGKtF1vDRKD6KkOlJz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvAc3e%2FbtsGYMNLKhw%2F7RjzGKtF1vDRKD6KkOlJz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;866&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;임베디드&amp;nbsp;모드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H2 데이터베이스는 자바로 개발되어 있고, JVM안에서 메모리 모드로 동작하는 특별한 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 애플리케이션을&amp;nbsp;실행할&amp;nbsp;때&amp;nbsp;H2&amp;nbsp;데이터베이스도&amp;nbsp;해당&amp;nbsp;JVM&amp;nbsp;메모리에&amp;nbsp;포함해서&amp;nbsp;함께&amp;nbsp;실행할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB를 애플리케이션에&amp;nbsp;내장해서&amp;nbsp;함께&amp;nbsp;실행한다고&amp;nbsp;해서&amp;nbsp;임베디드&amp;nbsp;모드(Embedded&amp;nbsp;mode)라&amp;nbsp;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료 시 함께 종료되고, 데이터도 모두 사라진다. (자바 메모리를 함께 사용하는 라이브러리처럼 동작)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&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;스프링 부트 - 기본 SQL 스크립트를 사용해서 데이터베이스를 초기화하는 기능 (feat. src/test/resources/schema.sql)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리&amp;nbsp;DB는&amp;nbsp;애플리케이션이&amp;nbsp;종료될&amp;nbsp;때&amp;nbsp;함께&amp;nbsp;사라지기&amp;nbsp;때문에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 실행 시점에 데이터베이스 테이블도 새로&amp;nbsp;만들어주어야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스프링 부트는&amp;nbsp;SQL&amp;nbsp;스크립트를&amp;nbsp;실행해서&amp;nbsp;애플리케이션&amp;nbsp;로딩&amp;nbsp;시점에&amp;nbsp;데이터베이스를&amp;nbsp;초기화하는&amp;nbsp;기능을&amp;nbsp;제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;src/test/resources/schema.sql&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 schema.sql 로 해야하고, 파일은 src/test/resources 하위에 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/test/resources/schema.sql&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1714148416749&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;drop table if exists item CASCADE; 
create table item
(
    id        bigint generated by default as identity, 
    item_name varchar(10),
    price integer, 
    quantity integer,
primary key (id) 
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로그&amp;nbsp;확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본&amp;nbsp;SQL&amp;nbsp;스크립트가&amp;nbsp;잘&amp;nbsp;실행되는지&amp;nbsp;로그로&amp;nbsp;확인하려면&amp;nbsp;다음이&amp;nbsp;추가되어&amp;nbsp;있는지&amp;nbsp;확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/test/resources/application.properteis&lt;/p&gt;
&lt;pre id=&quot;code_1714148501701&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#schema.sql
logging.level.org.springframework.jdbc=debug&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714148512576&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;..init.ScriptUtils     : 0 returned as update count for SQL: create table item 
( id bigint generated by default as identity, item_name varchar(10), price 
integer, quantity integer, primary key (id) )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&amp;nbsp;-&amp;nbsp;스프링&amp;nbsp;부트와&amp;nbsp;임베디드&amp;nbsp;모드&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스프링&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;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;test - application.properties에 &amp;nbsp;별다른 정보가 없으면 스프링 부트는 임베디드 모드로 접근하는 데이터소스( DataSource )를 만들어서 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;conn0:&amp;nbsp;url=jdbc:h2:mem:d8fb3a29-caf7-4b37-9b6c-b0eed9985454&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;로그를 보면 jdbc:h2:mem 뒤에 임의의 데이터베이스 이름이 들어가 있다. 이것은 혹시라도 여러 데이터소스가 사용될 때 같은 데이터베이스를 사용하면서 발생하는 충돌을 방지하기 위해 스프링&amp;nbsp;부트가&amp;nbsp;임의의&amp;nbsp;이름을&amp;nbsp;부여한&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;임베디드&amp;nbsp;데이터베이스&amp;nbsp;이름을&amp;nbsp;스프링&amp;nbsp;부트가&amp;nbsp;기본으로&amp;nbsp;제공하는&amp;nbsp;jdbc:h2:mem:testdb&amp;nbsp;로&amp;nbsp;고정하고&amp;nbsp;싶으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.datasource.generate-unique-name=false 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Transactional을 사용하면 데스트의 중요한 원칙 2가지를 지킬 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테스트는 다른 테스트와 격리해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테스트는 반복해서 실행할 수 있어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트는 test - application.properties에 아무 설정이 없다면? 임베디드 DB를 자동으로 실행시켜준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고자료 및 출처&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&lt;/a&gt;&lt;/p&gt;</description>
      <category>강의메모</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/140</guid>
      <comments>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch3-%ED%85%8C%EC%8A%A4%ED%8A%B8#entry140comment</comments>
      <pubDate>Sat, 27 Apr 2024 01:34:37 +0900</pubDate>
    </item>
    <item>
      <title>[강의메모] 스프링 DB 2편 -데이터 접근 활용 기술 - ch2. 스프링 JdbcTemplate</title>
      <link>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch2-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate</link>
      <description>&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인프런 김영한님&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;스프링 DB 2편 - 데이터&amp;nbsp;접근&amp;nbsp;활용&amp;nbsp;기술&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;강의 중 2장을 보고 핵심 내용을 정리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JdbcTemplate 소개 및 장단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL을 직접 사용하는 경우에 스프링이 제공하는 JdbcTemplate은 아주 좋은 선택지다. JdbcTemplate은 JDBC를&amp;nbsp;매우&amp;nbsp;편리하게&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설정의&amp;nbsp;편리함&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate은 spring-jdbc 라이브러리에 포함되어 있는데, 이 라이브러리는 스프링으로 JDBC를&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;반복&amp;nbsp;문제&amp;nbsp;해결&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JdbcTemplate은 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업을&amp;nbsp;대신&amp;nbsp;처리해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발자는&amp;nbsp;SQL을&amp;nbsp;작성하고,&amp;nbsp;전달할&amp;nbsp;파리미터를&amp;nbsp;정의하고,&amp;nbsp;응답&amp;nbsp;값을&amp;nbsp;매핑하기만&amp;nbsp;하면&amp;nbsp;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의&amp;nbsp;반복&amp;nbsp;작업을&amp;nbsp;대신&amp;nbsp;처리해준다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&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;/li&gt;
&lt;li&gt;statement 를 준비하고 실행&lt;/li&gt;
&lt;li&gt;결과를 반복하도록 루프를 실행&lt;/li&gt;
&lt;li&gt;커넥션 종료, statement , resultset 종료 트랜잭션 다루기 위한 커넥션 동기화&lt;/li&gt;
&lt;li&gt;예외&amp;nbsp;발생시&amp;nbsp;스프링&amp;nbsp;예외&amp;nbsp;변환기&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;동적&amp;nbsp;SQL을&amp;nbsp;해결하기&amp;nbsp;어렵다.&lt;/li&gt;
&lt;li&gt;ORM과 비교 시 SQL Mapper 특성상 개발자가 직접 SQL을 작성해야한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JdbcTemplate&amp;nbsp;기능&amp;nbsp;정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요&amp;nbsp;기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate이&amp;nbsp;제공하는&amp;nbsp;주요&amp;nbsp;기능은&amp;nbsp;다음과&amp;nbsp;같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JdbcTemplate&lt;/b&gt; : 순서 기반 파라미터 바인딩을 지원한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NamedParameterJdbcTemplate&lt;/b&gt; : 이름 기반 파라미터 바인딩을 지원한다. (권장)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SimpleJdbcInsert&lt;/b&gt; : INSERT SQL을 편리하게 사용할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SimpleJdbcCall&lt;/b&gt; : 스토어드 프로시저를 편리하게 호출할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JdbcTemplate 사용법 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 JdbcTemplate 사용 방법 공식 메뉴얼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714146085578&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling :: Spring Framework&quot; data-og-description=&quot;Some query methods return a single value. To retrieve a count or a specific value from one row, use queryForObject(..). The latter converts the returned JDBC Type to the Java class that is passed in as an argument. If the type conversion is invalid, an Inv&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Some query methods return a single value. To retrieve a count or a specific value from one row, use queryForObject(..). The latter converts the returned JDBC Type to the Java class that is passed in as an argument. If the type conversion is invalid, an Inv&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조회&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단건&amp;nbsp;조회&amp;nbsp;-&amp;nbsp;숫자&amp;nbsp;조회&lt;/h4&gt;
&lt;pre id=&quot;code_1714146110985&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int rowCount = jdbcTemplate.queryForObject(&quot;select count(*) from t_actor&quot;, 
Integer.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의&amp;nbsp;로우를&amp;nbsp;조회할&amp;nbsp;때는&amp;nbsp;queryForObject()&amp;nbsp;를&amp;nbsp;사용하면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 대상이 객체가 아니라 단순 데이터&amp;nbsp;하나라면&amp;nbsp;타입을&amp;nbsp;Integer.class&amp;nbsp;,&amp;nbsp;String.class&amp;nbsp;와&amp;nbsp;같이&amp;nbsp;지정해주면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단건&amp;nbsp;조회&amp;nbsp;-&amp;nbsp;숫자&amp;nbsp;조회,&amp;nbsp;파라미터&amp;nbsp;바인딩&lt;/h4&gt;
&lt;pre id=&quot;code_1714146168259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int countOfActorsNamedJoe = jdbcTemplate.queryForObject(
&quot;select count(*) from t_actor where first_name = ?&quot;, Integer.class, &quot;Joe&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자&amp;nbsp;하나와&amp;nbsp;파라미터&amp;nbsp;바인딩&amp;nbsp;예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단건&amp;nbsp;조회&amp;nbsp;-&amp;nbsp;문자&amp;nbsp;조회&lt;/h4&gt;
&lt;pre id=&quot;code_1714146194202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String lastName = jdbcTemplate.queryForObject(
	&quot;select last_name from t_actor where id = ?&quot;, 
	String.class, 1212L);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자&amp;nbsp;하나와&amp;nbsp;파라미터&amp;nbsp;바인딩&amp;nbsp;예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단건 조회 - 객체 조회(1)&lt;/h4&gt;
&lt;pre id=&quot;code_1714146244670&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Actor actor = jdbcTemplate.queryForObject(
        &quot;select first_name, last_name from t_actor where id = ?&quot;,
        (resultSet, rowNum) -&amp;gt; {
            Actor newActor = new Actor();
            newActor.setFirstName(resultSet.getString(&quot;first_name&quot;)); 
            newActor.setLastName(resultSet.getString(&quot;last_name&quot;));
            return newActor;
        },
        1212L);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체&amp;nbsp;하나를&amp;nbsp;조회한다.&amp;nbsp;결과를&amp;nbsp;객체로&amp;nbsp;매핑해야&amp;nbsp;하므로&amp;nbsp;RowMapper&amp;nbsp;를&amp;nbsp;사용해야&amp;nbsp;한다.&amp;nbsp;여기서는&amp;nbsp;람다를&amp;nbsp;사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목록 조회 - 객체(2)&lt;/h4&gt;
&lt;pre id=&quot;code_1714146282714&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Actor&amp;gt; actors = jdbcTemplate.query(
        &quot;select first_name, last_name from t_actor&quot;,
        (resultSet, rowNum) -&amp;gt; {
            Actor actor = new Actor();
            actor.setFirstName(resultSet.getString(&quot;first_name&quot;)); 
            actor.setLastName(resultSet.getString(&quot;last_name&quot;));
            return actor;
        });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 로우를 조회할 때는 query() 를 사용하면 된다. 결과를 리스트로 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를&amp;nbsp;객체로&amp;nbsp;매핑해야&amp;nbsp;하므로&amp;nbsp;RowMapper&amp;nbsp;를&amp;nbsp;사용해야&amp;nbsp;한다.&amp;nbsp;여기서는&amp;nbsp;람다를&amp;nbsp;사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;목록 조회 - 객체(3)&lt;/h4&gt;
&lt;pre id=&quot;code_1714146407468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final RowMapper&amp;lt;Actor&amp;gt; actorRowMapper = (resultSet, rowNum) -&amp;gt; { 
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString(&quot;first_name&quot;)); 
    actor.setLastName(resultSet.getString(&quot;last_name&quot;));
    return actor; 
};
public List&amp;lt;Actor&amp;gt; findAllActors() {
    return this.jdbcTemplate.query(&quot;select first_name, last_name from t_actor&quot;, actorRowMapper);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 로우를 조회할 때는 query() 를 사용하면 된다. 결과를 리스트로 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는&amp;nbsp;RowMapper&amp;nbsp;를&amp;nbsp;분리했다.&amp;nbsp;이렇게&amp;nbsp;하면&amp;nbsp;여러&amp;nbsp;곳에서&amp;nbsp;재사용&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경(INSERT,&amp;nbsp;UPDATE,&amp;nbsp;DELETE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 변경할 때는 jdbcTemplate.update() 를 사용하면 된다. 참고로 int 반환값을 반환하는데, SQL 실행&amp;nbsp;결과에&amp;nbsp;영향받은&amp;nbsp;로우&amp;nbsp;수를&amp;nbsp;반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;등록&lt;/h4&gt;
&lt;pre id=&quot;code_1714146482477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbcTemplate.update(
	&quot;insert into t_actor (first_name, last_name) values (?, ?)&quot;, 
	&quot;Leonor&quot;, &quot;Watling&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수정&lt;/h4&gt;
&lt;pre id=&quot;code_1714146493315&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbcTemplate.update(
	&quot;update t_actor set last_name = ? where id = ?&quot;, 
	&quot;Banjo&quot;, 5276L);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;삭제&lt;/h4&gt;
&lt;pre id=&quot;code_1714146504647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbcTemplate.update(
	&quot;delete from t_actor where id = ?&quot;, 
	Long.valueOf(actorId));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기타 기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의&amp;nbsp;SQL을&amp;nbsp;실행할&amp;nbsp;때는&amp;nbsp;execute()&amp;nbsp;를&amp;nbsp;사용하면&amp;nbsp;된다.&amp;nbsp;테이블을&amp;nbsp;생성하는&amp;nbsp;DDL에&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DDL&lt;/h4&gt;
&lt;pre id=&quot;code_1714146533073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbcTemplate.execute(&quot;create table mytable (id integer, name varchar(100))&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스토어드 프로시저 호출&lt;/h4&gt;
&lt;pre id=&quot;code_1714146542741&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbcTemplate.update(
	&quot;call SUPPORT.REFRESH_ACTORS_SUMMARY(?)&quot;, 
	Long.valueOf(unionId));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서&amp;nbsp;가장&amp;nbsp;간단하고&amp;nbsp;실용적인&amp;nbsp;방법으로&amp;nbsp;SQL을&amp;nbsp;사용하려면&amp;nbsp;JdbcTemplate을&amp;nbsp;사용하면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA와 같은 ORM 기술을 사용하면서 동시에 SQL을 직접 작성해야 할 때가 있는데, 그때도 JdbcTemplate을 함께&amp;nbsp;사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&amp;nbsp;JdbcTemplate의&amp;nbsp;최대&amp;nbsp;단점이&amp;nbsp;있는데,&amp;nbsp;바로&amp;nbsp;동적&amp;nbsp;쿼리&amp;nbsp;문제를&amp;nbsp;해결하지&amp;nbsp;못한다는&amp;nbsp;점이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 SQL을 자바&amp;nbsp;코드로&amp;nbsp;작성하기&amp;nbsp;때문에&amp;nbsp;SQL&amp;nbsp;라인이&amp;nbsp;코드를&amp;nbsp;넘어갈&amp;nbsp;때&amp;nbsp;마다&amp;nbsp;문자&amp;nbsp;더하기를&amp;nbsp;해주어야&amp;nbsp;하는&amp;nbsp;단점도&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적&amp;nbsp;쿼리&amp;nbsp;문제를&amp;nbsp;해결하면서&amp;nbsp;동시에&amp;nbsp;SQL도&amp;nbsp;편리하게&amp;nbsp;작성할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;도와주는&amp;nbsp;기술이&amp;nbsp;바로&amp;nbsp;MyBatis&amp;nbsp;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료 및 출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714146695524&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;스프링 DB 2편 - 데이터 접근 활용 기술 | 김영한 - 인프런&quot; data-og-description=&quot;김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLI25c/hyVVFrvUe0/zXKVoua8ReTrboYi5tUMQk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bEkT3a/hyVVAqdD93/IcdRNTpdJuSubWhUzdXdHK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dMul3k/hyVVIV4fJk/cIPxoGL6cJw4S5FHwYjL21/img.png?width=1238&amp;amp;height=775&amp;amp;face=1071_657_1142_734&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLI25c/hyVVFrvUe0/zXKVoua8ReTrboYi5tUMQk/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/bEkT3a/hyVVAqdD93/IcdRNTpdJuSubWhUzdXdHK/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dMul3k/hyVVIV4fJk/cIPxoGL6cJw4S5FHwYjL21/img.png?width=1238&amp;amp;height=775&amp;amp;face=1071_657_1142_734');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 DB 2편 - 데이터 접근 활용 기술 | 김영한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>강의메모</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/139</guid>
      <comments>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch2-%EC%8A%A4%ED%94%84%EB%A7%81-JdbcTemplate#entry139comment</comments>
      <pubDate>Sat, 27 Apr 2024 00:51:36 +0900</pubDate>
    </item>
    <item>
      <title>[강의메모] 스프링 DB 2편 -데이터 접근 활용 기술 - ch1. 시작 (feat.@EventListener(ApplicationReadyEvent.class),Import, Profile)</title>
      <link>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch1-%EC%8B%9C%EC%9E%91-featEventListenerApplicationReadyEventclassImport-Profile</link>
      <description>&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인프런 김영한님&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;스프링 DB 2편 - 데이터&amp;nbsp;접근&amp;nbsp;활용&amp;nbsp;기술&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;강의 중 1장을 보고 핵심 내용을 정리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@EventListener(ApplicationReadyEvent.class)&lt;/h2&gt;
&lt;pre id=&quot;code_1714145001504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor 
public class TestDataInit {
  private final ItemRepository itemRepository; 
    /**
     * 확인용 초기 데이터 추가 
     */
    @EventListener(ApplicationReadyEvent.class) 
  public void initData() {
        log.info(&quot;test data init&quot;);
        itemRepository.save(new Item(&quot;itemA&quot;, 10000, 10)); 
        itemRepository.save(new Item(&quot;itemB&quot;, 20000, 20)); 
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨테이너가 &lt;b&gt;완전히 초기화를 다 끝내고,&amp;nbsp;실행&amp;nbsp;준비가&amp;nbsp;되었을&amp;nbsp;때&amp;nbsp;발생하는&amp;nbsp;이벤트&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 이 시점에 해당 애노테이션이 붙은 메서드(위에선 initData)를&amp;nbsp;호출해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@PostContstruct와의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 이 기능 대신 @PostConstruct 를 사용할 경우 AOP 같은 부분이 아직 다 처리되지 않은 시점에&amp;nbsp;호출될&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에,&amp;nbsp;간혹&amp;nbsp;문제가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 @Transactional 과 관련된 AOP가 적&amp;nbsp;용되지&amp;nbsp;않은&amp;nbsp;상태로&amp;nbsp;호출될&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EventListener(ApplicationReadyEvent.class)&amp;nbsp;는&amp;nbsp;AOP를&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;않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Import&lt;/h2&gt;
&lt;pre id=&quot;code_1714145277828&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Import(MemoryConfig.class)
@SpringBootApplication(scanBasePackages = &quot;hello.itemservice.web&quot;)
public class ItemServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ItemServiceApplication.class, args);
    }

    @Bean
    @Profile(&quot;local&quot;)
    public TestDataInit testDataInit(ItemRepository itemRepository) {
        return new TestDataInit(itemRepository);
    }
}&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;@Import(MemoryConfig.class)&amp;nbsp;:&amp;nbsp;앞서&amp;nbsp;설정한&amp;nbsp;MemoryConfig&amp;nbsp;를&amp;nbsp;설정&amp;nbsp;파일로&amp;nbsp;사용한다.&lt;/li&gt;
&lt;li&gt;scanBasePackages = &quot;hello.itemservice.web&quot; : 여기서는 컨트롤러만 컴포넌트 스캔을 사용하고,&amp;nbsp;나머지는&amp;nbsp;직접&amp;nbsp;수동&amp;nbsp;등록한다.&amp;nbsp;그래서&amp;nbsp;컴포넌트&amp;nbsp;스캔&amp;nbsp;경로를&amp;nbsp;hello.itemservice.web&amp;nbsp;하위로&amp;nbsp;지정했다.&lt;/li&gt;
&lt;li&gt;@Profile(&quot;local&quot;) : 특정 프로필의 경우에만 해당 스프링 빈을 등록한다. &lt;br /&gt;여기서는 local 이라는 이름의 프로필이 사용되는 경우에만 testDataInit 이라는 스프링 빈을 등록한다. 이 빈은 앞서 본 것인데,&amp;nbsp;편의상 초기&amp;nbsp;데이터를&amp;nbsp;만들어서&amp;nbsp;저장하는&amp;nbsp;빈이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로필&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 로딩 시점에 application.properties 의 spring.profiles.active 속성을 읽어서 프로필로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;프로필은&amp;nbsp;로컬(나의&amp;nbsp;PC),&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;예를 들어서 로컬PC에서는 로컬 PC에 설치된 데이터베이스에 접근해야 하고, 운영 환경에서는 운영 데이터베이스에&amp;nbsp;접근해야 한다면 서로 설정 정보가 달라야 한다. 심지어 환경에 따라서 다른 스프링 빈을 등록해야 할 수 도 있다. 프로필을&amp;nbsp;사용하면&amp;nbsp;이런&amp;nbsp;문제를&amp;nbsp;깔끔하게&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;main 프로필&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;/src/main/resources 하위의 application.properties&lt;/h4&gt;
&lt;pre id=&quot;code_1714145427522&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.profiles.active=local&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위치의 application.properties 는 /src/main 하위의 자바 객체를 실행할 때 (주로 main() ) 동작하는 스프링 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;spring.profiles.active=local 이라고 하면 스프링은 local 이라는 프로필로&amp;nbsp;동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로&amp;nbsp;프로필을&amp;nbsp;지정하지&amp;nbsp;않으면&amp;nbsp;디폴트(&amp;nbsp;default&amp;nbsp;)&amp;nbsp;프로필이&amp;nbsp;실행된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;test 프로필&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;/src/test/resources&amp;nbsp;하위의&amp;nbsp;application.properties&lt;/h4&gt;
&lt;pre id=&quot;code_1714145541262&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.profiles.active=test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위치의 application.properties 는 /src/test 하위의 자바 객체를 실행할 때 동작하는 스프링 설정이다. &lt;br /&gt;주로 테스트 케이스를 실행할 때 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.profiles.active=test&amp;nbsp;로&amp;nbsp;설정하면&amp;nbsp;스프링은&amp;nbsp;test&amp;nbsp;라는&amp;nbsp;프로필로&amp;nbsp;동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고자료 및 출처&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&quot;&gt;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard&lt;/a&gt;&lt;/p&gt;</description>
      <category>강의메모</category>
      <author>k9want</author>
      <guid isPermaLink="true">https://k9want.tistory.com/138</guid>
      <comments>https://k9want.tistory.com/entry/%EA%B0%95%EC%9D%98%EB%A9%94%EB%AA%A8-%EC%8A%A4%ED%94%84%EB%A7%81-DB-2%ED%8E%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EA%B8%B0%EC%88%A0-ch1-%EC%8B%9C%EC%9E%91-featEventListenerApplicationReadyEventclassImport-Profile#entry138comment</comments>
      <pubDate>Sat, 27 Apr 2024 00:34:14 +0900</pubDate>
    </item>
  </channel>
</rss>