<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>전뇌 페이지</title>
    <link>https://the0.tistory.com/</link>
    <description>컴퓨터온열마사지기술사
</description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 05:54:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>the0</managingEditor>
    <image>
      <title>전뇌 페이지</title>
      <url>https://tistory1.daumcdn.net/tistory/5676092/attach/418f4219af6f4609b3bd4fe3d3e9b02c</url>
      <link>https://the0.tistory.com</link>
    </image>
    <item>
      <title>[Fixture Monkey] giveMeBuilder().set()의 값이 적용되지 않음</title>
      <link>https://the0.tistory.com/95</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey 1.1.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;span style=&quot;color: #9d9d9d;&quot;&gt;Google Guava 라이브러리의&lt;/span&gt; Range.class는 giveMeBuilder().set()으로 value가 적용되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(String, int, enum 등 &lt;b&gt;자바 기본 자료형은 문제 없음&lt;/b&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1760921666014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_set_range() {
    Range&amp;lt;Instant&amp;gt; targetRange = Range.closed(
            Instant.parse(&quot;2025-01-01T00:00:00.000001Z&quot;),
            Instant.parse(&quot;2025-12-31T23:59:59.999999Z&quot;)
    );

    var foo = fixture.giveMeBuilder(Foo.class)
            .setNull(&quot;id&quot;)
            .set(&quot;period&quot;, targetRange) // ⭐️ 이 부분
            .sample();

    assertThat(foo.getPeriod()).isEqualTo(targetRange);
}&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: #ee2323;&quot;&gt;테스트 결과:&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760921695880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.opentest4j.AssertionFailedError: 
expected: [2025-01-01T00:00:00.000001Z..2025-12-31T23:59:59.999999Z]
 but was: [2025-05-18T00:59:34.612115Z..2025-11-23T00:59:34.612115Z]&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey는 Range 타입 객체 생성을 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 CustomArbitraryIntrospector를 활용해 Range 객체를 생성할 수 있는 RangeInstantArbitraryIntrospector도 만들어 두었다.&lt;/p&gt;
&lt;pre id=&quot;code_1760922983912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Range&amp;lt;Instant&amp;gt; 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제
 */
public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector{
    @Override
    public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context){
        Property property = context.getResolvedProperty();
        Class&amp;lt;?&amp;gt; type = Types.getActualType(property.getType());
        List&amp;lt;AnnotatedType&amp;gt; typeArguments = Types.getGenericsTypes(property.getAnnotatedType());
        Class&amp;lt;?&amp;gt; genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst());

        if (!type.equals(Range.class)
                || typeArguments.size() != 1
                || !genericType.equals(Instant.class)) {
            return ArbitraryIntrospectorResult.NOT_INTROSPECTED;
        }
        
        Instant startTime = // 난수 생성 로직 생략
        Instant endTime = // 난수 생성 로직 생략

        Range&amp;lt;Instant&amp;gt; rangeValue = Range.closed(startTime, endTime);
		
        return new ArbitraryIntrospectorResult(
                CombinableArbitrary.from(rangeValue)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760923108147&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
        .pushArbitraryIntrospector(
                new MatcherOperator&amp;lt;&amp;gt;(
                        new AssignableTypeMatcher(Range.class).intersect(new SingleGenericTypeMatcher()),
                        rangeInstantArbitraryIntrospector
                )
        )
        .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 해결 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Values.just()를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1760923319962&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void test_set_range() {
    Range&amp;lt;Instant&amp;gt; targetRange = Range.closed(
            Instant.parse(&quot;2025-01-01T00:00:00.000001Z&quot;),
            Instant.parse(&quot;2025-12-31T23:59:59.999999Z&quot;)
    );

    var foo = fixture.giveMeBuilder(Foo.class)
            .setNull(&quot;id&quot;)
            .set(&quot;period&quot;, Values.just(targetRange)) // ⭐️ 이 부분
            .sample();

    assertThat(foo.getPeriod()).isEqualTo(targetRange);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1. 추가 설명&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.1 Values.just()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey는 기본적으로 set()을 사용할 때,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본&amp;nbsp;값을&amp;nbsp;그대로&amp;nbsp;사용하지&amp;nbsp;않고&amp;nbsp;내부적으로&amp;nbsp;값을&amp;nbsp;분해해서&amp;nbsp;재구성하는&amp;nbsp;깊은&amp;nbsp;복사를&amp;nbsp;수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 이미 만들어진 객체를 내부 작업(분해-재구성) 없이 직접 할당하고 싶으면&amp;nbsp;Values.just()를 사용하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.2. Range 타입 값이 set 되지 않았던 원인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey는 Range.class를 깊은 복사하지 못하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value를 set하기에 앞서, Fixture Monkey는 Range 클래스 자체를 분석한다.&lt;/p&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: #9d9d9d;&quot;&gt;NodeSetDecomposedValueManipulator.java&amp;nbsp;참고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isContainer() = false &amp;rarr; 일반 객체로 인식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; children.isEmpty() = false &amp;amp;&amp;amp; isInterface() = false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 루트 객체에 대해서는 &lt;b&gt;setArbitrary()&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출을 하지 않고 &lt;/span&gt;자식 필드 값을 처리한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;arbitrary 필드란&lt;br /&gt;이 값이 설정된 경우 사용자가 원하는 값(.set())을,&lt;br /&gt;설정되지 않으면 ArbitraryIntrospector가 자동 생성하는 값을 사용하도록 지시하는 필드이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.set()을 했음에도 불구하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture Monkey는 Range.closed() 분배 후 재조립이 되지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 추후에 arbitrary = null&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 커스텀 인트로스펙터인 RangeInstantArbitraryIntrospector 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; RangeInstantArbitraryIntrospector 내부에서 생성된 랜덤 값이 적용&lt;/p&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;※ Values.just() 인 경우, Range.class 클래스 분석 없이 setArbitrary(&lt;span style=&quot;color: #9d9d9d;&quot;&gt;CombinableArbitrary.from(exactValue)&lt;/span&gt;) 를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  삽질의 추억</category>
      <category>fixturemonkey</category>
      <category>givemebuilder</category>
      <category>Set</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/95</guid>
      <comments>https://the0.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 20 Oct 2025 12:31:32 +0900</pubDate>
    </item>
    <item>
      <title>[Fixture Monkey] CustomArbitraryIntrospector를 등록하는 여러 방법</title>
      <link>https://the0.tistory.com/94</link>
      <description>&lt;figure id=&quot;og_1760868413546&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편&quot; data-og-description=&quot;불변 객체(&amp;#96;google.common.collect.Range&amp;#96;)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.사용 버전: 1.1.11 ⬇️ 코드만 먼저 보기더보기더보기더보기/** * Range 타입에 대한 값을 반환하는 사용자 정&quot; data-og-host=&quot;the0.tistory.com&quot; data-og-source-url=&quot;https://the0.tistory.com/93&quot; data-og-url=&quot;https://the0.tistory.com/93&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsXTPP/hyZLhtDWdH/FLsy1SrgHL3SOlHvWda8Dk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/qunlY/hyZL39BrGw/toDlpHmMnK1X9C7L0TH9yK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://the0.tistory.com/93&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://the0.tistory.com/93&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsXTPP/hyZLhtDWdH/FLsy1SrgHL3SOlHvWda8Dk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/qunlY/hyZL39BrGw/toDlpHmMnK1X9C7L0TH9yK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;불변 객체(`google.common.collect.Range`)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.사용 버전: 1.1.11 ⬇️ 코드만 먼저 보기더보기더보기더보기/** * Range 타입에 대한 값을 반환하는 사용자 정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;the0.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅 글(사용자 정의 인트로스펙터 예제 코드 - 기초편)과 이어집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서를 참고하여 Fixture Monkey의 CustomArbitrayIntrospector를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;customIntrospector를 objectIntrospector에 등록했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;참고:&amp;nbsp;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/#다른-인트로스펙터와-함께-사용&quot;&gt;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/#다른-인트로스펙터와-함께-사용&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760868166766&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .objectIntrospector(new FailoverIntrospector(
        Arrays.asList(
            rangeInstantArbitraryIntrospector,  // ⭐️ 사용자 정의 인트로스펙터
            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
            BuilderArbitraryIntrospector.INSTANCE,
            FieldReflectionArbitraryIntrospector.INSTANCE,
            BeanArbitraryIntrospector.INSTANCE
        )
    ))
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;`giveMeOne()`&lt;/span&gt; 혹은 &lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;`giveMeBuilder().sample()`&lt;/span&gt; 등 수행 과정을 디버깅해본 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Request/Response 객체가 생성될 때마다 rangeInstantArbitraryIntrospector에 접근한다.&lt;/p&gt;
&lt;pre id=&quot;code_1760870052085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector {

    @Override
    public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) {

        Property property = context.getResolvedProperty();
        Class&amp;lt;?&amp;gt; type = Types.getActualType(property.getType());
        List&amp;lt;AnnotatedType&amp;gt; typeArguments = Types.getGenericsTypes(property.getAnnotatedType());
        Class&amp;lt;?&amp;gt; genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst());

        if (!type.equals(Range.class)
                || typeArguments.size() != 1
                || !genericType.equals(Instant.class)) {
            return ArbitraryIntrospectorResult.NOT_INTROSPECTED;
        }

        // 후략
    }
}&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;이 RangeInstantArbitraryIntrospector는 Range.class 이면서 제네릭이 Instant인 타입을 위한 Custom Introspector이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Foo.class 를 비롯한 사용자 정의 객체들이 모두 접근하고, if 조건문에서 return 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Foo.class는 예시 클래스입니다. &lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;⬇️&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1760870494528&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.time.Instant;
import com.google.common.collect.Range;

public record Foo(
	...
	Range&amp;lt;Instant&amp;gt; period,
 	...
) {
}

