<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>당니의 개발자 스토리</title>
    <link>https://cainyun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 18 May 2026 19:02:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>clainy</managingEditor>
    <item>
      <title>Spring Bean 생명주기</title>
      <link>https://cainyun.tistory.com/387</link>
      <description>&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;Spring을 사용하다 보면 Bean이라는 개념을 계속 보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean은 단순하게&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt; Spring이 관리하는 객체&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;그런데 이 Bean은 그냥 만들어졌다가 끝나는 게 아니다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; Bean의 생명주기&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;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;객체 생성
&amp;rarr; 의존성 주입
&amp;rarr; 초기화 메서드
&amp;rarr; 사용
&amp;rarr; 소멸 메서드
&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;/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 class=&quot;haxe&quot;&gt;&lt;code&gt;new 객체()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 Container 안에서 Bean 객체를 만든다.&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;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class WasherUser {

    private final Washer washer;

    public WasherUser(Washer washer) {
        this.washer = washer;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 WasherUser를 만들 때 필요한 Washer를 찾아서 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 DI 단계다.&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;&lt;b&gt;초기화 메서드&lt;/b&gt;&lt;/span&gt;가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 &quot;사용하기 전에 준비하는 단계&quot; 라고 보면 된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;DB 연결 준비
외부 API 연결
파일 열기
캐시 준비&lt;/code&gt;&lt;/pre&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;Spring에서는 이걸 이렇게 작성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@PostConstruct
public void init() {
    System.out.println(&quot;초기화 작업 실행&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;의존성 주입이 끝난 뒤&lt;/span&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;그 다음 실제로 Bean을 사용하는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 이렇게 꺼내 쓴다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ApplicationContext ctx = ...;
WasherUser user = ctx.getBean(WasherUser.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 이렇게 해석하면 된다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Spring아 WasherUser 하나 줘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 만들어진 Bean을 꺼내서 사용하는 것이다.&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: #8a3db6;&quot;&gt;&lt;b&gt;소멸 단계&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean이 더 이상 필요 없어질 때 실행된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;DB 연결 끊기
파일 닫기
리소스 정리&lt;/code&gt;&lt;/pre&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;Spring에서는 이렇게 작성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@PreDestroy
public void destroy() {
    System.out.println(&quot;종료 작업 실행&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Container가 종료&lt;/b&gt;될 때 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;자동으로 실행&lt;/b&gt;&lt;/span&gt;된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 중요한 조건이 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;초기화 / 소멸 메서드는 파라미터가 없어야 한다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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;이제 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;@Component&lt;/span&gt;&lt;/b&gt;가 아니라 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;@Bean&lt;/b&gt; &lt;/span&gt;방식도 보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean(initMethod = &quot;init&quot;, destroyMethod = &quot;destroy&quot;)
public SWasher sWasher() {
    return new SWasher();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt; 초기화&lt;/span&gt;&lt;/b&gt;와&lt;b&gt; &lt;span style=&quot;color: #ef5369;&quot;&gt;소멸 메서드&lt;/span&gt;&lt;/b&gt;를 이름으로 지정하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;initMethod &amp;rarr; 초기화 메서드
destroyMethod &amp;rarr; 소멸 메서드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 해당 메서드를 찾아서 실행해준다.&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: #ef5369;&quot;&gt;&lt;b&gt;@PostConstruct&lt;/b&gt;&lt;/span&gt;와&lt;span style=&quot;color: #0593d3;&quot;&gt; &lt;b&gt;@Bean&lt;/b&gt;&lt;/span&gt; 방식은 어떻게 다를까?&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@PostConstruct / @PreDestroy &amp;rarr; 묵시적 방식
@Bean(initMethod, destroyMethod) &amp;rarr; 명시적 방식
&lt;/code&gt;&lt;/pre&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;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/387</guid>
      <comments>https://cainyun.tistory.com/387#entry387comment</comments>
      <pubDate>Wed, 6 May 2026 17:33:34 +0900</pubDate>
    </item>
    <item>
      <title>@Autowired와 @Qualifier</title>
      <link>https://cainyun.tistory.com/386</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;getBean으로 Bean을 꺼낼 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 같은 타입 Bean이 여러 개 있다면 어떻게 해결할까?&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;Spring에서 DI를 쓰다 보면 자연스럽게 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;@Autowired&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;b&gt;@Autowired&lt;/b&gt;는 필요한 Bean을 자동으로 넣어주는 애노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 코드가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class WasherUser {

    private final Washer washer;

    public WasherUser(Washer washer) {
        this.washer = washer;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Washer 타입 Bean을 Spring이 찾아서 자동으로 넣어준다.&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 class=&quot;angelscript&quot;&gt;&lt;code&gt;1. Washer 타입을 찾는다
2. Container에서 Bean 검색
3. 하나 있으면 바로 주입
&lt;/code&gt;&lt;/pre&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;같은 타입 Bean이 여러 개 있을 때다.&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 class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
public class SWasher implements Washer {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
public class TurboWasher implements Washer {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Washer 타입 Bean이 2개다.&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 class=&quot;isbl&quot;&gt;&lt;code&gt;public WasherUser(Washer washer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 입장에서는 이렇게 된다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Washer 타입 달라고 했는데 두 개 있는데?
&lt;/code&gt;&lt;/pre&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;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;NoUniqueBeanDefinitionException
&lt;/code&gt;&lt;/pre&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: #ef5369;&quot;&gt;&lt;b&gt; @Qualifier&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Qualifier는&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt; Bean&lt;/span&gt;을 &lt;span style=&quot;color: #009a87;&quot;&gt;이름으로 지정&lt;/span&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;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Component(&quot;sWasher&quot;)
public class SWasher implements Washer {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Component(&quot;turboWasher&quot;)
public class TurboWasher implements Washer {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 주입할 때 이렇게 쓴다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;public WasherUser(@Qualifier(&quot;turboWasher&quot;) Washer washer) {
    this.washer = washer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;b&gt;Spring&lt;/b&gt;은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;turboWasher Bean&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;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 타입으로 후보 찾기 (Washer)
2. 이름으로 필터링 (turboWasher)
3. 최종 Bean 선택
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;@Autowired&lt;/b&gt;&lt;/span&gt;는 기본적으로 타입으로 Bean을 찾는다.&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;그래서 선택 기준을 추가로 주는 것이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@Qualifier&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;여기서 Bean 이름은 어떻게 정해질까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component를 이렇게 쓰면&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class SWasher {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean 이름은 기본적으로 클래스 이름에서 앞글자만 소문자로 바뀐다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sWasher
&lt;/code&gt;&lt;/pre&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;그래서 @Qualifier에도 이 이름을 넣어야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Qualifier(&quot;sWasher&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 방법이 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component(&quot;myWasher&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 직접 이름을 지정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Bean 이름은 myWasher가 된다.&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;@Bean도 마찬가지다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public SWasher sWasher() {
    return new SWasher();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Bean 이름은 메서드 이름을 따라간다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sWasher
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;@Qualifier(&quot;sWasher&quot;)&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;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Autowired
타입 기준으로 Bean을 찾는다&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Qualifier
이름 기준으로 Bean을 선택한다&lt;/code&gt;&lt;/pre&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@Primary&lt;/b&gt;&lt;/span&gt;는 여러 Bean 중에서 &lt;b&gt;기본으로 사용할 Bean을 지정&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@Primary
public class SWasher implements Washer {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Washer 타입이 여러 개 있어도,&lt;br /&gt;기본적으로 SWasher가 먼저 선택된다.&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;이 상태에서는 @Qualifier 없이도 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 특정 상황에서 다른 Bean을 쓰고 싶다면&lt;br /&gt;그때는 @Qualifier를 같이 사용하면 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 타입으로 후보 찾기
2. @Primary 있으면 우선 선택
3. @Qualifier 있으면 정확하게 선택
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실무에서는 이렇게 사용한다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;기본 Bean은 @Primary
특정 상황은 @Qualifier
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 한 줄로 정리하면 이렇다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Autowired는 타입으로 찾고
@Qualifier는 이름으로 고른다
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/386</guid>
      <comments>https://cainyun.tistory.com/386#entry386comment</comments>
      <pubDate>Wed, 6 May 2026 16:57:05 +0900</pubDate>
    </item>
    <item>
      <title>@Component vs @Bean</title>
      <link>https://cainyun.tistory.com/385</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;묵시적 DI와 명시적 DI는 어떻게 다를까&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;Spring에서 DI를 배우다 보면 자연스럽게 두 가지 방식이 등장한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@Bean
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 보면 둘 다&lt;b&gt; Bean 등록&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 결론부터 보면 이렇게 정리된다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component &amp;rarr; 자동 등록 (묵시적 DI)
@Bean &amp;rarr; 직접 등록 (명시적 DI)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 하나씩 이해해보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;&lt;/span&gt;부터 보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class SWasher implements Washer {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성하면 Spring은 이 클래스를 자동으로 Bean으로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 가능한 이유는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ComponentScan&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;Spring은 실행될 때 특정 패키지를 스캔하면서 @Component가 붙은 클래스를 전부 찾아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내부에서 이런 흐름이 일어난다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;1. 클래스 발견 (@Component)
2. 객체 생성
3. Container에 Bean으로 등록
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개발자는 &lt;b&gt;new를 한 번도 쓰지 않는다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;묵시적 DI&lt;/b&gt;&lt;/span&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;이걸 Bean으로 만들어주세요&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 코드를 직접 쓰지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 Spring이 알아서 처리한다.&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Controller
Service
Repository
비즈니스 로직 클래스&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 클래스들은 대부분 @Component 계열로 등록한다.&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 class=&quot;less&quot;&gt;&lt;code&gt;@Controller
@Service
@Repository
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들도 전부&lt;span style=&quot;color: #009a87;&quot;&gt; &lt;b&gt;@Component&lt;/b&gt;&lt;/span&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;이제 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@Bean&lt;/b&gt;&lt;/span&gt;을 보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public SWasher sWasher() {
        return new SWasher();
    }
}&lt;/code&gt;&lt;/pre&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;이번에는 Spring이 자동으로 찾는 게 아니라,&lt;br /&gt;개발자가 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;직접 Bean을 등록&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. @Configuration 클래스 로딩
2. @Bean 메서드 실행
3. 반환 객체를 Bean으로 등록
&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;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;이 객체를 Bean으로 등록해줘&quot;
&lt;/code&gt;&lt;/pre&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: #006dd7;&quot;&gt;&lt;b&gt;명시적 DI&lt;/b&gt;&lt;/span&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 차이가 하나 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component는 클래스 기반
@Bean은 메서드 기반
&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;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component는 클래스 위에 붙인다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
class A {}
&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;@Bean은 메서드 위에 붙인다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Bean
public A a() {
    return new A();
}
&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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;&lt;/span&gt;를 사용하는 경우다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;내가 만든 클래스
구조가 단순한 객체
비즈니스 로직 클래스
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는 무조건 &lt;b&gt;@Component 계열을 쓰는 게 &lt;span style=&quot;color: #006dd7;&quot;&gt;편하다&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 &lt;b&gt;@Bean을 사용하는 경우는 명확&lt;/b&gt;하다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;외부 라이브러리 객체
생성 과정이 복잡한 객체
설정이 필요한 객체
&lt;/code&gt;&lt;/pre&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;ObjectMapper&lt;/b&gt;를 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;ObjectMapper mapper = new ObjectMapper();
&lt;/code&gt;&lt;/pre&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;그래서 여기에 @Component를 붙일 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 이렇게 등록한다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 @Bean이 필요한 이유다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 중요한 차이는 &lt;span style=&quot;color: #f89009;&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;@Bean에서는&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; 파라미터로 자동 주입이 가능&lt;/b&gt;&lt;/span&gt;하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
public WasherUser washerUser(SWasher washer) {
    WasherUser user = new WasherUser();
    user.setWasher(washer);
    return user;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 washer는 Spring이 자동으로 넣어준다.&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: #8a3db6;&quot;&gt;&lt;b&gt;@Autowired&lt;/b&gt;&lt;/span&gt;를 따로 안 써도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 보면 처음에는 이렇게 생각하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;어차피 set으로 넣는 거면 강결합 아닌가?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 누가 객체를 &lt;b&gt;new로 생성했느냐&lt;/b&gt; 가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new로 생성한 것이 아니면 강한 결합으로 볼 수 없다.&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 class=&quot;mipsasm&quot;&gt;&lt;code&gt;Spring이 SWasher 생성
&amp;rarr; washerUser 메서드 호출
&amp;rarr; washer를 파라미터로 전달
&amp;rarr; WasherUser에 주입
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 &lt;b&gt;new&lt;/b&gt; SWasher()를 한 적이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;객체 생성 책임은 여전히 Spring&lt;/span&gt;&lt;/b&gt;에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결합도가 낮다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;묵시적 DI (@Component)&lt;/h4&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;자동 등록
코드 간결
구조 파악 어려울 수 있음
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;명시적 DI (@Bean)&lt;/h4&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;직접 등록
구조 명확
코드 길어질 수 있음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실무에서는 이렇게 사용한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;기본은 @Component
필요할 때만 @Bean
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 외부 라이브러리는 거의 무조건 @Bean이다.&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/385</guid>
      <comments>https://cainyun.tistory.com/385#entry385comment</comments>
      <pubDate>Wed, 6 May 2026 16:18:06 +0900</pubDate>
    </item>
    <item>
      <title>DI는 왜 필요할까? 객체 생성 책임과 Spring Container</title>
      <link>https://cainyun.tistory.com/384</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Java로 프로그램을 만들다 보면 자연스럽게 객체를 생성하게 된다.&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class WasherUser {

    private SWasher washer = new SWasher();

    public void wash(){
        washer.wash();
    }
}&lt;/code&gt;&lt;/pre&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;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;이 코드는 WasherUser가 SWasher에 강하게 묶여 있는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 코드 안에 이렇게 박혀 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;new SWasher()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 WasherUser는 이렇게 말하는 것과 같다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;나는 무조건 SWasher만 쓸 거야
&lt;/code&gt;&lt;/pre&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class TurboWasher {}
&lt;/code&gt;&lt;/pre&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;pre class=&quot;haxe&quot;&gt;&lt;code&gt;private TurboWasher washer = new TurboWasher();
&lt;/code&gt;&lt;/pre&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장한 개념이 DI, 의존성 주입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI는 말 그대로 '필요한 객체를 외부에서 넣어준다'는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 이렇게 바꿔보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class WasherUser {

    private Washer washer;

    public WasherUser(Washer washer) {
        this.washer = washer;
    }

    public void wash(){
        washer.wash();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 변화는 new가 사라졌다는 점이다.&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;이제 WasherUser는 이렇게 말한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;나는 washer가 필요해
누가 만들어줄지는 몰라
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 외부에서 객체를 넣어준다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Washer washer = new SWasher();
WasherUser user = new WasherUser(washer);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 DI다.&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Washer washer = new TurboWasher();
WasherUser user = new WasherUser(washer);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 바꾸면 된다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;객체 생성 책임과 객체 사용 책임을 분리한다
&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;span style=&quot;letter-spacing: 0px;&quot;&gt;즉, 여기까지는 순수 Java DI다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Spring이 등장한다.&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;Spring은 이 DI를 자동으로 해주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 직접 객체를 생성하지 않아도, Spring이 대신 객체를 만들고 넣어준다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Component
public class SWasher implements Washer {}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class WasherUser {

    private final Washer washer;

    public WasherUser(Washer washer) {
        this.washer = washer;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 Spring이 하는 일은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. SWasher 발견
2. WasherUser 발견
3. SWasher 객체 생성
4. WasherUser 생성 시 washer 자동 주입
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 new를 한 번도 쓰지 않았다.&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;color: #f89009;&quot;&gt;&lt;b&gt;IoC (Inversion of Control)&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;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;개발자가 객체 생성
개발자가 객체 연결
개발자가 전체 흐름 제어
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 방식은 이렇다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Spring이 객체 생성
Spring이 객체 연결
Spring이 전체 흐름 제어
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 제어권이 개발자에서 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;이 구조를 관리하는 공간이 바로 Spring Container다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Container는 쉽게 말하면 객체 창고다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Bean을 저장
Bean을 생성
Bean을 관리
Bean을 주입
&lt;/code&gt;&lt;/pre&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;여기서 Bean이라는 개념이 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean은 단순하다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Spring이 관리하는 객체
&lt;/code&gt;&lt;/pre&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;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;SWasher
WasherUser
MemberService
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 객체들이 전부 Bean이 된다.&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;그리고 Container에서 이렇게 꺼낼 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ApplicationContext ctx = ...;
WasherUser user = ctx.getBean(WasherUser.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 이렇게 해석하면 된다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Spring아 WasherUser 하나 줘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 이미 만들어둔 객체를 꺼내 쓰는 것이다.&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;여기까지 정리하면 DI의 핵심은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;객체를 직접 만들지 않는다
필요한 객체를 외부에서 받는다
생성과 관리는 Spring이 담당한다
&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;/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;예를 들어 테스트에서는 가짜 객체를 넣을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Washer fake = new FakeWasher();
WasherUser user = new WasherUser(fake);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 없이도 테스트가 가능하다.&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;Spring이 Bean을 생성하고 관리하기 때문에,&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;마지막으로 한 줄 정리하면 이렇다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;DI는 객체 생성 책임을 외부로 넘겨 결합도를 낮추는 설계
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/384</guid>
      <comments>https://cainyun.tistory.com/384#entry384comment</comments>
      <pubDate>Wed, 6 May 2026 15:55:26 +0900</pubDate>
    </item>
    <item>
      <title>Spring 프로젝트 배포 흐름 정리: Docker, Kubernetes</title>
      <link>https://cainyun.tistory.com/383</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프로젝트를 만들다 보면 처음에는 코드 작성에 집중하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller를 만들고, Service를 만들고, Repository를 만들고, 테스트 코드도 작성한다.&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;ldquo;내 컴퓨터에서 실행되는 건 알겠는데, 이걸 실제 서버에서는 어떻게 돌리지?&amp;rdquo;&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Docker, Kubernetes&lt;span style=&quot;color: #000000;&quot;&gt;를 이해하는 것&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;p data-ke-size=&quot;size16&quot;&gt;Docker는 실행 파일과 실행 환경을 함께 묶어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes는 그렇게 실행된 컨테이너들을 운영 환경에서 관리해준다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프로젝트를 서버에서 실행하려면 먼저 실행 가능한 결과물이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven은 이 결과물을 만들어주는 역할을 한다.&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;Maven은 코드를 컴파일하고, 테스트를 실행하고, 필요한 파일들을 하나로 묶어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 프로젝트에서는 보통 jar 파일이 만들어진다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;mvn clean package
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 끝나면 보통 이런 파일이 생긴다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;target/app.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 jar 파일은 실행 가능한 자바 프로그램이다.&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;서버에 Java가 설치되어 있다면 아래처럼 실행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;java -jar app.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 Maven으로 jar 파일을 만들고 서버에서 실행하면 끝나는 것처럼 보인다.&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;span style=&quot;color: #ef5369;&quot;&gt;환경 차이&lt;/span&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;내 컴퓨터에는 Java 17이 설치되어 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에는 Java 11만 있을 수도 있다.&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;그냥 단순하게 'jar 파일만 옮기면 끝'이라고 생각하면 문제가 생길 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker는 왜 필요할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Docker&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;background-color: #f6e199;&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;Spring Boot 애플리케이션을 실행하려면 Java가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker는 &lt;b&gt;app.jar&lt;/b&gt;와 &lt;b&gt;Java 실행 환경&lt;/b&gt;을 함께 묶어서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Docker Image&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: #ef5369;&quot;&gt;&lt;b&gt;Docker Image&lt;/b&gt;&lt;/span&gt;는&lt;b&gt; 실행 준비가 끝난&lt;/b&gt; 포장 상태라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 아직 실행된 것은 아니지만, 언제든 &lt;span style=&quot;color: #ef6f53;&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;프로젝트에서 자주 보이는 간단한 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Dockerfile&lt;/b&gt;&lt;/span&gt;은 이런 식으로 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk

COPY target/app.jar app.jar

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은 Java &lt;b&gt;17&lt;/b&gt; 환경을 기반으로 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven으로 만든&lt;b&gt; app.jar&lt;/b&gt;를 &lt;b&gt;이미지 안&lt;/b&gt;에 복사한 뒤,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨테이너&lt;/b&gt;가 실행될 때 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;java -jar app.jar&lt;/span&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;Docker Image는 docker build 명령어로 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;docker build -t my-spring-app .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;b&gt;my-spring-app &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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이미지를 실제로 실행하면 Container&lt;/b&gt;&lt;/span&gt;가 된다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;docker run my-spring-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Docker Image는 실행 준비가 끝난 포장이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Container는 그 포장을 실제로 실행한 상태다.&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;이 말은 같은 Spring 애플리케이션을 여러 개 실행할 수 있다는 뜻이다.&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;/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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 여기서 이제,&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kubernetes는 대체 왜 필요할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 하나라면 Docker만으로도 관리할 수 있다.&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: #ef5369;&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;컨테이너가 죽었을 때 다시 실행해야 하고,&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt;&lt;/span&gt;는 이런 &lt;span style=&quot;background-color: #f6e199;&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;Kubernetes는 컨테이너가 죽으면 다시 실행한다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 Kubernetes에서 나오는 Pod란 무엇일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes에서는 컨테이너를 직접 하나씩 관리하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Pod라는 단위&lt;/b&gt;&lt;/span&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;Pod는 Kubernetes에서 &lt;span style=&quot;color: #ef5369;&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;보통 처음 이해할 때는&lt;b&gt; Pod 하나 안에 컨테이너 하나&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;Spring 애플리케이션 컨테이너가 하나 실행되면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes에서는 Pod 하나가 실행된다고 볼 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deployment는 무엇일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod 하나만 띄우는 것은 Docker로 컨테이너 하나 실행하는 것과 크게 다르지 않다.&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;Deployment&lt;/b&gt;는 &lt;b&gt;Pod의 개수를 관리하는 설정&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;예를 들어 Spring 애플리케이션 Pod를 3개 유지하고 싶다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deployment에 3개를 유지하라고 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;replicas: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Kubernetes는 Pod 3개가 유지되도록 관리한다.&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;만약 Pod 하나가 죽으면 Kubernetes가 다시 하나를 만든다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Deployment는 원하는 상태를 계속 유지하게 해주는 설정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Service는 무엇일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pod가 여러 개&lt;/b&gt; 있으면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;사용자의 요청을 어디로 보낼지&lt;/b&gt;&lt;/span&gt; 정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 Pod 하나하나의 주소를 직접 알고 접근하는 것은 현실적이지 않다.&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;Service는 여러 Pod 앞에서 요청을 받아주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 Service로 요청을 보내고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service는 뒤에 있는 Pod 중 하나로 요청을 전달한다.&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;이때 요청을 여러 Pod에 나눠주는 것을 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;로드 분산&lt;/b&gt;&lt;/span&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로드&lt;/b&gt;는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요청량&lt;/b&gt;&lt;/span&gt;이나 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;작업량&lt;/b&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;로드 밸런싱&lt;/b&gt;&lt;/span&gt;은 이 로드 분산을 실제로 수행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 요청을 Pod 1, Pod 2, Pod 3에 나눠 보내는 것이다.&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;Spring 프로젝트 배포 흐름은 이렇게 연결된다.&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;Maven은 Spring 코드를 jar 파일로 만든다.&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;Docker는 jar 파일과 Java 실행 환경을 함께 묶어 이미지로 만든다.&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;Docker Image를 실행하면 Container가 된다.&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;Kubernetes는 Container를 Pod 단위로 실행하고 관리한다.&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;Deployment는 Pod 개수를 유지한다.&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;Service는 요청을 여러 Pod로 나눠준다.&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;처음에는 Maven, Docker, Kubernetes가 서로 다른 개념처럼 느껴진다.&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;Maven은 실행 파일을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker는 실행 환경까지 함께 포장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes는 운영 환경에서 컨테이너들이 안정적으로 돌아가도록 관리한다.&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;이렇게 보면 Docker와 Kubernetes가 왜 필요한지 조금 더 자연스럽게 이해된다.&lt;/p&gt;</description>
      <category>CS</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/383</guid>
      <comments>https://cainyun.tistory.com/383#entry383comment</comments>
      <pubDate>Wed, 6 May 2026 15:29:38 +0900</pubDate>
    </item>
    <item>
      <title>Maven, 빌드 자동화 도구에 대해서</title>
      <link>https://cainyun.tistory.com/382</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프로젝트를 하다 보면 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Maven&lt;/span&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;그래서 처음에는 Maven도 Spring처럼 프레임워크인가? 라는 생각이 들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Maven은 애플리케이션의 흐름을 제어하지 않는다.&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;Maven은 프로젝트를 빌드하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;필요한 라이브러리를 관리해주는 도구&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Maven은 무엇을 하는 도구일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven은 크게 두 가지 역할을 한다.&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: #ef5369;&quot;&gt;&lt;b&gt;빌드 자동화&lt;/b&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;의존성 관리&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 필요한 라이브러리를 자동으로 다운로드하고 관리해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pom.xml이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven 프로젝트를 보면 &lt;b&gt;pom.xml&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;/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;예를 들어 JUnit을 사용하고 싶다면 이렇게 작성한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;junit-jupiter&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 추가하면 Maven이 알아서 JUnit 라이브러리를 다운로드해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dependency란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dependency는 '이 프로젝트가 의존하는 라이브러리' 를 의미한다.&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;Spring, JUnit, MySQL Driver 같은 것들이 모두 dependency다.&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;scope란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scope는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이 라이브러리를 언제 사용할지&lt;/b&gt;&lt;/span&gt;를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 scope는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;compile&lt;/b&gt;&lt;br /&gt;기본값이다. 컴파일할 때도 사용하고 실행할 때도 사용한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;runtime&lt;/b&gt;&lt;br /&gt;실행할 때만 필요하다. 컴파일할 때는 필요 없다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;provided&lt;/b&gt;&lt;br /&gt;이미 서버에서 제공되는 라이브러리다. 예를 들어 Servlet API가 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;test&lt;/b&gt;&lt;br /&gt;테스트 코드에서만 사용하는 라이브러리다. JUnit이 여기에 해당한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Servlet API와 Tomcat&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 프로젝트를 하면 Servlet 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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Servlet API&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;웹 요청을 처리하기 위한 규칙&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HttpServletRequest&lt;/b&gt;, &lt;b&gt;HttpServletResponse&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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Tomcat&lt;/b&gt;&lt;/span&gt;은 웹 서버이면서 &lt;b&gt;Servlet Container&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;Servlet을 실행해주는 프로그램&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;그래서 Tomcat이 있으면 우리가 만든 서블릿을 실행할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JDBC Driver&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB를 사용하려면 JDBC Driver가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 자바 프로그램과 DB를 연결해주는 라이브러리다.&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;MySQL을 쓰면 MySQL Driver를 dependency로 추가해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Maven&lt;/b&gt; 의 life cycle이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Maven&lt;/b&gt;에는 정해진 작업 흐름이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 &lt;b&gt;lifecycle&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;clean&lt;/b&gt; &amp;rarr; 이전 빌드 결과 삭제&lt;br /&gt;&lt;b&gt;compile&lt;/b&gt; &amp;rarr; 코드 컴파일&lt;br /&gt;&lt;b&gt;test&lt;/b&gt; &amp;rarr; 테스트 실행&lt;br /&gt;&lt;b&gt;package&lt;/b&gt; &amp;rarr; jar 또는 war 생성&lt;br /&gt;&lt;b&gt;install&lt;/b&gt; &amp;rarr; 로컬 저장소에 저장&lt;br /&gt;&lt;b&gt;deploy&lt;/b&gt; &amp;rarr; 서버에 배포&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 순서대로 실행해준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;jar와 war&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 결과물은 보통 jar 또는 war 형태로 나온다.&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;jar&lt;/b&gt;는 &lt;b&gt;일반 자바 애플리케이션 파&lt;/b&gt;일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;war&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;size16&quot;&gt;Tomcat 같은 서버에 올릴 때는 war 파일을 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;plugin과 goal&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven은 plugin을 통해 기능을 확장한다.&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;plugin 안에는 goal이라는 작업 단위가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 compile, test 같은 작업도 내부적으로는 plugin의 goal이다.&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/382</guid>
      <comments>https://cainyun.tistory.com/382#entry382comment</comments>
      <pubDate>Wed, 6 May 2026 14:59:49 +0900</pubDate>
    </item>
    <item>
      <title>JUnit과 Assertion으로 테스트 코드 작성하기</title>
      <link>https://cainyun.tistory.com/381</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring으로 개발을 하다 보면 자연스럽게 테스트 코드를 작성하게 된다.&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;이때 사용하는 것이 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;JUnit&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JUnit이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit은 자바에서&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; 테스트 코드를 작성하고 실행하기 위한 라이브러리&lt;/b&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;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public int add(int a, int b) {
    return a + b;
}
&lt;/code&gt;&lt;/pre&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void addTest() {
    int result = add(2, 3);
    Assertions.assertEquals(5, result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성하면 add(2, 3)의 결과가 5인지 자동으로 검증해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다르면 테스트가 실패한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Test는 무엇일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Test&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;JUnit은 @Test가 붙은 메서드를 찾아서 자동으로 실행한다.&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;b&gt;실행 전이나 후에 공통 작업&lt;/b&gt;이 필요한 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 사용하는 것이 아래 어노테이션이다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@BeforeEach
void setUp() {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테스트가 실행되기 전에 매번 실행된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@AfterEach
void tearDown() {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테스트가 끝난 후에 실행된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@BeforeAll
static void init() {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 테스트 시작 전에 한 번만 실행된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@AfterAll
static void close() {
}
&lt;/code&gt;&lt;/pre&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;&lt;b&gt;@DisplayName&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 이름을 더 보기 좋게 바꾸고 싶을 때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@DisplayName(&quot;덧셈 테스트&quot;)
@Test
void addTest() {
}&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;size18&quot;&gt;&lt;b&gt;Assertion이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Assertion은 결과가&lt;span style=&quot;color: #ef5369;&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;가장 많이 사용하는 것이 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;assertEquals&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Assertions.assertEquals(기대값, 실제값);&lt;/code&gt;&lt;/pre&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;(&lt;span style=&quot;color: #ef5369;&quot;&gt;기대값&lt;/span&gt;, &lt;span style=&quot;color: #006dd7;&quot;&gt;실제값&lt;/span&gt;)&lt;/b&gt;&lt;/span&gt; 순서로 작성해야 한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertEquals(5, result);
&lt;/code&gt;&lt;/pre&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;size18&quot;&gt;자주 사용하는 Assertion&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertNotEquals(1, result);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 다를 때 통과한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertNull(value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 null이면 통과한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertNotNull(value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 null이 아니면 통과한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertTrue(condition);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 true면 통과한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertFalse(condition);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 false면 통과한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Assertions.assertThrows(Exception.class, () -&amp;gt; {
    method();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외가 발생하면 통과한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;Assertions.assertAll(
    () -&amp;gt; Assertions.assertEquals(1, a),
    () -&amp;gt; Assertions.assertEquals(2, b)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertAll을 쓰면 여러 검증을 한 번에 수행한다.&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;equals&lt;/b&gt;와 &lt;b&gt;same&lt;/b&gt; 차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertEquals는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;값이 같은지&lt;/b&gt;&lt;/span&gt;를 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertSame은 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;객체가 같은지&lt;/b&gt;&lt;/span&gt;를 비교한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Assertions.assertSame(obj1, obj2);&lt;/code&gt;&lt;/pre&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;Given - When - Then&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성할 때 자주 사용하는 구조가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given - When - Then 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Given&lt;/b&gt;&lt;br /&gt;테스트를 위한 준비&lt;br /&gt;&lt;br /&gt;&lt;b&gt;When&lt;/b&gt;&lt;br /&gt;테스트할 동작 수행&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Then&lt;/b&gt;&lt;br /&gt;결과 검증&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void addTest() {

    // Given
    int a = 2;
    int b = 3;

    // When
    int result = add(a, b);

    // Then
    Assertions.assertEquals(5, result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 테스트 코드가 읽기 쉬워진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;F.I.R.S.T 원칙&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Fast&lt;/b&gt;&lt;/span&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;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Independent&lt;/b&gt;&lt;/span&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;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;Repeatable&lt;/b&gt;&lt;/span&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;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Self-Validating&lt;/b&gt;&lt;/span&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;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Timely&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;코드 작성과 함께 테스트를 작성하는 것이 좋다.&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/381</guid>
      <comments>https://cainyun.tistory.com/381#entry381comment</comments>
      <pubDate>Wed, 6 May 2026 14:51:47 +0900</pubDate>
    </item>
    <item>
      <title>Spring Bean과 Spring Container, 객체를 Spring이 관리하는 이유</title>
      <link>https://cainyun.tistory.com/380</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서 DI를 배우고 나면 자연스럽게 이런 생각이 든다.&lt;br /&gt;&lt;br /&gt;&quot;객체를 외부에서 넣어준다는 건 알겠어.&lt;br /&gt;근데 대체 누가 객체를 만들고 관리하는 거지?&quot;&lt;br /&gt;&lt;br /&gt;여기서 등장하는 개념이 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Spring Bean&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Spring Container&lt;/b&gt;&lt;/span&gt;다.&lt;br /&gt;&lt;br /&gt;Spring에서는 객체를 개발자가 계속 직접 new로 만드는 방식보다,&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Spring이 객체를 생성하고 관리하도록&lt;/b&gt; 맡기는 방식을 많이 사용한다.&lt;br /&gt;&lt;br /&gt;그리고&amp;nbsp;Spring이&amp;nbsp;관리하는&amp;nbsp;객체를&amp;nbsp;Bean이라고&amp;nbsp;부른다.&lt;br /&gt;&lt;br /&gt;이 구조를 이해하면 왜 Spring에서&lt;b&gt; @Component, @Service, @Repository, @Autowired&lt;/b&gt; 같은 어노테이션을 사용하는지도 자연스럽게 연결된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring&amp;nbsp;Container란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring&amp;nbsp;Container는&amp;nbsp;Spring이&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;객체를&amp;nbsp;생성하고&amp;nbsp;관리하는&amp;nbsp;공간&lt;/b&gt;&lt;/span&gt;이다.&lt;br /&gt;원래&amp;nbsp;자바에서는&amp;nbsp;객체를&amp;nbsp;직접&amp;nbsp;생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778044668474&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MemberService memberService = new MemberService();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 new를 사용해서 직접 객체를 만든다.&lt;br /&gt;그런데 Spring에서는 객체를 개발자가 직접 계속 만들지 않는다.&lt;br /&gt;&lt;br /&gt;대신&amp;nbsp;Spring&amp;nbsp;Container가&amp;nbsp;객체를&amp;nbsp;만들고&amp;nbsp;보관한다.&lt;br /&gt;그리고 필요한 곳에 객체를 넣어준다.&lt;br /&gt;&lt;br /&gt;예를 들어 이런 코드가 있다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1778044773154&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

}&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@Service&lt;/b&gt;&lt;/span&gt;를 붙이면 Spring은 실행될 때 이 클래스를 보고 객체를 생성한다.&lt;br /&gt;그리고&amp;nbsp;Spring&amp;nbsp;Container&amp;nbsp;안에서&amp;nbsp;관리한다.&lt;br /&gt;&lt;br /&gt;즉, 개발자가 직접 new MemberService()를 호출하지 않아도 Spring이 객체를 만들어두는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bean이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bean&lt;/b&gt;은&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&amp;nbsp;Spring이&amp;nbsp;관리하는&amp;nbsp;객체&lt;/b&gt;&lt;/span&gt;를 말한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778044847331&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

}&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;즉, 이 객체는 Spring이 관리하므로 Bean이다.&lt;br /&gt;반대로 그냥 일반 객체를 만들면 Bean이 아니다.&lt;/p&gt;
&lt;pre id=&quot;code_1778044873865&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MemberService memberService = new MemberService();&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;그러므로, 얘는 Bean이 아니기 때문에 Spring Container가 관리하지 않는다.&lt;br /&gt;즉&amp;nbsp;정리하면&amp;nbsp;이런&amp;nbsp;느낌이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 왜 굳이 Spring이 객체를 관리할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장&amp;nbsp;큰&amp;nbsp;이유는&amp;nbsp;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;객체&amp;nbsp;연결을&amp;nbsp;자동으로&amp;nbsp;해주기&amp;nbsp;때문&lt;/b&gt;&lt;/span&gt;이다.&lt;br /&gt;&lt;br /&gt;예를 들어 회원 정보를 저장하는 Repository와 회원가입 로직을 처리하는 Service가 있다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1778044947057&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public class MemberRepository {

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1778044953697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}&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;MemberService는 MemberRepository가 필요하다.&lt;br /&gt;원래라면&lt;b&gt; 개발자가 직접 객체를 만들고 연결&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778044981169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MemberRepository repository = new MemberRepository();

MemberService service = new MemberService(repository);&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;br /&gt;하지만 프로젝트가 커지면 객체 수가 엄청 많아진다.&lt;br /&gt;&lt;br /&gt;Controller, Service, Repository, Config 객체들이 계속 늘어난다.&lt;br /&gt;그걸 전부 개발자가 new로 만들고 연결하면 코드가 복잡해진다.&lt;br /&gt;그래서 Spring이 대신 객체를 만들고 연결해주는 것이다.&lt;br /&gt;&lt;br /&gt;Spring이 실행되면서&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1.&amp;nbsp;MemberRepository&amp;nbsp;객체&amp;nbsp;생성&lt;br /&gt;2.&amp;nbsp;MemberService&amp;nbsp;객체&amp;nbsp;생성&lt;br /&gt;3.&amp;nbsp;MemberService&amp;nbsp;생성자에&amp;nbsp;MemberRepository&amp;nbsp;넣기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;과정을&amp;nbsp;자동으로&amp;nbsp;수행한다.&lt;br /&gt;이걸 DI라고 했다.&lt;br /&gt;&lt;br /&gt;즉, DI를 실제로 수행하는 주체가 Spring Container다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Component, @Service, @Repository는 왜 쓰는 걸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어노테이션들은 Spring에게 '이 객체를 관리해줘' 라고 알려주는 역할이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778045473192&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class OrderService {

}&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;Spring은 이 클래스를 보고 Bean으로 등록한다.&lt;br /&gt;&lt;b&gt;@Service&lt;/b&gt;도&amp;nbsp;비슷하다.&lt;/p&gt;
&lt;pre id=&quot;code_1778045489600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

}&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;&lt;/span&gt;와 거의 같다.&lt;br /&gt;다만 역할을 더 명확하게 표현하기 위해 사용하는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- @Controller &amp;rarr; 요청 처리&lt;br /&gt;-&amp;nbsp;@Service&amp;nbsp;&amp;rarr;&amp;nbsp;비즈니스&amp;nbsp;로직&lt;br /&gt;-&amp;nbsp;@Repository&amp;nbsp;&amp;rarr;&amp;nbsp;DB&amp;nbsp;접근&lt;br /&gt;-&amp;nbsp;@Component&amp;nbsp;&amp;rarr;&amp;nbsp;일반&amp;nbsp;컴포넌트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이런 식으로 역할을 나눠서 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;@Autowired는 뭘까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@Autowired&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;필요한 객체를 자동으로 연결해달라&lt;/b&gt;&lt;/span&gt;는 의미다.&lt;/p&gt;
&lt;pre id=&quot;code_1778045579960&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;

}&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;Spring은&amp;nbsp;MemberRepository&amp;nbsp;Bean을&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: #ef5369;&quot;&gt;&lt;b&gt;생성자 주입을 더 많이 사용&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778045607047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}&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;Spring은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;생성자를 보고&lt;/b&gt;&lt;/span&gt; 필요한 객체를 자동으로 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;왜 생성자 주입을 더 많이 사용할까?&lt;br /&gt;이유는&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;안정성과&amp;nbsp;테스트&lt;/b&gt;&lt;/span&gt; 때문이다.&lt;br /&gt;&lt;br /&gt;생성자 주입을 사용하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 생성될 때 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;필요한 의존성이 반드시 들어오게&lt;/b&gt;&lt;/span&gt; 만들 수 있다.&lt;br /&gt;예를 들어 MemberRepository 없이는 MemberService가 동작할 수 없다면,&lt;br /&gt;생성자에서 무조건 받게 만드는 것이 더 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 테스트할 때도 편하다.&lt;/p&gt;
&lt;pre id=&quot;code_1778045677015&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MemberRepository fakeRepository = new FakeMemberRepository();

MemberService service = new MemberService(fakeRepository);&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;br /&gt;&lt;br /&gt;만약 클래스 내부에서 직접 new MemberRepository()를 했다면,&lt;br /&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;싱글톤이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Bean은 &lt;b&gt;기본적으로 &lt;span style=&quot;color: #f89009;&quot;&gt;싱글톤&lt;/span&gt;으로 관리&lt;/b&gt;된다.&lt;br /&gt;싱글톤은 객체를&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;하나만 생성해서 여러 곳에서 공유하는 방식&lt;/b&gt;&lt;/span&gt;이다.&lt;br /&gt;&lt;br /&gt;예를 들어 MemberService Bean이 있다면,&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;br /&gt;&lt;br /&gt;왜&amp;nbsp;이렇게&amp;nbsp;할까?&lt;br /&gt;계속 새로 만들면 메모리 사용량이 늘어나고 성능도 떨어질 수 있기 때문이다.&lt;br /&gt;&lt;br /&gt;특히 웹 서버에서는 동시에 수많은 요청이 들어온다.&lt;br /&gt;그때마다 객체를 계속 새로 만들면 부담이 커질 수 있다.&lt;br /&gt;그래서&amp;nbsp;Spring은&amp;nbsp;기본적으로&amp;nbsp;Bean을&amp;nbsp;하나만&amp;nbsp;생성하고&amp;nbsp;공유한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bean&amp;nbsp;생명주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Bean도 &lt;b&gt;생성되고 사라지는 과정&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 객체 생성&lt;br /&gt;2.&amp;nbsp;의존성&amp;nbsp;주입&lt;br /&gt;3.&amp;nbsp;초기화&amp;nbsp;작업&lt;br /&gt;4.&amp;nbsp;사용&lt;br /&gt;5.&amp;nbsp;종료&amp;nbsp;작업&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 DB 연결 객체라면 종료 전에 연결을 닫아야 할 수도 있다.&lt;br /&gt;Spring은&amp;nbsp;이런&amp;nbsp;생명주기도&amp;nbsp;관리해줄&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;즉 Spring Container는 단순히 객체만 저장하는 것이 아니라,&lt;br /&gt;&lt;b&gt;객체&amp;nbsp;생성부터&lt;span style=&quot;color: #409d00;&quot;&gt;&amp;nbsp;관리,&amp;nbsp;연결,&amp;nbsp;종료&lt;/span&gt;까지&amp;nbsp;담당하는&amp;nbsp;공간&lt;/b&gt;이다.&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/380</guid>
      <comments>https://cainyun.tistory.com/380#entry380comment</comments>
      <pubDate>Wed, 6 May 2026 14:39:41 +0900</pubDate>
    </item>
    <item>
      <title>Spring의 DI, AOP, PSA, POJO 이해하기</title>
      <link>https://cainyun.tistory.com/379</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring을 공부하다 보면 DI, AOP, PSA, POJO라는 단어가 계속 나온다.&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;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;반복되는 공통 기능은 따로 분리해주고,&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: #ef5369;&quot;&gt;&lt;b&gt;DI, AOP, PSA, POJO&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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DI란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&gt;&lt;/span&gt;의 약자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ef5369;&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;여기서 먼저 의존성이라는 말을 이해해야 한다.&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;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Car {

    private Engine engine = new Engine();

    public void drive() {
        engine.start();
        System.out.println(&quot;자동차가 출발합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 Car는 Engine을 직접 만들고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;new&lt;/b&gt; Engine()을 Car 클래스 안에서 직접 호출하고 있다.&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;하지만 이렇게 작성하면 Car는 Engine이라는 객체에 강하게 묶인다.&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;만약 나중에 Engine을 다른 종류로 바꾸고 싶으면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 일반 엔진이 아니라 전기 엔진을 쓰고 싶다면 Car 코드 안쪽을 직접 수정해야 할 수 있다.&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;DI는 이 문제를 줄이기 위한 방식이다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;알아서&lt;/b&gt; &lt;/span&gt;넣어주는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        engine.start();
        System.out.println(&quot;자동차가 출발합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Car가 new 를 통해 Engine을 직접 생성하지 않는다.&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;코드를 보면, 생성자를 통해 외부에서 Engine을 받는다.&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;Car는 drive()라는 자기 역할에 집중할 수 있다.&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;background-color: #f6e199;&quot;&gt;&lt;b&gt;필요한 객체를 외부에서 넣어주는 것이 의존성 주입&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 Spring이 해준다!&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;예를 들어 회원가입 기능을 처리하는 MemberService가 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 회원 정보를 저장하는 MemberRepository가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public void join(Member member) {
        memberRepository.save(member);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberService는 MemberRepository가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 직접 new MemberRepository()를 하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자로 MemberRepository를 받아서 사용한다.&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;Spring은 실행될 때 필요한 MemberRepository 객체를 찾아서 MemberService에 넣어준다.&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;DI를 쓰면 객체 사이의 결합도를 낮출 수 있다.&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;구현체가 바뀌어도 사용하는 쪽 코드가 덜 흔들린다.&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;그래서 DI를 사용하면 유지보수가 쉬워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트하기도 편해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? 원문 코드에서 그냥 new로 박아넣었으면 test 할 때도 불편한데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI로 주업받는 애면 test 할 때 그냥 MockDB만 내가 박아주면 되니까 편해진다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;MemberRepository fakeRepository = new FakeMemberRepository();

MemberService memberService = new MemberService(fakeRepository);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 의존성을 외부에서 넣을 수 있으면, 상황에 따라 필요한 객체를 바꿔 끼우기 쉽다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AOP란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Aspect Oriented Programming&lt;/b&gt;&lt;/span&gt;의 약자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리말로는 &lt;span style=&quot;color: #ef5369;&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;AOP는 여러 기능에 반복해서 들어가는 공통 코드를 따로 빼놓고,&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; 필요한 코드도 있다.&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;span style=&quot;color: #009a87;&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;AOP가 없으면 코드가 이렇게 섞일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public void order() {
    System.out.println(&quot;주문 시작 로그&quot;);

    checkAuth();

    // 주문 로직
    saveOrder();
    decreaseStock();

    System.out.println(&quot;주문 종료 로그&quot;);
}
&lt;/code&gt;&lt;/pre&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;AOP를 사용하면 이런 공통 기능을 따로 분리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public void order() {
    saveOrder();
    decreaseStock();
}
&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서 자주 보는 &lt;b&gt;AOP 예시&lt;/b&gt;가 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@Transactional&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Transactional
public void order() {
    saveOrder();
    decreaseStock();
    savePayment();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에는 트랜잭션 시작, commit, rollback 코드가 직접 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Spring은 이 메서드 실행 전후를 감싸서 트랜잭션 기능을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 AOP 이다.&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;AOP는 공통 기능을 따로 분리하고,&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PSA란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PSA는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Portable Service Abstraction&lt;/b&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;개발자가 &lt;b&gt;일관된 방식으로 사용&lt;/b&gt;할 수 있게 해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 말은 &lt;span style=&quot;background-color: #f6e199;&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;추상화는 복잡한 내부 구현을 숨기고,&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;
&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;PSA&lt;/b&gt;도 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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;대표적인 예가 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;트랜잭션 처리&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 접근 기술에는 여러 가지가 있다.&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;JDBC를 직접 쓸 수도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyBatis를 쓸 수도 있고,&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;MyBatis에서 트랜잭션을 처리하는 방식과 JPA에서 트랜잭션을 처리하는 방식은 내부적으로 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Spring에서는 개발자가 보통 같은 방식으로 트랜잭션을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Transactional
public void save() {
    // DB 저장 로직
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 구체적으로 내부 구현을 알 필요 없이, 그냥 @Transactional을 사용하면 끝이다&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;이게 PSA의 장점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술이 바뀌어도 사용하는 코드가 크게 흔들리지 않는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface TxManager {
    void begin();
    void commit();
    void rollback();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인터페이스를 기준으로 여러 구현체가 있을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class MyBatisTxManager implements TxManager {

    public void begin() {
        // MyBatis 방식 트랜잭션 시작
    }

    public void commit() {
        // MyBatis 방식 커밋
    }

    public void rollback() {
        // MyBatis 방식 롤백
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class JpaTxManager implements TxManager {

    public void begin() {
        // JPA 방식 트랜잭션 시작
    }

    public void commit() {
        // JPA 방식 커밋
    }

    public void rollback() {
        // JPA 방식 롤백
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 쪽에서는 구체적인 구현체를 매번 신경 쓰지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TxManager라는 추상화된 타입을 기준으로 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POJO란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POJO는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Plain Old Java Object&lt;/b&gt;&lt;/span&gt;의 약자다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 쉽게 생가갛면, &lt;span style=&quot;color: #ef5369;&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;특정 프레임워크나 라이브러리에 강하게 묶이지 않은 평범한 자바 클래스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 클래스는 POJO에 가깝다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Member {

    private String name;
    private int age;

    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
&lt;/code&gt;&lt;/pre&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;그 코드는 특정 기술에 강하게 묶이게 된다.&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 class=&quot;scala&quot;&gt;&lt;code&gt;public class MemberService extends SomeFrameworkClass implements SomeFrameworkInterface {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 MemberService는 SomeFrameworkClass나 SomeFrameworkInterface에 강하게 의존하게 된다.&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;Spring은 가능하면 &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 그 객체를 관리하면서 필요한 기능을 붙여주는 방식이다.&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;예를 들어 이런 Service 클래스가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Transactional
    public void order(Order order) {
        orderRepository.save(order);
    }
}
&lt;/code&gt;&lt;/pre&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;OrderService는 주문이라는 비즈니스 로직을 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 이 객체를 관리하면서 DI로 OrderRepository를 넣어준다.&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;그리고 @Transactional을 보고 AOP 방식으로 트랜잭션을 적용해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 접근 기술이 달라져도 PSA를 통해 일관된 방식으로 사용할 수 있게 도와준다.&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;즉, POJO를 기반으로 DI, AOP, PSA가 적용되는 구조다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Spring의 주요 특징을 POJO 기반의 DI, AOP, PSA라고 말한다.&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;POJO의 장점은 코드가 단순하다는 것이다.&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;예를 들어 계산 기능이 있는 클래스라면 Spring 서버를 띄우지 않고도 테스트할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드는 그냥 객체를 만들고 메서드를 호출하면 된다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Calculator calculator = new Calculator();

int result = calculator.add(10, 20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 POJO는 자유롭고 유연하다.&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;blockquote data-ke-style=&quot;style2&quot;&gt;DI는 필요한 객체를 외부에서 넣어주는 방식이다.&lt;br /&gt;AOP는 여러 곳에 반복되는 공통 기능을 분리해서 필요한 곳에 적용하는 방식이다.&lt;br /&gt;PSA는 복잡한 기술 차이를 추상화해서 일관된 방식으로 사용할 수 있게 하는 방식이다.&lt;br /&gt;POJO는 특정 프레임워크에 강하게 묶이지 않은 평범한 자바 객체다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/379</guid>
      <comments>https://cainyun.tistory.com/379#entry379comment</comments>
      <pubDate>Wed, 6 May 2026 11:50:38 +0900</pubDate>
    </item>
    <item>
      <title>Spring이 JDBC와 트랜잭션 작업을 줄여주는 방식</title>
      <link>https://cainyun.tistory.com/378</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 프레임워크와 라이브러리의 차이, 그리고 IoC에 대해 정리했다.&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;이번 글에서는 Spring이 실제 DB에서 반복 코드를 어떻게 줄여주는지 정리해보려고 한다!!&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;Spring을 처음 배우면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링은 개발자가 비즈니스 로직에 집중할 수 있게 해준다&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 말을 많이 들어봤을 것이다..&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;JDBC&lt;/b&gt;&lt;/span&gt;와&lt;span style=&quot;color: #006dd7;&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;자바 프로그램에서 데이터를 저장하거나 조회하려면 DB와 연결해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 기본적으로 사용하는 기술이 JDBC다.&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: #f89009;&quot;&gt;&lt;b&gt;JDBC&lt;/b&gt;&lt;/span&gt;는 &lt;b&gt;J&lt;/b&gt;ava &lt;b&gt;D&lt;/b&gt;ata&lt;b&gt;b&lt;/b&gt;ase &lt;b&gt;C&lt;/b&gt;onnectivity의 약자이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서&lt;span style=&quot;color: #f89009;&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;JDBC를 사용하면 DB에 연결되니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 코드에서 SQL을 실행할 수 있게 된다!!&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;JDBC를 직접 사용하면 대략 이런 코드가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Connection conn = DriverManager.getConnection(
    &quot;jdbc:mysql://localhost:3306/testdb&quot;,
    &quot;root&quot;,
    &quot;password&quot;
);

PreparedStatement ps = conn.prepareStatement(&quot;SELECT * FROM user&quot;);

ResultSet rs = ps.executeQuery();

while (rs.next()) {
    String name = rs.getString(&quot;name&quot;);
    System.out.println(name);
}

rs.close();
ps.close();
conn.close();
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 &lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Connection&lt;/span&gt;&lt;/b&gt;은 DB와 연결된 통로라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 프로그램이 DB에 접근하려면 먼저 이 연결 통로를 열어야 한다.&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: #ef5369;&quot;&gt;&lt;b&gt;PreparedStatement&lt;/b&gt;&lt;/span&gt;는 SQL을 실행하기 위해 준비된 객체이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT * FROM user 같은 SQL문을 DB에 보내기 위해 사용한다.&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: #ef5369;&quot;&gt;&lt;b&gt;ResultSet&lt;/b&gt; &lt;/span&gt;안에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;SQL 실행 결과&lt;/b&gt;&lt;/span&gt;가 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 user 테이블에서 조회된 여러 행의 데이터가 ResultSet에 담기고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;rs.next()&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;마지막에 close()를 호출하는 것도 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결은 무한히 만들 수 있는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열어놓고 닫지 않으면 연결이 계속 쌓이고, 결국 사용할 수 있는 DB 연결이 부족해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 connection leak이라고 부르기도 한다.&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;즉, JDBC를 직접 사용하면 개발자가 직접 챙겨야 하는 일이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결을 열고, SQL을 준비하고, SQL을 실행하고, 결과를 꺼내고, 사용한 자원을 닫아야 한다.&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;문제는 이런 흐름이 DB 작업을 할 때마다 반복된다는 것이다!!!!&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;Spring을 몰랐을 때는 개발자가 DB 관리, 트랜잭션 처리, JDBC 코드 작성, 예외 관리까지 직접 챙겨야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Spring을 사용하면 DB 관리, 선언적 트랜잭션 처리, 템플릿 제공, 범용 예외 처리 같은 부분을 Spring이 도와준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 B에게 10,000원을 보내는 상황을 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 사실 두 단계다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A 계좌에서 10,000원 차감&lt;/li&gt;
&lt;li&gt;B 계좌에 10,000원 추가&lt;/li&gt;
&lt;/ol&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;만약 1번은 성공했는데 2번에서 오류가 나면 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A의 돈은 빠져나갔는데 B는 돈을 받지 못한 상태가 된다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;rollback&lt;/b&gt;&lt;/span&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 모든 작업이 성공해서 &lt;b&gt;최종 반영&lt;/b&gt;하는 것을 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;commit&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;background-color: #f6e199;&quot;&gt;&lt;b&gt;트랜잭션은 이런 commit과 rollback을 관리하는 개념&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여러 DB 작업이 모두 성공해야만 최종 반영하고,&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;Spring 없이, &lt;b&gt;JDBC만 사용&lt;/b&gt;할 때 트랜잭션을 직접 처리하려면 이런 코드가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Connection conn = null;

try {
    conn = DriverManager.getConnection(url, user, password);

    conn.setAutoCommit(false);

    // 1. A 계좌에서 돈 차감
    // 2. B 계좌에 돈 추가

    conn.commit();
} catch (Exception e) {
    if (conn != null) {
        conn.rollback();
    }
} finally {
    if (conn != null) {
        conn.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;setAutoCommit&lt;/b&gt;(false)는 자동으로 DB에 반영하지 말라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 모두 수행한 뒤 성공하면 commit()을 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 예외가 발생하면 rollback()을 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에는 connection도 닫아야 한다.&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;commit을 빼먹을 수도 있고, rollback 처리를 잘못할 수도 있고, connection을 닫지 않을 수도 있다.&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;Spring을 사용하면 이 과정을 훨씬 간단하게 처리할 수 있다!!!&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;&lt;b&gt;@Transactional&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Transactional
public void transfer() {
    // 1. A 계좌에서 돈 차감
    // 2. B 계좌에 돈 추가
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 위에 @Transactional을 붙이면&lt;b&gt; Spring이 트랜잭션을 대신 관리&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;메서드가 정상적으로 끝나면 commit한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 예외가 발생하면 rollback한다.&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;개발자는 commit, rollback 코드를 &lt;span style=&quot;color: #ef5369;&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;이런 방식을 &lt;span style=&quot;background-color: #f6e199;&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;선언적이라는 말은 직접 세부 코드를 작성하는 대신,&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;실제 트랜잭션 시작, commit, rollback 처리는 Spring이 맡는다.&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;그래서 JDBC를 직접 사용하면 DB 연결 열기, SQL 준비하기, SQL 실행하기, 결과 꺼내기, 자원 닫기, 예외 처리하기가 계속 반복된다.&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;Spring은 이런 반복 흐름을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;JdbcTemplate&lt;/b&gt;&lt;/span&gt;으로 줄여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate을 사용하면 개발자는 SQL과 결과 처리에 더 집중할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;String sql = &quot;SELECT name FROM user WHERE id = ?&quot;;

String name = jdbcTemplate.queryForObject(
    sql,
    String.class,
    1
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서는 DB 연결을 열고 닫는 코드가 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 연결이 안 일어나는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring과 JdbcTemplate이 뒤에서 처리해주는 것이다.&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;개발자는 어떤 SQL을 실행할지, 결과를 어떤 타입으로 받을지에 집중하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate은 JDBC를 아예 없애는 기술이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC의 반복 코드를 줄여주는 도구에 가깝다.&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;Connection을 열고 닫는 일, 예외를 처리하는 일처럼 반복되는 부분은 Spring이 맡는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 SQL과 결과 매핑처럼 실제로 필요한 부분을 작성한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 예외 처리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC를 직접 사용하면 SQLException 같은 예외를 자주 만나게 된다.&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;SQLException&lt;/b&gt;은 DB 작업 중 문제가 생겼을 때 발생하는 예외다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 SQL 문법이 틀렸거나, DB 연결에 실패했거나, 제약 조건을 위반했을 때 발생할 수 있다.&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 기술이나 드라이버마다 예외의 세부 형태가 다를 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL을 사용할 때와 Oracle을 사용할 때 세부 예외가 다르게 나타날 수 있다.&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 기술별 예외를 직접 신경 써야 하는 상황이 생긴다.&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 기술에 강하게 묶일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring은 DB 관련 예외를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;DataAccessException&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 쪽에서는 &lt;span style=&quot;background-color: #ffc1c8;&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;예를 들어 MySQL을 쓰든, Oracle을 쓰든, JDBC를 쓰든, JPA를 쓰든&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 접근 중 발생하는 예외를 Spring 방식으로 일관되게 다룰 수 있다.&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 기술에 너무 강하게 묶이지 않고 코드를 작성할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 &lt;span style=&quot;color: #666666; background-color: #9feec3;&quot;&gt;&lt;b&gt;DB 작업에서 도와주는 부분&lt;/b&gt;&lt;/span&gt;은 크게 네 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. DB 연결과 자원 관리를 도와준다.&lt;br /&gt;2. 트랜잭션 처리를 @Transactional로 간단하게 만든다.&lt;br /&gt;3. JdbcTemplate 같은 템플릿으로 반복 코드를 줄인다.&lt;br /&gt;4. DB 예외를 일관된 방식으로 처리할 수 있게 해준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론!! Spring을 쓰지 않아도 JDBC만으로 프로그램을 만들 수 있다.&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;DB 연결을 직접 열고 닫아야 하고, 트랜잭션을 직접 commit, rollback 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외도 직접 처리해야 하고, 반복되는 JDBC 코드도 계속 작성해야 한다.&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;Spring을 사용하면 이런 복잡하고귀찮은 작업들을 Spring 프레임워크가 대신 해준다...&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;개발자는 DB 구조를 이해해야 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 필요한 상황도 판단해야 하며, 예외 상황도 고려해야 할 수 있어야 된다고 생각한다!&lt;/p&gt;</description>
      <category>Java, Spring</category>
      <author>clainy</author>
      <guid isPermaLink="true">https://cainyun.tistory.com/378</guid>
      <comments>https://cainyun.tistory.com/378#entry378comment</comments>
      <pubDate>Wed, 6 May 2026 11:28:16 +0900</pubDate>
    </item>
  </channel>
</rss>