// 예시: [&quot;2025-10-06 01:00:00.000+00&quot;,&quot;2025-10-06 09:00:00.000+00&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;커스텀 Introspector를 특정 Class에만 적용하는 방법이 있을까?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 아티클에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;u&gt;인트로스펙터를 설정하는 방법&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;참고 자료:&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;- &lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://naver.github.io/fixture-monkey/v1-0-0-kor/docs/fixture-monkey-options/generation-options/#arbitraryintrospector&quot;&gt;https://naver.github.io/fixture-monkey/v1-0-0-kor/docs/fixture-monkey-options/generation-options/#arbitraryintrospector&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;- &lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://naver.github.io/fixture-monkey/v1-0-0-kor/docs/fixture-monkey-options/generation-options/#arbitraryintrospector&quot;&gt;https://naver.github.io/fixture-monkey/v1-0-0-kor/docs/fixture-monkey-options/generation-options/#arbitraryintrospector&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;0. Fixture Monkey 인트로스펙터 적용/검증 순서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaArbitraryIntrospector&lt;/b&gt; : String.class,&amp;nbsp;char.class,&amp;nbsp;int.class,&amp;nbsp;Long.class&amp;nbsp;등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;JavaTimeArbitraryIntrospector&lt;/b&gt; : Date.class,&amp;nbsp;LocalDateTime.class,&amp;nbsp;Instant.class,&amp;nbsp;ZoneId.class&amp;nbsp;등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;priorityIntrospector&lt;/b&gt; : Boolean.class,&amp;nbsp;boolean.class&amp;nbsp;UUID.class,&amp;nbsp;Enum&amp;nbsp;타입&lt;br /&gt;&amp;rarr; &lt;b&gt;containerIntrospector&lt;/b&gt; : Optional.class,&amp;nbsp;Set.class,&amp;nbsp;Queue.class,&amp;nbsp;Stream.class,&amp;nbsp;Map.class&amp;nbsp;등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;objectIntrospector&lt;/b&gt; : JavaBean 패턴을 따르는 객체들을 분석, 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;fallbackIntrospector&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;이 아티클에서 일련의 모든 내장 Introspector를 &lt;u&gt;JavaDefaultArbitrary&lt;/u&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: #9d9d9d;&quot;&gt;참고 ⬇️&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #9d9d9d; text-align: start;&quot;&gt;Fixture Monkey 소스코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760873412401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
JavaDefaultArbitraryGeneratorBuilder.java
line 168 ~ 181
*/

public IntrospectedArbitraryGenerator build() {
	return new IntrospectedArbitraryGenerator(
		new MatchArbitraryIntrospector(
			Arrays.asList(
				new JavaArbitraryIntrospector(this.javaTypeArbitraryGeneratorSet),
				new JavaTimeArbitraryIntrospector(this.javaTimeArbitraryGeneratorSet),
				this.priorityIntrospector,
				this.containerIntrospector,
				this.objectIntrospector,
				this.fallbackIntrospector
			)
		)
	);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &amp;nbsp;기존 방식 : objectIntrospector&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인트로스펙터를 설정한 기존 방식은 다음과 같이 objectIntrospector에 등록했다.&lt;/p&gt;
&lt;pre id=&quot;code_1760871072430&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .objectIntrospector(new FailoverIntrospector(
        Arrays.asList(
            rangeInstantArbitraryIntrospector,  // ⭐️ 사용자 정의 인트로스펙터
            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
            BuilderArbitraryIntrospector.INSTANCE,
            FieldReflectionArbitraryIntrospector.INSTANCE,
            BeanArbitraryIntrospector.INSTANCE
        )
    ))
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.1. objectIntrospector 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObjectIntrospector는 JavaBean 패턴을 따르는 객체들을 분석하고 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내에서 작성한 DTO, DAO 등 클래스 객체들이 ObjectIntrospector의 주 대상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.2. 문제, 원인과 결론&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 : 모든 Request/Response 객체가 생성될 때마다 rangeInstantArbitraryIntrospector에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인: JavaBean 객체 하나하나가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;FailoverIntrospector(Arrays.asList()) 에 등록되어 있는 순서대로&lt;span&gt; 인트로스펙터에 접근해보기 때문&lt;/span&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;결론: 내가 원하는 Range.class인 경우에만 접근하고, 나머지 경우는 접근조차 하지 않도록 objectIntrospector가 아닌 ArbitraryIntrospector를 설정한다.&lt;/span&gt;&lt;/span&gt;&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. &amp;nbsp;개선 방식 : ArbitraryIntrospector 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArbitraryIntrospector 는 Arbitrary를 생성하는 방법을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Arbitrary 생성 전략을 기반으로 Arbitrary가 만들어지고, 이를 기반으로 ObjectIntropsector를 활용해 객체가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 옵션을 사용하여 특정 타입에 대해 사용자 정의 ArbitraryIntrospector를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2.1. ArbitraryIntrospector 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- pushExactTypeArbitraryIntrospector&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- pushAssignableTypeArbitraryIntrospector&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- pushArbitraryIntrospector&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.2. pushExactTypeArbitraryIntrospector&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 클래스와&amp;nbsp;&lt;b&gt;정확히&amp;nbsp;일치&lt;/b&gt;하는 타입에만 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속 관계는 무시된다.&lt;/p&gt;
&lt;pre id=&quot;code_1760875244619&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Animal {}

// Dog 클래스 (Animal을 상속)
public class Dog extends Animal {}

// Animal 타입에만 적용, Dog는 적용 안됨.
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .pushExactTypeArbitraryIntrospector(Animal.class, customIntrospector)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2.1.2.&lt;span&gt; pushAssignableTypeArbitraryIntrospector&lt;/span&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;span style=&quot;color: #9d9d9d;&quot;&gt;특정 Class가 어떤 클래스/인터페이스를 상속/구현했는지 체크하는&lt;/span&gt; isAssignableFrom()을 사용하므로 상속 관계를 포함한다.&lt;/p&gt;
&lt;pre id=&quot;code_1760875282682&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Animal {}

// Dog 클래스 (Animal을 상속)
public class Dog extends Animal {}

// Animal과 Dog 타입 모두 적용.
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .pushAssignableTypeArbitraryIntrospector(Animal.class, customIntrospector)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.3. pushArbitraryIntrospector&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MatcherOperator&amp;lt;ArbitraryIntrospector&amp;gt;를 직접 구현&lt;/b&gt;하고, 해당 MatcherOperator가 적용된다.&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;pushArbitraryIntrospector의 예제코드는 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.2. 성능상 이점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArbitraryIntrospector 설정은 JavaArbitraryIntrospector&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(-&amp;gt; JavaTimeArbitraryIntrospector -&amp;gt; ... -&amp;gt; fallbackIntrospector)&lt;/span&gt;보다 이전에 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;참고 소스코드 ⬇️&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1760876688823&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
FixtureMonkeyOptionsBuilder.java
line 637 ~ 651
*/

// JavaDefaultArbitrary의 Introspector들
ArbitraryGenerator defaultArbitraryGenerator = 
	defaultIfNull(this.defaultArbitraryGenerator, this.javaDefaultArbitraryGeneratorBuilder::build);

// 사용자 정의 ArbitraryIntrospector들
List&amp;lt;ArbitraryIntrospector&amp;gt; typedArbitraryIntrospectors = arbitraryIntrospectors
	.getList()
	.stream()
	.map(TypedArbitraryIntrospector::new)
	.collect(Collectors.toList());

ArbitraryGenerator introspectedGenerator =
	new IntrospectedArbitraryGenerator(new MatchArbitraryIntrospector(typedArbitraryIntrospectors));

// 사용자 정의 ArbitraryIntrospector가 JavaDefaultArbitrary 보다 우선된다.
defaultArbitraryGenerator = new MatchArbitraryGenerator(
	Arrays.asList(introspectedGenerator, defaultArbitraryGenerator)
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 커스텀한 ArbitraryIntrospector가 Introspector 체인의 가장 앞에 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArbitraryIntrospector에서 객체 생성에 성공하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;objectIntrospector를 비롯한 여러 기본 Introspectors 를 거치지 않으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;불필요한 인트로스펙터 호출을 피하고 오버헤드를 줄일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2.3. 사용 시 유의사항&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;ArbitraryIntrospector&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(pushExactTypeArbitraryIntrospector, &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;pushAssignableTypeArbitraryIntrospector, pushArbitraryIntrospector&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;)&lt;/span&gt;는 가장 마지막에 등록한 설정이 가장 먼저 적용되는 선입 후출(First-In, Last-Out) 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;가장 높은 우선순위를 두어야 하는 설정은 가장 마지막에 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760878332233&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    // 가장 낮은 우선순위로 두고 싶은 설정을 먼저 작성한다. (JavaDefaultIntrospector보단 우선됨)
    .pushAssignableTypeArbitraryIntrospector(LowPriority.class, customIntrospector3)
    // 가장 높은 우선순위로 두고 싶은 설정
    .pushAssignableTypeArbitraryIntrospector(HighPriority.class, customIntrospector1)
    .objectIntrospector(...)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ List 형식으로 여러 개를 한번에 등록할 순 없음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 매개변수화된 클래스(parameterized class)를 등록하는 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 아티클에서도 다루었던 Range.class를 예시로 기술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Range class 문서:&amp;nbsp;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&lt;/a&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;Range는 Range&amp;lt;C extends Comparable&amp;gt; 형식이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 제네릭 타입으로 Instant를 사용하여 Range&amp;lt;Instant&amp;gt; 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 Parameterized Class는 pushArbitraryIntrospector로만 등록할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1760879615484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private final FixtureMonkey FIXTURE_MONKEY = FixtureMonkey.builder()
            .pushArbitraryIntrospector(
                    new MatcherOperator&amp;lt;&amp;gt;(
                            new AssignableTypeMatcher(Range.class),
                            rangeInstantArbitraryIntrospector
                    )
            )
            .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.1. 제네릭 개수 체크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수화된 클래스의 제네릭 타입 개수까지 미리 검증하도록 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1760879806936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private final FixtureMonkey FIXTURE_MONKEY = FixtureMonkey.builder()
            .pushArbitraryIntrospector(
                    new MatcherOperator&amp;lt;&amp;gt;(
                            new AssignableTypeMatcher(Range.class).intersect(new SingleGenericTypeMatcher()),
                            rangeInstantArbitraryIntrospector
                    )
            )
            .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Range&amp;lt;C&amp;gt; 는 하나의 제네릭을 가지므로 &lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;`.intersect(new SingleGenericTypeMatcher())`&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;커스텀한 Pair&amp;lt;T, T&amp;gt; 와 같이 두개의 제네릭을 가진다면 &lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;`.intersect(new DoubleGenericTypeMatcher())`&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;현재(25.10.19) 기준 세 개의 제네릭 매칭까지 지원한다. (&lt;span style=&quot;color: #ee2323; background-color: #dddddd;&quot;&gt;`TripleGenericTypeMatcher`&lt;/span&gt;)&lt;/p&gt;</description>
      <category>  개발백과</category>
      <category>fixturemonkey</category>
      <category>introspector</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/94</guid>
      <comments>https://the0.tistory.com/94#entry94comment</comments>
      <pubDate>Sun, 19 Oct 2025 22:21:37 +0900</pubDate>
    </item>
    <item>
      <title>[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편</title>
      <link>https://the0.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;불변 객체(&lt;span style=&quot;color: #ef5369;&quot;&gt;`google.common.collect.Range`&lt;/span&gt;)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 버전: 1.1.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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1759720262889&quot; class=&quot;pgsql&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Range&amp;lt;Instant&amp;gt; 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제
 */
public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector {
    @Override
    public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) {
	// 1단계: 이 인트로스펙터가 이 타입을 처리해야 하는지 확인
    	// Range.class 추출
        Property property = context.getResolvedProperty();
        Class&amp;lt;?&amp;gt; type = Types.getActualType(property.getType());
        // Instant.class 추출
        List&amp;lt;AnnotatedType&amp;gt; typeArguments = Types.getGenericsTypes(property.getAnnotatedType());
        Class&amp;lt;?&amp;gt; genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst());

        if (!type.equals(Range.class)
                || typeArguments.size() != 1
                || !genericType.equals(Instant.class)) {
            // 대상 타입이 아니면 다른 인트로스펙터가 처리하도록 함
            return ArbitraryIntrospectorResult.NOT_INTROSPECTED;
        }
        
        // ===== 난수 생성 예시 =====
        Instant baseTime = Instant.now();

        long offset1 = (long) ((Math.random() * 730) - 365);
        long offset2 = (long) ((Math.random() * 730) - 365);

        Instant time1 = baseTime.plus(offset1, ChronoUnit.DAYS);
        Instant time2 = baseTime.plus(offset2, ChronoUnit.DAYS);

        Instant startTime = time1.isBefore(time2) ? time1 : time2;
        Instant endTime = time1.isBefore(time2) ? time2 : time1;
        // ===== 난수 생성 예시 =====

        Range&amp;lt;Instant&amp;gt; rangeValue = Range.closed(startTime, endTime);
		
        // 상수 값 반환
        return new ArbitraryIntrospectorResult(
                CombinableArbitrary.from(rangeValue)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759720270005&quot; class=&quot;haxe&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 사용자 정의 인트로스펙터 생성
ArbitraryIntrospector rangeInstantArbitraryIntrospector = new RangeInstantArbitraryIntrospector();

// 사용자 정의 인트로스펙터를 먼저 시도하고,
// 적용되지 않으면 표준 인트로스펙터로 대체하는 Fixture Monkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .objectIntrospector(new FailoverIntrospector(
        Arrays.asList(
            rangeInstantArbitraryIntrospector,  // 사용자 정의 인트로스펙터를 먼저 시도
            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
            BuilderArbitraryIntrospector.INSTANCE,
            FieldReflectionArbitraryIntrospector.INSTANCE,
            BeanArbitraryIntrospector.INSTANCE
        )
    ))
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;size18&quot;&gt;Fixture Monkey에 내장되어 있는 인트로스펙터는 다음과 같은 대상 클래스를 지원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Setter가 있는 클래스&amp;nbsp;&lt;/li&gt;
&lt;li&gt;생성자가 있는 불변 클래스&lt;/li&gt;
&lt;li&gt;혼합 필드 접근 방식의 클래스&lt;/li&gt;
&lt;li&gt;빌더 패턴을 사용하는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1759687249710&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;인트로스펙터&quot; data-og-description=&quot;인트로스펙터란 무엇인가요? # Fixture Monkey의 인트로스펙터(Introspector)는 테스트 객체를 생성하는 방법을 결정하는 도구입니다. 테스트 객체를 생성하는 &amp;ldquo;공장&amp;quot;이라고 생각하면 됩니다. 인트로&quot; data-og-host=&quot;naver.github.io&quot; data-og-source-url=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/introspector/&quot; data-og-url=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/introspector/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXIxve/hyZKdd72fI/GmtRSnMkaEzdyDQrzzBu31/img.png?width=1920&amp;amp;height=810&amp;amp;face=0_0_1920_810,https://scrap.kakaocdn.net/dn/xSWSP/hyZKvTN6tV/3RPhAsIpXkOQOkX22tvxnk/img.png?width=1920&amp;amp;height=810&amp;amp;face=0_0_1920_810&quot;&gt;&lt;a href=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/introspector/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/introspector/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXIxve/hyZKdd72fI/GmtRSnMkaEzdyDQrzzBu31/img.png?width=1920&amp;amp;height=810&amp;amp;face=0_0_1920_810,https://scrap.kakaocdn.net/dn/xSWSP/hyZKvTN6tV/3RPhAsIpXkOQOkX22tvxnk/img.png?width=1920&amp;amp;height=810&amp;amp;face=0_0_1920_810');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;인트로스펙터&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인트로스펙터란 무엇인가요? # Fixture Monkey의 인트로스펙터(Introspector)는 테스트 객체를 생성하는 방법을 결정하는 도구입니다. 테스트 객체를 생성하는 &amp;ldquo;공장&quot;이라고 생각하면 됩니다. 인트로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;naver.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 예를 들어&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Google Guava 라이브러리의 Range 타입이나&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Java Inet4Address, Inet6Address 타입은&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/net/Inet4Address.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/net/Inet4Address.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;정적 팩터리 메서드로 생성되는 불변 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, setter도 없고, public 생성자도 없고, 필드 직접 접근도 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;불변 객체도 랜덤 테스트 객체를 지원하기 위해서는 사용자 정의 인트로스펙터를 작성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 Range&amp;lt;Instant&amp;gt; 에 대한 사용자 정의 인트로스펙터를 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;참고: Fixture Monkey 공식 문서 &amp;gt; 객체 생성 &amp;gt; 사용자 정의 인트로스펙터 만들기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/&lt;/a&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759687943140&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.time.Instant;
import com.google.common.collect.Range;

public record Foo(
	...
	Range&amp;lt;Instant&amp;gt; period,
 	...
) {
}

// 예시: [&quot;2025-10-06 01:00:00.000+00&quot;,&quot;2025-10-06 09:00:00.000+00&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Foo 객체 내에 &amp;nbsp;[Instant, Instant] 를 담아 기간을 나타내는 period 필드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내장 인트로스펙터만 사용하면 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1759718195246&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// BuilderArbitraryIntrospector가 아니더라도 모든 Intraspector가 아래와 같은 에러를 출력한다.
WARN  &quot;BuilderArbitraryIntrospector&quot; is failed to introspect &quot;example.demo.foo.model.Foo&quot; type.

Failed to generate type &quot;Foo&quot;
java.lang.IllegalArgumentException: Failed to generate type &quot;Foo&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Foo 객체를 만들지 못하는 이유는 내장 인트로스펙터로는 Range&amp;lt;Instant&amp;gt;를 생성하지 못하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Range 타입을 지원하지 않는 이유는 해당 타입이 불변 객체이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Range에 대한 공식 문서를 참고해서 기존 인트로스펙터의 지원 여부를 판단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&quot;&gt;https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Setter가 있는 클래스 -&amp;gt; ❎&lt;/li&gt;
&lt;li&gt;생성자가 있는 불변 클래스 -&amp;gt; ❎&lt;/li&gt;
&lt;li&gt;혼합 필드 접근 방식의 클래스 -&amp;gt; ❎&lt;/li&gt;
&lt;li&gt;빌더 패턴을 사용하는 클래스 -&amp;gt; ❎&lt;/li&gt;
&lt;/ul&gt;
&lt;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: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/&quot;&gt;https://naver.github.io/fixture-monkey/v1-1-0-kor/docs/generating-objects/custom-introspector/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1759688802939&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
 * Range&amp;lt;Instant&amp;gt; 타입에 대한 값을 반환하는 사용자 정의 인트로스펙터 예제
 */
public class RangeInstantArbitraryIntrospector implements ArbitraryIntrospector {
    @Override
    public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) {
	// 1단계: 이 인트로스펙터가 이 타입을 처리해야 하는지 확인
    	// Range.class 추출
        Property property = context.getResolvedProperty();
        Class&amp;lt;?&amp;gt; type = Types.getActualType(property.getType());
        // Instant.class 추출
        List&amp;lt;AnnotatedType&amp;gt; typeArguments = Types.getGenericsTypes(property.getAnnotatedType());
        Class&amp;lt;?&amp;gt; genericType = typeArguments.isEmpty() ? null : Types.getActualType(typeArguments.getFirst());

        if (!type.equals(Range.class)
                || typeArguments.size() != 1
                || !genericType.equals(Instant.class)) {
            // 대상 타입이 아니면 다른 인트로스펙터가 처리하도록 함
            return ArbitraryIntrospectorResult.NOT_INTROSPECTED;
        }
        
        // ===== 난수 생성 예시 =====
        Instant baseTime = Instant.now();

        long offset1 = (long) ((Math.random() * 730) - 365);
        long offset2 = (long) ((Math.random() * 730) - 365);

        Instant time1 = baseTime.plus(offset1, ChronoUnit.DAYS);
        Instant time2 = baseTime.plus(offset2, ChronoUnit.DAYS);

        Instant startTime = time1.isBefore(time2) ? time1 : time2;
        Instant endTime = time1.isBefore(time2) ? time2 : time1;
        // ===== 난수 생성 예시 =====

        Range&amp;lt;Instant&amp;gt; rangeValue = Range.closed(startTime, endTime);
		
        // 상수 값 반환
        return new ArbitraryIntrospectorResult(
                CombinableArbitrary.from(rangeValue)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&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;pre id=&quot;code_1759688920862&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용자 정의 인트로스펙터 생성
ArbitraryIntrospector rangeInstantArbitraryIntrospector = new RangeInstantArbitraryIntrospector();

// 사용자 정의 인트로스펙터를 먼저 시도하고,
// 적용되지 않으면 표준 인트로스펙터로 대체하는 Fixture Monkey 생성
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
    .objectIntrospector(new FailoverIntrospector(
        Arrays.asList(
            rangeInstantArbitraryIntrospector,  // 사용자 정의 인트로스펙터를 먼저 시도
            ConstructorPropertiesArbitraryIntrospector.INSTANCE,
            BuilderArbitraryIntrospector.INSTANCE,
            FieldReflectionArbitraryIntrospector.INSTANCE,
            BeanArbitraryIntrospector.INSTANCE
        )
    ))
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;다른 인트로스펙터 설정 방식은 아래의 아티클에서 기술한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(CustomArbitraryIntrospector를&amp;nbsp;등록하는&amp;nbsp;여러&amp;nbsp;방법)&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760880316923&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Fixture Monkey] CustomArbitraryIntrospector를 등록하는 여러 방법&quot; data-og-description=&quot;[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편불변 객체(&amp;#96;google.common.collect.Range&amp;#96;)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.사용 버전: 1.1.11 ⬇️ 코드만 &quot; data-og-host=&quot;the0.tistory.com&quot; data-og-source-url=&quot;https://the0.tistory.com/94&quot; data-og-url=&quot;https://the0.tistory.com/94&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8IeRR/hyZLuA0ub5/I2o7KlnTCDjw0dZ6fkCXAk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ha68O/hyZLpzKwLS/5HUp2ZDxbw7mja0XK27RkK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://the0.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://the0.tistory.com/94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8IeRR/hyZLuA0ub5/I2o7KlnTCDjw0dZ6fkCXAk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ha68O/hyZLpzKwLS/5HUp2ZDxbw7mja0XK27RkK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Fixture Monkey] CustomArbitraryIntrospector를 등록하는 여러 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[Fixture Monkey] 불변 객체를 위한 사용자 정의 인트로스펙터 예제 코드 - 기초편불변 객체(`google.common.collect.Range`)를 위한 사용자 정의 인트로스펙터 예제 코드입니다.사용 버전: 1.1.11 ⬇️ 코드만&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;the0.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후엔 Range&amp;lt;Instant&amp;gt; 타입이 담긴 Foo 객체가 잘 생성되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 실행 시 에러가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시 클래스인 Range.class 처럼 일부 클래스는&lt;/p&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;u&gt;giveMeBuilder().set(&quot;period&quot;, range) 의 값이 적용되지 않는 문제&lt;/u&gt;&lt;/span&gt;가 발생할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이에 관한 내용은 다음 아티클에서 기술한다.&lt;/p&gt;
&lt;figure id=&quot;og_1760931274490&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Fixture Monkey] giveMeBuilder().set()의 값이 적용되지 않음&quot; data-og-description=&quot;1. 문제 상황Fixture Monkey 1.1.11 버전 사용 Google Guava 라이브러리의 Range.class는 giveMeBuilder().set()으로 value가 적용되지 않음(String, int, enum 등 자바 기본 자료형은 문제 없음)@Testvoid test_set_range() { Range ta&quot; data-og-host=&quot;the0.tistory.com&quot; data-og-source-url=&quot;https://the0.tistory.com/95&quot; data-og-url=&quot;https://the0.tistory.com/95&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNZBYk/hyZLX2MH4a/sYHZTtPcqR66WQlxKbWdx0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cB5G2T/hyZL5GwzaZ/DtG38rPTZ5gXnQyLbQi2N1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://the0.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://the0.tistory.com/95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNZBYk/hyZLX2MH4a/sYHZTtPcqR66WQlxKbWdx0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cB5G2T/hyZL5GwzaZ/DtG38rPTZ5gXnQyLbQi2N1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Fixture Monkey] giveMeBuilder().set()의 값이 적용되지 않음&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 문제 상황Fixture Monkey 1.1.11 버전 사용 Google Guava 라이브러리의 Range.class는 giveMeBuilder().set()으로 value가 적용되지 않음(String, int, enum 등 자바 기본 자료형은 문제 없음)@Testvoid test_set_range() { Range ta&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;the0.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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>  개발백과</category>
      <category>fixturemonkey</category>
      <category>introspector</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/93</guid>
      <comments>https://the0.tistory.com/93#entry93comment</comments>
      <pubDate>Mon, 6 Oct 2025 12:11:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] 트랜잭션 끝나도 DB 커넥션 릴리즈 안됨 (feat. OSIV)</title>
      <link>https://the0.tistory.com/91</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;개발 환경:&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 3.4.4, Spring Data JPA(Hibernate 6.6.11)&lt;/li&gt;
&lt;li&gt;한 메서드 내에 DB 접근와 SSE send를 하고 있다.&lt;/li&gt;
&lt;li&gt;SSE가 tomcat thread로 동기 처리된다. (원래는 비동기 처리해야함. &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;커넥션 점유 테스트를 위해 이렇게 둠.&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;커넥션 점유를 5초 이상하면 로그를 출력하도록 설정했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;해당 메서드(~=스레드) 에서 커넥션을 계속 점유하고 있다는 로그가 출력된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;`maximum-pool-size` 를 초과하면 에러가 발생한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사건의 발단이 된 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;코드가 길어서 축약했다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public SseEmitter subscribe(AuthUser authUser) {
    Long userId = userRepository.findByOauthId(authUser.getOauthId())
            .orElseThrow(() -&amp;gt; new BusinessExceptionHandler(&quot;유저가 존재하지 않습니다.&quot;, ErrorCode.NOT_FOUND_ERROR))
            .getId();

    SseEmitter emitter = new SseEmitter(TIMEOUT);
    emitters.put(userId, emitter);
    ...
    
    try {
        emitter.send(SseEmitter.event().name(&quot;connect&quot;).data(&quot;SSE 연결 완료&quot;));
    } catch (IOException e) {
        emitter.complete();
        emitters.remove(userId);
    }
    return emitter;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
    hikari:
      leak-detection-threshold: 5000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;size18&quot;&gt;기존에 알고 있던 사실:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userRepository.findByOauthId 를 실행하기 위해 엔티티 매니저가 DB 커넥션을 획득한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userRepository.findByOauthId 트랜잭션이 종료됨에 따라 엔티티 매니저가 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;의문점은 다음과 같다.&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;userRepository.findByOauthId 트랜잭션이 종료됨에 따라 &lt;br /&gt;엔티티 매니저가 DB 커넥션을 릴리즈해야하는데..&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;왜 계속 점유를 하고 있는거지?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;의문점 해결:&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, OSIV 에 대해서 이해를 해야한다.&lt;/p&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: #9d9d9d;&quot;&gt;참고&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;0. 검색 키워드&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;open-in-view&lt;/li&gt;
&lt;li&gt;OSIV&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;1.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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 에는 open-in-view 설정의 기본 값이 true이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 아무 설정을 하지 않았으므로 당연히 `open-in-view=true` 로 설정되어 있었다.&lt;/p&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;이 값이 true이면&lt;/span&gt;,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA의 EntityManager는 클라이언트에게 HTTP 응답을 완료할 때까지 열려있다.(= 응답이 끝나고 나서 em.close()를 한다.)&lt;/li&gt;
&lt;li&gt;Hibernate x.x 에서 DB의 conn.close()는 EntityManger.close() 가 동작하는 과정에서 내부적으로 conn.close()가 실행된다.&lt;/li&gt;
&lt;li&gt;위의 두 내용을 종합하면 =&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;DB connection은 HTTP 응답 반환 이후에 close 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;SSE 를 동기 처리한 코드에서는 위의 이유+`SSE 는 close 되기 전까지 thread를 물고 있는다.` 라는 복합 이유로 인해 DB 커넥션이 계속 점유되고 있는 것이다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 트랜잭션이 끝나도 DB 커넥션이 반납되지 않았던 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;caret-color: #ee2323;&quot;&gt;&lt;b&gt;추가 분석:&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;왜, 어떻게&lt;/u&gt; 세션이 끝날 때까지 커넥션이 붙잡혀 있는지 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate를 비롯한 jpa 라이브러리 코드를 추척했다.&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;style4&quot; /&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;hibernate.connection.handling_mode&lt;/b&gt; 임을 알게 되었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;출처: 챗지피티, &lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://jaimemin.tistory.com/2704&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jaimemin.tistory.com/2704&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;* 위의 블로그는 시퀀스 다이어그램도 있어서 읽어보면 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ hibernate의 handling_mode 에 관한 자료가 많이 없다...&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 Hibernate를 부트스트래핑하는 과정을 배제하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;일단 Hibernate 자체만을 먼저 분석한다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcSettings.java에서 connection_handling의 &lt;span style=&quot;color: #ee2323;&quot;&gt;기본 값&lt;/span&gt;이&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임을 알 수 있다. 이는 hibernate 5.2+ 부터 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/re0Pv/btsOUy84m2X/9qUkbK2vXO6phO8IPuSQG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/re0Pv/btsOUy84m2X/9qUkbK2vXO6phO8IPuSQG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/re0Pv/btsOUy84m2X/9qUkbK2vXO6phO8IPuSQG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fre0Pv%2FbtsOUy84m2X%2F9qUkbK2vXO6phO8IPuSQG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2264&quot; height=&quot;1058&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&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_HANDLING 의 종류가 무엇이 있는지는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PhysicalConnectionHandlingMode.java에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;1276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB9zZT/btsOVI3S5Q9/62WcIKrb0SuMxe7h9fhKjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB9zZT/btsOVI3S5Q9/62WcIKrb0SuMxe7h9fhKjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB9zZT/btsOVI3S5Q9/62WcIKrb0SuMxe7h9fhKjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB9zZT%2FbtsOVI3S5Q9%2F62WcIKrb0SuMxe7h9fhKjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2246&quot; height=&quot;1276&quot; data-origin-width=&quot;2246&quot; data-origin-height=&quot;1276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;한국어로 번역해서 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 234px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 18px;&quot;&gt;&lt;b&gt;PhysicalConnectionHandlingMode&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 18px;&quot;&gt;&lt;b&gt;주석 번역&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 54px;&quot;&gt;IMMEDIATE_ACQUISITION_AND_HOLD&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 54px;&quot;&gt;세션이&amp;nbsp;열리는&amp;nbsp;즉시&amp;nbsp;획득되며&lt;br /&gt;세션이 닫힐 때까지 유지된다.&lt;br /&gt;이는 유일하게 유효한 조합이며 커넥션을 즉시 획득하는 것을 포함한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 54px;&quot;&gt;DELAYED_ACQUISITION_AND_HOLD&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 54px;&quot;&gt;필요한&amp;nbsp;즉시&amp;nbsp;획득되며&lt;br /&gt;세션이 닫힐 때까지 유지된다.&lt;br /&gt;이는 원래 Hibernate 동작이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 36px;&quot;&gt;DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 36px;&quot;&gt;필요한&amp;nbsp;즉시&amp;nbsp;획득되며&lt;br /&gt;각 명령문이 실행된 후 해제된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 36px;&quot;&gt;DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 36px;&quot;&gt;필요한&amp;nbsp;즉시&amp;nbsp;획득되며,&lt;br /&gt;커밋 또는 롤백 전에 해제된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 36.395349%; height: 36px;&quot;&gt;DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION&lt;/td&gt;
&lt;td style=&quot;width: 63.604651%; height: 36px;&quot;&gt;필요한&amp;nbsp;즉시&amp;nbsp;획득되며,&lt;br /&gt;각 트랜잭션이 완료된 후 해제된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;의문점 하나, (애매하게 해결됨)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;DELAYED_ACQUISITION_AND_HOLD 이 &quot;This is the original Hibernate behavior&quot; 라며 기본 동작이라는 주석이 적혀 있는데,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;JdbcSettings.java 에는 DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION가 default 라고 나와 있다.&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: #9d9d9d;&quot;&gt;&lt;span style=&quot;caret-color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;&lt;span&gt;Q. &lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;the original behavior&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;!= &lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;default&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;behavior&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;&amp;nbsp;인건가?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;span style=&quot;caret-color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;A.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hibernate.atlassian.net/browse/HHH-5184?focusedCommentId=74692&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hibernate.atlassian.net/browse/HHH-5184?focusedCommentId=74692&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이 링크에서 DELAYED_ACQUISITION_AND_HOLD 를 &quot;original legacy behavior&quot; 라고 표현한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;DELAYED_ACQUISITION_AND_HOLD를 &lt;/span&gt;the original behavior라고 표현하는 것에 대해 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;default&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;behavior 보단 legacy behavior 로 이해하면 될 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 JdbcSettings.java의 CONNECTION_HANDLING 은 JdbcEnvironmentInitator.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;JdbcEnvironmentInitator.java의 TemporaryJdbcSessionOwner() 메서드 내부 로직의 일부를 캡쳐했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mu0CJ/btsOXOaXLr0/Z58gJPgikkRL9inx1yhJEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mu0CJ/btsOXOaXLr0/Z58gJPgikkRL9inx1yhJEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mu0CJ/btsOXOaXLr0/Z58gJPgikkRL9inx1yhJEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmu0CJ%2FbtsOXOaXLr0%2FZ58gJPgikkRL9inx1yhJEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2154&quot; height=&quot;412&quot; data-origin-width=&quot;2154&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate.connection.handling_mode 를 application.yml에서 직접 설정하지 않았으므로&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;hibernate.connection.handling_mode 를 설정하는 방법은 다음과 같다.&lt;br /&gt;
&lt;pre id=&quot;code_1751092344180&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  jpa:
    properties:
      hibernate.connection.handling_mode=&lt;/code&gt;&lt;/pre&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;specifiedHandlingMode 는 null 이고 이에 따라 getDefaultConnectionHandlingMode() 가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcResourceLocalTransactionCoordinatorBuilderImpl.java의 getDefaultConnectionHandlingMode() 는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn0VsX/btsOWfNYNtW/S7tqTGKBYHHvbknAHyzuR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn0VsX/btsOWfNYNtW/S7tqTGKBYHHvbknAHyzuR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn0VsX/btsOWfNYNtW/S7tqTGKBYHHvbknAHyzuR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn0VsX%2FbtsOWfNYNtW%2FS7tqTGKBYHHvbknAHyzuR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;260&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JdbcSettings.java에서 connection_handling의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기본 값&lt;/span&gt;이 DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION 라는 증명을 종료한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 왔으면 HIbernate 는 DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&quot;DB 커넥션은 필요한 즉시 획득되며, 각 트랜잭션이 완료된 후 해제된다.&quot;&lt;/u&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;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;즉, 실제로는&amp;nbsp;&lt;b&gt;DELAYED_ACQUISITION_AND_HOLD 정책&lt;/b&gt;을 따르고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;u&gt;Spring이 Hibernate를 부트스트래핑하는 과정&lt;/u&gt;&lt;/span&gt;에서 CONNECTION_HANDING &lt;span style=&quot;color: #ee2323;&quot;&gt;설정이 어떻게 바뀌는지&lt;/span&gt; 분석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate가 부트스트래핑 될때 중점적으로 봐야하는 파일은 HibernateJpaVendorAdapter.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;이름대로 HibernateJpaVendorAdapter는 Hibernate를 JPA 구현체로 설정하기 위한 어댑터 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HibernateJpaVendorAdapter를&amp;nbsp;사용하면&amp;nbsp;Hibenate의&amp;nbsp;설정을&amp;nbsp;JPA에&amp;nbsp;자동&amp;nbsp;적용해준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JPA는 추상화된 데이터 접근을 제공하기 때문에 특정 벤더에 종속적이지 않다.&lt;br /&gt;HIbernate는 JPA의 구현체 중 하나이다.&lt;br /&gt;출처: https://dev-coco.tistory.com/74&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HibernateJpaVendorAdapter 클래스에서 중점적으로 봐야하는 메서드는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setPrepareConnection(boolean prepareConnection) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUeEHz/btsOXNC9vEB/958R2itkEujzAkk9chI8u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUeEHz/btsOXNC9vEB/958R2itkEujzAkk9chI8u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUeEHz/btsOXNC9vEB/958R2itkEujzAkk9chI8u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUeEHz%2FbtsOXNC9vEB%2F958R2itkEujzAkk9chI8u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1912&quot; height=&quot;1118&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prepareConnection 값은 true 가 default 값이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Spring 4.3.1 부터 적용되고 있다.&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;격리 수준과 읽기 전용 여부 같은 트랜잭션 특성 정보를 JDBC 커넥션에 직접 적용할지 결정한다.&lt;/li&gt;
&lt;li&gt;prepareConnection=true 인 경우 CONNECTION_HANDLING 값은 &lt;span style=&quot;color: #ee2323;&quot;&gt;DELAYED_ACQUISITION_AND_HOLD&lt;/span&gt;로 적용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Spring이 setPrepareConnection() 메서드를 Hibernate에 hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_HOLD를 어떻게, 어디서 전달하느냐를 따라가본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 길어서 캡쳐 대신 복붙으로 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 HibernateJpaVendorAdapter 클래스 내부의 메서드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public Map&amp;lt;String, Object&amp;gt; getJpaPropertyMap(PersistenceUnitInfo pui) {
    return buildJpaPropertyMap(this.jpaDialect.prepareConnection &amp;amp;&amp;amp; // 1번 ✅
          pui.getTransactionType() != PersistenceUnitTransactionType.JTA);
}

@Override
public Map&amp;lt;String, Object&amp;gt; getJpaPropertyMap() {
    return buildJpaPropertyMap(this.jpaDialect.prepareConnection); // 1번 ✅
}

private Map&amp;lt;String, Object&amp;gt; buildJpaPropertyMap(boolean connectionReleaseOnClose) {
    Map&amp;lt;String, Object&amp;gt; jpaProperties = new HashMap&amp;lt;&amp;gt;();

    // ... 다른 설정들 jpaProperties.put()

    if (connectionReleaseOnClose) {
       jpaProperties.put(AvailableSettings.CONNECTION_HANDLING,
             PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD); // 2번 ✅
    }

    // For SpringBeanContainer to be called on Hibernate 6.2
    jpaProperties.put(&quot;hibernate.cdi.extensions&quot;, &quot;true&quot;);

    return jpaProperties;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 1번 ✅ 에서 prepareConnection = true 로 인자가 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. buildJpaPropertyMap의 파라미터 connectionReleaseOnClose 는 true를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. if(connectionReleaseOnClose) 조건문은 true로, 조건문 내부가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 1번 ✅ 에서 handling mode 값이&amp;nbsp;&lt;b&gt;DELAYED_ACQUISITION_AND_HOLD&lt;/b&gt; 로 설정되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 언급했던 &amp;nbsp;JdbcEnvironmentInitator.java#TemporaryJdbcSessionOwner() 내부 로직을 확인한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lDzSo/btsOWCbn2wm/9GMRiAY6oWeHAj9OonwS1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lDzSo/btsOWCbn2wm/9GMRiAY6oWeHAj9OonwS1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lDzSo/btsOWCbn2wm/9GMRiAY6oWeHAj9OonwS1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlDzSo%2FbtsOWCbn2wm%2F9GMRiAY6oWeHAj9OonwS1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2154&quot; height=&quot;412&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;JpaPropertiesMap 에 CONNECTION_HANDLING 값이 DELAYED_ACQUISITION_AND_HOLD 으로 명시되어 설정되었으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate가 부트스트래핑되는 과정에서 specifiedHandlingMode는 더이상 null 값이 아니게 되고, specifiedHandlingMode(DELAYED_ACQUISITION_AND_HOLD) 으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open-in-view 가 true이면 DB connection은 EntityManager 가 종료되기 전까지 유지된다는 증명을 완료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;추가 참고자료&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://karsei.pe.kr/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://karsei.pe.kr/46&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1751099736215&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] JPA 와 Mybatis 동시 사용 시 Connection Deadlock 벗어난 이슈 정리&quot; data-og-description=&quot;계기PHP 에서 Java 로 작업을 진행하면서 기존에 작성했던 Query 문을 사용해야 할 필요가 생었겼다. 근데 한번에 효율적으로 데이터들을 불러오려고 작성된 Query 를 사용하려다보니 우선은 Query 문&quot; data-og-host=&quot;karsei.pe.kr&quot; data-og-source-url=&quot;https://karsei.pe.kr/46&quot; data-og-url=&quot;https://karsei.pe.kr/46&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YXrOc/hyZcna4Hi4/PDKl4C8FNc2lK968qPOnX0/img.png?width=800&amp;amp;height=222&amp;amp;face=0_0_800_222,https://scrap.kakaocdn.net/dn/20T0Y/hyZf5GzJY9/reMAwNyUHTn0CqA2YtuAZk/img.png?width=800&amp;amp;height=222&amp;amp;face=0_0_800_222,https://scrap.kakaocdn.net/dn/M88cF/hyZbswKVz7/Dyf41RnSkMxTtTaYKNAr61/img.png?width=1200&amp;amp;height=333&amp;amp;face=0_0_1200_333&quot;&gt;&lt;a href=&quot;https://karsei.pe.kr/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://karsei.pe.kr/46&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YXrOc/hyZcna4Hi4/PDKl4C8FNc2lK968qPOnX0/img.png?width=800&amp;amp;height=222&amp;amp;face=0_0_800_222,https://scrap.kakaocdn.net/dn/20T0Y/hyZf5GzJY9/reMAwNyUHTn0CqA2YtuAZk/img.png?width=800&amp;amp;height=222&amp;amp;face=0_0_800_222,https://scrap.kakaocdn.net/dn/M88cF/hyZbswKVz7/Dyf41RnSkMxTtTaYKNAr61/img.png?width=1200&amp;amp;height=333&amp;amp;face=0_0_1200_333');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] JPA 와 Mybatis 동시 사용 시 Connection Deadlock 벗어난 이슈 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;계기PHP 에서 Java 로 작업을 진행하면서 기존에 작성했던 Query 문을 사용해야 할 필요가 생었겼다. 근데 한번에 효율적으로 데이터들을 불러오려고 작성된 Query 를 사용하려다보니 우선은 Query 문&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;karsei.pe.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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>  삽질의 추억</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/91</guid>
      <comments>https://the0.tistory.com/91#entry91comment</comments>
      <pubDate>Fri, 27 Jun 2025 02:27:57 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] 서비스 레이어의 어느 메서드에 @Transactional을 안붙이면?</title>
      <link>https://the0.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 3.x, Spring Data JPA(+ Hibernate) 를 쓰는 환경이다.&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;exampleService 클래스의 한 메서드인 example() 이다.&lt;/li&gt;
&lt;li&gt;example()은 authUser 를 인자값으로 받아 이를 userRepository에 Read로 접근한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public exampleResponse example(AuthUser authUser) {
    User user = userRepository.findByOauthId(authUser.getOauthId())
            .orElseThrow(() -&amp;gt; new BusinessExceptionHandler(&quot;유저가 존재하지 않습니다.&quot;, ErrorCode.NOT_FOUND_ERROR));
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 가진 의문점은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DB에 접근하는 코드를 가진 메서드에 @Transactional을 붙이지 않으면 &lt;br /&gt;&lt;b&gt;누가, 언제 DB 커넥션을 획득하고 릴리즈하는가?&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 내가 알고 있던 사실은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;@Transactional 어노테이션이 없는 Repository 직접 접근의 경우&lt;br /&gt;각 쿼리( 예: repository.method() ) 호출마다 독립적인 트랜잭션이 생성되고 쿼리 실행이 완료되면 커넥션이 반환된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위의 코드에 이어서 설명) UserRepository 처럼 개발자가 정의하는 커스텀 Repository 인터페이스는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data JPA가 &lt;b&gt;SimpleJpaRepository&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. 검색 키워드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SimpleJpaRepository 프록시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 초반 부분, Reflection 설명 전까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pingpongdev.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pingpongdev.tistory.com/25&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750953977903&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Data JPA 는 어떻게 interface 만으로도 동작할까? (feat. reflection, proxy)&quot; data-og-description=&quot;Spring Data JPA를 공부하면서 궁금한 것이 있었습니다. public interface MemberRepository extends JpaRepository { List findAllByName(String name); } 위와 같이 MemberRepository는 인터페이스고, @Repository 애노테이션을 붙여 &quot; data-og-host=&quot;pingpongdev.tistory.com&quot; data-og-source-url=&quot;https://pingpongdev.tistory.com/25&quot; data-og-url=&quot;https://pingpongdev.tistory.com/25&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mhGNv/hyZchV1Air/IbYtsz8LzYsQ3YIkf1asWK/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/nWqhy/hyZcigjGSV/rvBzhB7EtfKYG0oNAPePk1/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/ehA9Px/hyZccNUZRT/ajYyGgnkjQsZFGQSaVbdgK/img.png?width=1317&amp;amp;height=826&amp;amp;face=0_0_1317_826&quot;&gt;&lt;a href=&quot;https://pingpongdev.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pingpongdev.tistory.com/25&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mhGNv/hyZchV1Air/IbYtsz8LzYsQ3YIkf1asWK/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/nWqhy/hyZcigjGSV/rvBzhB7EtfKYG0oNAPePk1/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/ehA9Px/hyZccNUZRT/ajYyGgnkjQsZFGQSaVbdgK/img.png?width=1317&amp;amp;height=826&amp;amp;face=0_0_1317_826');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 는 어떻게 interface 만으로도 동작할까? (feat. reflection, proxy)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA를 공부하면서 궁금한 것이 있었습니다. public interface MemberRepository extends JpaRepository { List findAllByName(String name); } 위와 같이 MemberRepository는 인터페이스고, @Repository 애노테이션을 붙여&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pingpongdev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleJpaRepository 클래스에는 여러 메서드들이 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;메서드 종류는 IDE를 통해서 볼 수도 있고 혹은 공식문서(&lt;a style=&quot;color: #9d9d9d;&quot; title=&quot;docs.spring.io#SimpleJpaRepository&quot; href=&quot;https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html&lt;/a&gt;) 가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를 들어, 자주 사용하는 save(), findAll(), delete() 등 기본적인 CRUD 메서드들이 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요하게 봐야하는 부분은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;SimpleJpaRepository &lt;b&gt;클래스 레벨에서 @Transactional(readOnly = true) 이 적용&lt;/b&gt;되어 있다!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;각 메서드 별로 @Transactional 등&lt;/b&gt; 적절한 트랙잭션 설정이 되어 있다!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
@Transactional(readOnly = true) // ✅
public class SimpleJpaRepository&amp;lt;T, ID&amp;gt; implements JpaRepositoryImplementation&amp;lt;T, ID&amp;gt; {
	
    @Override
	@Transactional // ✅
	public &amp;lt;S extends T&amp;gt; S save(S entity) {

		Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);

		if (entityInformation.isNew(entity)) {
			entityManager.persist(entity);
			return entity;
		} else {
			return entityManager.merge(entity);
		}
	}
    
    @Override
	@Transactional // ✅
	public void deleteById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		findById(id).ifPresent(this::delete);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 내가 알고 있던 사실은 사실이 맞다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;@Transactional 어노테이션이 없는 Repository 직접 접근의 경우&lt;br /&gt;각 쿼리( 예: repository.method() ) 호출마다 독립적인 트랜잭션이 생성되고 쿼리 실행이 완료되면 커넥션이 반환된다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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 style=&quot;color: #333333; text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DB에 접근하는 코드를 가진 메서드에 @Transactional을 붙이지 않으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;누가, 언제 DB 커넥션을 획득하고 릴리즈하는가?&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제&lt;/b&gt; DB 커넥션을 획득하고 릴리즈하는가?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존에 알고 있었을 트랜잭션 관련 사실:&amp;nbsp;&lt;br /&gt;@Transactional 이 수행됨에 따라 DB 커넥션을 획득하고, 트랜잭션이 종료됨에 따라 DB 커넥션이 릴리즈된다.&lt;br /&gt;&lt;br /&gt;SimpleJpaRepository 클래스 내에 각 CRUD 메서드들은 @Transactional 이 적용되어 있다.&lt;br /&gt;&lt;br /&gt;예를 들어 Service layer의 어느 메서드 내에 customRepository.save() 를 한다.&lt;br /&gt;이 save() 메서드는 내부적으로 @Transactional이 적용되어 트랜잭션 수행된다.&lt;br /&gt;@Transactional이 수행됨에 따라 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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;누가&lt;/b&gt;&amp;nbsp;DB 커넥션을 획득하고 릴리즈하는가?&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JpaTransactionManager에 의해 entityManagerFactory로부터 엔티티 매니저가 생성된다.&amp;nbsp;&lt;br /&gt;각 엔티티 매니저는 커넥션 풀에 있는 한 커넥션을 점유해 DB에 접근한다.&lt;br /&gt;이후 엔티티 매니저가 커넥션을 릴리즈한다.&lt;br /&gt;(일반 개념. 이 아티클 주제인 `@Transactional을 서비스 레이어 메서드에 붙/안붙 차이` 와는 직접 관련X. )&lt;/blockquote&gt;</description>
      <category>  개발백과</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/90</guid>
      <comments>https://the0.tistory.com/90#entry90comment</comments>
      <pubDate>Fri, 27 Jun 2025 01:38:48 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] 커넥션은 언제 릴리즈 될까 (w/ @Transactional)</title>
      <link>https://the0.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 와 Hibernate를 사용하고&lt;/p&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;이 아티클은 connection.close()가 실행되는 step을 트레이싱한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractPlatformTransactionManager에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;doCommit() 혹은 doRollback() 이후 doCleanupAfterCompletion()이 실행된다.&lt;/p&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: #9d9d9d;&quot;&gt;레퍼런스&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. AbstractPlatformTransactionManager 의 doCleanupAfterCompletion() 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/AbstractPlatformTransactionManager.html#doCleanupAfterCompletion(java.lang.Object)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/AbstractPlatformTransactionManager.html#doCleanupAfterCompletion(java.lang.Object)&lt;/a&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. 시퀀스 다이어그램 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750919627336&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dongho Sim's dev story|스프링 Custom TransactionManager 구현&quot; data-og-description=&quot;Stats: comments&quot; data-og-host=&quot;dhsim86.github.io&quot; data-og-source-url=&quot;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&quot; data-og-url=&quot;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yvTMB/hyZbypXG2X/S5xDvPo6aDqntY2dXSfXn1/img.png?width=735&amp;amp;height=970&amp;amp;face=0_0_735_970&quot;&gt;&lt;a href=&quot;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dhsim86.github.io/web/2017/11/04/spring_custom_transactionmanager-post.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yvTMB/hyZbypXG2X/S5xDvPo6aDqntY2dXSfXn1/img.png?width=735&amp;amp;height=970&amp;amp;face=0_0_735_970');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dongho Sim's dev story|스프링 Custom TransactionManager 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Stats: comments&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dhsim86.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 인프런 답변 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/community/questions/589711/커넥션-종료?srsltid=AfmBOop-yun7Z8Cynu3ySNOhy_9zlrXA2MObvA-5HD7wsnlNVKQbdEB4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/community/questions/589711/커넥션-종료?srsltid=AfmBOop-yun7Z8Cynu3ySNOhy_9zlrXA2MObvA-5HD7wsnlNVKQbdEB4&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 를 사용하고 있으므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사용되는 트랜잭션 매니저는 PlatformTransactionManager의 구현체 중 하나인 &lt;b&gt;JpaTransactionManager&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;JpaTransactionManager.java의 doCleanupAfterCompletion() 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1750920225225&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void doCleanupAfterCompletion(Object transaction) {
    JpaTransactionObject txObject = (JpaTransactionObject) transaction;

    // Remove the entity manager holder from the thread, if still there.
    // (Could have been removed by EntityManagerFactoryUtils in order
    // to replace it with an unsynchronized EntityManager).
    if (txObject.isNewEntityManagerHolder()) {
        TransactionSynchronizationManager.unbindResourceIfPossible(obtainEntityManagerFactory());
    }
    txObject.getEntityManagerHolder().clear();

    // Remove the JDBC connection holder from the thread, if exposed.
    if (getDataSource() != null &amp;amp;&amp;amp; txObject.hasConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(getDataSource());
        ConnectionHandle conHandle = txObject.getConnectionHolder().getConnectionHandle();
        if (conHandle != null) {
            try {
                getJpaDialect().releaseJdbcConnection(conHandle,
                        txObject.getEntityManagerHolder().getEntityManager());
            }
            catch (Throwable ex) {
                // Just log it, to keep a transaction-related exception.
                logger.error(&quot;Failed to release JDBC connection after transaction&quot;, ex);
            }
        }
    }

    getJpaDialect().cleanupTransaction(txObject.getTransactionData());

    // Remove the entity manager holder from the thread.
    if (txObject.isNewEntityManagerHolder()) {
        EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
        if (logger.isDebugEnabled()) {
            logger.debug(&quot;Closing JPA EntityManager [&quot; + em + &quot;] after transaction&quot;);
        }
        EntityManagerFactoryUtils.closeEntityManager(em); //❗️❗️❗️
    }
    else {
        logger.debug(&quot;Not closing pre-bound JPA EntityManager after transaction&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드에서 DB connection 릴리즈와 관련이 있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 마지막 if문 내부에 있음
EntityManagerFactoryUtils.closeEntityManager(em);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EntityManagerFactoryUtils.java의 closeEntityManager() 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1750922276886&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Close the given JPA EntityManager,
 * catching and logging any cleanup exceptions thrown.
 * @param em the JPA EntityManager to close (may be {@code null})
 * @see jakarta.persistence.EntityManager#close()
 */
public static void closeEntityManager(@Nullable EntityManager em) {
    if (em != null) {
        try {
            if (em.isOpen()) {
                em.close(); //❗️❗️❗️
            }
        }
        catch (Throwable ex) {
            logger.error(&quot;Failed to release JPA EntityManager&quot;, ex);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드에서 DB connection 릴리즈와&amp;nbsp;관련이 있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;em.close();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA + Hibernate 를 사용하고 있으므로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Hibernate가 JPA의 EntityManager 인터페이스를 Session으로 구현하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Session은 다시 내부적으로 &lt;b&gt;SessionImpl&lt;/b&gt;로 구현되고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SessionImpl.java 의 close() 코드는 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;closeWithoutOpenChecks() 도 함께 가져왔다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1750923050820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void close() throws HibernateException {
    if ( isClosed() ) {
        if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) {
            throw new IllegalStateException( &quot;Illegal call to #close() on already closed Session/EntityManager&quot; );
        }

        log.trace( &quot;Already closed&quot; );
        return;
    }

    closeWithoutOpenChecks();
}

public void closeWithoutOpenChecks() throws HibernateException {
    if ( log.isTraceEnabled() ) {
        log.tracef( &quot;Closing session [%s]&quot;, getSessionIdentifier() );
    }

    final EventManager eventManager = getEventManager();
    final HibernateMonitoringEvent sessionClosedEvent = eventManager.beginSessionClosedEvent();

    // todo : we want this check if usage is JPA, but not native Hibernate usage
    final SessionFactoryImplementor sessionFactory = getSessionFactory();
    try {
        if ( sessionFactory.getSessionFactoryOptions().isJpaBootstrap() ) {
            // Original hibernate-entitymanager EM#close behavior
            checkSessionFactoryOpen();
            checkOpenOrWaitingForAutoClose();
            if ( fastSessionServices.discardOnClose || !isTransactionInProgressAndNotMarkedForRollback() ) {
                super.close(); //❗️❗️❗️
            }
            else {
                //Otherwise, session auto-close will be enabled by shouldAutoCloseSession().
                prepareForAutoClose(); //❗️❗️❗️
            }
        }
        else {
            super.close(); //❗️❗️❗️
        }
    }
    finally {
        final StatisticsImplementor statistics = sessionFactory.getStatistics();
        if ( statistics.isStatisticsEnabled() ) {
            statistics.closeSession();
        }

        eventManager.completeSessionClosedEvent( sessionClosedEvent, this );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메서드에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DB connection&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;릴리즈와 관련있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;super.close();
// 혹은
prepareForAutoClose();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;super.close() 명령&lt;/u&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. sessionFactory.getSessionFactoryOptions().isJpaBootstrap() 이 false 인 경우&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;즉, JPA 부트스트랩 모드가 아닌 경우이다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;SessionFactoryOptions.java#isJpaBootstrap() 주석은 다음과 같다.&lt;br /&gt;&lt;br /&gt;Was building of the SessionFactory initiated through JPA bootstrapping, or through Hibernate's native bootstrapping?&lt;br /&gt;Returns:true&amp;nbsp;indicates&amp;nbsp;the&amp;nbsp;SessionFactory&amp;nbsp;was&amp;nbsp;built&amp;nbsp;through&amp;nbsp;JPA&amp;nbsp;bootstrapping;&amp;nbsp;&lt;br /&gt;false&amp;nbsp;indicates&amp;nbsp;it&amp;nbsp;was&amp;nbsp;built&amp;nbsp;through&amp;nbsp;native&amp;nbsp;bootstrapping.&lt;br /&gt;&lt;br /&gt;해석-&lt;br /&gt;SessionFactory가 JPA 부트스트래핑으로 빌드되었으면 true,&lt;br /&gt;HIbernate의 네이티브 부트스트래핑으로 빌드외었으면 false.&lt;br /&gt;&lt;br /&gt;결론-&lt;br /&gt;Spring Data JPA를 사용하면 isJpaBootstrap() == true이고, Hibernate만 직접 사용하면 false이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Hibernate가 JPA 부트스트랩 모드이면서&amp;nbsp;2-1 혹은 2-2 둘 중 하나만 만족해도 세션이 정리된다. (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: #333333;&quot;&gt;2-1. fastSessionServices.discardOnClose == true -&amp;gt; 즉시 세션 정리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2-2. &lt;span style=&quot;text-align: start;&quot;&gt;!isTransactionInProgressAndNotMarkedForRollback() == true&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;트랜잭션이 진행 중이지 않거나 rollback된 상태 둘 중 하나라도 참이면 된다.&lt;br /&gt;&lt;br /&gt;내부 코드는 다음과 같다.&lt;br /&gt;
&lt;pre id=&quot;code_1750924346242&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private boolean isTransactionInProgressAndNotMarkedForRollback() {
    if ( waitingForAutoClose ) {
        return getSessionFactory().isOpen()
            &amp;amp;&amp;amp; getTransactionCoordinator().isTransactionActive( false );
    }
    else {
        return !isClosed()
            &amp;amp;&amp;amp; getTransactionCoordinator().isTransactionActive( false );
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&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;style3&quot; /&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;u&gt;prepareForAutoClose() 명령&lt;/u&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;Hibernate가 JPA 부트스트랩 모드이면서 2-1 혹은 2-2 둘 다 만족하지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2-1. fastSessionServices.discardOnClose == false&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2-2. !isTransactionInProgressAndNotMarkedForRollback() == false&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;즉, isTransactionInProgressAndNotMarkedForRollback() == true.&lt;br /&gt;&lt;br /&gt;트랜잭션이 진행 중이면서 아직 rollback 된 상태로 마킹되지 않음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prepareForAutoClose() 의 내부 코드는 AbstractSharedSessionContract.java에서 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1750925584723&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protected void prepareForAutoClose() {
    waitingForAutoClose = true;
    closed = true;
    // For non-shared transaction coordinators, we have to add the observer
    if ( !isTransactionCoordinatorShared ) {
        addSharedSessionTransactionObserver( transactionCoordinator );
    }
}

protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 addSharedSessionTransactionObserver는 해당 클래스에 구현되어있지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionImpl 에서 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1750925663071&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) {
    transactionObserver = new TransactionObserver() {
        @Override
        public void afterBegin() {
        }

        @Override
        public void beforeCompletion() {
            if ( isOpen() &amp;amp;&amp;amp; getHibernateFlushMode() !=  FlushMode.MANUAL ) {
                managedFlush();
            }
            actionQueue.beforeTransactionCompletion();
            beforeTransactionCompletionEvents();
        }

        @Override
        public void afterCompletion(boolean successful, boolean delayed) {
            afterTransactionCompletion( successful, delayed );
            if ( !isClosed() &amp;amp;&amp;amp; autoClose ) {
                managedClose(); //❗️❗️❗️
            }
        }
    };
    transactionCoordinator.addObserver( transactionObserver );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 메서드에서 DB 커넥션 릴리즈와 관련있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;managedClose()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;managedClose() 또한 SessionImpl 에 구현되어 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;private void managedClose() {
    log.trace( &quot;Automatically closing session&quot; );
    closeWithoutOpenChecks();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mangedClose()에서 호출하는 closeWithoutOpenChecks()는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionImpl#close() 내부에서 세션을 닫기 위해 호출하는 메서드 closeWithoutOpenChecks()와 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, closeWithoutOpenChecks() -&amp;gt; prepareAutoClose() -&amp;gt; ... -&amp;gt; closeWithoutOpenChecks() 로 다시 돌아오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 돌아왔을 때는 `!isTransactionInProgressAndNotMarkedForRollback() == true` 이므로 super.close()가 실행된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌되었든 호출된 super.close() 를 다시 파고든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionImpl.java#closeWithoutOpenChecks()&lt;span&gt; 의 super.close()는 AbstractSharedSessionContract.java에 구현되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public void close() {
    if ( closed &amp;amp;&amp;amp; !waitingForAutoClose ) {
       return;
    }

    try {
       delayedAfterCompletion();
    }
    catch ( HibernateException e ) {
       if ( getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
          throw getExceptionConverter().convert( e );
       }
       else {
          throw e;
       }
    }

    if ( sessionEventsManager != null ) {
       sessionEventsManager.end();
    }

    if ( transactionCoordinator != null ) {
       removeSharedSessionTransactionObserver( transactionCoordinator );
    }

    try {
       if ( shouldCloseJdbcCoordinatorOnClose( isTransactionCoordinatorShared ) ) {
          jdbcCoordinator.close(); // ✅
       }
    }
    finally {
       setClosed();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;jdbcCoordinator.close();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdbcCoordinator는 인터페이스로, 이를 구현한 구현체는 JdbcCoordinatorImpl.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;JdbcCoordinatorImpl.java의 close() 메서드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public Connection close() {
    LOG.tracev( &quot;Closing JDBC container [{0}]&quot;, this );
    Connection connection;
    try {
       if ( currentBatch != null ) {
          LOG.closingUnreleasedBatch();
          currentBatch.release();
       }
    }
    finally {
       connection = logicalConnection.close(); // ✅
    }
    return connection;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;connection = logicalConnection.close();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogicalConnection는 인터페이스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogicalConnection&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;|&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogicalConnectionImplementor&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;|&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbstractLogicalConnectionImplementor&lt;br /&gt;|&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogicalConnectionManagedImpl&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogicalConnectionManagedImpl에서 구현된 close() 는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public Connection close() {
    if ( closed ) {
       return null;
    }

    getResourceRegistry().releaseResources();

    log.trace( &quot;Closing logical connection&quot; );
    try {
       releaseConnection(); // ✅
    }
    finally {
       // no matter what
       closed = true;
       log.trace( &quot;Logical connection closed&quot; );
    }
    return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 메서드에서 DB 커넥션 릴리즈와 관련있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;releaseConnection()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private void releaseConnection() {
    final Connection localVariableConnection = this.physicalConnection;
    if ( localVariableConnection == null ) {
       return;
    }

    // We need to set the connection to null before we release resources,
    // in order to prevent recursion into this method.
    // Recursion can happen when we release resources and when batch statements are in progress:
    // when releasing resources, we'll abort the batch statement,
    // which will trigger &quot;logicalConnection.afterStatement()&quot;,
    // which in some configurations will release the connection.
    this.physicalConnection = null;
    try {
       try {
          getResourceRegistry().releaseResources();
          if ( !localVariableConnection.isClosed() ) {
             sqlExceptionHelper.logAndClearWarnings( localVariableConnection );
          }
       }
       finally {
          jdbcConnectionAccess.releaseConnection( localVariableConnection ); // ✅
       }
    }
    catch (SQLException e) {
       throw sqlExceptionHelper.convert( e, &quot;Unable to release JDBC Connection&quot; );
    }
    finally {
       observer.jdbcConnectionReleaseEnd();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;jdbcConnectionAccess.releaseConnection( localVariableConnection );&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdbcConnectionAccess는 인터페이스로, 이를 구현한 구현체는 JdbcConnectionAccessConnectionProviderImpl이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcConnectionAccessConnectionProviderImpl.java 의 releaseConnection() 은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
public void releaseConnection(Connection connection) throws SQLException {
    if ( connection != this.jdbcConnection ) {
       throw new PersistenceException(
             String.format(
                   &quot;Connection [%s] passed back to %s was not the one obtained [%s] from it&quot;,
                   connection,
                   JdbcConnectionAccessConnectionProviderImpl.class.getName(),
                   jdbcConnection
             )
       );
    }

    // Reset auto-commit
    if ( !wasInitiallyAutoCommit ) {
       try {
          if ( jdbcConnection.getAutoCommit() ) {
             jdbcConnection.setAutoCommit( false );
          }
       }
       catch (SQLException e) {
          log.info( &quot;Was unable to reset JDBC connection to no longer be in auto-commit mode&quot; );
       }
    }

    // Release the connection
    connectionProvider.closeConnection( jdbcConnection ); // ✅
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위의 메서드에서 DB 커넥션 릴리즈와 관련있는 코드는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;connectionProvider.closeConnection( jdbcConnection );&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Hikari CP를 사용하고 있으므로, 이를 기준으로 설명을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConnectionProvider는 인터페이스로, 이를 구현한 구현체는 HikariConnectionProvider.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;HikariConnectionProvider.java의 closeConnection() 은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fff8ed; color: #546e7a; text-align: start;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
public void closeConnection(Connection conn) throws SQLException
{
   conn.close();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conn.close() 도 이어서 트레이싱할 수 있긴하나 여기서 마친다.&lt;/p&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>  개발백과</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/89</guid>
      <comments>https://the0.tistory.com/89#entry89comment</comments>
      <pubDate>Thu, 26 Jun 2025 16:53:38 +0900</pubDate>
    </item>
    <item>
      <title>[Github Actions] attempted methods [none password]</title>
      <link>https://the0.tistory.com/88</link>
      <description>&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;님 ssh 비밀번호에 '$' 가 포함되어 있지 않은지?????????????&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상황:&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- github actions 사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- workflow 내용은 다음과 같음&lt;/p&gt;
&lt;pre id=&quot;code_1748290191453&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      -  name: Deploy
         uses: appleboy/ssh-action@master
         with:
          host: ${{ env.SSH_HOST }}
          username: ${{ env.SSH_USER }}
          password: ${{ env.SSH_PASSWORD }}
          script: |
            ...&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: #ee2323; text-align: start;&quot;&gt;ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain 에러 발생&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;포인트&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;- PasswordAuthentication yes 로 설정되어있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 방화벽 문제 XXXXX&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;- secrets. 가 아닌 env.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제 원인:&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ssh password 값에 $이 존재한 것이 문제였다.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 비밀번호가 ssh 접속을 위한 비밀번호가 abc1&lt;span style=&quot;color: #ee2323;&quot;&gt;$2&lt;/span&gt;defghIj 라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github actions는 echo로 환경변수를 설정할 때, shell이 $을 변수 참조 기호로 해석한다.&lt;/p&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. 따라서 비밀번호 abc1&lt;span style=&quot;color: #ee2323;&quot;&gt;$2&lt;/span&gt;defghIj&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;에서 $2 를 &quot;2라는 환경변수&quot; 로 인식한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그리고 (당연히 나는 2라는 환경변수를 설정하지 않았으니) 정의되지 않은 환경변수는 빈 문자열로 치환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 결과적으로 abc1&lt;span style=&quot;color: #ee2323;&quot;&gt;$2&lt;/span&gt;defghIj&amp;nbsp;에서 $2가 사라진 abc1defghIj 가 환경변수에 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. ssh 접속을 위한 password로 잘못된 값인 abc1defghIj 이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨리 해결하고 딴거해야해서 비번 $말고 딴걸로 바꿈&lt;/p&gt;</description>
      <category>  삽질의 추억</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/88</guid>
      <comments>https://the0.tistory.com/88#entry88comment</comments>
      <pubDate>Tue, 27 May 2025 05:47:21 +0900</pubDate>
    </item>
    <item>
      <title>[FastAPI, PostgreSQL] postgresql://와 postgresql+asyncpg:// 의 차이</title>
      <link>https://the0.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;글을 작성하게 된 이유:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;FastAPI 에서 Session 생성을 하기 위해 postgresql url 을 주입하는 도중에 여러 프로젝트의 소스코드마다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;postgresql://로 시작하는 url도 있고 postgresql+asyncpg:// 로 시작하는 url도 있었는데 그 차이가 궁금했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;그리고 이것 때문에 프로젝트 실행하는 과정에서 삽질을 하기도 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;두 URL은 주로 &lt;b&gt;동기 방식&lt;/b&gt;과 &lt;b&gt;비동기 방식&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;postgresql://&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 url은 &lt;span style=&quot;color: #1a5490;&quot;&gt;psycopg2&lt;/span&gt;와 같은 &lt;b&gt;동기식 드라이버&lt;/b&gt;를 사용해&lt;span style=&quot;color: #1a5490;&quot;&gt; PostgreSQL&lt;/span&gt; 에 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&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: #9d9d9d;&quot;&gt;일반적인&lt;/span&gt; &lt;u&gt;동기 실행 모델&lt;/u&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: #9d9d9d;&quot;&gt;&lt;b&gt;*&lt;/b&gt;참고로 파이썬은 동기 방식으로 동작하도록 설계된 언어이다. 물론 asyncio 등 라이브러리로 비동기 방식을 지원하긴하지만 &lt;b&gt;일반적으로는&lt;/b&gt; 그러하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;postgresql+asyncpg://&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;asyncpg&lt;/span&gt;라는&lt;b&gt; 비동기 드라이버&lt;/b&gt;를 사용해&lt;span style=&quot;color: #1a5490;&quot;&gt; PostgreSQL&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;&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;이 url은 &lt;span style=&quot;color: #1b711d;&quot;&gt;FastAPI&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;  &lt;b&gt;FastAPI가 비동기 프레임워크라고요?!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;네. 그렇다고 합니다...&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;FastAPI는 uvicorn과 함께 비동기적인 형태로 동작합니다.. 더보기(나중에 더 알아보자는 뜻~^^)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  개발백과</category>
      <category>fastapi</category>
      <category>PostgreSQL</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/86</guid>
      <comments>https://the0.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 18 Oct 2024 17:06:52 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 원격에서 다른 원격의 외부 엔드포인트 호출 안됨</title>
      <link>https://the0.tistory.com/84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;상황:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;원격 VM&lt;/span&gt; &lt;b&gt;A&lt;/b&gt;, &lt;b&gt;B&lt;/b&gt; 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A&lt;/b&gt;에 &lt;span style=&quot;color: #006dd7;&quot;&gt;도커로 띄운 Spring Boot &lt;b&gt;a1&lt;/b&gt;&lt;/span&gt; 이 &lt;b&gt;B&lt;/b&gt;&amp;nbsp;서버의 엔드포인트로 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;Docker 컨테이너로 실행시킨 스프링 부트는 잘 돌아가나 A -&amp;gt;B 호출만 안되는 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨테이너를 실행한 명령어&lt;/p&gt;
&lt;pre id=&quot;code_1727679564081&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d \
  --name my-backend \
  -p 8080:8080 \
  my-backend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방법:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 &lt;b&gt;`--network host`&lt;/b&gt; 옵션을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1727679627963&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d \
  --name my-backend \
  -p 8080:8080 \
  --network host \
  my-backend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 방화벽이나 보안 그룹 설정의 문제일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약&lt;/span&gt; 호스트의 방화벽 설정이 docker0 인터페이스를 통한 통신을 제한하고 있다면 &lt;span style=&quot;color: #333333;&quot;&gt;외부 통신에 문제를 일으킬 수 있다.&lt;/span&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;figure id=&quot;og_1727680197892&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Docker 컨테이너에서 외부 인터넷 접속이 되지 않을때&quot; data-og-description=&quot;호스트에서는 인터넷 접속이 원활하게 되나 컨테이너 내부에서는 인터넷이 되지 않는 경우가 있다. 많은 블로그들에서는 docker run 실행시 --network=host 옵션을 추가해서 기본 bridge가 아닌 host로 만&quot; data-og-host=&quot;albear.app&quot; data-og-source-url=&quot;https://albear.app/11&quot; data-og-url=&quot;https://albear.app/11&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kFhzP/hyXaxy4WhJ/f8CbYcYuiuAmwJevPnYft0/img.png?width=601&amp;amp;height=431&amp;amp;face=0_0_601_431,https://scrap.kakaocdn.net/dn/goJKw/hyXaAWSdNM/OL0awS8SMGfPJICK3uRHj1/img.png?width=601&amp;amp;height=431&amp;amp;face=0_0_601_431&quot;&gt;&lt;a href=&quot;https://albear.app/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://albear.app/11&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kFhzP/hyXaxy4WhJ/f8CbYcYuiuAmwJevPnYft0/img.png?width=601&amp;amp;height=431&amp;amp;face=0_0_601_431,https://scrap.kakaocdn.net/dn/goJKw/hyXaAWSdNM/OL0awS8SMGfPJICK3uRHj1/img.png?width=601&amp;amp;height=431&amp;amp;face=0_0_601_431');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너에서 외부 인터넷 접속이 되지 않을때&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;호스트에서는 인터넷 접속이 원활하게 되나 컨테이너 내부에서는 인터넷이 되지 않는 경우가 있다. 많은 블로그들에서는 docker run 실행시 --network=host 옵션을 추가해서 기본 bridge가 아닌 host로 만&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;albear.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;나는 회사 서버 방화벽 괜히 건드리기 무서워서 안했다..^^&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그게 뭔데&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트 네트워크를 사용하면&lt;b&gt; 마치 호스트 내에서 애플리케이션을 실행한 것과 같아져&lt;/b&gt; 별도의 포드포워딩 없이 호스트의 네트워크를 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(같은말 반복) 네트워크를 호스트 모드로 실행하면 새로운 내부 IP를 할당받을 필요 없이&amp;nbsp;곧바로 localhost를 통해 접속할 수 있게 된다.&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;호스트 네트워크를 직접 사용하기에 NAT(Network Address Translation) 과정이 필요 없어진다.&lt;/p&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;gt; 그리고 어쩐지 docker ps를 했을 때 `0.0.0.0:8080 =&amp;gt; 8080` 같은 표시도 사라진다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 외부로부터 공격 접근이 온다면 호스트의 네트워크까지 직접적으로 공격당할 수 있으므로 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 궁금한건 docker network host/bridge 키워드로 검색하길&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>  삽질의 추억/로컬에선 되는데 원격에서 안됨;</category>
      <category>docker</category>
      <category>docker network</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/84</guid>
      <comments>https://the0.tistory.com/84#entry84comment</comments>
      <pubDate>Wed, 2 Oct 2024 17:57:30 +0900</pubDate>
    </item>
    <item>
      <title>오프라인 상태에서 cURL 사용하기</title>
      <link>https://the0.tistory.com/83</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;오프라인 상태에서 cURL을 사용할 수 있을까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 요청 대상이 동일 네트워크/서버&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: #ee2323;&quot;&gt;인터넷 연결 여부와 상관없이&lt;/span&gt; cURL 테스트가 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;가능&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&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: #666666;&quot;&gt;&amp;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;u&gt;내부 네트워크의 라우팅 기능&lt;/u&gt;이 데이터를 대상 서버로 직접 전달하기 때문에 외부 인터넷 연결이 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 요청 대상이 외부 서버&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프라인 상태에서 외부 서버로 요청을 보내는 것은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&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;p data-ke-size=&quot;size16&quot;&gt;서버가 인터넷에 연결되지 않은 상태에서는 외부 서버에 요청을 보낼 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  개발백과</category>
      <category>CURL</category>
      <author>the0</author>
      <guid isPermaLink="true">https://the0.tistory.com/83</guid>
      <comments>https://the0.tistory.com/83#entry83comment</comments>
      <pubDate>Tue, 1 Oct 2024 14:13:04 +0900</pubDate>
    </item>
  </channel>
</rss>