<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>상어코딩</title>
    <link>https://dding-shark.tistory.com/</link>
    <description>뒷마당 설계와 스프링을 공부하는 상어를 수상할 정도로 조아 하는 개발자 입니다.
git : https://github.com/DDINGJOO</description>
    <language>ko</language>
    <pubDate>Mon, 27 Apr 2026 01:53:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dding-shark</managingEditor>
    <image>
      <title>상어코딩</title>
      <url>https://tistory1.daumcdn.net/tistory/7478079/attach/c1a9629103a44558a12f3a8bb820a390</url>
      <link>https://dding-shark.tistory.com</link>
    </image>
    <item>
      <title>Spring Data JPA @Modifying &amp;mdash; 벌크 쿼리와 영속성 컨텍스트 동기화</title>
      <link>https://dding-shark.tistory.com/429</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;spring-data-jpa-modifying--벌크-쿼리와-영속성-컨텍스트-동기화&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;Spring
Data JPA @Modifying — 벌크 쿼리와 영속성 컨텍스트 동기화&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;어제 데이터 마이그레이션 돌린 뒤 조회가 이상하다&amp;quot;는 리포트로 시작해
쿼리 로그를 파보면, 대개는 벌크 UPDATE 뒤에 같은 트랜잭션에서 조회한
엔티티가 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;옛 값&lt;/strong&gt;으로 돌아오고 있다. Spring Data JPA의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 처음 쓰면 거의 반드시 밟는 함정이고, 원리는
단순한데 증상이 쉽게 눈에 띄지 않아서 &amp;quot;DB에는 반영됐는데 애플리케이션
로그만 이상하다&amp;quot;는 식으로 오진단되기 쉽다. 뿌리는 &lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편 영속성
컨텍스트&lt;/a&gt;에서 본 1차 캐시다. 벌크 쿼리는 그 캐시를
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;우회&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;이 왜 존재하는지, 두
플래그(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically&lt;/code&gt;)가 정확히 무엇을 켜고 끄는지, 벌크
쿼리가 영속성 컨텍스트를 우회하는 메커니즘, 그리고 실무에서 자주
넘어지는 네 가지 함정을 Spring Boot 3.x / Spring Data JPA 3.x /
Hibernate 6.x 기준으로 정리한다. 특히 다음은 거의 모두가 한 번은
밟는다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt; JPQL을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;에 썼는데
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 빼먹으면 런타임 예외가 난다&lt;/strong&gt; — SELECT
전제인 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;가 쓰기 쿼리를 거부한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 UPDATE 뒤 같은 트랜잭션에서 조회가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;stale&lt;/code&gt;이다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt;를 안
켰기 때문이고, DB는 이미 바뀌었는데 1차 캐시만 옛날 값이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByStatus(...)&lt;/code&gt; 파생 메서드가
느리다&lt;/strong&gt; — 이름이 주는 인상과 달리 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 DELETE가 아니라
SELECT 후 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt; 반복&lt;/strong&gt;이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 UPDATE 뒤 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;이 증가하지
않는다&lt;/strong&gt; — JPQL 벌크는 버전 자동 증가를 건너뛴다(&lt;a href=&quot;./39_jpa-locking-optimistic-pessimistic.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;39편&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;왜 → 플래그 → 메커니즘 → 주의 → 실무&lt;/strong&gt; 순으로
읽으면 가장 자연스럽다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-modifying은-왜-필요한가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) @Modifying은 왜
필요한가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-clearautomatically--flushautomatically-기본값-false가-함정의-원인&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2)
clearAutomatically / flushAutomatically: 기본값 false가 함정의
원인&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-벌크-쿼리가-영속성-컨텍스트를-우회하는-메커니즘&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) 벌크
쿼리가 영속성 컨텍스트를 우회하는 메커니즘&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-흔한-버그-시나리오-stale-엔티티&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) 흔한 버그 시나리오:
stale 엔티티&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-jpql-벌크-vs-native-벌크-sqlrestriction이-적용되지-않는다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5)
JPQL 벌크 vs Native 벌크: @SQLRestriction이 적용되지 않는다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-version-자동-증가가-일어나지-않는다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6) @Version 자동
증가가 일어나지 않는다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-영향-받은-row-수-반환과-해석&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7) 영향 받은 row 수 반환과
해석&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-파생-delete-vs-modifying-delete-cascade와-이벤트의-갈림길&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8)
파생 delete vs @Modifying DELETE: cascade와 이벤트의 갈림길&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-saveallandflush--deleteallinbatch--편리함의-뒷면&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9)
saveAllAndFlush / deleteAllInBatch — 편리함의 뒷면&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-트랜잭션-경계와-service-transactional&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) 트랜잭션
경계와 Service @Transactional&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-modifying은-왜-필요한가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) @Modifying은 왜 필요한가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기본적으로 SELECT
전제&lt;/strong&gt;다. Repository 메서드에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;만 붙이면 해당
JPQL은 &amp;quot;읽기 쿼리&amp;quot;로 해석되고, 실행 결과를 반환 타입으로 매핑한다.
그런데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt;나 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DELETE&lt;/code&gt; JPQL은 결과 집합이
없다. 대신 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영향 받은 row 수&lt;/strong&gt;를 돌려주는 쓰기 쿼리다.
여기에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;만 붙이면 Spring Data JPA는 &amp;quot;SELECT로
해석했는데 SELECT가 아니다&amp;quot;라는 맥락에서 런타임 예외를 던진다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기 — @Modifying 없이 UPDATE JPQL&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;updateOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 코드는 앱을 띄우면 잘 뜬다. 메서드를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;호출하는
순간&lt;/strong&gt; &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;InvalidDataAccessApiUsageException&lt;/code&gt; 계열
예외가 나간다(구체 원인은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Query.executeUpdate()&lt;/code&gt;를 써야
하는데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getResultList()&lt;/code&gt;를 호출한 Hibernate 쪽
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QueryExecutionRequestException&lt;/code&gt;이다). 해결은 간단하다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 붙여 &amp;quot;이건 쓰기 쿼리다&amp;quot;를 Spring Data JPA에
알려준다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;updateOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반환 타입은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;int&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;void&lt;/code&gt;
중 하나를 쓴다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;int&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer&lt;/code&gt;면 영향 받은 row
수가 돌아오고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;void&lt;/code&gt;면 결과를 무시한다. 그리고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션이 필수&lt;/strong&gt;다.
Service 계층에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;이 있거나, 호출 스택 어디선가
트랜잭션이 열려 있어야 한다. 없으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TransactionRequiredException&lt;/code&gt;이 뜬다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉬운 지점이 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 붙였다고
끝난 게 아니다. 이 애노테이션은 &amp;quot;쓰기 쿼리임을 선언&amp;quot;만 할 뿐,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성 컨텍스트 동기화&lt;/strong&gt;는 별도 플래그로 제어한다. 다음
섹션에서 그 플래그를 본다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-clearautomatically--flushautomatically-기본값-false가-함정의-원인&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2)
clearAutomatically / flushAutomatically: 기본값 false가 함정의 원인&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;에는 두 개의 불리언 속성이 있다. 둘 다 기본값
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;이고, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;실무에서 대부분의 버그는 이 두 기본값을
모르고 쓰는 데서 나온다&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; flushAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;updateOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;속성&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;시점&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;역할&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기본값&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;벌크 쿼리 실행 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직전&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;영속성 컨텍스트의 pending 변경을 DB에 반영&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;벌크 쿼리 실행 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직후&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;영속성 컨텍스트를 비워 stale 엔티티 제거&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;2-1-flushautomatically가-필요한-상황&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-1) flushAutomatically가
필요한 상황&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;메모리에서 바꾼 엔티티가 있는데 그게 DB에 반영되기 전에 벌크 쿼리가
나가면, 벌크 쿼리는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;옛 상태&lt;/strong&gt;를 기준으로 실행된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 같은 트랜잭션 안&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setLastLoginAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 1차 캐시에서만 변경, DB 미반영&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;inactivateStaleUsers&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 벌크 UPDATE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;위 코드에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;u.setLastLoginAt&lt;/code&gt;은 아직 DB로 flush되지
않았다. 벌크 UPDATE가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;lastLoginAt &amp;lt; cutoff&lt;/code&gt; 조건으로
실행된다면, 방금 수정한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;u&lt;/code&gt;는 DB에서 여전히 옛
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;lastLoginAt&lt;/code&gt; 값이므로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;잘못된 대상&lt;/strong&gt;에 포함될
수 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically = true&lt;/code&gt;는 이 벌크 쿼리
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직전에 flush&lt;/strong&gt;를 강제로 걸어 pending 변경을 먼저 DB에
반영한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;물론 JPQL을 쓰면 FlushModeType &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt; 기본값이 이미 &amp;quot;JPQL
실행 직전 flush&amp;quot;를 걸기 때문에, 순수 JPQL 환경에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically&lt;/code&gt;가 중복일 수 있다. 하지만 FlushMode를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MANUAL&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;COMMIT&lt;/code&gt;으로 바꾼 경로나 네이티브
쿼리에서는 이 플래그가 진짜로 일을 한다. 실무에서 혼동 방지를 위해
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;그냥 같이 켜두는 편&lt;/strong&gt;이 무난하다.&lt;/p&gt;
&lt;h3 id=&quot;2-2-clearautomatically가-필요한-상황&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-2) clearAutomatically가
필요한 상황&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;벌크 쿼리가 DB의 row를 바꿨는데 그 row에 해당하는 엔티티가 이미 1차
캐시에 있으면, 이제 캐시의 엔티티는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;stale&lt;/strong&gt;(옛 값)
상태다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically = true&lt;/code&gt;는 벌크 쿼리 **직후에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;**를 걸어 영속성 컨텍스트를 통째로 비운다. 이후
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt; 같은 조회는 1차 캐시를 거치지 않고 DB로 다시
내려가 최신 값을 가져온다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 &amp;quot;왜 두 플래그 다 기본값이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;인가&amp;quot;라는 질문이
생긴다. 설계 관점에서는 &amp;quot;Spring Data JPA가 사용자의 영속성 컨텍스트를
임의로 건드리지 않는다&amp;quot;는 보수적 기본값이다. 하지만 실무에서는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;둘 다 켜는 게 디폴트&lt;/strong&gt;에 가깝다. 다음 섹션에서 그 이유를
메커니즘 관점에서 본다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-벌크-쿼리가-영속성-컨텍스트를-우회하는-메커니즘&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) 벌크
쿼리가 영속성 컨텍스트를 우회하는 메커니즘&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편
영속성 컨텍스트&lt;/a&gt;에서 본 것처럼, JPA의 1차 캐시는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속화된
엔티티 인스턴스를 그대로 들고 있는 Map&lt;/strong&gt;이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById(1L)&lt;/code&gt;을 호출하면 먼저 1차 캐시를 보고, 있으면 DB
조회 없이 캐시의 인스턴스를 돌려준다. 이 캐시가 변경 감지(dirty
checking)와 동일성 보장의 토대다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;벌크 쿼리는 이 구조를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정면으로 우회&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;44_spring-data-jpa-modifying-bulk-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;44_spring-data-jpa-modifying-bulk-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;다이어그램의 핵심은 간단하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 쿼리는 DB에 직접 SQL을
쏜다.&lt;/strong&gt; 1차 캐시에 있는 엔티티 인스턴스를 찾아서 필드를
갱신하거나 하지 않는다. Hibernate는 그 벌크 쿼리가 어떤 row를 건드릴지
미리 알 방법이 없고, 안다 해도 캐시의 모든 인스턴스를 일일이 갱신하는 건
부담이 크다. 그래서 기본 동작은 &amp;quot;DB에만 반영하고 메모리는 건드리지
않는다&amp;quot;이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 동작 자체를 &amp;quot;버그&amp;quot;로 생각하면 방향이 어긋난다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;의 clear/flush 플래그는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;이 비동기를
수동으로 해소하는 레버&lt;/strong&gt;다. 플래그를 안 켠 건 레버를 안 당긴
것뿐이고, 당기지 않은 채 조회를 계속하면 메모리와 DB가 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;두 개의
서로 다른 진실&lt;/strong&gt;을 들고 있게 된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-흔한-버그-시나리오-stale-엔티티&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) 흔한 버그 시나리오: stale
엔티티&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;말로만 설명하면 감이 안 오니 실제로 재현되는 패턴 세 가지를 본다.&lt;/p&gt;
&lt;h3 id=&quot;4-1-시나리오-1-조회-후-벌크-update-다시-조회&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) 시나리오 1:
조회 후 벌크 UPDATE, 다시 조회&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deactivateOld&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 1차 캐시 로드, active=true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;deactivateInactive&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;            &lt;span class=&quot;co&quot;&gt;// 벌크 UPDATE (u 포함)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isActive&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        log&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;여전히 active&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 여기로 빠진다 (stale)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// clearAutomatically 안 붙임&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update User u set u.active = false where u.lastLoginAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deactivateInactive&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DB 상의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;user#1&lt;/code&gt;은 이미 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;active=false&lt;/code&gt;인데,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;u&lt;/code&gt; 인스턴스는 1차 캐시 히트로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;active=true&lt;/code&gt;로
남아 있다. 로그는 &amp;quot;여전히 active&amp;quot;를 찍고, 디버거로 들여다봐도 값이 맞아
보여서 원인 추적이 오래 걸린다. 해결은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying(clearAutomatically = true)&lt;/code&gt; 또는 벌크 이후 명시적
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.refresh(u)&lt;/code&gt;다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-시나리오-2-벌크-이후-변경-감지가-동작하지-않는-것처럼-보임&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2)
시나리오 2: 벌크 이후 변경 감지가 동작하지 않는 것처럼 보임&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;patchAndBulk&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setNickname&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;kim&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 변경 감지 대상&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;bulkSomething&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// clearAutomatically = true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 여기서 u는 detached 상태&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-9&quot;&gt;&lt;a href=&quot;#cb7-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setNickname&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;park&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 이 변경은 flush되지 않는다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-10&quot;&gt;&lt;a href=&quot;#cb7-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically = true&lt;/code&gt;가 편리하긴 하지만 부작용이
있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;1차 캐시 전체&lt;/strong&gt;를
비우므로, 벌크 쿼리 이전에 조회해서 수정 중이던 엔티티까지
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;detached&lt;/strong&gt; 상태로 바뀐다. 커밋 시점에 변경 감지가 돌지
않아 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setNickname(&amp;quot;park&amp;quot;)&lt;/code&gt;는 조용히 사라진다. 해법은 두
가지다. 하나는 벌크 쿼리를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션의 가장 앞&lt;/strong&gt;으로 옮겨
관리 중인 엔티티가 없을 때 실행하는 것. 다른 하나는 벌크 후
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;로 다시 로드해 관리 상태로 재진입시키는 것.&lt;/p&gt;
&lt;h3 id=&quot;4-3-시나리오-3-네이티브-쿼리로-update-후-조회&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-3) 시나리오 3:
네이티브 쿼리로 update 후 조회&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;update orders set status = &amp;#39;DONE&amp;#39; where created_at &amp;lt; ?1&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; nativeQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkCloseOld&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네이티브 쿼리는 Hibernate 입장에서 &amp;quot;이 쿼리가 어떤 엔티티를
건드리는지&amp;quot; 정적으로 알 수 없다. 그래서 1차 캐시가 네이티브 UPDATE를
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전혀 인지하지 못한다&lt;/strong&gt;. JPQL 벌크보다 stale 위험이 더 큰
경로이고, 이 경우는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically = true&lt;/code&gt;를 거의 강제로
붙여야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-jpql-벌크-vs-native-벌크-sqlrestriction이-적용되지-않는다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5)
JPQL 벌크 vs Native 벌크: @SQLRestriction이 적용되지 않는다&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL 벌크와 네이티브 벌크는 문법 차이만 있는 게 아니다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA
추상화 계층을 거치는지 여부&lt;/strong&gt;가 가장 큰 차이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 벌크&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Native 벌크&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대상&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 이름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;테이블 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;파싱 주체&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Hibernate&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 드라이버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자동 적용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;수동 포함 필요&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상속 전략&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Hibernate가 해석&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;수동 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Filter&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;적용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;미적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;1차 캐시 인지&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 이름으로 유추 가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전혀 인지 못함&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;유연성&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPA 문법 범위 내&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;SQL 전체 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// JPQL 벌크 — @SQLRestriction 자동 반영&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.amount &amp;gt; :a&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkJpql&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; a&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Native 벌크 — soft-delete 조건 수동 포함&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-8&quot;&gt;&lt;a href=&quot;#cb9-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;update orders set status = ?1 where amount &amp;gt; ?2 and deleted = false&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-9&quot;&gt;&lt;a href=&quot;#cb9-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       nativeQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-10&quot;&gt;&lt;a href=&quot;#cb9-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkNative&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; a&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;a href=&quot;./40_jpa-auditing-soft-delete.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;40편 soft-delete&lt;/a&gt;에서
본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction(&amp;quot;deleted = false&amp;quot;)&lt;/code&gt;는 JPQL이 생성하는
모든 쿼리에 자동으로 붙지만, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네이티브 쿼리에는 붙지
않는다&lt;/strong&gt;. 네이티브로 벌크 UPDATE를 날리면 논리적으로
지워진(deleted=true) row까지 업데이트해버린다. 이 실수는 디버깅 때도 잘
안 보인다. &amp;quot;모든 row를 업데이트했다&amp;quot;는 결과만 보면 맞게 돌아간 것처럼
보이기 때문이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네이티브 쿼리를 써야 하는 상황은 실제로 존재한다. JPQL이 지원하지
않는 DB 고유 함수, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ON CONFLICT&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;RETURNING&lt;/code&gt; 같은
문법이 필요하면 네이티브가 답이다. 다만 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네이티브로 가는 순간
JPA의 안전망이 전부 빠진다&lt;/strong&gt;는 걸 의식해야 한다. soft-delete
조건, 상속 디스크리미네이터, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Filter&lt;/code&gt;는 전부 수동으로
관리해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-jpql-벌크-update가-건드리지-못하는-것&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) JPQL 벌크
UPDATE가 건드리지 못하는 것&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL 벌크 UPDATE는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 필드만&lt;/strong&gt; 다룰 수 있다. 연관
엔티티(ManyToOne, OneToOne)의 필드를 한 UPDATE 안에서 건드리려 하면 파싱
단계에서 막힌다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기 — 컴파일/파싱 시점에 예외&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.member.status = &amp;#39;VIP&amp;#39; where o.amount &amp;gt; :a&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;upgradeMembers&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; a&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이건 SQL로 치면 조인 UPDATE인데, JPA 표준 JPQL은 UPDATE에서 조인을
허용하지 않는다(Hibernate 일부 버전에서 부분적으로 허용되지만 표준
범위를 벗어난다). 해법은 서브쿼리로 우회하거나, 관련 엔티티 벌크
UPDATE를 별도 쿼리로 분리하는 것이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 — 서브쿼리 IN으로 우회&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    update Member m set m.status = &amp;#39;VIP&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    where m.id in (select o.member.id from Order o where o.amount &amp;gt; :a)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-7&quot;&gt;&lt;a href=&quot;#cb11-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;upgradeMembers&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; a&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네이티브 쿼리로 가면 DB 문법의 UPDATE JOIN을 쓸 수 있지만, 그 순간
§5의 안전망이 전부 빠진다는 점을 감수해야 한다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-flushmode와의-상호작용&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) FlushMode와의 상호작용&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically&lt;/code&gt;가 하는 일은 벌크 쿼리 직전에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.flush()&lt;/code&gt;를 강제로 호출하는 것이다. 그런데 JPA의 기본
FlushMode는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt;이고, 이미 JPQL 쿼리 실행 전에 flush를 거는
게 표준 동작이다. 그럼 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically = true&lt;/code&gt;는 왜
필요한가.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;FlushMode가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;COMMIT&lt;/code&gt; 또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MANUAL&lt;/code&gt;로
바뀌어 있을 때&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt;의 자동 flush가 꺼진
상태에서도 벌크 쿼리 직전에 flush를 보장&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네이티브 쿼리일 때&lt;/strong&gt; — Hibernate는 네이티브 쿼리가
어떤 엔티티를 건드릴지 모르므로, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt; FlushMode에서도
보수적으로만 flush를 걸거나 아예 걸지 않는다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically = true&lt;/code&gt;가 실질적으로 의미를 가지는
경로&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 범위가 겹치는 복잡한 흐름&lt;/strong&gt; — 명시적
선언으로 &amp;quot;이 쿼리 직전은 반드시 flush&amp;quot;를 코드에 남기기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;평문 FlushMode를 건드리지 않는 일반 서비스에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically = true&lt;/code&gt;가 중복처럼 보일 수 있지만,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네이티브 벌크나 FlushMode 커스터마이징이 섞이는 순간 진짜로
필요&lt;/strong&gt;해진다. 켜두는 비용이 거의 없으므로 기본으로 붙이는 편이
안전하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-version-자동-증가가-일어나지-않는다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6) @Version 자동 증가가
일어나지 않는다&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;a href=&quot;./39_jpa-locking-optimistic-pessimistic.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;39편
낙관적/비관적 락&lt;/a&gt;에서 본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;은 엔티티의 변경 감지
경로에서 자동으로 증가한다. 그런데 JPQL 벌크 UPDATE는 이 메커니즘을
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;건너뛴다&lt;/strong&gt;. DB에 UPDATE 문을 직접 쏘므로 Hibernate의 버전
증가 로직이 끼어들 틈이 없다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기 — @Version 증가 안 됨&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkWithoutVersion&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;벌크 UPDATE 이후 DB에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;status&lt;/code&gt;가 바뀌었지만
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;version&lt;/code&gt; 컬럼은 그대로다. 이 상태로 다른 트랜잭션이 같은
row에 낙관적 락을 걸고 동시에 수정을 시도하면, 낙관적 락 충돌 탐지가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;무의미&lt;/strong&gt;해진다. 벌크로 바꾼 변경이 동시성 관점에서는
&amp;quot;일어나지 않은 일&amp;quot;처럼 취급된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해결은 두 가지다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 1: Hibernate 확장 UPDATE VERSIONED&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update versioned Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkVersioned&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-6&quot;&gt;&lt;a href=&quot;#cb13-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 2: SET 절에 version 명시적 증가&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-7&quot;&gt;&lt;a href=&quot;#cb13-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-8&quot;&gt;&lt;a href=&quot;#cb13-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s, o.version = o.version + 1 where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-9&quot;&gt;&lt;a href=&quot;#cb13-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkExplicitVersion&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update versioned&lt;/code&gt;는 Hibernate 확장 문법으로, JPA 표준은
아니지만 Hibernate 6에서 문제없이 작동한다. &amp;quot;이 UPDATE에 걸리는 row들의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;도 함께 증가시켜라&amp;quot;를 선언한다. 표준을 유지하고
싶으면 두 번째 방식처럼 SET 절에 명시한다. 어느 쪽을 써도 좋지만,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;낙관적 락을 쓰는 엔티티의 벌크 UPDATE는 버전 증가를 반드시
동반&lt;/strong&gt;해야 한다는 규율을 잡는 게 핵심이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-영향-받은-row-수-반환과-해석&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) 영향 받은 row 수 반환과
해석&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드의 반환 타입이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;int&lt;/code&gt;면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영향 받은 row 수&lt;/strong&gt;가 돌아온다. JDBC의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeUpdate()&lt;/code&gt; 반환값이 그대로 전달된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; flushAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;update Order o set o.status = :s where o.createdAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;closeOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 호출측&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; affected &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;closeOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CLOSED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;log&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;닫힌 주문: {}건&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; affected&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;affected &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 조건에 맞는 주문이 없었다 (에러 아님)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-10&quot;&gt;&lt;a href=&quot;#cb14-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반환값이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;0&lt;/code&gt;이라고 해서 예외 상황은 아니다. 단순히
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건에 맞는 row가 없었다&lt;/strong&gt;는 뜻이다. 여기서 실수하기 쉬운
게 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;0&lt;/code&gt;을 낙관적 락 충돌로 오해하는 것이다. 낙관적 락 충돌은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OptimisticLockException&lt;/code&gt;으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;예외&lt;/strong&gt;가 나가지,
벌크 쿼리의 영향 row 수 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;0&lt;/code&gt;으로 표현되지 않는다. 두 개념을
섞으면 에러 핸들링이 틀어진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반대로 &amp;quot;정확히 1건 업데이트되어야 한다&amp;quot;는 계약이 있는 경우는 반환값을
검증한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; affected &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;closeById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;affected &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;예상 1건, 실제 &amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; affected &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;건&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 패턴은 &amp;quot;특정 조건 하에서만 변경&amp;quot;(conditional update, 예:
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where id = :id and status = &amp;#39;OPEN&amp;#39;&lt;/code&gt;)을 구현할 때 유용하다.
JPA 수준의 낙관적 락 없이도 애플리케이션 레벨의 조건부 업데이트를 구현할
수 있다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-파생-delete-vs-modifying-delete-cascade와-이벤트의-갈림길&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8)
파생 delete vs @Modifying DELETE: cascade와 이벤트의 갈림길&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA는 메서드 이름 파싱으로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByXxx&lt;/code&gt;를
자동 생성한다. 이름만 보면 &amp;quot;조건 기반 벌크 DELETE&amp;quot;처럼 느껴진다. 실제
구현은 그렇지 않다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 파생 메서드 — 이름만 보면 벌크 DELETE 같지만...&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deleteByStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;파생된 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByStatus&lt;/code&gt;의 내부 동작은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SELECT →
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt; 반복&lt;/strong&gt;이다. 즉 다음 두 단계로
쪼개진다.&lt;/p&gt;
&lt;ol type=&quot;1&quot; style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;select o from Order o where o.status = :s&lt;/code&gt; — 조건에 맞는
엔티티를 전부 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;먼저 조회&lt;/strong&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;조회된 엔티티 각각에 대해 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;entityManager.remove(order)&lt;/code&gt;
호출&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 방식의 장점은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@OneToMany(cascade = REMOVE)&lt;/code&gt;나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreRemove&lt;/code&gt; 이벤트 리스너가 정상 동작&lt;/strong&gt;한다는 것이다.
자식 엔티티 cascade 삭제, 감사 로그 기록, 도메인 이벤트 발행 같은 훅이
전부 돌아간다. 단점은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;성능&lt;/strong&gt;이다. 조건에 맞는 row가
10000건이면 SELECT 한 번 + DELETE 10000번이다. 영속성 컨텍스트도 그만큼
부풀어 오른다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반대로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 명시한 DELETE JPQL은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB에
DELETE 한 방&lt;/strong&gt;이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;delete from Order o where o.status = :s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkDelete&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이쪽은 SQL 한 번으로 끝난다. 대신 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;cascade = REMOVE&lt;/code&gt;도,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreRemove&lt;/code&gt;도 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작하지 않는다&lt;/strong&gt;. Hibernate가
엔티티 인스턴스를 거치지 않기 때문이다. 자식 엔티티는 DB의 외래키
제약(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ON DELETE CASCADE&lt;/code&gt;)에 의존하거나, 자식을 먼저 지우는
쿼리를 별도로 날려야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;비교 항목&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;파생 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByXxx&lt;/code&gt;&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; DELETE&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;쿼리 수&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;SELECT 1 + DELETE N&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DELETE 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascade = REMOVE&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작 안 함&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreRemove&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PostRemove&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작 안 함&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;soft-delete (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;적용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;적용(JPQL 한정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성 컨텍스트&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 관리&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;우회 (clear 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;적합한 규모&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;~수백 건&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;수천~수만 건&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉬운 건 &amp;quot;이름이 같은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteXxx&lt;/code&gt;끼리 당연히
같은 동작&amp;quot;이라는 가정이다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteById(1L)&lt;/code&gt;과
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; DELETE는 완전히 다른 경로다. 도메인 이벤트에
의존하는 코드라면 파생 메서드를 쓰고, 대량 삭제가 목적이라면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; DELETE로 가되 cascade를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직접&lt;/strong&gt;
책임진다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-saveallandflush--deleteallinbatch--편리함의-뒷면&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9)
saveAllAndFlush / deleteAllInBatch — 편리함의 뒷면&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA 3.x의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaRepository&lt;/code&gt;에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAllAndFlush&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch&lt;/code&gt;가 있다. 둘
다 벌크 처리에 자주 거론되는데 동작 방식은 꽤 다르다.&lt;/p&gt;
&lt;h3 id=&quot;9-1-saveallandflush&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) saveAllAndFlush&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;saveAllAndFlush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orders&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;내부 구현은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll(orders)&lt;/code&gt; 후
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.flush()&lt;/code&gt;다. &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편 N+1 편&lt;/a&gt;의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt; 함정과 같은 문제를 공유한다. 즉 &amp;quot;한 번에 저장&amp;quot;처럼
보이지만 실제로는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;save&lt;/code&gt;를 루프로 반복하고 마지막에 flush만
추가로 건다. JDBC batch insert는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.batch_size&lt;/code&gt; + SEQUENCE
전략&lt;/strong&gt;이 갖춰져 있어야만 자동으로 묶이고, IDENTITY 전략이면
아무리 메서드를 바꿔도 INSERT가 N번 나간다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAllAndFlush&lt;/code&gt;는 &amp;quot;flush 타이밍을 앞당기는&amp;quot; 목적에만
집중한다고 이해하는 편이 정확하다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-deleteallinbatch&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) deleteAllInBatch&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 인자 있는 버전&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;deleteAllInBatch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orders&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-4&quot;&gt;&lt;a href=&quot;#cb19-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 인자 없는 버전 — 위험&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-5&quot;&gt;&lt;a href=&quot;#cb19-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;deleteAllInBatch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;인자가 있는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch(Iterable)&lt;/code&gt;는 전달된 엔티티의
PK를 모아 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 방의 DELETE 쿼리&lt;/strong&gt;로 처리한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete from orders where id in (?, ?, ...)&lt;/code&gt; 형태다. SELECT
선행이 없고 cascade/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreRemove&lt;/code&gt;도 돌지 않는다. 성능은 파생
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByXxx&lt;/code&gt;보다 훨씬 빠르다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;문제는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;인자 없는&lt;/strong&gt; &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch()&lt;/code&gt;다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete from orders&lt;/code&gt; 한 방을 날린다. 테이블 전체 삭제다. 운영
환경에서 이걸 실수로 부르면 돌이킬 수 없다. 이름이 &amp;quot;batch&amp;quot;라서 &amp;quot;일괄
처리&amp;quot; 정도로 읽히지만, 실제로는 &amp;quot;조건 없는 전체 DELETE&amp;quot;다. 리포지토리
확장이나 테스트 코드에서 특히 조심해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;메서드&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;쿼리 형태&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascade/이벤트&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;위험도&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT N회(배치 조건 충족 시 묶임)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; 동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAllAndFlush&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT N회 + flush&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; 동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAll(entities)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;SELECT + DELETE N회&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch(entities)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete ... where id in (...)&lt;/code&gt; 1회&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작 안 함&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete from ...&lt;/code&gt; 1회 (&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전체&lt;/strong&gt;)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작 안 함&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;높음&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;빠르다&amp;quot;는 이유로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch&lt;/code&gt;를 남용하면 감사
로그나 cascade에 의존하던 도메인 로직이 조용히 무력화된다. 이 메서드는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테스트 픽스처 정리&lt;/strong&gt;나 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;명확히 전체 정리가 목적인
배치&lt;/strong&gt;에서만 쓰는 편이 안전하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-트랜잭션-경계와-service-transactional&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) 트랜잭션 경계와
Service @Transactional&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드는 트랜잭션이 필수라고 §1에서 짚었다.
여기에 숨은 함정이 있다. Service 계층에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;을 안
달고 Repository의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드만 바로 호출해도
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;예외가 안 난다&lt;/strong&gt;는 점이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Service — @Transactional 없음&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-4&quot;&gt;&lt;a href=&quot;#cb20-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-5&quot;&gt;&lt;a href=&quot;#cb20-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; OrderRepository orderRepo&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-6&quot;&gt;&lt;a href=&quot;#cb20-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-7&quot;&gt;&lt;a href=&quot;#cb20-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;closeOld&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-8&quot;&gt;&lt;a href=&quot;#cb20-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;closeOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CLOSED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-9&quot;&gt;&lt;a href=&quot;#cb20-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// 동작은 한다. 그런데 안전하지 않다.&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-10&quot;&gt;&lt;a href=&quot;#cb20-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-11&quot;&gt;&lt;a href=&quot;#cb20-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;왜 동작하는가. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SimpleJpaRepository&lt;/code&gt;의 기본 구현에
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;이 이미 걸려 있기&lt;/strong&gt; 때문이다.
Repository 메서드가 트랜잭션을 열고, 메서드가 끝나면 닫는다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TransactionRequiredException&lt;/code&gt;이 안 나는 이유다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;문제는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;경계가 Repository 메서드 한 번으로 끝난다&lt;/strong&gt;는
것이다. Service에서 벌크 쿼리 전후로 다른 작업을 섞으면, 그 작업은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 쿼리와 다른 트랜잭션&lt;/strong&gt;에서 실행된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb21&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb21-1&quot;&gt;&lt;a href=&quot;#cb21-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-2&quot;&gt;&lt;a href=&quot;#cb21-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;badFlow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-3&quot;&gt;&lt;a href=&quot;#cb21-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// 트랜잭션 1&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-4&quot;&gt;&lt;a href=&quot;#cb21-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setNickname&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;kim&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                           &lt;span class=&quot;co&quot;&gt;// 트랜잭션 1 (사실상 안 열림, OSIV 아니면)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-5&quot;&gt;&lt;a href=&quot;#cb21-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    userRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;bulkSomething&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                 &lt;span class=&quot;co&quot;&gt;// 트랜잭션 2&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-6&quot;&gt;&lt;a href=&quot;#cb21-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    someOtherRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                        &lt;span class=&quot;co&quot;&gt;// 트랜잭션 3&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-7&quot;&gt;&lt;a href=&quot;#cb21-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 흐름은 원자성을 포기한 상태다. 벌크 쿼리는 성공했는데 그 다음
save가 실패하면 복원이 안 된다. &amp;quot;당연히 같이 묶였을 것&amp;quot;이라는 기대가
무너진다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb22&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb22-1&quot;&gt;&lt;a href=&quot;#cb22-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-2&quot;&gt;&lt;a href=&quot;#cb22-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-3&quot;&gt;&lt;a href=&quot;#cb22-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-4&quot;&gt;&lt;a href=&quot;#cb22-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-5&quot;&gt;&lt;a href=&quot;#cb22-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; OrderRepository orderRepo&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-6&quot;&gt;&lt;a href=&quot;#cb22-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-7&quot;&gt;&lt;a href=&quot;#cb22-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// Service에서 명시적으로 경계를 연다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-8&quot;&gt;&lt;a href=&quot;#cb22-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;closeOld&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-9&quot;&gt;&lt;a href=&quot;#cb22-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;closeOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CLOSED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-10&quot;&gt;&lt;a href=&quot;#cb22-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// 이후 작업도 같은 트랜잭션에 묶인다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-11&quot;&gt;&lt;a href=&quot;#cb22-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-12&quot;&gt;&lt;a href=&quot;#cb22-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;규칙은 단순하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Repository의 내부 트랜잭션에 의존하지 말고,
호출측 Service에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;을 명시한다&lt;/strong&gt;. 이
규칙을 따르면 벌크 쿼리 전후의 모든 작업이 한 트랜잭션으로 묶이고,
원자성과 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt;의 효과가 예측 가능한 범위에
들어온다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-읽기-전용과-쓰기의-경계-분리&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) 읽기 전용과 쓰기의 경계
분리&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;조회 서비스와 쓰기 서비스를 분리하는 규율이 도움이 된다. 조회는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt;, 쓰기는 기본
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;이다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드는 쓰기
경로에만 등장해야 하고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;readOnly = true&lt;/code&gt; 트랜잭션 안에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;을 부르면 Hibernate가 런타임에 거부하지는 않지만
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;의도와 맞지 않는 흐름&lt;/strong&gt;이다. 리뷰어가 잡기 쉽도록 경계를
명시적으로 나눠두자.&lt;/p&gt;
&lt;h3 id=&quot;10-2-내부-호출과-프록시-우회&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2) 내부 호출과 프록시 우회&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;은 AOP 프록시로 작동한다. 같은 클래스
내부에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;this.method()&lt;/code&gt; 로 호출하면 프록시를 거치지 않아
트랜잭션이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;열리지 않는다&lt;/strong&gt;. 아래 코드는 흔한
함정이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb23&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb23-1&quot;&gt;&lt;a href=&quot;#cb23-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기 — 내부 호출로 @Transactional 우회&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-2&quot;&gt;&lt;a href=&quot;#cb23-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-3&quot;&gt;&lt;a href=&quot;#cb23-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-4&quot;&gt;&lt;a href=&quot;#cb23-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-5&quot;&gt;&lt;a href=&quot;#cb23-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;publicEntry&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-6&quot;&gt;&lt;a href=&quot;#cb23-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;doBulk&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// self-invocation: 프록시 우회&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-7&quot;&gt;&lt;a href=&quot;#cb23-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-8&quot;&gt;&lt;a href=&quot;#cb23-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-9&quot;&gt;&lt;a href=&quot;#cb23-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-10&quot;&gt;&lt;a href=&quot;#cb23-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;doBulk&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-11&quot;&gt;&lt;a href=&quot;#cb23-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;closeOldOrders&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CLOSED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cutoff&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-12&quot;&gt;&lt;a href=&quot;#cb23-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-13&quot;&gt;&lt;a href=&quot;#cb23-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;publicEntry&lt;/code&gt;가 호출될 때 트랜잭션은 열리지 않고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;doBulk&lt;/code&gt;의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;은 무시된다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 메서드가 자체 트랜잭션으로
떨어지거나(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SimpleJpaRepository&lt;/code&gt; 기본), 전파 설정에 따라
예외가 난다. 해결은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션이 필요한 메서드를 외부 진입점으로
만들고&lt;/strong&gt;, 내부에서 트랜잭션을 여는 우회가 필요하면 별도 빈으로
분리하거나 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AopContext.currentProxy()&lt;/code&gt; 또는 자기 자신 주입
같은 트릭을 쓰는 것이다. 가장 깔끔한 건 진입점 자체를 트랜잭션 경계로
삼는 설계다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;의 기본값은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically = true, flushAutomatically = true&lt;/code&gt;라고
몸에 새겨두자.&lt;/strong&gt; 쓰기 쿼리를 뒤이어 조회하는 경로가 없어도 켜서
나쁠 것이 없고, 누락이 만드는 stale 버그는 디버깅 비용이 크다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Service 계층에 **명시적 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;**을 붙여
Repository 내부 트랜잭션에 의존하지 말 것. 벌크 쿼리 전후가 한
트랜잭션에 묶여야 원자성과 clear의 효과가 예측된다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;벌크 UPDATE 이후에도 같은 트랜잭션에서 해당 엔티티를 계속 써야
한다면, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt; 대신
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.refresh(entity)&lt;/code&gt; 또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;로 재조회하는
편이 안전하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;1차 캐시 전체&lt;/strong&gt;를
비우므로 다른 변경까지 같이 날아간다는 점을 잊지 말 것&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;네이티브 벌크 쿼리는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Filter&lt;/code&gt;, 상속 디스크리미네이터가 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;적용되지
않는다&lt;/strong&gt;. soft-delete를 쓰는 도메인이면 네이티브 UPDATE/DELETE는
WHERE 절에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted = false&lt;/code&gt;를 수동으로 포함하는 걸 코드 리뷰
체크리스트에 넣는다 (&lt;a href=&quot;./40_jpa-auditing-soft-delete.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;40편&lt;/a&gt;)&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;을 쓰는 엔티티의 벌크 UPDATE는 반드시 버전을
함께 올리자. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update versioned&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SET o.version = o.version + 1&lt;/code&gt;. 안 그러면 낙관적 락이 있어도
없는 것과 같다 (&lt;a href=&quot;./39_jpa-locking-optimistic-pessimistic.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;39편&lt;/a&gt;)&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch()&lt;/code&gt; (인자 없는 버전)는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;운영
Repository에서 숨기거나 아예 사용 금지&lt;/strong&gt;로 규율한다. 테스트
픽스처 정리 경로에서만 제한적으로 허용&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;대량 삭제는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteAllInBatch(Iterable)&lt;/code&gt;의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IN&lt;/code&gt; 절 크기에 주의한다. Oracle은 IN 절 1000개 한도가 있고,
MySQL/PostgreSQL도 실용상 수천 개가 한계다. 더 크면 청크 단위로
쪼갠다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;파생 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByXxx&lt;/code&gt;가 느리다고 느껴지면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; DELETE로 바꾸기 전에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascade와 이벤트
리스너 의존성&lt;/strong&gt;을 먼저 점검한다. 이 의존성이 살아 있어야 하는
경로면 파생 메서드가 맞고, 아니면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;이 맞다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;11-1-테스트에서-stale-엔티티-잡기&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-1) 테스트에서 stale 엔티티
잡기&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; 플래그 누락은 유닛 테스트로도 잘 드러나지
않는다. 단위 테스트는 단일 메서드만 호출하고 끝나는 경우가 많아 &amp;quot;벌크
이후 같은 트랜잭션에서 다시 조회&amp;quot;라는 시나리오 자체가 만들어지지 않기
때문이다. 회귀 방지는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;통합 테스트&lt;/strong&gt;에서 의도적으로 그
흐름을 재현한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb24&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb24-1&quot;&gt;&lt;a href=&quot;#cb24-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-2&quot;&gt;&lt;a href=&quot;#cb24-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-3&quot;&gt;&lt;a href=&quot;#cb24-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderBulkIT &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-4&quot;&gt;&lt;a href=&quot;#cb24-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-5&quot;&gt;&lt;a href=&quot;#cb24-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt; OrderRepository orderRepo&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-6&quot;&gt;&lt;a href=&quot;#cb24-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@PersistenceContext&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-7&quot;&gt;&lt;a href=&quot;#cb24-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-8&quot;&gt;&lt;a href=&quot;#cb24-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-9&quot;&gt;&lt;a href=&quot;#cb24-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; 벌크_UPDATE_후_조회가_최신값을_반환한다&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-10&quot;&gt;&lt;a href=&quot;#cb24-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// given&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-11&quot;&gt;&lt;a href=&quot;#cb24-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Order o &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OPEN&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-12&quot;&gt;&lt;a href=&quot;#cb24-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-13&quot;&gt;&lt;a href=&quot;#cb24-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-14&quot;&gt;&lt;a href=&quot;#cb24-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// when&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-15&quot;&gt;&lt;a href=&quot;#cb24-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;bulkClose&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getCreatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;plusDays&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-16&quot;&gt;&lt;a href=&quot;#cb24-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-17&quot;&gt;&lt;a href=&quot;#cb24-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// then — stale이면 여기서 터진다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-18&quot;&gt;&lt;a href=&quot;#cb24-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Order reloaded &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-19&quot;&gt;&lt;a href=&quot;#cb24-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;fu&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;reloaded&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;CLOSED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-20&quot;&gt;&lt;a href=&quot;#cb24-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-21&quot;&gt;&lt;a href=&quot;#cb24-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying(clearAutomatically = true)&lt;/code&gt;가 빠져 있으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;reloaded&lt;/code&gt;는 1차 캐시에서 나와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Status.OPEN&lt;/code&gt;을
돌려준다. 이 테스트가 그 순간 빨갛게 뜨므로 PR 단계에서 누락이 잡힌다.
테스트 안에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직접 부르지 말
것&lt;/strong&gt;이 핵심이다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 부르면 프로덕션 코드의
플래그 누락이 가려진다.&lt;/p&gt;
&lt;h3 id=&quot;11-2-대량-처리의-청크-분할&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-2) 대량 처리의 청크 분할&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Oracle의 IN 절 1000개 제한 외에도, 수만 건 이상의 벌크 DELETE는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 로그와 락 범위&lt;/strong&gt;를 급격히 키운다. 같은
트랜잭션에 10만 건 DELETE가 통째로 묶이면 다른 커넥션의 조회가
블로킹되거나 테이블 레벨 락이 길어진다. 실무에서는 청크 분할로
피한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb25&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb25-1&quot;&gt;&lt;a href=&quot;#cb25-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-2&quot;&gt;&lt;a href=&quot;#cb25-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-3&quot;&gt;&lt;a href=&quot;#cb25-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderCleanupService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-4&quot;&gt;&lt;a href=&quot;#cb25-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-5&quot;&gt;&lt;a href=&quot;#cb25-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; OrderRepository orderRepo&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-6&quot;&gt;&lt;a href=&quot;#cb25-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-7&quot;&gt;&lt;a href=&quot;#cb25-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;cleanupChunked&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; chunkSize&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-8&quot;&gt;&lt;a href=&quot;#cb25-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; total &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-9&quot;&gt;&lt;a href=&quot;#cb25-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; deleted&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-10&quot;&gt;&lt;a href=&quot;#cb25-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-11&quot;&gt;&lt;a href=&quot;#cb25-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            deleted &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deleteChunk&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; chunkSize&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-12&quot;&gt;&lt;a href=&quot;#cb25-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            total &lt;span class=&quot;op&quot;&gt;+=&lt;/span&gt; deleted&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-13&quot;&gt;&lt;a href=&quot;#cb25-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;deleted &lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-14&quot;&gt;&lt;a href=&quot;#cb25-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; total&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-15&quot;&gt;&lt;a href=&quot;#cb25-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-16&quot;&gt;&lt;a href=&quot;#cb25-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-17&quot;&gt;&lt;a href=&quot;#cb25-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-18&quot;&gt;&lt;a href=&quot;#cb25-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deleteChunk&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; chunkSize&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-19&quot;&gt;&lt;a href=&quot;#cb25-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;deleteOldOrdersLimit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cutoff&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; chunkSize&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-20&quot;&gt;&lt;a href=&quot;#cb25-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-21&quot;&gt;&lt;a href=&quot;#cb25-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Repository 쪽은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LIMIT&lt;/code&gt; 문법이 DB에 따라 다르므로
네이티브로 적는 경우가 많다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb26&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb26-1&quot;&gt;&lt;a href=&quot;#cb26-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-2&quot;&gt;&lt;a href=&quot;#cb26-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-3&quot;&gt;&lt;a href=&quot;#cb26-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    delete from orders&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-4&quot;&gt;&lt;a href=&quot;#cb26-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    where created_at &amp;lt; ?1&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-5&quot;&gt;&lt;a href=&quot;#cb26-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    limit ?2&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-6&quot;&gt;&lt;a href=&quot;#cb26-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; nativeQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb26-7&quot;&gt;&lt;a href=&quot;#cb26-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;deleteOldOrdersLimit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime cutoff&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; limit&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;청크마다 트랜잭션을 나누는 설계는 중간 실패 시 이미 삭제된 row를
되살리지 못한다는 트레이드오프가 있다. &amp;quot;전체 원자성&amp;quot;과 &amp;quot;운영 중 블로킹
최소화&amp;quot; 중 후자가 더 중요한 도메인(로그, 만료 세션, 감사 기록)에서는
청크 분할이 기본이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;의 두 플래그
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flushAutomatically&lt;/code&gt;는
기본값 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;이고, 이걸 모른 채 벌크 쿼리를 쓰는 순간 영속성
컨텍스트와 DB가 다른 진실을 들고 있게 된다&lt;/strong&gt;. 벌크 쿼리는 1차
캐시를 우회하도록 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;설계된&lt;/strong&gt; 동작이므로 플래그로 수동
동기화를 걸어야 하고, 네이티브 쿼리는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;까지 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전부
수동&lt;/strong&gt;으로 챙겨야 한다. 파생 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleteByXxx&lt;/code&gt;와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt; DELETE는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;이름이 비슷해도 완전히 다른
경로&lt;/strong&gt;이며, cascade와 이벤트 의존성을 기준으로 갈라서
선택한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: Spring Data JPA, @Modifying, 벌크 쿼리, 영속성
컨텍스트, clearAutomatically, flushAutomatically, JPQL, Native Query,
@Version, deleteAllInBatch&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>@Modifying</category>
      <category>@version</category>
      <category>clearAutomatically</category>
      <category>deleteAllInBatch</category>
      <category>flushAutomatically</category>
      <category>JPQL</category>
      <category>Native Query</category>
      <category>spring data jpa</category>
      <category>벌크 쿼리</category>
      <category>영속성 컨텍스트</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/429</guid>
      <comments>https://dding-shark.tistory.com/429#entry429comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:30:49 +0900</pubDate>
    </item>
    <item>
      <title>Hibernate StatelessSession과 ScrollableResults &amp;mdash; 대량 처리에서 영속성 컨텍스트를 버리는 선택</title>
      <link>https://dding-shark.tistory.com/428</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;hibernate-statelesssession과-scrollableresults--대량-처리에서-영속성-컨텍스트를-버리는-선택&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;Hibernate
StatelessSession과 ScrollableResults — 대량 처리에서 영속성 컨텍스트를
버리는 선택&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;100만 건짜리 정산 데이터를 하룻밤에 털어넣어야 하는 일이 생긴다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;for&lt;/code&gt; 루프 안에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;memberRepository.save(...)&lt;/code&gt;를
돌리다 보면 어느 순간 힙이 차고 GC가 폭주하다가 끝내 OOM으로 멈춘다.
&amp;quot;그러면 중간에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush()&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear()&lt;/code&gt;를 섞으면
되잖아&amp;quot;라는 조언이 따라 나오고, 그걸 넣으면 메모리는 버티는데 이번엔
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;가 느리게 불리고, 이벤트 리스너가 중간에서 자기
할 일을 한다. 속도가 기대만큼 안 나온다. 이 지점에서 Hibernate는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성 컨텍스트를 통째로 버리는 선택지&lt;/strong&gt;를 준비해 뒀다.
그 이름이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이고, 읽기 쪽 짝이
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편 영속성
컨텍스트와 @Transactional&lt;/a&gt;의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대량 처리 관점
속편&lt;/strong&gt;이다. 12편은 &amp;quot;트랜잭션 경계 안에서 EntityManager가 무슨
일을 하는지&amp;quot;를 그렸고, 여기서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;그 EntityManager를 내려놓거나
우회해야 하는 상황&lt;/strong&gt;을 본다. 일반 Session이 들고 있는 세 가지
기능(1차 캐시·Dirty Checking·Write-Behind)은 100건짜리 트랜잭션에서는
편리함이지만, 100만 건짜리 배치에서는 전부 부담이 된다. 그 부담을 벗기로
결정했을 때 써야 하는 도구를 정리한다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;100만 건 insert에서 OOM&lt;/strong&gt; — 1차 캐시 clear를 빠뜨려
엔티티가 힙에 쌓인다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;StatelessSession으로 insert 했는데 BaseEntity의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;가 안 불린다&lt;/strong&gt; — JPA Entity Listener가
우회된다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Order&amp;gt;&lt;/code&gt; Repository 메서드가 커서를
못 열고 전체 로딩된다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;이 빠져
있다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;가 스트리밍이 아니라
전체 로드한다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FETCH_SIZE=Integer.MIN_VALUE&lt;/code&gt;가
필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;왜 → 구조 → 도구별 선택지 → 드라이버 제약 →
이벤트·캐시 상호작용 → 실무&lt;/strong&gt; 순으로, Spring Boot 3.x / Hibernate
6.x / Jakarta Persistence 3.x (jakarta.persistence) 기준으로
정리한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-100만-건-처리가-왜-힘든가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) 100만 건 처리가 왜
힘든가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-일반-session의-메모리-구조-복습&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) 일반 Session의
메모리 구조 복습&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-statelesssession-개요-잃는-것과-얻는-것&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3)
StatelessSession 개요: 잃는 것과 얻는 것&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-statelesssession의-네-가지-메서드&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) StatelessSession의
네 가지 메서드&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-대량-insert-패턴-비교-일반-session-vs-statelesssession&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) 대량
Insert 패턴 비교: 일반 Session vs StatelessSession&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-scrollableresults-커서-기반-스트리밍-조회&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6)
ScrollableResults: 커서 기반 스트리밍 조회&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-stream-entity-spring-data-jpa-스타일&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7) Stream Entity:
Spring Data JPA 스타일&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-드라이버별-scrollableresults-제약-mysql과-postgresql&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8)
드라이버별 ScrollableResults 제약: MySQL과 PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-entity-listener와-2차-캐시와의-상호작용&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) Entity
Listener와 2차 캐시와의 상호작용&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-spring-batch의-itemwriter와-어떻게-맞물리나&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) Spring
Batch의 ItemWriter와 어떻게 맞물리나&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-100만-건-처리가-왜-힘든가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) 100만 건 처리가 왜 힘든가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대량 처리의 &amp;quot;힘듦&amp;quot;은 크게 두 축에서 온다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;메모리&lt;/strong&gt;와
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;오버헤드&lt;/strong&gt;다. 메모리 축은 &amp;quot;엔티티가 힙에 쌓여서 OOM이
난다&amp;quot;, 오버헤드 축은 &amp;quot;건당 부가 작업이 누적돼서 TPS가 안 나온다&amp;quot;. 둘 다
일반 Session(=EntityManager)이 제공하는 기능이 원인이 된다. 즉,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;평소에는 편리함이었던 것이 수량이 커지면 그대로 비용으로
전환&lt;/strong&gt;된다. 이 전환 지점을 이해하는 것이 이 글 전체의
출발선이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;구체적으로 건당 쌓이는 비용을 나열하면 이렇다. 엔티티 하나를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 할 때마다 (1) 1차 캐시에 인스턴스가 들어가고, (2)
필드 스냅샷이 한 부 더 복사되고, (3) action queue에 INSERT 액션이
적재되고, (4) &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;·Envers·이벤트 리스너가 순서대로
돌고, (5) 2차 캐시가 켜져 있으면 캐시 정합성 처리가 뒤따른다. 건당 얼마
안 돼 보이지만 100만 번이면 힙에 100만 세트가 쌓인다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일반 Session 건당 비용&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;100만 건 환산&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 인스턴스 (1차 캐시)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;100만 인스턴스 상주&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;스냅샷 필드 사본&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 크기 × 100만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;action queue 엔트리&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;큐 길이 100만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;이벤트 리스너 호출&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;리스너 수 × 100만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;2차 캐시 put/invalidate&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;캐시 put 100만&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush()&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear()&lt;/code&gt;를 주기적으로
섞으면 (1)·(2)·(3)은 주기마다 비워진다. 하지만 (4)·(5)는 호출 횟수
자체가 줄지 않는다. 이벤트 리스너가 무거운 프로젝트(감사 로그, 도메인
이벤트 발행)일수록 flush/clear 패턴의 개선폭이 기대보다 작다. 이 한계를
벗어나려면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;건당 비용 자체를 줄이는 도구&lt;/strong&gt;가 필요하다.
그게 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;한편 조회 쪽에도 같은 문제가 있다. 100만 row짜리
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findAll()&lt;/code&gt;을 호출하면 JDBC 드라이버가 전체 결과를 메모리에
올린 뒤 Hibernate가 그걸 엔티티로 물들여 1차 캐시에 쌓는다. DB 커서로
천천히 흘려보내는 스트리밍이 필요하다 — 그게
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt; 쪽이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-일반-session의-메모리-구조-복습&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) 일반 Session의 메모리 구조
복습&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;을 이해하려면 먼저 버릴 대상이 무엇인지
정확히 알아야 한다. 일반 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Session&lt;/code&gt;이 가진 세 가지 기능을 &lt;a href=&quot;./12_spring-persistence-context-transactional.md#1-영속성-컨텍스트란-무엇인가-세-가지-역할&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편
§1~§6&lt;/a&gt;에서 다뤘으니 간단히 되짚기만 한다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;1차 캐시, Dirty
Checking(스냅샷), Write-Behind(action queue)&lt;/strong&gt;. 이 셋은 한
덩어리로 묶여 있고, 한 덩어리가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;org.hibernate.Session&lt;/code&gt;의
영속성 컨텍스트다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;43_hibernate-stateless-session-scrollable-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;43_hibernate-stateless-session-scrollable-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 그림이 이 글 나머지 전부의 밑그림이다. 일반 Session은 엔티티 한
건을 받으면 그 건에 대한 다섯 개의 자료구조와 후크가 함께 움직인다.
편리하지만 건당 비용이 크고, 상주 메모리가 엔티티 수에 선형으로
증가한다. 반대편의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 이 다섯을 전부 안
쓴다. 엔티티가 들어오면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;즉시 SQL로 변환해서 JDBC에 넘기고
잊는다&lt;/strong&gt;. 건당 비용이 SQL 실행 비용으로만 남는다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;그러면 일반 Session을 쓰면서 flush/clear만 잘 해도 되지 않나&amp;quot;라는
질문이 당연히 나온다. 여기서 틀리기 쉽다. flush/clear는 (1)·(2)·(3)만
턴다. 이벤트 리스너 호출 횟수 자체는 줄지 않고, 스냅샷을 &amp;quot;찍었다가
비우는&amp;quot; 작업이 반복적으로 일어나 CPU도 절약이 크지 않다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정말로
단순한 insert/update/delete&lt;/strong&gt;라면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;
쪽이 한 단계 더 싸다는 것이 핵심이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-statelesssession-개요-잃는-것과-얻는-것&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) StatelessSession
개요: 잃는 것과 얻는 것&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 이름 그대로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상태가 없는
세션&lt;/strong&gt;이다. Hibernate 공식 표현으로는 &amp;quot;a command-oriented API&amp;quot; —
&amp;quot;명령 지향 API&amp;quot;다. 엔티티 객체를 &amp;quot;관리&amp;quot;하는 대신, 호출자가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;insert&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete&lt;/code&gt;를 명시적으로
지시하면 그대로 SQL로 변환해서 즉시 보낸다. JDBC 한 꺼풀 위의 얇은
레이어라고 보면 가깝다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Spring Boot 3.x에서 StatelessSession 획득&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManagerFactory emf&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkInsert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; users&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    SessionFactory sf &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; emf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Transaction tx &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;beginTransaction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;User u &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; users&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 즉시 INSERT&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;얻는 것&amp;quot;부터 보자. 힙 상주량이 엔티티 수에 무관해진다. 스냅샷을 안
찍으니 CPU도 덜 쓴다. action queue를 안 타니 flush 시 일괄 처리 로직이
건너뛰어진다(단, JDBC 레벨 배치는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.batch_size&lt;/code&gt;로 여전히 먹는다). 2차 캐시의
put/invalidate 비용도 없다. 종합하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단순 CRUD의 건당 최소
비용에 수렴&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;잃는 것&amp;quot;이 더 길다. 표로 정리한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기능&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일반 Session&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;StatelessSession&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;1차 캐시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Dirty Checking&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt; (insert/update 수동 호출)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Write-Behind&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt; (즉시 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;cascade&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt; (자식 엔티티 수동 insert)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPA Entity Listener (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; 등)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Hibernate 이벤트&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일부만&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;2차 캐시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O (put/invalidate)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt; (우회)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Lazy 프록시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;X&lt;/strong&gt; (프록시 초기화 불가)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 표의 함의가 큰 항목 두 개만 더 풀자. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascade&lt;/strong&gt;가
작동 안 한다는 말은, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order&lt;/code&gt; 한 건을 insert할 때 자식
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderItem&lt;/code&gt;들을 자동으로 넣어주지 않는다는 뜻이다. 호출자가
부모 insert 후 자식들을 각각 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ss.insert(item)&lt;/code&gt;으로 돌려야
한다. 연관관계가 복잡한 도메인이라면 배치 코드가 도메인 로직을
재구성하는 꼴이 되므로, 이 비용을 받아들일 수 있는지 먼저 판단해야
한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA Entity Listener가 안 불린다&lt;/strong&gt;는 항목은 특히
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedAt&lt;/code&gt;을 자동 세팅하는
프로젝트에서 크게 다가온다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;으로 넣은 행은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로 들어간다. 수동으로 세팅해
주거나, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; 대신 DB의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DEFAULT CURRENT_TIMESTAMP&lt;/code&gt;로 바꿔 두는 우회가 필요하다. 뒤
&lt;a href=&quot;#9-entity-listener와-2차-캐시와의-상호작용&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§9 Entity
Listener와 2차 캐시와의 상호작용&lt;/a&gt;에서 다시 본다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-statelesssession의-네-가지-메서드&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) StatelessSession의 네
가지 메서드&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이 제공하는 쓰기 메서드는 네 개다. 이름
그대로 읽으면 의미가 선명하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;메서드&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;의미&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;비고&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;insert(entity)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT 즉시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt;로 JDBC 배치 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update(entity)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;UPDATE 즉시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;모든 필드로 UPDATE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete(entity)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DELETE 즉시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt; 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;upsert(entity)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;MERGE 즉시&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate 6.3+&lt;/strong&gt;, DB 지원 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update&lt;/code&gt;는 12편에서 본 Dirty Checking이 아니라 &amp;quot;호출
시점에 전달받은 엔티티 전체 필드로 UPDATE를 만든다&amp;quot;는 의미다. 어떤
필드가 바뀌었는지 Hibernate가 알 방법이 없으므로(스냅샷이 없음),
기본적으로 모든 필드를 UPDATE한다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;에서는 의미를 가지지 않는다는 점도 참고해
두자.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;upsert&lt;/code&gt;는 Hibernate 6.3에서 들어온 비교적 새 메서드다.
내부적으로 DB의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MERGE&lt;/code&gt; 구문(또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ON CONFLICT&lt;/code&gt;)을
사용해 &amp;quot;있으면 UPDATE, 없으면 INSERT&amp;quot;를 단일 SQL로 처리한다. 주의할 점은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MERGE&lt;/code&gt;를 지원해야 한다&lt;/strong&gt;는 것이고,
MySQL처럼 직접 지원이 없으면 Hibernate가 대체 전략(예:
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ON DUPLICATE KEY UPDATE&lt;/code&gt;)을 쓴다. 대상 DB의 방언(Dialect)이
어떤 SQL을 생성하는지 한 번 로그로 확인해 두는 편이 안전하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조회&lt;/strong&gt;도 가능하다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createQuery&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;get(Class, id)&lt;/code&gt; 같은 메서드가
있고, JPQL도 돌아간다. 결과 엔티티는 detached 상태로 돌아오고, 동일성
보장도 없다(같은 ID를 두 번 조회하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다른 인스턴스&lt;/strong&gt;가
반환된다). 이 점이 12편 §4의 &amp;quot;1차 캐시 동일성&amp;quot;과 크게 달라지는
지점이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: StatelessSession에서 동일성 기대&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User a &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;User&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User b &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;User&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;assert&lt;/span&gt; a &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; b&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// false. 매번 새 SELECT + 새 인스턴스&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: 대량 스트리밍용으로만 쓰고, 단건 조회/수정은 일반 EntityManager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 &amp;quot;같은 엔티티 그래프를 여러 번 읽고
고치는&amp;quot; 흐름에는 부적합하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 방향 스트림&lt;/strong&gt;(읽으면
쓰거나, 읽으면 잊거나)에 맞춰 설계돼 있다고 기억하면 된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-대량-insert-패턴-비교-일반-session-vs-statelesssession&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5)
대량 Insert 패턴 비교: 일반 Session vs StatelessSession&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;구체적인 코드 두 벌로 체감해 보자. 10만 건을 넣는다고 가정한다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-일반-entitymanager--flushclear&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) 일반 EntityManager +
flush/clear&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BulkInsertService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@PersistenceContext&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkInsertViaEm&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;UserRow&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; rows&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; batchSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// hibernate.jdbc.batch_size와 맞춤&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt; rows&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt; i&lt;span class=&quot;op&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-11&quot;&gt;&lt;a href=&quot;#cb3-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toEntity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;rows&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;op&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-12&quot;&gt;&lt;a href=&quot;#cb3-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;op&quot;&gt;%&lt;/span&gt; batchSize &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-13&quot;&gt;&lt;a href=&quot;#cb3-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;    &lt;span class=&quot;co&quot;&gt;// action queue → JDBC 배치&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-14&quot;&gt;&lt;a href=&quot;#cb3-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;    &lt;span class=&quot;co&quot;&gt;// 1차 캐시 비움&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-15&quot;&gt;&lt;a href=&quot;#cb3-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-16&quot;&gt;&lt;a href=&quot;#cb3-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-17&quot;&gt;&lt;a href=&quot;#cb3-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-18&quot;&gt;&lt;a href=&quot;#cb3-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 코드의 동작을 12편 §6 Write-Behind 관점으로 풀면 이렇다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 100번이 action queue에 쌓이고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush&lt;/code&gt;에서 JDBC 배치로 묶여 나가고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear&lt;/code&gt;로 1차
캐시가 비워진다. 이걸 1000번 반복해서 10만 건을 넣는다. 메모리는 버티고,
배치 INSERT도 먹는다. 하지만 건당 스냅샷이 한 번 찍혔다가 비워지고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;가 건마다 호출되고, 2차 캐시가 켜져 있으면
건마다 put이 일어난다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;주의: &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@GeneratedValue(strategy = IDENTITY)&lt;/code&gt;를 쓰는
엔티티라면 &lt;a href=&quot;./12_spring-persistence-context-transactional.md#6-write-behind-sql-지연-실행-큐&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편
§6&lt;/a&gt;에서 본 대로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;배치 INSERT가 먹지 않는다&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 시점에 ID를 받기 위해 즉시 SQL을 보낸다. 배치를
쓰려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt; 전략(또는 수동 할당)이어야 한다. MySQL처럼
시퀀스가 없는 DB에서는 수동 할당 +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements=true&lt;/code&gt;로 우회한다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-statelesssession&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) StatelessSession&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BulkInsertService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManagerFactory emf&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;bulkInsertViaStateless&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;UserRow&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; rows&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        SessionFactory sf &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; emf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-10&quot;&gt;&lt;a href=&quot;#cb4-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            Transaction tx &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;beginTransaction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-11&quot;&gt;&lt;a href=&quot;#cb4-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-12&quot;&gt;&lt;a href=&quot;#cb4-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;UserRow r &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; rows&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-13&quot;&gt;&lt;a href=&quot;#cb4-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;toEntity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-14&quot;&gt;&lt;a href=&quot;#cb4-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setCreatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Instant&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// @PrePersist 안 불리므로 수동 세팅&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-15&quot;&gt;&lt;a href=&quot;#cb4-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                    &lt;span class=&quot;co&quot;&gt;// 즉시. batch_size=100이면 내부 배치&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-16&quot;&gt;&lt;a href=&quot;#cb4-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-17&quot;&gt;&lt;a href=&quot;#cb4-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-18&quot;&gt;&lt;a href=&quot;#cb4-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-19&quot;&gt;&lt;a href=&quot;#cb4-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-20&quot;&gt;&lt;a href=&quot;#cb4-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-21&quot;&gt;&lt;a href=&quot;#cb4-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-22&quot;&gt;&lt;a href=&quot;#cb4-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-23&quot;&gt;&lt;a href=&quot;#cb4-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-24&quot;&gt;&lt;a href=&quot;#cb4-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 가지만 지적한다. 첫째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;이
없다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 Spring의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaTransactionManager&lt;/code&gt;와 묶이지 않는다 — 자체
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Transaction&lt;/code&gt; 객체로 트랜잭션 경계를 관리한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; 메서드 안에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;openStatelessSession&lt;/code&gt;을 열면 바깥 트랜잭션과 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;같은
JDBC 커넥션을 공유하지 않는다&lt;/strong&gt;. 동일 커넥션이 필요하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;sf.openStatelessSession(conn)&lt;/code&gt; 오버로드로 명시적으로
커넥션을 넘겨주는 패턴을 써야 한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;둘째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;을 수동으로 세팅&lt;/strong&gt;했다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;가 우회되기 때문에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditingEntityListener&lt;/code&gt;도 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity&lt;/code&gt;의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;도 실행되지 않는다. 이 자리가 실무에서 놓치기
쉬운 함정이다. 이어지는 &lt;a href=&quot;#9-entity-listener와-2차-캐시와의-상호작용&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§9 Entity Listener와
2차 캐시와의 상호작용&lt;/a&gt;에서 대안을 정리한다.&lt;/p&gt;
&lt;h3 id=&quot;5-3-비교&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-3) 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일반 EM + flush/clear&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;StatelessSession&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;메모리 상주&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;batchSize 만큼&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;거의 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;스냅샷 비용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;건당 발생&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;action queue&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;사용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;우회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JDBC 배치&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt; + 전략에 따라&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt;로 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;비동작&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;cascade&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;비동작&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; 연동&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자연스럽게 됨&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;수동 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;권장 시나리오&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;복잡한 도메인 배치&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;단순 대량 insert/update/delete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단순하면 StatelessSession, 복잡하면 일반 EM +
flush/clear&lt;/strong&gt;. 이 한 줄이 선택 기준의 뼈대다. &amp;quot;단순&amp;quot;의 기준이
애매하면 체크리스트로 내려본다 — cascade 없이 부모만 있나, Entity
Listener가 필요 없나, 2차 캐시에 의존 안 하나. 셋 다 &amp;quot;그렇다&amp;quot;면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이 훨씬 싸다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-scrollableresults-커서-기반-스트리밍-조회&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6)
ScrollableResults: 커서 기반 스트리밍 조회&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;조회 쪽 짝이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;다. DB 커서를 열어 둔 채로
row를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 건씩&lt;/strong&gt; 애플리케이션으로 흘려보낸다. Hibernate의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;org.hibernate.query.Query&lt;/code&gt;에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;scroll()&lt;/code&gt;
메서드로 얻는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderProcessor &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@PersistenceContext&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;processAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        org&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; q &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;/span&gt;
&lt;span id=&quot;cb5-10&quot;&gt;&lt;a href=&quot;#cb5-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT o FROM Order o WHERE o.status = :s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-11&quot;&gt;&lt;a href=&quot;#cb5-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PENDING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-12&quot;&gt;&lt;a href=&quot;#cb5-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;org&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-13&quot;&gt;&lt;a href=&quot;#cb5-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-14&quot;&gt;&lt;a href=&quot;#cb5-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setFetchSize&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-15&quot;&gt;&lt;a href=&quot;#cb5-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-16&quot;&gt;&lt;a href=&quot;#cb5-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ScrollableResults&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; scroll &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ScrollMode&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FORWARD_ONLY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-17&quot;&gt;&lt;a href=&quot;#cb5-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-18&quot;&gt;&lt;a href=&quot;#cb5-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;scroll&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-19&quot;&gt;&lt;a href=&quot;#cb5-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                Order o &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; scroll&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-20&quot;&gt;&lt;a href=&quot;#cb5-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-21&quot;&gt;&lt;a href=&quot;#cb5-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(++&lt;/span&gt;i &lt;span class=&quot;op&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-22&quot;&gt;&lt;a href=&quot;#cb5-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-23&quot;&gt;&lt;a href=&quot;#cb5-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 1차 캐시 비움&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-24&quot;&gt;&lt;a href=&quot;#cb5-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-25&quot;&gt;&lt;a href=&quot;#cb5-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-26&quot;&gt;&lt;a href=&quot;#cb5-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-27&quot;&gt;&lt;a href=&quot;#cb5-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-28&quot;&gt;&lt;a href=&quot;#cb5-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;핵심 파라미터는 두 개다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(1000)&lt;/code&gt;은 &amp;quot;JDBC
드라이버가 DB에서 한 번에 끌어올 row 수&amp;quot;다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollMode.FORWARD_ONLY&lt;/code&gt;는 &amp;quot;커서가 앞으로만 간다&amp;quot;는 뜻으로,
가장 효율적인 모드다. Hibernate는 다른
모드(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SCROLL_INSENSITIVE&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SCROLL_SENSITIVE&lt;/code&gt;)도
지원하지만 DB·드라이버별 제약이 크고 대부분 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FORWARD_ONLY&lt;/code&gt;로
충분하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;는 DB 쪽
메모리를 절약하는 게 아니다&lt;/strong&gt;. DB는 일반 SELECT와 똑같이 결과
집합을 만든다. 다만 애플리케이션 쪽 JDBC 드라이버가 결과를 한꺼번에 받지
않고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize&lt;/code&gt;만큼씩 나눠 받는다. 그래서
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;애플리케이션 힙&lt;/strong&gt;을 절약하는 효과가 핵심이다. 그리고
커서가 열려 있는 동안 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션이 살아 있어야&lt;/strong&gt; 한다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt;이 반드시 필요하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;또 하나, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;가 없으면 스트리밍이
무의미해진다&lt;/strong&gt;. 1000건을 받고 나면 그 1000건이 1차 캐시에 쌓인
채로 다음 1000건이 온다. 엔티티는 계속 누적되고 결국 OOM이다. &amp;quot;커서는 DB
스트리밍이지만 엔티티는 EntityManager에 쌓인다&amp;quot;는 게 처음엔 잘 안
그려지는데, 한 번 OOM을 맞으면 확실히 기억에 남는다. &lt;a href=&quot;#8-드라이버별-scrollableresults-제약-mysql과-postgresql&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§8
드라이버별 ScrollableResults 제약: MySQL과 PostgreSQL&lt;/a&gt;에서 한 번 더
나온다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;과
묶으면 어떨까. 가능하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear&lt;/code&gt;가 필요 없으니 스트리밍 코드가 더 단순해진다. 대신
엔티티가 detached 상태로 오므로 lazy 필드 접근은 불가능하다. 읽기
전용으로 &amp;quot;row → 가공 → 다른 저장소&amp;quot; 같은 흐름에 잘 맞는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    org&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; q &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT o FROM Order o WHERE o.status = :s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PENDING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setFetchSize&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ScrollableResults&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; scroll &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ScrollMode&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;FORWARD_ONLY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;scroll&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;fu&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;scroll&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;co&quot;&gt;// em.clear() 불필요&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-12&quot;&gt;&lt;a href=&quot;#cb6-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-stream-entity-spring-data-jpa-스타일&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) Stream Entity: Spring
Data JPA 스타일&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;를 감싼 더 자바다운
API를 준비해 뒀다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Entity&amp;gt;&lt;/code&gt;를 반환하는
Repository 메서드다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT o FROM Order o WHERE o.status = :s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Stream&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;streamByStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; Status s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;사용 코드는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;try-with-resources&lt;/code&gt;로 스트림을 닫아야 한다.
스트림을 닫으면 내부 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;가 닫히고, 커서와
결과집합이 해제된다. 닫지 않으면 커넥션이 반환되지 않아 풀이
말라간다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OrderRepository orderRepo&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-10&quot;&gt;&lt;a href=&quot;#cb8-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-11&quot;&gt;&lt;a href=&quot;#cb8-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;processPending&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-12&quot;&gt;&lt;a href=&quot;#cb8-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Stream&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; stream &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepo&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;streamByStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PENDING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-13&quot;&gt;&lt;a href=&quot;#cb8-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-14&quot;&gt;&lt;a href=&quot;#cb8-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            stream&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-15&quot;&gt;&lt;a href=&quot;#cb8-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-16&quot;&gt;&lt;a href=&quot;#cb8-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(++&lt;/span&gt;i&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-17&quot;&gt;&lt;a href=&quot;#cb8-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-18&quot;&gt;&lt;a href=&quot;#cb8-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-19&quot;&gt;&lt;a href=&quot;#cb8-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-20&quot;&gt;&lt;a href=&quot;#cb8-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-21&quot;&gt;&lt;a href=&quot;#cb8-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-22&quot;&gt;&lt;a href=&quot;#cb8-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 가지 포인트. 첫째,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt;가 반드시 메서드에
있어야 한다&lt;/strong&gt;. 커서가 트랜잭션 수명에 묶여 있으므로, 트랜잭션
밖에서 스트림을 돌리면 예외가 터지거나(드라이버/설정에 따라) 아예 전체
로딩으로 떨어진다. 둘째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;try-with-resources&lt;/code&gt;로
스트림을 닫는다&lt;/strong&gt;. Spring Data의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&lt;/code&gt;은 커서를
품고 있는 자원이므로 Java &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&lt;/code&gt;의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;close()&lt;/code&gt;
규약을 지켜야 한다. 셋째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 주기적으로
호출&lt;/strong&gt;한다 — &lt;a href=&quot;#6-scrollableresults-커서-기반-스트리밍-조회&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§6
ScrollableResults: 커서 기반 스트리밍 조회&lt;/a&gt;의 이유와 동일.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Entity&amp;gt;&lt;/code&gt;는 내부적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;를 쓰므로 드라이버 제약도 그대로 상속된다.
MySQL에서 스트리밍이 안 되면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&lt;/code&gt;도 안 된다. 다음 절에서
본다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-드라이버별-scrollableresults-제약-mysql과-postgresql&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8)
드라이버별 ScrollableResults 제약: MySQL과 PostgreSQL&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Entity&amp;gt;&lt;/code&gt;의
가장 큰 함정이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;드라이버 설정&lt;/strong&gt;이다. Hibernate가 &amp;quot;스트리밍
쿼리&amp;quot;라고 마크해도, JDBC 드라이버가 실제로 커서 페치로 동작하는지는
드라이버마다 조건이 다르다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-mysql-mysql-connector-j&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) MySQL (mysql-connector-j)&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;MySQL JDBC 드라이버는 기본적으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전체 결과를 메모리에
올린다&lt;/strong&gt;. 스트리밍을 하려면 두 방법 중 하나를 쓴다.&lt;/p&gt;
&lt;pre class=&quot;text&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;1. Statement.setFetchSize(Integer.MIN_VALUE)
   → 순수 JDBC 스트리밍 모드. 커서 없이 row 단위로 흘려받음.
   → Hibernate 6에서는 setFetchSize에 특수값을 줘야 발동.

2. useCursorFetch=true (커넥션 URL) + setFetchSize(N&amp;gt;0)
   → 서버 측 커서를 쓴다. 드라이버가 배치별로 가져옴.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;1번은 &amp;quot;결과집합 전체를 한 번에 건내지 않고 row 하나씩 네트워크로
스트림&amp;quot;하는 옵션이다. Hibernate에서 이걸 쓸 때는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;쿼리
레벨로&lt;/strong&gt; &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;query.setFetchSize(Integer.MIN_VALUE)&lt;/code&gt;를
부르는 방식을 기본으로 둔다. 전역 프로퍼티
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.fetch_size=Integer.MIN_VALUE&lt;/code&gt;를 걸어도 동작은
하지만, 그 순간 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;모든 쿼리가 스트리밍 모드로 바뀌어 커넥션 잠금
범위가 전역으로 넓어진다&lt;/strong&gt;. 커서가 열려 있는 동안 해당
커넥션에서는 다른 쿼리를 동시에 실행할 수 없으므로, 배치 경로에만
국소적으로 걸어 두는 편이 안전하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;2번은 JDBC URL에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;useCursorFetch=true&lt;/code&gt;를 붙이고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize&lt;/code&gt;를 양수로 지정하는 방식이다. 서버 측 커서를
사용하므로 1번보다 범용적이지만, DB 쪽 리소스를 더 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode yaml&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode yaml&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;# application.yml&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;datasource&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; jdbc:mysql://localhost:3306/app?useCursorFetch=true&amp;amp;rewriteBatchedStatements=true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jpa&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-8&quot;&gt;&lt;a href=&quot;#cb10-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jdbc&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-9&quot;&gt;&lt;a href=&quot;#cb10-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch_size&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements=true&lt;/code&gt;는 &lt;a href=&quot;./42_hibernate-batch-insert-update.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;42편&lt;/a&gt;에서 본 배치
INSERT용 옵션이지만, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;useCursorFetch&lt;/code&gt;와 충돌하지 않으므로
같이 써도 된다. &amp;quot;읽기도 쓰기도 스트리밍&amp;quot;이 되는 접속 URL을 만드는 게
목표다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-postgresql-pgjdbc&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) PostgreSQL (pgjdbc)&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;PostgreSQL은 다르게 생겼다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize &amp;gt; 0&lt;/code&gt;이면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;자동으로 서버 측 커서를 연다&lt;/strong&gt;. 추가 URL 옵션이 필요
없다. 단 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 가지 조건&lt;/strong&gt;이 있다: &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;autoCommit이
false&lt;/strong&gt;여야 한다. autoCommit 상태에서 커서를 열면 커밋 즉시
커서가 닫혀버리므로 배치 페치가 의미를 잃는다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;스프링에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; 메서드 안에서 실행하면
자동으로 autoCommit이 false가 되므로, 트랜잭션 경계를 제대로 그어 두면
설정만으로 스트리밍이 붙는다. 트랜잭션 없이 Repository의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&lt;/code&gt; 메서드를 호출하는 실수가 가장 흔한 사고
지점이다.&lt;/p&gt;
&lt;h3 id=&quot;8-3-드라이버-제약-요약&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-3) 드라이버 제약 요약&lt;/h3&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;설정 예&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;MySQL&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize = Integer.MIN_VALUE&lt;/code&gt; (스트리밍)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(Integer.MIN_VALUE)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;MySQL&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;useCursorFetch=true&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize &amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;URL 옵션 + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(1000)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;PostgreSQL&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;autoCommit=false&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize &amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(1000)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Oracle&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize &amp;gt; 0&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(1000)&lt;/code&gt; (트랜잭션 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;H2&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;기본 지원&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize(1000)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. &amp;quot;Hibernate 쪽에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setFetchSize&lt;/code&gt;만 주면
어떻게든 스트리밍 되겠지&amp;quot;는 착각이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;드라이버가 실제로
커서/스트리밍 모드로 동작하는지 확인&lt;/strong&gt;해야 한다. 확인 방법은
간단하다 — DB 쪽 프로파일러(MySQL &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;performance_schema&lt;/code&gt;,
PostgreSQL &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;pg_stat_activity&lt;/code&gt;)에서 커서가 열리는지 보면 된다.
또는 아주 큰 테이블을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&lt;/code&gt;으로 뽑을 때 JVM 힙 사용량을
모니터링해서, 메모리가 급격히 치솟으면 스트리밍이 안 되는 상태다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-entity-listener와-2차-캐시와의-상호작용&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) Entity Listener와
2차 캐시와의 상호작용&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이 우회하는 것 중에 가장 실무에 영향이
큰 둘이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA Entity Listener&lt;/strong&gt;와 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;2차
캐시&lt;/strong&gt;다. 각각 함정과 대응을 정리한다.&lt;/p&gt;
&lt;h3 id=&quot;9-1-prepersistauditingentitylistener&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1)
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditingEntityListener&lt;/code&gt;&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity&lt;/code&gt;에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;로 자동 감사
필드를 쓰는 프로젝트가 많다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession.insert&lt;/code&gt;는 이
리스너를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전혀 호출하지 않는다&lt;/strong&gt;. 결과적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedAt&lt;/code&gt;이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로
들어간다. 제약조건 위반으로 실패하거나, 조용히 null이 박혀서 다음 날
장애로 드러난다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 리스너에 의존한 채 StatelessSession 사용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;AuditingEntityListener&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedDate&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Instant createdAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedDate&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Instant updatedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-7&quot;&gt;&lt;a href=&quot;#cb11-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-8&quot;&gt;&lt;a href=&quot;#cb11-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-9&quot;&gt;&lt;a href=&quot;#cb11-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// StatelessSession에서 insert → createdAt == null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대응은 세 가지 중 하나다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 1: 엔티티 생성 시점에 수동 세팅&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;User u &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;toEntity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Instant now &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Instant&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setCreatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;now&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;u&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setUpdatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;now&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-6&quot;&gt;&lt;a href=&quot;#cb12-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-7&quot;&gt;&lt;a href=&quot;#cb12-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-8&quot;&gt;&lt;a href=&quot;#cb12-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 2: DB DEFAULT로 옮기기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-9&quot;&gt;&lt;a href=&quot;#cb12-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// CREATE TABLE user (&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-10&quot;&gt;&lt;a href=&quot;#cb12-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//   ...&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-11&quot;&gt;&lt;a href=&quot;#cb12-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//   created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-12&quot;&gt;&lt;a href=&quot;#cb12-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// );&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-13&quot;&gt;&lt;a href=&quot;#cb12-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 엔티티에서는 @Column(insertable = false) 또는 null 허용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-14&quot;&gt;&lt;a href=&quot;#cb12-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-15&quot;&gt;&lt;a href=&quot;#cb12-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 3: 대량 insert 대상 엔티티를 별도로 유지&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-16&quot;&gt;&lt;a href=&quot;#cb12-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 감사 필드를 명시 필드로 (리스너 없이)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서는 1번이 가장 흔하고 안전하다. 배치 경로에서만 필드를
명시적으로 세팅하면 일반 서비스 경로의 리스너는 그대로 살릴 수 있다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate의 순수 이벤트 시스템(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreInsertEventListener&lt;/code&gt;
등)은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일부&lt;/strong&gt; 동작한다.
하지만 JPA 스펙이 정의한
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreUpdate&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PostLoad&lt;/code&gt;
같은 라이프사이클 콜백은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;우회된다&lt;/strong&gt;. 이 구분을 기억해
두자. &amp;quot;감사는 JPA 리스너&amp;quot;, &amp;quot;물리적 인덱스 유지는 Hibernate 이벤트&amp;quot;라면
후자는 일부 살 수 있다는 뜻이다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-2차-캐시&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) 2차 캐시&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 2차 캐시를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;우회&lt;/strong&gt;한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;insert&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;update&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;delete&lt;/code&gt; 어느 것도
캐시를 갱신하지 않고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;get&lt;/code&gt;도 캐시를 읽지 않는다. 이 비대칭이
실무에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;stale 캐시&lt;/strong&gt; 문제를 만든다.&lt;/p&gt;
&lt;pre class=&quot;text&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;[시나리오]
1. 일반 서비스 경로로 User(id=1)를 조회 → 2차 캐시에 적재
2. 배치가 StatelessSession으로 User(id=1)의 email을 UPDATE
3. 이후 일반 서비스 경로로 User(id=1) 조회 → 2차 캐시 hit → 옛 email 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대응은 배치 종료 후 캐시를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;명시적으로 flush&lt;/strong&gt;하는
것이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManagerFactory emf&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;afterBulkUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    SessionFactory sf &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; emf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;evictEntityData&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;User&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 특정 엔티티만&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 또는&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    sf&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;evictAllRegions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;              &lt;span class=&quot;co&quot;&gt;// 전체&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;덧붙여 배치 실행 중 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CacheMode.IGNORE&lt;/code&gt;&lt;/strong&gt;
같은 일반 Session 옵션은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;에서 설정할 대상이
아니다 — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 애초에 캐시를 무시하는 모드이기
때문이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-spring-batch의-itemwriter와-어떻게-맞물리나&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) Spring
Batch의 ItemWriter와 어떻게 맞물리나&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Batch는 청크(chunk) 단위로 &amp;quot;read → process → write&amp;quot;를 반복하는
프레임워크다. 재시작·스킵·재시도 같은 배치 특유의 요구를 지원한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ItemWriter&lt;/code&gt;에 여러 구현이 있고, JPA 쪽은 두 가지가
흔하다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaItemWriter&lt;/code&gt;&lt;/strong&gt; — 기본적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;EntityManager.merge&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush&lt;/code&gt;를 호출하고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;usePersist=true&lt;/code&gt; 설정 시 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt;로 전환된다. 즉
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;일반 Session + flush/clear 패턴&lt;/strong&gt;.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JdbcBatchItemWriter&lt;/code&gt;&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NamedParameterJdbcTemplate&lt;/code&gt;로 원시 SQL 배치. Hibernate를
거치지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;을 Spring Batch에서 쓰려면 **커스텀
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ItemWriter&lt;/code&gt;**를 만들어야 한다. 공식 제공 writer에는
없다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; StatelessSessionItemWriter&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; ItemWriter&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; SessionFactory sessionFactory&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Chunk&lt;span class=&quot;op&quot;&gt;&amp;lt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; T&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; chunk&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            Transaction tx &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;beginTransaction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;T item &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; chunk&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                    ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-12&quot;&gt;&lt;a href=&quot;#cb15-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-13&quot;&gt;&lt;a href=&quot;#cb15-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-14&quot;&gt;&lt;a href=&quot;#cb15-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Exception&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-15&quot;&gt;&lt;a href=&quot;#cb15-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-16&quot;&gt;&lt;a href=&quot;#cb15-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-17&quot;&gt;&lt;a href=&quot;#cb15-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-18&quot;&gt;&lt;a href=&quot;#cb15-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-19&quot;&gt;&lt;a href=&quot;#cb15-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-20&quot;&gt;&lt;a href=&quot;#cb15-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. Spring Batch의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Step&lt;/code&gt;은 자체적으로
트랜잭션 경계를
관리한다(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StepBuilder.chunk(size, transactionManager)&lt;/code&gt;). 그
트랜잭션과 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;의 내부 트랜잭션이
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별개의 JDBC 커넥션&lt;/strong&gt;으로 돌면, Step이 롤백해도
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이 이미 커밋한 insert는 되돌아가지 않는다.
재시작 시 중복이 생길 수 있다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해결은 같은 커넥션을 쓰게 하는 것이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: Step 트랜잭션과 같은 커넥션 사용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Autowired&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;DataSource&lt;/span&gt; dataSource&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-5&quot;&gt;&lt;a href=&quot;#cb16-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-6&quot;&gt;&lt;a href=&quot;#cb16-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Chunk&lt;span class=&quot;op&quot;&gt;&amp;lt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; T&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; chunk&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;SQLException&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-7&quot;&gt;&lt;a href=&quot;#cb16-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;Connection&lt;/span&gt; conn &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; DataSourceUtils&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getConnection&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;dataSource&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-8&quot;&gt;&lt;a href=&quot;#cb16-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;conn&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-9&quot;&gt;&lt;a href=&quot;#cb16-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Step의 트랜잭션 커넥션 재사용 → 바깥 트랜잭션에 얹힘&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-10&quot;&gt;&lt;a href=&quot;#cb16-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;T item &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; chunk&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-11&quot;&gt;&lt;a href=&quot;#cb16-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-12&quot;&gt;&lt;a href=&quot;#cb16-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-13&quot;&gt;&lt;a href=&quot;#cb16-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// tx.commit() 부르지 않음. Step이 커밋.&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-14&quot;&gt;&lt;a href=&quot;#cb16-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-15&quot;&gt;&lt;a href=&quot;#cb16-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;sessionFactory.openStatelessSession(conn)&lt;/code&gt; 오버로드로
커넥션을 명시적으로 넘기면, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;이 자체
트랜잭션을 시작하지 않고 주어진 커넥션 위에서 동작한다. 이 패턴이 Spring
Batch + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;의 정석이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;선택지&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaItemWriter&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;표준, 리스너 동작&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;느림, 메모리 부담&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JdbcBatchItemWriter&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가장 빠름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 매핑 따로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;커스텀 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSessionItemWriter&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;매핑 재사용 + 빠름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;트랜잭션 연결 주의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;**&amp;quot;단순 대량 insert는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;, 복잡한 데이터
흐름/재시작 요구는 Spring Batch&amp;quot;**가 거친 기준이다. 둘을 결합해야 하는
상황(대량이면서도 재시작 필요)에서는 위의 커스텀 writer 패턴이 제일
현실적이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;이 배치에서 1차 캐시·Dirty Checking·Write-Behind 중 무엇이
필요한가&amp;quot;를 먼저 판단한다&lt;/strong&gt;. 셋 다 필요 없다면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;. 하나라도 필요하면 일반 EntityManager +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush/clear&lt;/code&gt;. 이 질문이 선택의 출발이다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners&lt;/code&gt;를
쓰는 엔티티를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;으로 넣을 거면, 감사 필드를
수동 세팅하거나 DB &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DEFAULT&lt;/code&gt;로 내린다&lt;/strong&gt;. null로 박혀서
조용히 장애가 되는 패턴을 한 번 맞아보면 다시는 안 잊는다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Entity&amp;gt;&lt;/code&gt;를
쓰는 메서드에는 반드시
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt;&lt;/strong&gt;. 그리고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 주기적으로(보통 1000건) 부른다. &amp;quot;커서는 DB 쪽
스트림, 엔티티는 여전히 EM에 쌓인다&amp;quot;를 기억해 둔다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL에서 스트리밍이 안 된다면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;fetchSize&lt;/code&gt;와 URL
옵션부터 본다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;useCursorFetch=true&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer.MIN_VALUE&lt;/code&gt; 둘 중 하나가 필요하다. &amp;quot;설정만 바꾸면
공짜로 스트리밍&amp;quot;이 아니라, &amp;quot;드라이버가 어떤 모드로 돌고 있는지 확인&amp;quot;이
먼저다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;2차 캐시를 쓰는 프로젝트라면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;으로 insert/update/delete 후
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;evictEntityData&lt;/code&gt;로 캐시를 털어준다&lt;/strong&gt;. 안 털면 stale
응답이 다음 날 CS로 돌아온다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Spring Batch와 묶을 때는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;openStatelessSession(conn)&lt;/code&gt; 오버로드로 Step 트랜잭션
커넥션을 공유시킨다&lt;/strong&gt;. 자체 트랜잭션을 열면 재시작 시 중복 insert
위험이 생긴다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단건 조회·수정은 일반 EntityManager 그대로&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 &amp;quot;한 방향 대량 흐름&amp;quot;에만 꺼낸다. 같은
엔티티를 여러 번 고치는 흐름에서는 동일성 보장이 없어서 오히려 코드가
복잡해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 영속성 컨텍스트의 세 기능(1차
캐시·Dirty Checking·Write-Behind)을 통째로 버리는 대신 건당 비용을 SQL
실행 비용에 수렴시키는 도구이고, 읽기 쪽 짝이
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ScrollableResults&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Stream&amp;lt;Entity&amp;gt;&lt;/code&gt;다&lt;/strong&gt;.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;·cascade·2차 캐시가 모두
우회되므로&lt;/strong&gt;, 이걸 대가로 받아들일 수 있는 단순 대량 흐름에서만
선택한다. 드라이버별 스트리밍 조건(MySQL
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;useCursorFetch&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer.MIN_VALUE&lt;/code&gt;, PostgreSQL
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;autoCommit=false&lt;/code&gt;)을 확인하지 않으면 &amp;quot;스트리밍처럼 보이는
전체 로딩&amp;quot;이 된다는 점이 가장 자주 밟는 지뢰다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: Hibernate, StatelessSession,
ScrollableResults, Stream, Spring Data JPA, 배치, fetchSize, MySQL,
Spring Batch, 2차 캐시&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>2차 캐시</category>
      <category>FetchSize</category>
      <category>Hibernate</category>
      <category>mysql</category>
      <category>ScrollableResults</category>
      <category>spring batch</category>
      <category>spring data jpa</category>
      <category>StatelessSession</category>
      <category>Stream</category>
      <category>배치</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/428</guid>
      <comments>https://dding-shark.tistory.com/428#entry428comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:30:12 +0900</pubDate>
    </item>
    <item>
      <title>Hibernate Batch Insert/Update &amp;mdash; JDBC batch와 영속성 컨텍스트가 만나는 지점</title>
      <link>https://dding-shark.tistory.com/427</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;hibernate-batch-insertupdate--jdbc-batch와-영속성-컨텍스트가-만나는-지점&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;Hibernate
Batch Insert/Update — JDBC batch와 영속성 컨텍스트가 만나는 지점&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;1만 건 insert가 20초 걸린다&amp;quot;는 리포트가 날아왔다. 로그를 열어 보니
INSERT가 정말로 1만 번 찍혀 있다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.batch_size=100&lt;/code&gt;은 분명히 설정했는데, 배치로
묶여야 할 쿼리가 한 줄씩 순서대로 나가고 있다. &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편에서 다룬 N+1&lt;/a&gt;의
마지막에서 잠깐 언급한 &amp;quot;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;IDENTITY 전략은 JDBC batch insert를
비활성화한다&lt;/strong&gt;&amp;quot;는 문장이 바로 이 장면이다. 이번 글은 그 문장을
본격적으로 풀어낸다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate의 batch는 JDBC batch를 감싸는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상위
계층&lt;/strong&gt;이다. 드라이버가 실제로 네트워크에 뭘 보내는지, Hibernate가
flush 시점에 무엇을 어떻게 모으는지, 그 사이에서 PK 생성 전략이나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;가 어떻게 batch를 깨는지를 순서대로 본다.
특히 다음 네 가지는 설정을 &amp;quot;맞췄다고 생각했는데&amp;quot; 실제로는 틀려 있는
경우가 압도적으로 많다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size=100&lt;/code&gt; 설정했는데 SQL이 여전히
row-by-row로 나간다&lt;/strong&gt; — PK가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType.IDENTITY&lt;/code&gt;라서 batch를 모을 수가 없다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL 기본 설정에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements&lt;/code&gt;
없이 batch 효과가 미미하다&lt;/strong&gt; — JDBC 레벨에서 batch는 만들어지지만
실제 네트워크에는 INSERT가 그대로 여러 번 나간다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch 처리 중 OOM이 난다&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.flush()&lt;/code&gt;만 호출하고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 빠뜨려
영속성 컨텍스트에 엔티티가 무한히 쌓인다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;를 붙인 엔티티는 batch로 묶이지
않는다&lt;/strong&gt; — 엔티티마다 UPDATE SQL 모양이 달라져 JDBC batch의
전제(같은 SQL 문자열) 자체가 깨진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JDBC batch의 기본 → Hibernate batch가 감싸는 방식 →
필수 설정 → IDENTITY 제약 → order_inserts/updates → 대량 persist 패턴 →
대량 update의 대안 → 드라이버별 옵션 → 측정&lt;/strong&gt; 순으로 정리한다.
Spring Boot 3.x / Hibernate 6.x / Jakarta Persistence 3.x 기준이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-왜-row-by-row는-느린가-네트워크-왕복의-누적&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) 왜
row-by-row는 느린가: 네트워크 왕복의 누적&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-jdbc-batch의-기본-preparedstatementaddbatch&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) JDBC
Batch의 기본: PreparedStatement.addBatch&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-hibernate-batch가-jdbc-batch를-감싸는-방식&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) Hibernate
batch가 JDBC batch를 감싸는 방식&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-필수-설정-3종-batch_size--order_inserts--order_updates&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) 필수
설정 3종: batch_size / order_inserts / order_updates&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-identity가-batch를-죽이는-이유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) IDENTITY가 batch를
죽이는 이유&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-batch_versioned_data와-dynamicupdate-트레이드오프&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6)
batch_versioned_data와 @DynamicUpdate 트레이드오프&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-대량-persist-saveall은-해답이-아니다-flushclear-패턴&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7)
대량 persist: saveAll은 해답이 아니다, flush/clear 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-jpql-bulk-update가-우선인-이유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8) JPQL bulk UPDATE가
우선인 이유&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-jdbc-드라이버별-rewritebatchedstatements&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) JDBC
드라이버별 rewriteBatchedStatements&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-hibernate-statistics--p6spy로-측정&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) Hibernate
Statistics + p6spy로 측정&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-statelesssession-예고와-spring-batch-분리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11)
StatelessSession 예고와 Spring Batch 분리&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#13-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-왜-row-by-row는-느린가-네트워크-왕복의-누적&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) 왜
row-by-row는 느린가: 네트워크 왕복의 누적&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;INSERT가 1만 번 찍힌다&amp;quot;가 왜 20초가 되는가. 병목은 CPU도, 디스크도
아니다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네트워크 왕복(round trip)&lt;/strong&gt; 이다. 애플리케이션과
DB가 한 대의 서버에 있어도 TCP 소켓을 통한 요청/응답 왕복은 수십
마이크로초 단위로 쌓인다. 원격 DB라면 1ms 이상도 흔하다. INSERT 1만 건을
한 줄씩 보내면 왕복만 10초 가까이 된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: for문으로 한 건씩 insert를 날리는 경로&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order o &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; orders&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 매번 flush — 최악&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 패턴은 네트워크 왕복 × N에 추가로 매번 트랜잭션 로그 기록, 인덱스
유지, 트리거 실행 비용까지 전부 N번 낸다. DB가 한 번에 여러 건을 받으면
내부에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;버퍼링&lt;/strong&gt;, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;그룹 커밋&lt;/strong&gt;,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 로그 일괄 기록&lt;/strong&gt; 같은 최적화를 적용할 수 있는데,
한 건씩 보내면 이런 여지가 전부 사라진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해법의 방향은 두 층으로 나뉜다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JDBC 레벨&lt;/strong&gt;에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch()&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch()&lt;/code&gt;로 드라이버가 여러
문장을 한 왕복에 전송하게 하고, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 레벨&lt;/strong&gt;에서는
드라이버가 여러 INSERT를 단일 multi-VALUES 문장으로 재작성하게 한다.
Hibernate는 이 두 층 위에 다시 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;ORM 레벨의 배칭&lt;/strong&gt;을
얹는다. 세 층이 합쳐져야 실제 배치가 먹힌다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;42_hibernate-batch-insert-update-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;42_hibernate-batch-insert-update-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 단계의 차이를 모르면 &amp;quot;batch 설정했는데 왜 안 빨라?&amp;quot;의 원인을 잘못
짚는다. Hibernate가 만들어주는 건 2번까지다. 3번은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;드라이버
옵션&lt;/strong&gt;으로 별도로 켜야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-jdbc-batch의-기본-preparedstatementaddbatch&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) JDBC Batch의
기본: PreparedStatement.addBatch&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JDBC의 batch는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Statement&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreparedStatement&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch()&lt;/code&gt;로 여러 문장을
쌓은 뒤 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch()&lt;/code&gt;로 한 번에 보내는 API다. Hibernate도
이 API를 그대로 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 순수 JDBC 예시&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; sql &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;insert into orders(id, amount) values (?, ?)&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;PreparedStatement&lt;/span&gt; ps &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; conn&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;prepareStatement&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order o &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; orders&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setLong&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setInt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getAmount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addBatch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[]&lt;/span&gt; counts &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;executeBatch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;핵심 제약은 한 가지다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch&lt;/code&gt;는 같은 SQL
문자열에 대해서만 파라미터를 바꿔 쌓을 수 있다.&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreparedStatement&lt;/code&gt; 하나에 대한 API이므로 당연한 결과다.
INSERT와 UPDATE를 섞을 수도 없고, 같은 INSERT라도 컬럼 목록이 다르면
묶을 수 없다. 이 제약이 뒤에서 본
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_inserts&lt;/code&gt;&lt;/strong&gt; 와
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;&lt;/strong&gt; 함정의 근본 원인이
된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;의 반환은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;int[]&lt;/code&gt;이고 각 자리의
값은 해당 문장이 영향 준 row 수(또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SUCCESS_NO_INFO&lt;/code&gt;)다.
드라이버는 이 배열을 만들기 위해 내부적으로 결과를 하나씩 매칭하며,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;multi-VALUES로 재작성된 경우 원래의 개별 row 수를 잃어버릴 수
있다&lt;/strong&gt;는 주의점이 따라온다. MySQL에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements=true&lt;/code&gt;일 때
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt; 결과가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Statement.SUCCESS_NO_INFO&lt;/code&gt;로 바뀌는 이유가 이것이다. 영향
row 수에 의존하는 로직이 있다면 알고 있어야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-hibernate-batch가-jdbc-batch를-감싸는-방식&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) Hibernate
batch가 JDBC batch를 감싸는 방식&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate의 batch는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;flush 시점에 쌓아둔 INSERT/UPDATE를 같은
SQL끼리 묶어 JDBC batch로 전송&amp;quot;&lt;/strong&gt; 하는 상위 계층이다. 영속성
컨텍스트가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;merge&lt;/code&gt;/변경 감지로 만들어낸
쓰기 작업들을 flush 시점에 종류별로 분류하고, 같은 SQL 문자열이 연속되는
구간마다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch&lt;/code&gt;를 쌓았다가 끊기는 지점에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;를 호출한다.&lt;/p&gt;
&lt;h3 id=&quot;3-1-왜-연속되는-구간이-중요한가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-1) 왜 &amp;quot;연속되는 구간&amp;quot;이
중요한가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JDBC batch는 같은 SQL 문자열만 모을 수 있다고 했다. Hibernate는
엔티티를 persist한 순서대로 SQL을 만들기 때문에, 엔티티 타입이 번갈아
나오면 batch가 중간에 끊긴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// persist 순서: Member → Order → Member → Order → ...&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Hibernate가 만드는 SQL: INSERT member, INSERT order, INSERT member, INSERT order, ...&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 같은 SQL이 연속되지 않아 batch가 매번 1건짜리로 끊어진다&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 문제를 푸는 설정이 &lt;a href=&quot;#4-필수-설정-3종-batch_size--order_inserts--order_updates&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§4&lt;/a&gt;의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_inserts&lt;/code&gt;다. 같은 타입끼리 재정렬해 SQL이 연속되도록
만든다.&lt;/p&gt;
&lt;h3 id=&quot;3-2-flush-시점과-batch_size의-관계&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-2) flush 시점과
batch_size의 관계&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size=100&lt;/code&gt;은 &amp;quot;한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;에 최대
100건 쌓는다&amp;quot;는 의미다. 쌓인 양이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt;에 도달하면 그
자리에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;가 실행되고, flush가 끝날 때까지 쌓인
나머지는 마지막에 한꺼번에 flush된다. 중요한 건
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt;는 &amp;quot;flush 시점까지 엔티티를 얼마나
쌓아두는가&amp;quot;를 결정하지 않는다&lt;/strong&gt;는 점이다. 그건 애플리케이션
코드에서 직접 &lt;a href=&quot;#7-대량-persist-saveall은-해답이-아니다-flushclear-패턴&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§7의
flush/clear 패턴&lt;/a&gt;으로 제어해야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-필수-설정-3종-batch_size--order_inserts--order_updates&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4)
필수 설정 3종: batch_size / order_inserts / order_updates&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate batch를 제대로 쓰려면 세 개가 한 세트다. 하나만 빼먹어도
batch가 반쪽만 작동한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode yaml&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode yaml&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jpa&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jdbc&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;batch_size&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;100&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order_inserts&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order_updates&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;batch_versioned_data&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;각 설정이 끄는/켜는 비용이 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;설정&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;역할&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;없으면 어떻게 되나&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jdbc.batch_size&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;flush 시 JDBC batch에 묶을 최대 statement 수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;배치 자체가 안 된다&lt;/strong&gt;. 설정 없으면 Hibernate는 매
statement마다 즉시 execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_inserts&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;같은 엔티티 타입의 INSERT를 연속 배치로 재정렬&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 타입이 섞여 있으면 batch가 중간에 끊겨 크기가 1로 수렴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_updates&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;같은 모양의 UPDATE를 연속 배치로 재정렬&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;가 아니더라도 UPDATE 순서가 엔티티
타입별로 섞이면 batch 깨짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_versioned_data&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드가 있는 엔티티도 UPDATE batch 허용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;기본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;. 낙관적 잠금을 쓰는 엔티티는 batch 자체가 안
됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;4-1-batch_size는-얼마가-적당한가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) batch_size는 얼마가
적당한가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;경험적으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;50 ~ 200&lt;/strong&gt;이 일반적이다. 너무 작으면
batch의 이득이 적고, 너무 크면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreparedStatement&lt;/code&gt;에
바인딩되는 파라미터가 많아져 DB의 파라미터 한도나 패킷 크기 제한에 걸릴
수 있다. PostgreSQL은 protocol 레벨에서 바인딩 파라미터
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;32,767&lt;/strong&gt; 제한이 확실히 걸리므로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;컬럼 수 × batch_size&lt;/code&gt;가 이 한도를 넘지 않도록 계산한다.
Oracle은 SQL 한 문장 내 expression 한도(대략 65k 수준)가 별도로 걸리므로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size × 컬럼 수&lt;/code&gt;가 그 근처에 닿지 않도록 감안한다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-전역-vs-엔티티별&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2) 전역 vs 엔티티별&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jdbc.batch_size&lt;/code&gt;는 SessionFactory 글로벌 설정이다. 엔티티
타입별로 INSERT/UPDATE batch 크기를 바꾸는 공식 수단은 없고, 필요하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;session.setJdbcBatchSize(int)&lt;/code&gt;로 세션 범위에서 임시
조정한다. (조회 쪽 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@BatchSize&lt;/code&gt;는 lazy loading IN-절 fetch
전용이라 여기서 말하는 batch와 완전히 다른 메커니즘이다 — &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편 §5&lt;/a&gt; 참조.)&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-identity가-batch를-죽이는-이유&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5) IDENTITY가 batch를 죽이는
이유&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기가 이 글의 가장 중요한 함정이다. &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편에서 언급했던&lt;/a&gt;
내용을 구조적으로 다시 본다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size=100&lt;/code&gt;을 설정하고도
SQL이 한 건씩 찍히는 케이스의 90%는 이 문제다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-identity의-타이밍&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) IDENTITY의 타이밍&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType.IDENTITY&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB의
auto-increment&lt;/strong&gt;(MySQL) 혹은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;serial/identity
컬럼&lt;/strong&gt;(PostgreSQL)에 PK 생성을 맡긴다. 구조적 제약은
단순하다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Hibernate가 엔티티를 영속 상태로 만들려면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK를 알아야
한다&lt;/strong&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;IDENTITY는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;INSERT를 실제로 실행해야만&lt;/strong&gt; DB가 PK를
돌려준다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;그러므로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 호출 시점에 INSERT를
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;즉시&lt;/strong&gt; 실행할 수밖에 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;즉 IDENTITY 전략은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt;를 부를 때마다 SQL이 나가야
하는 구조다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;flush까지 쌓아서 batch로 묶는 것 자체가
불가능하다.&lt;/strong&gt; Hibernate는 이 상황에서 조용히 batch를 비활성화하고
row-by-row로 실행한다. 로그에 경고 같은 게 뜨지도 않는다. 설정은 다 켜져
있는데 왜 batch가 안 되는지를 찾다가 시간을 녹이는 전형적 구간이다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-해결--sequence-또는-애플리케이션-생성-pk&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) 해결 —
SEQUENCE 또는 애플리케이션 생성 PK&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;PostgreSQL/Oracle은 시퀀스를 지원하므로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt;
전략으로 넘어가면 바로 해결된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;order_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;order_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;order_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize=50&lt;/code&gt;은 &amp;quot;시퀀스를 한 번 호출할 때 50개 ID를
미리 받아둔다&amp;quot;는 뜻이다. Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; 한 번으로
50개를 확보하고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 호출 시 메모리에서 PK를 부여한 뒤
INSERT를 모아 batch로 내보낸다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch와 시퀀스 호출 오버헤드 둘
다 줄인다.&lt;/strong&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;MySQL은 전통적으로 SEQUENCE를 쓰지 않고 AUTO_INCREMENT 관행이다. 이
경로에서 batch가 필요하면 선택지는 두 가지뿐이다.&lt;/p&gt;
&lt;ol type=&quot;1&quot; style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;애플리케이션 레벨 PK 생성&lt;/strong&gt; — TSID, UUID v7,
Snowflake ID 등. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 전에 애플리케이션이 PK를 결정해
두면 IDENTITY의 제약이 없어진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JdbcTemplate.batchUpdate로 내려가기&lt;/strong&gt; — JPA를
포기하고 순수 JDBC로 대량 insert&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;TSID/UUID 경로의 장단은 &lt;a href=&quot;./35_jpa-id-generation-composite-key.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;35편&lt;/a&gt;에서 다룬 바
있다. 이 글의 맥락에서 중요한 건 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;MySQL + IDENTITY에서 batch를
기대하지 말라&amp;quot;&lt;/strong&gt; 는 원칙이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;INSERT 쪽 함정은 PK였다. UPDATE 경로에는 다른 축의 함정이 둘 있다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;과 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-batch_versioned_data와-dynamicupdate-트레이드오프&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6)
batch_versioned_data와 @DynamicUpdate 트레이드오프&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;UPDATE 경로에는 두 개의 독립된 함정이 또 있다. 하나는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;, 다른 하나는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;다.&lt;/p&gt;
&lt;h3 id=&quot;6-1-version과-batch_versioned_data&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-1) @Version과
batch_versioned_data&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;낙관적 잠금에 쓰는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드가 있는 엔티티는
UPDATE에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where id = ? and version = ?&lt;/code&gt;&lt;/strong&gt; 절이
붙는다. Hibernate의 기본 동작은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;version 값이 바인딩에 포함되면
batch로 묶지 않는다&amp;quot;&lt;/strong&gt; 이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;낙관적 잠금 실패를 정확히
잡으려면 row 단위 영향 수 확인이 필요한데, batch의 집계 결과로는 어떤
row가 version 불일치로 0건 영향을 받았는지 식별이 애매해지므로
Hibernate가 기본적으로 batch를 끈다.&lt;/strong&gt; 이 보수적 동작을
명시적으로 뒤집는 스위치가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_versioned_data=true&lt;/code&gt;다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode yaml&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode yaml&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;batch_versioned_data&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;   # 기본 false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;켜두면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;이 있어도 UPDATE가 batch로 묶인다.
PostgreSQL/Oracle은 대체로 문제없다. MySQL도 최근 드라이버는 괜찮지만,
낙관적 잠금 실패(version 불일치로 0건 영향)가 섞인 batch의 결과 판별에서
드라이버별 미묘한 차이가 있을 수 있으니 운영 DB에서 한 번은 검증해보는
편이 안전하다.&lt;/p&gt;
&lt;h3 id=&quot;6-2-dynamicupdate와-batch는-사실상-공존-불가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-2)
@DynamicUpdate와 batch는 사실상 공존 불가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;는 &amp;quot;변경된 컬럼만 UPDATE SQL에 포함&amp;quot;시키는
Hibernate 확장이다. 넓은 엔티티에서 한두 컬럼만 바꿀 때 UPDATE
페이로드를 줄이는 효과가 있다. 문제는 &lt;a href=&quot;#2-jdbc-batch의-기본-preparedstatementaddbatch&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§2에서 본 JDBC
batch의 전제&lt;/a&gt;와 정면으로 충돌한다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;JDBC batch: &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;같은 SQL 문자열&lt;/strong&gt;만 묶을 수 있다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;: 엔티티마다 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;변경된 컬럼이
다르므로&lt;/strong&gt; UPDATE SQL 문자열도 다르다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;결과는 예상대로다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt; 엔티티의 UPDATE는 거의
batch로 안 묶인다. 변경 컬럼이 우연히 같은 엔티티들만 연속으로 나와야
하는데, 실무에서 그런 일관성은 기대하기 어렵다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;는 트레이드오프&lt;/strong&gt;이고, 대량 업데이트가
중요한 엔티티라면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;를 떼는 편이 맞다. 반대로
&amp;quot;대량이 아닌데 컬럼이 100개 넘는 엔티티&amp;quot;라면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;의 이득이 더 크다. 용도에 맞춰 고른다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-대량-persist-saveall은-해답이-아니다-flushclear-패턴&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) 대량
persist: saveAll은 해답이 아니다, flush/clear 패턴&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편 §13&lt;/a&gt;에서
다뤘지만 여기서 한 번 더 짚는다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaRepository.saveAll&lt;/code&gt;은
내부적으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;for문으로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;save&lt;/code&gt;를 반복&lt;/strong&gt;할 뿐이다.
batch 최적화를 보장하지 않는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// SimpleJpaRepository의 실제 구현&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;S &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; T&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;S&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;saveAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Iterable&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;S&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; entities&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;S&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; result &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;S entity &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; entities&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        result&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;entity&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt;이 batch로 묶이는 건 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate가 flush
시점에 쌓인 INSERT를 batch로 보낼 수 있는 조건이 갖춰진
경우뿐&lt;/strong&gt;이다. 그 조건은 &lt;a href=&quot;#4-필수-설정-3종-batch_size--order_inserts--order_updates&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§4의
설정 3종&lt;/a&gt; + &lt;a href=&quot;#5-identity가-batch를-죽이는-이유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§5의 PK
전략&lt;/a&gt;이다.&lt;/p&gt;
&lt;h3 id=&quot;7-1-메모리-폭발을-막는-flushclear&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-1) 메모리 폭발을 막는
flush/clear&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt;을 크게 한 번에 호출하면 또 다른 함정이 뒤따른다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성 컨텍스트가 엔티티로 가득 차 OOM이 난다.&lt;/strong&gt;
Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt;된 엔티티를 1차 캐시에 보관하고 변경
감지를 위한 스냅샷을 들고 있다. 10만 건을 영속화하면 10만 개의 스냅샷이
힙에 쌓인다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: 주기적으로 flush + clear&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; BATCH &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// batch_size의 몇 배 정도로 잡는 편&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt; orders&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt; i&lt;span class=&quot;op&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orders&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; i &lt;span class=&quot;op&quot;&gt;%&lt;/span&gt; BATCH &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 쌓인 INSERT를 JDBC batch로 내보낸다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 1차 캐시를 비운다 — 이걸 빠뜨리면 OOM&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-10&quot;&gt;&lt;a href=&quot;#cb8-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-11&quot;&gt;&lt;a href=&quot;#cb8-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;flush와 clear는 쌍&lt;/strong&gt;이다. flush만 하면 메모리가 안
줄고, clear만 하면 변경이 반영되지 않는다. 여기서 틀리기 쉬운 지점이
하나 더 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BATCH&lt;/code&gt; 주기는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.batch_size&lt;/code&gt;의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정수배&lt;/strong&gt;로
맞추는 편이 유리하다. batch_size가 100인데 주기가 150이면 50건이 batch로
못 묶이고 남아 마지막 flush에서 짧게 나간다. 주기적 flush 간격을 잘못
잡으면 batch 효율이 떨어진다.&lt;/p&gt;
&lt;h3 id=&quot;7-2-clearautomatically는-벌크-쿼리-전용&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-2)
clearAutomatically는 벌크 쿼리 전용&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying(clearAutomatically = true)&lt;/code&gt;은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 벌크
연산 뒤에&lt;/strong&gt; 영속성 컨텍스트를 비워주는 플래그다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt; 경로에는 적용되지 않는다. 루프 안에서 직접
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt;를 불러야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-jpql-bulk-update가-우선인-이유&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8) JPQL bulk UPDATE가 우선인
이유&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대량으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;같은 방식의 업데이트&lt;/strong&gt;를 걸어야 하는 경우,
batch로 여러 UPDATE를 묶는 것보다 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 단일 UPDATE&lt;/strong&gt;가
언제나 빠르다. 이유는 명확하다. batch로 묶어도 여전히 N개의 UPDATE
문장이 DB에 도달하지만, JPQL bulk UPDATE는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 쪽에서 한 문장으로
처리&lt;/strong&gt;된다. 네트워크 왕복도 1회, 트랜잭션 로그도 훨씬
간결하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 엔티티 단위로 상태를 바꾸는 루프&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; list &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findOldPendings&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;date&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order o &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; list&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;markShipped&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// dirty checking으로 UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// flush 시점에 N건의 UPDATE가 batch로 나가긴 하지만 여전히 N문장&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: JPQL bulk UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; flushAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;    update Order o&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;       set o.status = :shipped&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;     where o.status = :pending and o.createdAt &amp;lt; :d&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-8&quot;&gt;&lt;a href=&quot;#cb10-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;markShipped&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;shipped&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; OrderStatus shipped&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-9&quot;&gt;&lt;a href=&quot;#cb10-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;pending&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; OrderStatus pending&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-10&quot;&gt;&lt;a href=&quot;#cb10-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DB 한 문장으로 끝나고, 영향 row 수가 반환된다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-bulk-update의-주의점&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) bulk UPDATE의 주의점&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;공짜는 아니다. 몇 가지 비용이 따라온다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;2차 캐시 우회&lt;/strong&gt; — bulk UPDATE는 영속성 컨텍스트 1차
캐시와 Hibernate 2차 캐시를 우회한다. 같은 트랜잭션 내에서 이후 조회가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;옛 값&lt;/strong&gt;을 돌려줄 수 있고, 2차 캐시에 있는 엔티티는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;무효화해야&lt;/strong&gt; 한다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically=true&lt;/code&gt;로
1차 캐시를 비우고, 2차 캐시가 있다면 해당 region을 evict한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 자동 증가 안 됨&lt;/strong&gt; — 엔티티 단위
UPDATE가 아니므로 Hibernate가 version 컬럼을 자동으로 올리지 않는다.
필요하면 JPQL에 명시한다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;set o.version = o.version + 1&lt;/code&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 라이프사이클 콜백 생략&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreUpdate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PostUpdate&lt;/code&gt;가 호출되지 않는다. 이
훅에 의존하는 로직이 있으면 bulk는 적합하지 않다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;즉 bulk UPDATE는 &amp;quot;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 로직이 필요 없고 같은 변경을 많은
row에 적용&lt;/strong&gt;&amp;quot;할 때 최선이다. 엔티티 메서드를 통한 도메인 로직이
끼면 batch로 묶인 UPDATE 쪽이 맞다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-bulk-delete도-같은-논리&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) bulk DELETE도 같은 논리&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clearAutomatically &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;delete from Order o where o.status = &amp;#39;CANCELED&amp;#39; and o.canceledAt &amp;lt; :d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;purgeCanceled&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; LocalDateTime d&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;삭제도 엔티티 단위로 돌면 N번의 DELETE가 나가지만 JPQL bulk DELETE는
한 문장이다. 단, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@OneToMany(cascade = CascadeType.REMOVE)&lt;/code&gt;
같은 ORM 레벨 cascade는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;bulk에서 작동하지 않는다.&lt;/strong&gt; FK
제약이 걸려 있으면 먼저 자식 bulk DELETE, 그 다음 부모 bulk DELETE
순으로 두 문장을 직접 작성한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-jdbc-드라이버별-rewritebatchedstatements&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) JDBC 드라이버별
rewriteBatchedStatements&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기가 설정해놓고도 &amp;quot;그런데 왜 빠르지 않지?&amp;quot;로 가장 많이 막히는
지점이다. Hibernate가 JDBC batch로 문장을 보내도, 드라이버가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;네트워크에는 INSERT를 여전히 하나씩 보내는&lt;/strong&gt; 경우가 있다.
드라이버별 multi-VALUES 재작성 옵션을 별도로 켜야 한다.&lt;/p&gt;
&lt;h3 id=&quot;9-1-mysql--mariadb&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) MySQL / MariaDB&lt;/h3&gt;
&lt;pre style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jdbc:mysql://host:3306/db?rewriteBatchedStatements=true&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements=true&lt;/code&gt;를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;반드시
켜야&lt;/strong&gt; JDBC batch의 INSERT가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;INSERT ... VALUES (...), (...), (...)&lt;/code&gt; 한 문장으로
재작성된다. 기본값은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;다. 안 켜면 JDBC 레벨에서는
batch지만 실제 네트워크에는 INSERT가 그대로 여러 번 나간다. &lt;a href=&quot;#1-왜-row-by-row는-느린가-네트워크-왕복의-누적&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§1에서 본 세
단계&lt;/a&gt;의 2번에서 멈춘다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;MySQL에서 이 옵션을 켰을 때의 주의는 &lt;a href=&quot;#2-jdbc-batch의-기본-preparedstatementaddbatch&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§2 끝&lt;/a&gt;에서
언급한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt; 반환이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SUCCESS_NO_INFO&lt;/code&gt;로
바뀌는 것 외에도, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;UPDATE에는 적용되지 않는다&lt;/strong&gt;는 점이다.
MySQL 드라이버의 재작성은 INSERT만 대상이다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-postgresql&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) PostgreSQL&lt;/h3&gt;
&lt;pre style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jdbc:postgresql://host:5432/db?reWriteBatchedInserts=true&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;pgjdbc 9.4.1209 이상은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;reWriteBatchedInserts=true&lt;/code&gt;(대소문자 주의)로 같은 재작성을
지원한다. 기본 꺼짐. INSERT만 적용되고 UPDATE/DELETE에는 안 붙는다.&lt;/p&gt;
&lt;h3 id=&quot;9-3-oracle&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-3) Oracle&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Oracle 드라이버는 기본으로 JDBC batch를 잘 지원한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;oracle.jdbc.defaultExecuteBatch&lt;/code&gt;로 배치 크기의 기본값을 바꿀
수 있지만, Hibernate가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;를 명시적으로 호출하므로
일반적으로 따로 손대지 않는다.&lt;/p&gt;
&lt;h3 id=&quot;9-4-드라이버-설정-없이-batch가-보이는-착각&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-4) 드라이버 설정
없이 batch가 &amp;quot;보이는&amp;quot; 착각&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate SQL 로그를 보면 batch가 묶인 것처럼 보일 수 있다.
Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch&lt;/code&gt; 호출 단위로 로그를 찍기 때문이다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;실제 네트워크에 뭐가 나갔는지는 드라이버 로그를 봐야
확인된다.&lt;/strong&gt; p6spy는 JDBC 드라이버 앞단 프록시이므로 드라이버가
받은 것까지는 보여주지만, multi-VALUES로 재작성된 실제 와이어 프로토콜은
드라이버 내부에서 일어난다. MySQL에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;general_log&lt;/code&gt;,
PostgreSQL에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;log_statement=all&lt;/code&gt;로 DB 서버 로그를 켜고
보는 게 가장 확실하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-hibernate-statistics--p6spy로-측정&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) Hibernate Statistics
+ p6spy로 측정&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;batch가 실제로 먹히는지는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;숫자로&lt;/strong&gt; 봐야 한다. 설정만
켜고 &amp;quot;되겠지&amp;quot; 하는 건 십중팔구 틀린다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-hibernate-statistics&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) Hibernate Statistics&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode yaml&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode yaml&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jpa&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;generate_statistics&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; &lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;org.hibernate.stat&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; DEBUG&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;org.hibernate.SQL&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; DEBUG&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-10&quot;&gt;&lt;a href=&quot;#cb14-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;org.hibernate.orm.jdbc.bind&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; TRACE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Statistics&lt;/code&gt; 객체에서 batch 관련 핵심 지표는 다음과
같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;메서드&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;의미&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getEntityInsertCount()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Hibernate가 수행한 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 INSERT 수&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getEntityUpdateCount()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 UPDATE 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getPrepareStatementCount()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JDBC &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreparedStatement&lt;/code&gt; 준비 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getQueryExecutionCount()&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;쿼리 실행 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티 INSERT 수가 1만인데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PreparedStatement&lt;/code&gt; 준비 횟수가
1~10 수준이라면 batch가 제대로 먹힌 것이다. INSERT 수와 준비 횟수가
비슷하게 1만에 근접하면 batch가 전혀 안 된 것이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; 대량_저장이_batch로_묶인다&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Statistics stats &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getStatistics&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    stats&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; orders &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; IntStream&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;mapToObj&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;saveAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orders&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-12&quot;&gt;&lt;a href=&quot;#cb15-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;fu&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stats&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getEntityInsertCount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-13&quot;&gt;&lt;a href=&quot;#cb15-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// batch_size=100이면 준비 횟수는 10 수준이어야 한다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-14&quot;&gt;&lt;a href=&quot;#cb15-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;fu&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stats&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getPrepareStatementCount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isLessThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-15&quot;&gt;&lt;a href=&quot;#cb15-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;회귀 방지용으로 이런 테스트를 남겨두는 게 가장
확실&lt;/strong&gt;하다. 누군가 PK를 IDENTITY로 바꾸거나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;를 붙이면 이 테스트가 빨갛게 바뀐다.&lt;/p&gt;
&lt;h3 id=&quot;10-2-p6spy로-실제-sql-확인&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2) p6spy로 실제 SQL 확인&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;testImplementation&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.1&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;p6spy는 JDBC 드라이버 앞단에 프록시를 끼워 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;실행 시점의 완성된
SQL + 실행 시간&lt;/strong&gt;을 찍는다. Hibernate 레벨 로그와 달리 바인딩
파라미터가 모두 치환되고, 실행 지연도 함께 보인다. &amp;quot;batch가
먹히는지&amp;quot;보다 &amp;quot;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 왕복에 얼마나 걸렸는지&lt;/strong&gt;&amp;quot;를 확인하는
용도로 유용하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;한 가지 주의. p6spy는 드라이버의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt; 호출을 중계하므로
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate가 쌓은 batch의 각 addBatch 호출이 개별 SQL처럼 보일
수&lt;/strong&gt; 있다(드라이버가 무엇을 와이어로 보냈는지는 앞서 언급한 대로
별도 문제다). 로그를 읽을 때 &amp;quot;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;addBatch&lt;/code&gt; 로그 여러 줄 = 한
번의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;executeBatch&lt;/code&gt;&amp;quot;로 해석해야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-statelesssession-예고와-spring-batch-분리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11)
StatelessSession 예고와 Spring Batch 분리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기까지가 영속성 컨텍스트를 유지하는 일반 batch의 한계다. 더
극단적인 대량 처리가 필요하면 선택지는 두 가지로 갈린다.&lt;/p&gt;
&lt;h3 id=&quot;11-1-statelesssession&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-1) StatelessSession&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// [다음 편(43)](./43_hibernate-stateless-session-scrollable.md)에서 깊게 다룬다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;StatelessSession ss &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; sessionFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;openStatelessSession&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Transaction tx &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;beginTransaction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order o &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; orders&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        ss&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    tx&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성 컨텍스트가
없는&lt;/strong&gt; Hibernate 세션이다. 1차 캐시도, 변경 감지 스냅샷도, dirty
checking도 없다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;flush&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clear&lt;/code&gt;가 필요 없고
메모리 발자국이 거의 일정하다. 대신 가벼운 대가가 있다 —
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascading 무시, event listener 무시,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PostPersist&lt;/code&gt; 등 JPA 라이프사이클
콜백 무시&lt;/strong&gt;. 도메인 이벤트 발행 같은 로직이 엔티티 라이프사이클에
묶여 있다면 StatelessSession 경로에서는 수동으로 옮겨야 한다. 자세한
동작은 다음 글에서 다룬다.&lt;/p&gt;
&lt;h3 id=&quot;11-2-spring-batch로-chunk-처리&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-2) Spring Batch로 chunk
처리&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;수십만 ~ 수백만 건 규모는 애초에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도 배치 잡&lt;/strong&gt;으로
분리하는 편이 낫다. Spring Batch는 reader / processor / writer를 chunk
단위로 묶어 커밋 간격을 제어하고, 재시작/스킵/재시도 같은 운영 기능을
제공한다. 일반 API 트랜잭션과 같은 애플리케이션에서 돌더라도
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 경계와 타임아웃이 분리&lt;/strong&gt;된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;배치 잡 설계에서 중요한 건 세 가지다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 경계 분리&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(propagation = Propagation.REQUIRES_NEW)&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PROPAGATION_NEVER&lt;/code&gt; + 수동 관리. 긴 배치가 일반 요청
트랜잭션에 묶이면 타임아웃이 터진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;2차 캐시 비활성&lt;/strong&gt; — 배치 실행 구간에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CacheMode.IGNORE&lt;/code&gt;로 읽기/쓰기 모두 2차 캐시를 건너뛰게 한다.
캐시가 오염된다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재시작 가능성&lt;/strong&gt; — chunk 단위로 커밋하면 중간에
실패해도 마지막 성공 지점부터 재시작할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;API 컨트롤러에서 대량 데이터를 한 트랜잭션에 꽂는다&amp;quot;는 설계는 가급적
피한다. 데이터가 커지면 이 경로는 시한폭탄이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&amp;quot;batch_size 올렸는데 안 빨라요&amp;quot; 리포트가 오면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;첫 질문은 &amp;quot;PK
전략이 뭐예요?&amp;quot;&lt;/strong&gt; 다. MySQL + IDENTITY면 batch는 구조적으로 안
된다. 설정 아무리 만져도 헛수고다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;측정 없이 최적화하지 않는다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;generate_statistics=true&lt;/code&gt;
켜고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;getEntityInsertCount() / getPrepareStatementCount()&lt;/code&gt;
비율로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch 효율을 숫자로&lt;/strong&gt; 확인한다. 이 비율이 1에
가까우면 batch가 전혀 안 된 것이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;MySQL 드라이버 URL에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements=true&lt;/code&gt;가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;빠져 있는 환경이 절반&lt;/strong&gt; 이상이다. 기본 설정 템플릿에
박아두고 운영 DB에서 한 번 확인한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;대량 persist는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.flush() + em.clear()&lt;/code&gt; 주기적
호출이 필수&lt;/strong&gt;다. 주기는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt;의 정수배로.
빠뜨리면 OOM이고, 간격을 잘못 잡으면 batch 효율이 떨어진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;엔티티 단위 로직이 필요 없는 대량 업데이트는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL bulk
UPDATE 우선&lt;/strong&gt;. 2차 캐시와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;은 별도 관리.
엔티티 라이프사이클 콜백에 의존하는 경로면 batch UPDATE로 남긴다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;와 batch는 공존이 어렵다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;넓은
엔티티 + 소량 변경 빈번&lt;/strong&gt;이면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대량 업데이트&lt;/strong&gt;면 뗀다. 둘 다 필요하면 엔티티를 분리하는
게 맞다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;진짜 대량(수만 건 이상)은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA를 벗어난다.&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JdbcTemplate.batchUpdate&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StatelessSession&lt;/code&gt;,
Spring Batch 중 하나로 내려간다. &amp;quot;ORM으로 모든 걸 해결한다&amp;quot;는 기대를
버리는 순간부터 쉬워진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;배치 작업은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도 서비스/잡으로 분리&lt;/strong&gt;. API 트랜잭션과
타임아웃·전파 속성·캐시 모드가 다르다. 이 분리를 안 해두면 배치가 커질
때마다 API가 같이 아프다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;13-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate batch는 JDBC batch를 감싸는 상위 계층이고, 제대로
작동하려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;batch_size&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_inserts/updates&lt;/code&gt;
+ &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;rewriteBatchedStatements&lt;/code&gt; 세 층이 전부 맞아야
한다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType.IDENTITY&lt;/code&gt;는 이 체인을
근본적으로 끊어버리고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;는 batch와
트레이드오프 관계에 있으며, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll&lt;/code&gt;은 이름만큼 batch를
보장하지 않는다. 대량 경로는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 단위 로직이 필요한가 / 같은
변경을 많은 row에 반복하는가&lt;/strong&gt;로 분기해, 전자는 flush/clear
루프로, 후자는 JPQL bulk UPDATE로 간다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: Hibernate, JDBC Batch, batch_size,
order_inserts, IDENTITY, SEQUENCE, saveAll, rewriteBatchedStatements,
Spring Data JPA, bulk UPDATE&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>batch_size</category>
      <category>bulk update</category>
      <category>Hibernate</category>
      <category>identity</category>
      <category>JDBC Batch</category>
      <category>order_inserts</category>
      <category>rewritebatchedstatements</category>
      <category>saveAll</category>
      <category>SEQUENCE</category>
      <category>spring data jpa</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/427</guid>
      <comments>https://dding-shark.tistory.com/427#entry427comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:29:30 +0900</pubDate>
    </item>
    <item>
      <title>JPA AttributeConverter와 커스텀 타입 &amp;mdash; Enum&amp;middot;JSON&amp;middot;암호화 필드 매핑</title>
      <link>https://dding-shark.tistory.com/426</link>
      <description>&lt;h1&gt;JPA AttributeConverter와 커스텀 타입 — Enum·JSON·암호화 필드 매핑&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;엔티티 필드와 DB 컬럼이 1:1로 딱 떨어질 때는 매핑이 고민거리가 아니다. &lt;code&gt;String name&lt;/code&gt;은 &lt;code&gt;VARCHAR(64)&lt;/code&gt;로 가고, &lt;code&gt;BigDecimal price&lt;/code&gt;는 &lt;code&gt;NUMERIC&lt;/code&gt;으로 간다. 그런데 현실의 도메인은 이 깔끔한 대응에서 자주 빗나간다. enum을 숫자 대신 코드 문자열로 저장하고 싶고, 복잡한 설정 객체를 JSON 컬럼 하나에 담고 싶고, 주민번호는 암호화해서 넣어야 한다. 이럴 때 Hibernate와 직접 대화하는 고리가 &lt;strong&gt;&lt;code&gt;AttributeConverter&lt;/code&gt;&lt;/strong&gt;다. 필드 타입과 컬럼 타입 사이에 얇은 어댑터를 끼워 넣어, 객체 모델은 도메인 언어 그대로 두고 DB에는 원하는 형태로 저장하게 만드는 표준 JPA 장치다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;a href=&quot;./34_jpa-association-mapping.md&quot;&gt;34편 연관 매핑&lt;/a&gt;과 &lt;a href=&quot;./35_jpa-id-generation-composite-key.md&quot;&gt;35편 식별자 생성&lt;/a&gt;에 이어, &lt;strong&gt;한 엔티티 안의 필드 값 자체를 어떻게 변환해 저장할 것인가&lt;/strong&gt;를 다룬다. Enum 기본 저장 전략의 함정, Hibernate 6의 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;, 커스텀 JSON Converter, 암호화 필드가 검색 쿼리를 어떻게 망가뜨리는지, Money 같은 VO는 Converter와 &lt;code&gt;@Embeddable&lt;/code&gt; 중 어느 쪽이 맞는지 — 이 다섯 축이 오늘 정리할 주제다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@Enumerated&lt;/code&gt; 없이 enum 필드를 그냥 쓰면 &lt;code&gt;ORDINAL&lt;/code&gt;로 저장된다&lt;/strong&gt; — 스펙 기본값 함정. enum 순서 한 번 바꾸면 데이터가 뒤섞인다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hibernate 5에서 &lt;code&gt;hibernate-types-52&lt;/code&gt;로 JSON을 매핑하던 코드를 6으로 올리면 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;로 바꿀 수 있다&lt;/strong&gt; — 서드파티 의존을 뺄 수 있는 지점이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 필드에 &lt;code&gt;LIKE&lt;/code&gt;로 검색을 했는데 아무것도 안 나온다&lt;/strong&gt; — 암호문끼리는 패턴 매칭이 안 된다는 걸 매핑 이후에 깨닫는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@Convert&lt;/code&gt;를 &lt;code&gt;@Id&lt;/code&gt;에 붙였더니 Hibernate가 예외를 던진다&lt;/strong&gt; — Converter는 PK·버전·연관관계에 쓸 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글은 &lt;strong&gt;왜 → 계약 → 적용 방식 → Enum → JSON → 암호화 → VO → 한계 → 쿼리와 DynamicUpdate → 다른 SqlTypes → 실무&lt;/strong&gt; 순으로, Spring Boot 3.x / Hibernate 6.x / Jakarta Persistence 3.x / Jackson 2.x 기준으로 정리한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#1-%EC%BB%AC%EB%9F%BC%EA%B3%BC-%ED%95%84%EB%93%9C%EA%B0%80-11%EC%9D%B4-%EC%95%84%EB%8B%90-%EB%95%8C&quot;&gt;1) 컬럼과 필드가 1:1이 아닐 때&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-attributeconverter-%EA%B3%84%EC%95%BD-%EB%91%90-%EB%A9%94%EC%84%9C%EB%93%9C%EA%B0%80-%EC%A0%84%EB%B6%80%EB%8B%A4&quot;&gt;2) AttributeConverter 계약: 두 메서드가 전부다&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-convert-vs-converterautoapplytrue&quot;&gt;3) @Convert vs @Converter(autoApply=true)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-enum-%EC%A0%80%EC%9E%A5-enumtypestring%EA%B3%BC-%EC%BD%94%EB%93%9C%EA%B0%92-converter&quot;&gt;4) Enum 저장: EnumType.STRING과 코드값 Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-hibernate-6-jdbctypecodesqltypesjson-%ED%91%9C%EC%A4%80-json-%EB%A7%A4%ED%95%91&quot;&gt;5) Hibernate 6 @JdbcTypeCode(SqlTypes.JSON): 표준 JSON 매핑&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6-%EC%BB%A4%EC%8A%A4%ED%85%80-json-converter-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%A1%9C%EC%A7%81-%EC%A0%9C%EC%96%B4&quot;&gt;6) 커스텀 JSON Converter: 직렬화 로직 제어&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#7-%EC%95%94%ED%98%B8%ED%99%94-%ED%95%84%EB%93%9C%EC%99%80-%EA%B2%80%EC%83%89-%EB%B6%88%EA%B0%80-%ED%95%A8%EC%A0%95&quot;&gt;7) 암호화 필드와 검색 불가 함정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#8-moneyemail-%EA%B0%99%EC%9D%80-vo-converter-vs-embeddable&quot;&gt;8) Money·Email 같은 VO: Converter vs @Embeddable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#9-convert%EC%9D%98-%ED%95%9C%EA%B3%84-idversion%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B8%88%EC%A7%80&quot;&gt;9) @Convert의 한계: @Id·@Version·연관관계 금지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#10-json-%EC%BF%BC%EB%A6%AC%EC%99%80-dynamicupdate&quot;&gt;10) JSON 쿼리와 @DynamicUpdate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#11-jdbctypecode%EC%9D%98-%EB%8B%A4%EB%A5%B8-sqltypes-arrayuuidinet&quot;&gt;11) @JdbcTypeCode의 다른 SqlTypes: ARRAY·UUID·INET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#12-%EC%8B%A4%EB%AC%B4-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot;&gt;12) 실무 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#13-%ED%95%9C-%EC%A4%84-%EC%A0%95%EB%A6%AC&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1) 컬럼과 필드가 1:1이 아닐 때&lt;/h2&gt;
&lt;p&gt;JPA 매핑의 기본 가정은 단순하다 — &lt;strong&gt;필드 하나 = 컬럼 하나, 타입은 JDBC가 아는 범위&lt;/strong&gt;. Hibernate가 기본 제공하는 타입(&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;BigDecimal&lt;/code&gt;, &lt;code&gt;LocalDateTime&lt;/code&gt;, &lt;code&gt;UUID&lt;/code&gt; 등)은 이 가정에 그대로 들어맞는다. 그런데 도메인 모델을 그리다 보면 이 범위를 자꾸 벗어난다. &lt;code&gt;OrderStatus&lt;/code&gt; enum을 &lt;code&gt;&amp;#39;P&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;A&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;X&amp;#39;&lt;/code&gt; 같은 한 글자 코드로 넣고 싶고, 사용자 설정을 JSON으로 통째로 저장하고 싶고, 전화번호는 AES로 암호화해야 한다.&lt;/p&gt;
&lt;p&gt;해결 고리는 두 가지다. 첫째, &lt;strong&gt;Hibernate가 네이티브로 지원하는 고급 타입&lt;/strong&gt; — &lt;code&gt;@Enumerated(EnumType.STRING)&lt;/code&gt;, &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt; 같이 어노테이션 하나로 끝나는 경우. 둘째, &lt;strong&gt;&lt;code&gt;AttributeConverter&lt;/code&gt; 인터페이스로 변환 로직을 직접 작성&lt;/strong&gt; — enum을 코드값으로 저장하거나, 암호화하거나, VO를 단일 컬럼에 압축할 때. 이 두 도구는 경쟁이 아니라 보완 관계다. Hibernate 기본으로 해결되면 그쪽이 간결하고, 안 되면 Converter로 내려간다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 필드 타입 X → 컬럼 타입 Y 사이의 어댑터
public interface AttributeConverter&amp;lt;X, Y&amp;gt; {
    Y convertToDatabaseColumn(X attribute);
    X convertToEntityAttribute(Y dbData);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;X&lt;/code&gt;가 엔티티 필드 타입이고 &lt;code&gt;Y&lt;/code&gt;가 DB 컬럼 타입이다. &lt;code&gt;Y&lt;/code&gt;는 보통 &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;Long&lt;/code&gt;처럼 JDBC가 바로 아는 기본 타입이어야 한다. Converter는 &lt;strong&gt;값 변환 전용&lt;/strong&gt;이고, 연관관계·식별자·버전 같은 특수 필드에는 개입하지 않는다는 점이 출발 전제다. 이 경계는 &lt;a href=&quot;#9-convert%EC%9D%98-%ED%95%9C%EA%B3%84-idversion%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B8%88%EC%A7%80&quot;&gt;§9 @Convert의 한계&lt;/a&gt;에서 자세히 다룬다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2) AttributeConverter 계약: 두 메서드가 전부다&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AttributeConverter&amp;lt;X, Y&amp;gt;&lt;/code&gt;는 제네릭 인터페이스고, 구현해야 할 메서드는 정확히 두 개다. 계약 자체가 단순하기 때문에 Converter를 읽을 때는 &lt;strong&gt;변환 방향 두 개&lt;/strong&gt;만 머리에 두면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Converter
public class BooleanToYNConverter implements AttributeConverter&amp;lt;Boolean, String&amp;gt; {

    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        if (attribute == null) return null;
        return attribute ? &amp;quot;Y&amp;quot; : &amp;quot;N&amp;quot;;
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        if (dbData == null) return null;
        return &amp;quot;Y&amp;quot;.equals(dbData);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 컨버터는 &lt;code&gt;Boolean&lt;/code&gt; 필드를 &lt;code&gt;&amp;#39;Y&amp;#39;&lt;/code&gt;/&lt;code&gt;&amp;#39;N&amp;#39;&lt;/code&gt; 한 글자 컬럼으로 저장한다. 레거시 DB에서 흔히 보는 패턴이다. 두 메서드가 대칭이어야 한다는 점이 중요하다 — &lt;code&gt;convertToDatabaseColumn(true)&lt;/code&gt;가 &lt;code&gt;&amp;quot;Y&amp;quot;&lt;/code&gt;면 &lt;code&gt;convertToEntityAttribute(&amp;quot;Y&amp;quot;)&lt;/code&gt;가 &lt;code&gt;true&lt;/code&gt;를 돌려줘야 한다. 이 왕복(round-trip) 일관성이 깨지면 저장과 조회가 맞지 않아 엔티티 상태가 DB와 달라진다.&lt;/p&gt;
&lt;p&gt;여기서 틀리기 쉬운 지점이 &lt;strong&gt;null 처리&lt;/strong&gt;다. 필드가 nullable이면 두 메서드 모두 앞부분에서 null을 체크해 null을 그대로 돌려줘야 한다. Converter가 null을 받아 NPE를 던지면 DB에는 잘 들어가 있는 row가 엔티티로 올라오면서 조회 자체가 실패한다. 반대로 non-null 필드로 강제한다면 최소한 &lt;code&gt;Objects.requireNonNull&lt;/code&gt;로 의도를 드러내는 편이 낫다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 피하기: null 무방비
public String convertToDatabaseColumn(Boolean attr) {
    return attr ? &amp;quot;Y&amp;quot; : &amp;quot;N&amp;quot;;   // null 들어오면 NPE
}

// 선호: null-safe
public String convertToDatabaseColumn(Boolean attr) {
    return attr == null ? null : (attr ? &amp;quot;Y&amp;quot; : &amp;quot;N&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Converter는 Hibernate가 &lt;strong&gt;엔티티 저장 직전과 DB row 로드 직후&lt;/strong&gt;에 호출한다. 영속성 컨텍스트 안에서 필드를 바꿔도 Converter가 매번 도는 건 아니고, 실제 SQL이 나가는 시점(flush)과 SELECT 결과 매핑 시점에만 돈다. 그래서 Converter 구현을 무거운 로직(예: 원격 호출)으로 채우는 건 안전하지 않다. 순수한 값 변환에 국한해야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3) @Convert vs @Converter(autoApply=true)&lt;/h2&gt;
&lt;p&gt;Converter를 엔티티에 적용하는 방식은 두 가지다. 필드별 명시와 자동 적용. 이 둘의 차이는 단순히 문법이 아니라 &lt;strong&gt;가독성과 의도 파악&lt;/strong&gt;의 문제다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 방식 1: 필드별 @Convert (명시적)
@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @Convert(converter = BooleanToYNConverter.class)
    private Boolean paid;

    @Convert(converter = BooleanToYNConverter.class)
    private Boolean shipped;
}

// 방식 2: @Converter(autoApply = true) (자동 적용)
@Converter(autoApply = true)
public class BooleanToYNConverter implements AttributeConverter&amp;lt;Boolean, String&amp;gt; {
    // ...
}

// 자동 적용이면 엔티티에서는 아무것도 안 써도 됨
@Entity
public class Order {
    private Boolean paid;     // BooleanToYNConverter가 자동으로 걸린다
    private Boolean shipped;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;autoApply = true&lt;/code&gt;는 해당 &lt;code&gt;X&lt;/code&gt; 타입(위 예에서는 &lt;code&gt;Boolean&lt;/code&gt;)의 &lt;strong&gt;모든 필드에 자동으로 Converter를 적용&lt;/strong&gt;한다. 프로젝트 전체에서 &lt;code&gt;Boolean&lt;/code&gt;을 전부 &lt;code&gt;&amp;#39;Y&amp;#39;&lt;/code&gt;/&lt;code&gt;&amp;#39;N&amp;#39;&lt;/code&gt;으로 저장하기로 정했다면 편하다. 그런데 편한 만큼 함정도 있다 — 어떤 엔티티 코드만 보고는 Converter가 걸려 있는지 알 수 없다. 새 필드를 추가하다가 의도하지 않게 변환이 일어날 수도 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;방식&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;언제&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Convert(converter=...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;엔티티 코드만 봐도 명시적&lt;/td&gt;
&lt;td&gt;필드마다 반복&lt;/td&gt;
&lt;td&gt;일부 필드만 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Converter(autoApply=true)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;중복 제거, 정책 일관성&lt;/td&gt;
&lt;td&gt;엔티티 코드에 단서 없음&lt;/td&gt;
&lt;td&gt;프로젝트 전역 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;권장은 &lt;strong&gt;기본을 명시적 &lt;code&gt;@Convert&lt;/code&gt;로 두고, 정말 전역 정책이 명확할 때만 &lt;code&gt;autoApply&lt;/code&gt;를 쓰는 것&lt;/strong&gt;이다. 특히 enum 코드 매핑 Converter는 &lt;code&gt;autoApply = true&lt;/code&gt;가 자연스럽다(한 enum 타입은 한 가지 방식으로만 저장한다는 게 자명하니까). 반면 암호화 Converter는 &lt;code&gt;autoApply = false&lt;/code&gt;로 두고 &lt;code&gt;@Convert&lt;/code&gt;로 필드별 명시가 맞다 — 어떤 필드가 암호화되는지 엔티티에서 바로 보여야 한다.&lt;/p&gt;
&lt;p&gt;한 엔티티에서 &lt;code&gt;autoApply&lt;/code&gt;를 일시적으로 끄고 싶을 때는 &lt;code&gt;@Convert(disableConversion = true)&lt;/code&gt;를 쓸 수 있다. 드문 케이스지만 알아두면 유용하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
public class LegacyRow {
    // autoApply로 전역 Converter가 걸려 있어도 이 필드는 변환 제외
    @Convert(disableConversion = true)
    private Boolean rawFlag;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4) Enum 저장: EnumType.STRING과 코드값 Converter&lt;/h2&gt;
&lt;h3&gt;4-1) @Enumerated 기본값의 함정&lt;/h3&gt;
&lt;p&gt;Jakarta Persistence가 enum을 저장하는 방식은 두 가지다 — &lt;code&gt;EnumType.ORDINAL&lt;/code&gt;과 &lt;code&gt;EnumType.STRING&lt;/code&gt;. 이름 그대로 순서(정수)로 저장할지, 문자열로 저장할지다. 그런데 &lt;strong&gt;명시하지 않으면 기본값이 &lt;code&gt;ORDINAL&lt;/code&gt;&lt;/strong&gt;이라는 게 함정이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 피하기: @Enumerated 미명시 → ORDINAL로 저장
@Entity
public class Order {
    private OrderStatus status;   // 위험
}

public enum OrderStatus {
    PENDING,   // 0
    PAID,      // 1
    CANCELLED  // 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드로 &lt;code&gt;PAID&lt;/code&gt;를 저장하면 DB에는 &lt;code&gt;1&lt;/code&gt;이 들어간다. 문제는 시간이 지나 enum에 값을 추가할 때다. &lt;code&gt;PENDING&lt;/code&gt; 앞에 &lt;code&gt;DRAFT&lt;/code&gt;를 끼워 넣으면? 기존 DB의 &lt;code&gt;1&lt;/code&gt;은 이제 &lt;code&gt;PAID&lt;/code&gt;가 아니라 &lt;code&gt;PENDING&lt;/code&gt;을 의미하게 된다. &lt;strong&gt;데이터 손상 없이 enum 순서를 바꿀 수 없다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;해결은 두 가지다. &lt;strong&gt;기본은 &lt;code&gt;EnumType.STRING&lt;/code&gt;&lt;/strong&gt;, 컬럼 길이나 코드값이 중요하면 &lt;code&gt;AttributeConverter&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 선호: EnumType.STRING — 이름 그대로 저장
@Entity
public class Order {
    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private OrderStatus status;   // &amp;#39;PENDING&amp;#39;, &amp;#39;PAID&amp;#39;, &amp;#39;CANCELLED&amp;#39; 저장
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;EnumType.STRING&lt;/code&gt;은 enum 이름을 그대로 저장한다. 순서가 바뀌어도 안전하고, DB만 봐도 값의 의미를 알 수 있다. 단점은 컬럼이 &lt;code&gt;VARCHAR&lt;/code&gt;가 돼야 하고, enum 이름이 길면 저장 공간도 늘어난다는 점. 대부분의 도메인에서는 충분히 수용 가능한 비용이다.&lt;/p&gt;
&lt;h3&gt;4-2) 코드값 매핑이 필요하면 Converter&lt;/h3&gt;
&lt;p&gt;enum 이름이 너무 길거나, 레거시 시스템과 호환을 위해 3~4글자 코드로 저장해야 할 때는 &lt;code&gt;EnumType.STRING&lt;/code&gt;으로는 부족하다. 이때 &lt;code&gt;AttributeConverter&lt;/code&gt;로 내려간다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum OrderStatus {
    PENDING(&amp;quot;P&amp;quot;),
    PAID(&amp;quot;A&amp;quot;),        // Accepted
    CANCELLED(&amp;quot;X&amp;quot;);

    private final String code;

    OrderStatus(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

@Converter(autoApply = true)
public class OrderStatusConverter implements AttributeConverter&amp;lt;OrderStatus, String&amp;gt; {

    @Override
    public String convertToDatabaseColumn(OrderStatus status) {
        return status == null ? null : status.getCode();
    }

    @Override
    public OrderStatus convertToEntityAttribute(String code) {
        if (code == null) return null;
        return Arrays.stream(OrderStatus.values())
                .filter(s -&amp;gt; s.getCode().equals(code))
                .findFirst()
                .orElseThrow(() -&amp;gt;
                    new IllegalArgumentException(&amp;quot;Unknown OrderStatus code: &amp;quot; + code));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 DB에는 &lt;code&gt;&amp;#39;P&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;A&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;X&amp;#39;&lt;/code&gt;가 저장되고, 엔티티에서는 &lt;code&gt;OrderStatus&lt;/code&gt; enum으로 다룬다. &lt;code&gt;autoApply = true&lt;/code&gt;라 엔티티 필드에 추가 어노테이션이 필요 없다. 주의할 부분은 &lt;strong&gt;없는 코드를 만났을 때의 동작&lt;/strong&gt;이다. 위 구현은 &lt;code&gt;IllegalArgumentException&lt;/code&gt;을 던져 조회 자체를 실패시킨다. DB에 예상 못한 값이 들어있으면 부팅 직후가 아니라 그 row를 조회하는 순간 폭발한다. 이게 맞는 동작이긴 하다 — 데이터 무결성을 묵살하는 것보다는 낫다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plantuml&quot;&gt;@startuml
title AttributeConverter 동작: 저장과 로드 왕복

entity &amp;quot;Order (Entity)&amp;quot; as Entity {
  status : OrderStatus
}
component &amp;quot;OrderStatusConverter&amp;quot; as Converter
database &amp;quot;orders (Table)&amp;quot; as DB {
  status : VARCHAR(4)
}

Entity -&amp;gt; Converter : convertToDatabaseColumn(PAID)
note right
  flush 시점
  필드 값 → DB 값
end note
Converter -&amp;gt; DB : INSERT status = &amp;#39;A&amp;#39;

DB -&amp;gt; Converter : SELECT status → &amp;#39;A&amp;#39;
note right
  SELECT 결과 매핑
  DB 값 → 필드 값
end note
Converter -&amp;gt; Entity : convertToEntityAttribute(&amp;quot;A&amp;quot;) = PAID

@enduml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 다이어그램이 보여주는 건 두 가지다. 첫째, Converter는 &lt;strong&gt;flush와 SELECT 결과 매핑&lt;/strong&gt;이라는 두 경계에서만 동작한다. 둘째, 왕복이 대칭이어야 한다 — &lt;code&gt;PAID → &amp;#39;A&amp;#39; → PAID&lt;/code&gt;로 돌아와야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5) Hibernate 6 @JdbcTypeCode(SqlTypes.JSON): 표준 JSON 매핑&lt;/h2&gt;
&lt;h3&gt;5-1) 네이티브 JSON 매핑의 등장&lt;/h3&gt;
&lt;p&gt;Hibernate 5 시대에는 JSON 컬럼을 엔티티 필드로 매핑하려면 &lt;code&gt;hibernate-types-52&lt;/code&gt; 같은 서드파티 라이브러리를 썼다. &lt;code&gt;@TypeDef&lt;/code&gt; + &lt;code&gt;@Type(type = &amp;quot;jsonb&amp;quot;)&lt;/code&gt; 조합이 흔했다. Hibernate 6부터는 이 기능이 코어에 들어왔다 — &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;

@Entity
public class Product {
    @Id @GeneratedValue
    private Long id;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = &amp;quot;jsonb&amp;quot;)        // PostgreSQL jsonb
    private Map&amp;lt;String, Object&amp;gt; metadata;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = &amp;quot;jsonb&amp;quot;)
    private ProductSpec spec;                   // 임의 POJO도 가능
}

public record ProductSpec(
    String manufacturer,
    int warrantyMonths,
    List&amp;lt;String&amp;gt; tags
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;을 붙이면 Hibernate가 필드를 JSON 컬럼으로 인식한다. PostgreSQL에서는 &lt;code&gt;jsonb&lt;/code&gt;, MySQL 8+에서는 &lt;code&gt;JSON&lt;/code&gt;, 다른 DB는 &lt;code&gt;TEXT&lt;/code&gt; 등으로 자동 매핑된다. 직렬화는 클래스패스에 있는 Jackson(&lt;code&gt;ObjectMapper&lt;/code&gt;)을 써서 Hibernate가 자동으로 처리한다. Spring Boot 3.x는 Jackson이 기본 포함이므로 별도 설정이 거의 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt;&lt;/code&gt; 같은 동적 구조도 되고, &lt;code&gt;ProductSpec&lt;/code&gt; 같은 record/POJO도 된다. 엔티티가 아니어야 한다는 제약만 있다. 엔티티를 JSON 필드에 박으면 영속성 컨텍스트 추적 대상이 되지 않는 이상한 엔티티가 생긴다.&lt;/p&gt;
&lt;h3&gt;5-2) jsonb vs json 컬럼&lt;/h3&gt;
&lt;p&gt;PostgreSQL에서는 &lt;code&gt;json&lt;/code&gt;과 &lt;code&gt;jsonb&lt;/code&gt; 두 타입이 있다. 차이는 확실하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;타입&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;저장 형태&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;인덱스&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;속도&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;공간&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원본 텍스트 그대로&lt;/td&gt;
&lt;td&gt;기본 인덱스 제한적&lt;/td&gt;
&lt;td&gt;저장 빠름&lt;/td&gt;
&lt;td&gt;공백 유지해 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jsonb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파싱된 바이너리&lt;/td&gt;
&lt;td&gt;GIN 인덱스 가능&lt;/td&gt;
&lt;td&gt;조회 빠름&lt;/td&gt;
&lt;td&gt;공백 제거돼 작음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실무에서는 거의 항상 &lt;code&gt;jsonb&lt;/code&gt;다. GIN 인덱스로 JSON 내부 키에 인덱스를 걸 수 있고, 조회 시 매번 파싱하지 않으니 빠르다. &lt;code&gt;columnDefinition = &amp;quot;jsonb&amp;quot;&lt;/code&gt;로 명시해두면 DDL 자동 생성 시 &lt;code&gt;jsonb&lt;/code&gt;로 잡힌다.&lt;/p&gt;
&lt;h3&gt;5-3) Hibernate 5 → 6 마이그레이션 포인트&lt;/h3&gt;
&lt;p&gt;Hibernate 5에서 &lt;code&gt;hibernate-types&lt;/code&gt;를 썼던 코드는 Hibernate 6으로 올라가면서 아래처럼 단순해진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Hibernate 5 (hibernate-types-52 의존)
@TypeDef(name = &amp;quot;jsonb&amp;quot;, typeClass = JsonBinaryType.class)
@Entity
public class Product {
    @Type(type = &amp;quot;jsonb&amp;quot;)
    @Column(columnDefinition = &amp;quot;jsonb&amp;quot;)
    private Map&amp;lt;String, Object&amp;gt; metadata;
}

// Hibernate 6 (서드파티 제거)
@Entity
public class Product {
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = &amp;quot;jsonb&amp;quot;)
    private Map&amp;lt;String, Object&amp;gt; metadata;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hibernate-types&lt;/code&gt; 의존을 빼면 버전 호환 문제와 라이브러리 관리 부담이 한 겹 사라진다. Hibernate 6으로 업그레이드할 계획이 있으면 이 지점을 같이 정리하는 게 자연스럽다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6) 커스텀 JSON Converter: 직렬화 로직 제어&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;이 표준이긴 한데, 특정 상황에서는 &lt;strong&gt;JSON 직렬화 로직을 직접 제어&lt;/strong&gt;해야 할 때가 있다. 예를 들어 Jackson 기본 직렬화가 맞지 않는 필드(커스텀 &lt;code&gt;ObjectMapper&lt;/code&gt; 설정, 특정 필드 제외, 암호화된 JSON 등)를 다룰 때. 이때는 &lt;code&gt;AttributeConverter&lt;/code&gt;로 내려가는 게 맞다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Converter
public class JsonMapConverter implements AttributeConverter&amp;lt;Map&amp;lt;String, Object&amp;gt;, String&amp;gt; {

    private static final ObjectMapper MAPPER = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .setSerializationInclusion(JsonInclude.Include.NON_NULL);

    @Override
    public String convertToDatabaseColumn(Map&amp;lt;String, Object&amp;gt; attribute) {
        if (attribute == null) return null;
        try {
            return MAPPER.writeValueAsString(attribute);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(&amp;quot;Failed to serialize metadata&amp;quot;, e);
        }
    }

    @Override
    public Map&amp;lt;String, Object&amp;gt; convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isBlank()) return null;
        try {
            return MAPPER.readValue(dbData, new TypeReference&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;() {});
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(&amp;quot;Failed to deserialize metadata: &amp;quot; + dbData, e);
        }
    }
}

@Entity
public class Product {
    @Convert(converter = JsonMapConverter.class)
    @Column(columnDefinition = &amp;quot;TEXT&amp;quot;)     // 또는 &amp;quot;jsonb&amp;quot;
    private Map&amp;lt;String, Object&amp;gt; metadata;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 형태의 장점은 &lt;strong&gt;ObjectMapper 설정을 내가 쥐고 있다는 것&lt;/strong&gt;이다. &lt;code&gt;JavaTimeModule&lt;/code&gt; 등록, &lt;code&gt;NON_NULL&lt;/code&gt; 직렬화 정책, 커스텀 시리얼라이저 등 원하는 대로 조립할 수 있다. 단점은 컬럼이 &lt;code&gt;String&lt;/code&gt;으로 매핑된다는 점 — DB에 &lt;code&gt;jsonb&lt;/code&gt;로 저장하려면 컬럼 정의는 맞추더라도 Hibernate 입장에서는 그냥 텍스트다. PostgreSQL의 JSON 경로 쿼리 같은 네이티브 기능을 쓰려면 별도 함수 호출이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;언제 Converter 방식을 고르는가&lt;/strong&gt;. Jackson 직렬화를 세밀하게 제어해야 하거나, Hibernate 버전이 6 미만이거나, JSON 안의 특정 필드를 엔티티 레벨에서 미리 가공해야 할 때다. 그 외엔 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;이 간결하고 네이티브 기능 활용이 쉽다.&lt;/p&gt;
&lt;p&gt;예외 처리에서 틀리기 쉬운 지점이 있다. &lt;code&gt;JsonProcessingException&lt;/code&gt;을 &lt;code&gt;RuntimeException&lt;/code&gt;으로 그대로 감싸면 원인 정보가 사라지기 쉽다. 원본 DB 문자열이나 실패 컨텍스트를 메시지에 넣어두면 나중에 어떤 row가 문제인지 역추적이 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7) 암호화 필드와 검색 불가 함정&lt;/h2&gt;
&lt;p&gt;개인정보(PII) 컬럼을 DB에 평문으로 저장하면 안 되는 규정이 점점 늘어난다. 주민번호, 전화번호, 이메일, 계좌번호 같은 필드. 이를 &lt;strong&gt;애플리케이션 레벨에서 암호화&lt;/strong&gt;해 저장하는 표준 방법이 &lt;code&gt;AttributeConverter&lt;/code&gt;다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Converter
public class EncryptedStringConverter implements AttributeConverter&amp;lt;String, String&amp;gt; {

    private final CryptoService crypto;

    public EncryptedStringConverter(CryptoService crypto) {
        this.crypto = crypto;
    }

    @Override
    public String convertToDatabaseColumn(String plaintext) {
        return plaintext == null ? null : crypto.encrypt(plaintext);
    }

    @Override
    public String convertToEntityAttribute(String ciphertext) {
        return ciphertext == null ? null : crypto.decrypt(ciphertext);
    }
}

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    @Convert(converter = EncryptedStringConverter.class)
    @Column(length = 512)                    // 암호문은 평문보다 길다
    private String ssn;

    @Convert(converter = EncryptedStringConverter.class)
    @Column(length = 256)
    private String phoneNumber;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;암호화 알고리즘은 &lt;strong&gt;AES/GCM&lt;/strong&gt; 권장이다. IV(Initialization Vector)를 매번 새로 생성하고 암호문에 포함해 저장하면 같은 평문이 매번 다른 암호문이 된다. 키는 애플리케이션 코드에 박지 말고 &lt;strong&gt;KMS(AWS KMS, HashiCorp Vault 등)&lt;/strong&gt;에서 읽어온다. Converter는 Spring Bean이 아니므로 &lt;code&gt;@Converter&lt;/code&gt;에 &lt;code&gt;CryptoService&lt;/code&gt;를 직접 주입할 수는 없는데, Hibernate가 Converter 인스턴스화 시 Spring의 &lt;code&gt;AutowireCapableBeanFactory&lt;/code&gt;를 연결해주는 설정을 붙이거나, static holder 패턴으로 우회한다.&lt;/p&gt;
&lt;h3&gt;7-1) LIKE 검색이 작동하지 않는다&lt;/h3&gt;
&lt;p&gt;여기서 틀리기 쉬운 지점이 본격적으로 나온다. 암호화 필드는 &lt;strong&gt;평문 검색이 불가능&lt;/strong&gt;하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 피하기: 암호화 필드에 LIKE 검색 — 아무것도 안 나온다
@Query(&amp;quot;select u from User u where u.phoneNumber like concat(&amp;#39;%&amp;#39;, :suffix, &amp;#39;%&amp;#39;)&amp;quot;)
List&amp;lt;User&amp;gt; findByPhoneSuffix(String suffix);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DB에는 &lt;code&gt;&amp;quot;A7kZ3xQ9...&amp;quot;&lt;/code&gt; 같은 암호문이 들어있으니 &lt;code&gt;LIKE &amp;#39;%1234%&amp;#39;&lt;/code&gt; 패턴은 걸릴 리가 없다. &lt;code&gt;=&lt;/code&gt; 정확 일치 검색도 AES/GCM처럼 매번 IV가 달라지는 알고리즘에서는 같은 평문도 매번 다른 암호문이 되므로 &lt;strong&gt;동등 비교조차 안 된다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;해결은 세 갈래다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;검색 불가를 받아들이고&lt;/strong&gt; 업무 플로우를 재설계한다. 전화번호로 유저를 찾는 대신 유저 ID로 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결정적 암호화(deterministic encryption)&lt;/strong&gt;를 쓴다. 같은 평문이 항상 같은 암호문이 되도록 IV를 고정하거나 생략. 보안성은 살짝 떨어지지만 &lt;code&gt;=&lt;/code&gt; 검색이 가능해진다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해시 컬럼을 별도로&lt;/strong&gt; 둔다. &lt;code&gt;phone_hash = SHA256(phoneNumber)&lt;/code&gt; 같은 컬럼을 추가하고 검색에 쓴다. 해시는 역변환 불가이므로 복호화할 필요가 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 선호 3: 해시 컬럼 병행
@Entity
public class User {
    @Convert(converter = EncryptedStringConverter.class)
    private String phoneNumber;

    @Column(length = 64)
    private String phoneHash;     // SHA-256 hex. = 검색용 인덱스

    public void setPhoneNumber(String raw) {
        this.phoneNumber = raw;
        this.phoneHash = raw == null ? null : sha256Hex(raw);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해시 컬럼에는 인덱스를 걸 수 있고 &lt;code&gt;=&lt;/code&gt; 검색이 가능하다. 부분 일치가 필요한 경우는 방법이 까다로워지는데, 전화번호 끝 4자리 검색 같은 패턴은 해시를 여러 개(전화번호 전체 해시, 끝 4자리 해시) 저장하는 식으로 푼다.&lt;/p&gt;
&lt;h3&gt;7-2) toString·로그 노출 방지&lt;/h3&gt;
&lt;p&gt;암호화를 아무리 잘해도 애플리케이션 로그나 에러 메시지에 복호화된 평문이 찍히면 끝이다. Lombok을 쓰면 &lt;code&gt;@ToString&lt;/code&gt;에서 암호화 필드를 제외한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Getter @Setter
@ToString(exclude = {&amp;quot;ssn&amp;quot;, &amp;quot;phoneNumber&amp;quot;})   // 로그에 평문이 안 찍힌다
public class User {
    @Convert(converter = EncryptedStringConverter.class)
    private String ssn;

    @Convert(converter = EncryptedStringConverter.class)
    private String phoneNumber;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jackson으로 JSON 응답을 만들 때도 &lt;code&gt;@JsonIgnore&lt;/code&gt;나 마스킹 시리얼라이저를 붙인다. 암호화는 저장에서의 보호고, 노출 방지는 별도 관심사다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;8) Money·Email 같은 VO: Converter vs @Embeddable&lt;/h2&gt;
&lt;h3&gt;8-1) 선택지는 둘&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Money(amount, currency)&lt;/code&gt;, &lt;code&gt;Email(address)&lt;/code&gt;, &lt;code&gt;Address(...)&lt;/code&gt; 같은 값 객체(VO, Value Object)를 엔티티에 포함시킬 때 선택지가 두 개 있다. &lt;code&gt;@Embeddable&lt;/code&gt;로 여러 컬럼에 펼치거나, &lt;code&gt;AttributeConverter&lt;/code&gt;로 단일 컬럼에 압축하거나.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public record Money(BigDecimal amount, String currency) {}

// 방식 A: @Embeddable — 두 컬럼
@Embeddable
public class MoneyEmbeddable {
    @Column(name = &amp;quot;price_amount&amp;quot;)
    private BigDecimal amount;

    @Column(name = &amp;quot;price_currency&amp;quot;, length = 3)
    private String currency;
    // getter, equals, hashCode 필요
}

@Entity
public class Product {
    @Embedded
    private MoneyEmbeddable price;
}

// 방식 B: Converter — 한 컬럼에 &amp;quot;1000 KRW&amp;quot;
@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter&amp;lt;Money, String&amp;gt; {

    @Override
    public String convertToDatabaseColumn(Money money) {
        return money == null ? null : money.amount() + &amp;quot; &amp;quot; + money.currency();
    }

    @Override
    public Money convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isBlank()) return null;
        String[] parts = dbData.split(&amp;quot; &amp;quot;);
        return new Money(new BigDecimal(parts[0]), parts[1]);
    }
}

@Entity
public class Product {
    @Column(length = 32)
    private Money price;   // &amp;quot;1000 KRW&amp;quot;로 저장
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8-2) 언제 어느 쪽인가&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;판단 기준&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;@Embeddable&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;AttributeConverter&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;각 구성 요소를 &lt;strong&gt;개별 쿼리&lt;/strong&gt;로 필터링해야 하나&lt;/td&gt;
&lt;td&gt;가능 (&lt;code&gt;WHERE amount &amp;gt; 1000&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;불가 (문자열 파싱 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;각 구성 요소에 &lt;strong&gt;인덱스&lt;/strong&gt;를 걸어야 하나&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JPQL/Criteria에서 분해&lt;/strong&gt;해 접근해야 하나&lt;/td&gt;
&lt;td&gt;가능 (&lt;code&gt;m.amount&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컬럼 &lt;strong&gt;개수를 줄이고&lt;/strong&gt; 단일 문자열로 관리&lt;/td&gt;
&lt;td&gt;컬럼 여러 개 생성&lt;/td&gt;
&lt;td&gt;한 컬럼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VO가 &lt;strong&gt;record&lt;/strong&gt;로 간결하게 표현&lt;/td&gt;
&lt;td&gt;일반 클래스 필요(equals/hashCode)&lt;/td&gt;
&lt;td&gt;record 그대로 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실무 판단의 핵심은 &lt;strong&gt;쿼리 요구사항&lt;/strong&gt;이다. &lt;code&gt;amount&lt;/code&gt; 값으로 범위 검색을 하거나 집계를 돌려야 한다면 &lt;code&gt;@Embeddable&lt;/code&gt;이 압도적으로 유리하다. 반면 VO를 단순히 엔티티에 붙어 다니는 불투명 값으로 다룬다면(저장만 하고 그 필드로 쿼리할 일이 거의 없음) Converter가 컬럼 수를 줄여준다.&lt;/p&gt;
&lt;p&gt;record와 불변 VO를 선호한다면 Converter가 더 자연스럽다. &lt;code&gt;@Embeddable&lt;/code&gt;은 Jakarta Persistence 스펙상 &lt;strong&gt;기본 생성자&lt;/strong&gt;가 필요해 record를 그대로는 쓸 수 없다(Hibernate 6은 일부 지원이 있지만 제한적). Converter는 내부에서 record 생성자를 직접 호출하니 제약이 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;9) @Convert의 한계: @Id·@Version·연관관계 금지&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@Convert&lt;/code&gt;는 값 변환에 특화된 도구라, &lt;strong&gt;값 아닌 필드&lt;/strong&gt;에는 쓸 수 없다. Jakarta Persistence 3.x 스펙이 명시적으로 금지하는 영역을 먼저 정리해두는 게 사고를 예방하는 가장 빠른 길이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;금지 대상&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;이유&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Id&lt;/code&gt; 필드&lt;/td&gt;
&lt;td&gt;PK는 영속성 컨텍스트 동일성 식별에 쓰이므로 변환 개입 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Version&lt;/code&gt; 필드&lt;/td&gt;
&lt;td&gt;낙관적 락 버전 증가는 Hibernate가 직접 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@ManyToOne&lt;/code&gt;/&lt;code&gt;@OneToOne&lt;/code&gt; 등 연관관계&lt;/td&gt;
&lt;td&gt;FK 변환은 ID Generation·연관 매핑 영역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Temporal&lt;/code&gt; 붙은 필드&lt;/td&gt;
&lt;td&gt;날짜 타입은 별도 처리 경로가 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Enumerated&lt;/code&gt; 붙은 필드&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Enumerated&lt;/code&gt;와 &lt;code&gt;@Convert&lt;/code&gt;는 같은 필드에 공존 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 피하기: @Id에 Converter 붙이면 Hibernate가 예외
@Entity
public class Order {
    @Id
    @Convert(converter = MyIdConverter.class)   // 부팅 시 예외
    private OrderId id;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PK 변환이 필요하면 &lt;code&gt;@Convert&lt;/code&gt; 대신 &lt;strong&gt;&lt;code&gt;@IdClass&lt;/code&gt; / &lt;code&gt;@EmbeddedId&lt;/code&gt; / &lt;code&gt;@GenericGenerator&lt;/code&gt; / 커스텀 &lt;code&gt;IdentifierGenerator&lt;/code&gt;&lt;/strong&gt;로 내려가야 한다. &lt;a href=&quot;./35_jpa-id-generation-composite-key.md&quot;&gt;35편 식별자 생성&lt;/a&gt;에서 이 경로를 다뤘다.&lt;/p&gt;
&lt;p&gt;연관관계 변환이 필요한 경우(예: FK 컬럼 값을 암호화하거나 커스텀 포맷으로 저장)도 Converter로는 안 된다. 이 요구 자체가 드물고, 거의 항상 도메인 모델 재검토가 맞는 답이다. FK 컬럼을 직접 조작해야 한다는 건 엔티티 경계가 잘못 그려졌다는 신호에 가깝다.&lt;/p&gt;
&lt;p&gt;JPQL에서 Converter를 타지 않는 케이스도 주의해야 한다. Hibernate 6에서는 많이 개선됐지만, 일부 버전에서 &lt;code&gt;WHERE o.status = :status&lt;/code&gt; 같은 파라미터 바인딩이 Converter를 거치지 않고 날것의 값으로 나가는 경우가 있었다. 증상은 &amp;quot;엔티티로 조회는 되는데 &lt;code&gt;where&lt;/code&gt; 절이 안 먹는다&amp;quot;는 형태다. Hibernate 6.2 이후는 대부분 해결됐으나, 의심되면 실제 나가는 SQL을 로그로 찍어 확인하는 게 가장 빠르다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;10) JSON 쿼리와 @DynamicUpdate&lt;/h2&gt;
&lt;h3&gt;10-1) JSON 경로 쿼리&lt;/h3&gt;
&lt;p&gt;Hibernate 6은 JSON 경로 표현식을 JPQL에서 일부 지원한다. PostgreSQL의 경우 &lt;code&gt;function(...)&lt;/code&gt;을 통해 네이티브 함수를 호출할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// JSON 내부 키로 필터링 (PostgreSQL jsonb_extract_path_text)
@Query(&amp;quot;&amp;quot;&amp;quot;
    select p from Product p
    where function(&amp;#39;jsonb_extract_path_text&amp;#39;, p.metadata, &amp;#39;category&amp;#39;) = :category
    &amp;quot;&amp;quot;&amp;quot;)
List&amp;lt;Product&amp;gt; findByMetadataCategory(@Param(&amp;quot;category&amp;quot;) String category);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;metadata&lt;/code&gt; 컬럼이 &lt;code&gt;jsonb&lt;/code&gt;이고 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;으로 매핑된 상태. JSON 안의 &lt;code&gt;category&lt;/code&gt; 키가 특정 값과 일치하는 row를 찾는다. Querydsl에서는 Native Template 형태로 비슷하게 표현한다.&lt;/p&gt;
&lt;p&gt;성능은 GIN 인덱스가 붙어 있으면 빠르고, 없으면 풀스캔이라 느리다. JSON 컬럼에 자주 조회되는 키가 있다면 다음처럼 표현식 인덱스를 걸어둔다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_product_metadata_category
ON product USING GIN ((metadata-&amp;gt;&amp;#39;category&amp;#39;));&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10-2) @DynamicUpdate와 JSON 필드&lt;/h3&gt;
&lt;p&gt;JSON 필드를 갖는 엔티티에 특히 추천하는 한 가지 옵션이 &lt;code&gt;@DynamicUpdate&lt;/code&gt;다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@DynamicUpdate
public class Product {
    @Id @GeneratedValue
    private Long id;

    private String name;
    private BigDecimal price;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = &amp;quot;jsonb&amp;quot;)
    private Map&amp;lt;String, Object&amp;gt; metadata;   // 수백 KB가 될 수도
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기본 Hibernate는 엔티티를 UPDATE 할 때 &lt;strong&gt;모든 컬럼을 UPDATE 구문에 포함&lt;/strong&gt;한다. 한 필드만 바뀌었어도 전부 다시 쓴다. &lt;code&gt;name&lt;/code&gt;만 바꿨는데 &lt;code&gt;metadata&lt;/code&gt;에 수백 KB JSON이 들어있다면, 매번 그 JSON을 통째로 다시 쓰는 셈이다. 네트워크와 디스크 I/O가 무의미하게 커진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@DynamicUpdate&lt;/code&gt;를 붙이면 Hibernate가 &lt;strong&gt;실제 바뀐 필드만 포함한 UPDATE 문&lt;/strong&gt;을 런타임에 생성한다. 대신 SQL 캐싱 효과가 줄어 작은 엔티티에는 오히려 손해일 수 있다. &lt;strong&gt;JSON이나 대용량 컬럼이 있는 엔티티에 선택적으로 붙인다&lt;/strong&gt;는 게 권장이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;옵션&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;UPDATE 컬럼&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SQL 캐싱&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;대용량 컬럼 영향&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;모든 컬럼&lt;/td&gt;
&lt;td&gt;효율적 (동일 SQL 재사용)&lt;/td&gt;
&lt;td&gt;매 UPDATE마다 전체 재저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@DynamicUpdate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;변경된 컬럼만&lt;/td&gt;
&lt;td&gt;매번 새 SQL 생성&lt;/td&gt;
&lt;td&gt;변경 없으면 제외됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;11) @JdbcTypeCode의 다른 SqlTypes: ARRAY·UUID·INET&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@JdbcTypeCode&lt;/code&gt;는 JSON만을 위한 게 아니다. Hibernate 6은 여러 특수 SQL 타입을 표준화된 방식으로 노출한다. PostgreSQL을 쓰는 경우 특히 실용적인 타입들이 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;SqlTypes&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DB 타입&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;엔티티 필드 타입&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;용도&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JSON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jsonb&lt;/code&gt; / &lt;code&gt;JSON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Map&lt;/code&gt;, POJO, record&lt;/td&gt;
&lt;td&gt;구조화 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARRAY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL &lt;code&gt;text[]&lt;/code&gt;, &lt;code&gt;int[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String[]&lt;/code&gt;, &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;태그, 다중 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL &lt;code&gt;uuid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;java.util.UUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;식별자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL &lt;code&gt;inet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InetAddress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IP 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INTERVAL_SECOND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL &lt;code&gt;interval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;시간 간격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;XML&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;, POJO&lt;/td&gt;
&lt;td&gt;XML 컬럼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
public class Post {
    @Id @GeneratedValue
    private Long id;

    // PostgreSQL 배열 컬럼
    @JdbcTypeCode(SqlTypes.ARRAY)
    @Column(columnDefinition = &amp;quot;text[]&amp;quot;)
    private List&amp;lt;String&amp;gt; tags;

    // UUID 네이티브
    @JdbcTypeCode(SqlTypes.UUID)
    @Column(columnDefinition = &amp;quot;uuid&amp;quot;)
    private UUID publicId;

    // INET 주소
    @JdbcTypeCode(SqlTypes.INET)
    @Column(columnDefinition = &amp;quot;inet&amp;quot;)
    private InetAddress authorIp;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;배열 타입은 특히 간단한 태그 시스템 같은 경우에 편하다. 별도 조인 테이블을 만들 만큼 무겁지 않고, 순서가 필요 없다면 &lt;code&gt;text[]&lt;/code&gt;에 넣고 GIN 인덱스로 검색한다. Hibernate 5까지는 이걸 쓰려면 &lt;code&gt;hibernate-types&lt;/code&gt;에 의존했다.&lt;/p&gt;
&lt;p&gt;UUID는 PostgreSQL의 네이티브 &lt;code&gt;uuid&lt;/code&gt; 타입이 16바이트로 저장돼 &lt;code&gt;VARCHAR(36)&lt;/code&gt;으로 저장하는 것보다 절반 이하 공간을 쓴다. MySQL처럼 네이티브 UUID가 없는 DB에서는 &lt;code&gt;BINARY(16)&lt;/code&gt; + 변환 유틸로 해결해야 한다.&lt;/p&gt;
&lt;p&gt;DB가 이 타입들을 네이티브로 지원하지 않으면 &lt;code&gt;@JdbcTypeCode&lt;/code&gt;가 기대대로 동작하지 않으니 &lt;strong&gt;DB 벤더 매뉴얼과 Hibernate 방언(dialect) 지원 여부를 먼저 확인&lt;/strong&gt;해야 한다. 이식성이 중요한 프로젝트라면 &lt;code&gt;@JdbcTypeCode&lt;/code&gt;의 특수 타입 대신 기본 타입 + Converter가 안전할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;12) 실무 체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@Enumerated&lt;/code&gt;는 항상 &lt;code&gt;EnumType.STRING&lt;/code&gt;&lt;/strong&gt;. 기본값이 &lt;code&gt;ORDINAL&lt;/code&gt;이라는 스펙 함정을 깜빡하지 않는다. 레거시 코드에서 &lt;code&gt;@Enumerated&lt;/code&gt; 미명시된 enum 필드는 리팩터링 대상 1호다. 코드값 매핑이 필요하면 &lt;code&gt;@Converter(autoApply=true)&lt;/code&gt;로 내려간다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON 매핑은 Hibernate 6의 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt; 우선&lt;/strong&gt;. &lt;code&gt;hibernate-types&lt;/code&gt; 의존이 남아 있다면 6.x 업그레이드와 함께 제거한다. 직렬화 로직을 세밀하게 제어해야 할 때만 커스텀 &lt;code&gt;AttributeConverter&lt;/code&gt;로 내려간다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;암호화 필드에는 검색 요구사항을 먼저 확인&lt;/strong&gt;한다. &lt;code&gt;LIKE&lt;/code&gt; 검색이 필요하면 암호화 자체가 부적합하거나, 해시 컬럼을 병행해야 한다. 키 관리는 KMS에서. &lt;code&gt;@ToString(exclude=...)&lt;/code&gt;와 &lt;code&gt;@JsonIgnore&lt;/code&gt;로 로그·응답 노출까지 같이 막는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Converter는 PK·버전·연관관계에 쓸 수 없다&lt;/strong&gt;. 이 경계를 넘는 요구는 거의 항상 도메인 재설계의 신호다. &lt;code&gt;@EmbeddedId&lt;/code&gt;, &lt;code&gt;@IdClass&lt;/code&gt;, 커스텀 &lt;code&gt;IdentifierGenerator&lt;/code&gt; 쪽 루트를 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VO 매핑은 쿼리 요구로 판단&lt;/strong&gt;. 구성 요소로 WHERE 절이나 인덱스를 쓸 일이 있으면 &lt;code&gt;@Embeddable&lt;/code&gt;, 단순 저장이면 Converter. record와 불변성을 선호하면 Converter가 자연스럽다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대용량 JSON 필드가 있는 엔티티에는 &lt;code&gt;@DynamicUpdate&lt;/code&gt;&lt;/strong&gt;를 고려한다. 작은 엔티티에 일괄 적용하면 오히려 손해니 선택적으로. 한 필드만 자주 바뀌는 엔티티에서 UPDATE 크기가 눈에 띄게 줄어든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@Converter(autoApply = true)&lt;/code&gt;는 enum 코드 매핑처럼 정책이 명확할 때만&lt;/strong&gt;. 암호화 Converter는 명시적 &lt;code&gt;@Convert&lt;/code&gt;로 필드마다 적는다. 엔티티 코드만 봐도 변환 여부를 판단할 수 있어야 리뷰 비용이 낮다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Converter 예외는 원인 컨텍스트를 남긴다&lt;/strong&gt;. JSON 파싱 실패 시 원본 문자열이나 row ID 같은 단서를 메시지에 포함. &lt;code&gt;RuntimeException&lt;/code&gt;으로 감싸만 두면 어떤 row가 깨졌는지 역추적이 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL을 쓰면 &lt;code&gt;@JdbcTypeCode&lt;/code&gt;의 &lt;code&gt;ARRAY&lt;/code&gt;/&lt;code&gt;UUID&lt;/code&gt;/&lt;code&gt;INET&lt;/code&gt;을 적극 활용&lt;/strong&gt;한다. 네이티브 타입의 공간·성능 이점을 덤으로 얻는다. DB 벤더가 바뀔 가능성이 있는 프로젝트라면 이식성 손해와의 트레이드오프를 계산해둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;AttributeConverter&lt;/code&gt;는 필드 타입과 컬럼 타입 사이의 얇은 어댑터로, 값 변환에 한정해 쓰는 도구다&lt;/strong&gt;. &lt;strong&gt;Enum은 &lt;code&gt;EnumType.STRING&lt;/code&gt; 또는 코드값 Converter로, JSON은 Hibernate 6 &lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;으로, 암호화는 &lt;code&gt;@Convert&lt;/code&gt;에 검색 제약을 함께 설계&lt;/strong&gt;해야 사고가 없다. Converter는 PK·버전·연관관계에 쓸 수 없고, VO 매핑은 쿼리 요구로 &lt;code&gt;@Embeddable&lt;/code&gt;과 Converter 중에서 고르는 판단이 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;태그&lt;/strong&gt;: JPA, Hibernate, AttributeConverter, Enumerated, JdbcTypeCode, JSON 매핑, 암호화 필드, Embeddable, DynamicUpdate, Spring Boot 3&lt;/p&gt;</description>
      <category>CS/Spring</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/426</guid>
      <comments>https://dding-shark.tistory.com/426#entry426comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:28:58 +0900</pubDate>
    </item>
    <item>
      <title>JPA Auditing과 Soft Delete &amp;mdash; @CreatedDate&amp;middot;@AuditorAware&amp;middot;@SQLDelete</title>
      <link>https://dding-shark.tistory.com/425</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;jpa-auditing과-soft-delete--createddateauditorawaresqldelete&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;JPA
Auditing과 Soft Delete — @CreatedDate·@AuditorAware·@SQLDelete&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티마다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedAt&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdBy&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedBy&lt;/code&gt; 필드를 직접 세팅하는
코드를 한 번이라도 짜본 사람은 안다. 서비스 계층 어디에선가는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setCreatedAt(LocalDateTime.now())&lt;/code&gt;가 빠져 있고, 어떤
엔티티는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedBy&lt;/code&gt;가 계속 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로 남는다.
반대쪽에서는 &amp;quot;삭제된 회원&amp;quot;이라는 상태가 운영상 필요한데
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DELETE&lt;/code&gt; 쿼리로 날려버려서 주문 이력의 FK가 끊어지고,
정산팀이 새벽에 전화를 걸어온다. 이 두 문제는 별개로 보이지만 같은 결에
있다 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티의 생성·수정·삭제라는 라이프사이클을 서비스 코드에
흩어놓지 말고 영속성 계층에 공통 인프라로 박자&lt;/strong&gt;는 얘기다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Auditing&lt;/strong&gt;과 Hibernate의
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Soft Delete&lt;/strong&gt; 지원은 이 결을 구현한 두 축이다. 한쪽은
생성/수정 시각과 사용자 정보를 자동으로 채우고, 다른 쪽은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DELETE&lt;/code&gt;를 논리적 플래그로 바꾼다. 이 둘을 엔티티 한쪽에서
조합해두면, 서비스 계층은 도메인 로직에만 집중하고 감사 추적과 복구
가능성은 인프라가 책임진다. 그런데 실무에서는 이 구성이 자주 깨진다.
아래 네 가지는 거의 매번 밟는다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;가 작동하지 않는다&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt;를
BaseEntity에 안 붙여서 리스너가 한 번도 호출되지 않는다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Soft delete 후 같은 이메일로 재가입이 안 된다&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;unique&lt;/code&gt; 제약이 삭제된 행까지 포함해서 중복으로 잡는다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Where&lt;/code&gt;를 썼더니 Hibernate 6.3에서 deprecated
경고가 뜬다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;으로 교체해야
한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt;가 스케줄러 스레드에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;을 반환한다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt;가
없는 경로를 생각 못 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;a href=&quot;./10_spring-application-event-async.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10편
ApplicationEvent와 @Async&lt;/a&gt;, &lt;a href=&quot;./21_spring-security-context-propagation.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;21편 Security
컨텍스트 전파&lt;/a&gt;, &lt;a href=&quot;./34_jpa-association-mapping.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;34편 연관
매핑&lt;/a&gt;과 자매편이다. 10편이 비동기 이벤트 버스를 봤다면 여기서는 그
비동기 스레드에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt;가 어떻게 깨지는지를 보고,
21편이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt; 전파 자체를 봤다면 여기서는 그
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt;가 감사 데이터의 출처로 쓰일 때의 경계
문제를 본다. 34편이 FK의 양쪽을 봤다면 여기서는 soft delete된 부모의
자식 관계가 어떻게 dangling되는지를 본다. 전체 순서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;왜
공통인가 → Auditing 구성 → AuditorAware → 시간 타입 → BaseEntity →
Envers → Soft Delete 설계 → @SQLDelete/@SQLRestriction → Hibernate 6.4
@SoftDelete → @FilterDef → 실무&lt;/strong&gt; 순으로 정리한다. 버전은 Spring
Boot 3.x / Spring Data JPA 3.x / Hibernate 6.x / Jakarta Persistence 3.x
기준이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-audit-정보는-왜-공통-인프라인가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) Audit 정보는 왜 공통
인프라인가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-enablejpaauditing과-4가지-애너테이션&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2)
@EnableJpaAuditing과 4가지 애너테이션&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-auditoraware-구현과-security-컨텍스트&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) AuditorAware
구현과 Security 컨텍스트&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-datetimeprovider와-테스트-시계-고정&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4)
DateTimeProvider와 테스트 시계 고정&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-baseentity-패턴-mappedsuperclass와-entitylisteners&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5)
BaseEntity 패턴: @MappedSuperclass와 EntityListeners&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-시간-타입-선택-localdatetime-vs-offsetdatetime-vs-instant&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6)
시간 타입 선택: LocalDateTime vs OffsetDateTime vs Instant&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-hibernate-envers-전면-감사-로그의-비용과-이득&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7)
Hibernate Envers: 전면 감사 로그의 비용과 이득&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-soft-delete-설계-동기와-함정&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8) Soft Delete 설계 동기와
함정&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-sqldelete--sqlrestriction-hibernate-63&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) @SQLDelete +
@SQLRestriction (Hibernate 6.3+)&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-hibernate-64-softdelete-신규-애너테이션&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) Hibernate
6.4+ @SoftDelete 신규 애너테이션&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-filterdef--filter로-동적-필터&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) @FilterDef +
@Filter로 동적 필터&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-실무-체크리스트&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 실무 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#13-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-audit-정보는-왜-공통-인프라인가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) Audit 정보는 왜 공통
인프라인가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;언제 생겼고 언제 바뀌었는지, 누가 만들었고 누가 바꿨는지&amp;quot; — 이 네
가지는 도메인 로직이 아니다. 주문이라는 개념이 존재하기 위해 꼭 필요한
속성도 아니고, 주문 금액 계산에 관여하지도 않는다. 그런데 운영에
들어가면 이 네 가지가 없는 엔티티는 대부분 쓸모없는 엔티티다. 장애가
났을 때 &amp;quot;누가 언제 건드렸는지&amp;quot;를 모르면 원인을 추적할 수가 없다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기술적으로는 도메인이지만 의미적으로는 인프라&lt;/strong&gt;인
셈이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이걸 서비스 계층에 흩뿌리면 어떤 일이 벌어지는지 한 번 보자.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 서비스에서 직접 세팅&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Order &lt;span class=&quot;fu&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderCommand cmd&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; userId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cmd&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setCreatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setCreatedBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setUpdatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setUpdatedBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;updateStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; OrderStatus status&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; userId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;changeStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setUpdatedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 이거 매번 쓰면 까먹는다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setUpdatedBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// createdBy는 건드리면 안 되는데 실수로 건드리면?&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 가지 문제가 한 번에 드러난다. 첫째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;같은 코드가 모든 CUD
메서드에 중복&lt;/strong&gt;된다. 엔티티가 100개면 이 코드가 수백 줄로
흩어진다. 둘째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;한 군데라도 빠뜨리면 조용히 깨진다&lt;/strong&gt; —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedBy&lt;/code&gt;가 영원히 처음 값인 채로 남아도 컴파일 에러가 안
난다. 셋째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdBy&lt;/code&gt;를 실수로 update 경로에서
덮어쓸 수 있다&lt;/strong&gt;. 도메인 불변을 코드 규율로만 지키겠다는
얘기인데, 규율은 항상 어긋난다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Auditing&lt;/strong&gt;은 이 네 가지를 프레임워크
레벨에서 자동화한다. 엔티티의 라이프사이클
이벤트(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreUpdate&lt;/code&gt;)에 리스너를
걸어두고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;
애너테이션이 붙은 필드를 찾아서 값을 주입한다. 서비스 코드는 한 줄도
바뀌지 않고, 엔티티는 상속으로 네 필드를 받는다. 이게 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속성
이벤트에 인프라를 붙이는&lt;/strong&gt; 스프링의 전형적인 접근이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Soft Delete도 같은 맥락이다. &amp;quot;삭제됐다&amp;quot;는 상태를 도메인 필드로
표현하되, 조회 시 자동으로 제외하고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt; 호출을
UPDATE로 바꾸는 일은 서비스가 책임질 일이 아니다. Hibernate가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;으로 이걸
처리해준다. 네 축 — 생성/수정/삭제의 시각/주체 — 중 셋을 영속성 계층으로
내리면, 서비스는 본질적인 도메인 로직만 남는다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-enablejpaauditing과-4가지-애너테이션&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) @EnableJpaAuditing과
4가지 애너테이션&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Auditing 기능을 켜려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EnableJpaAuditing&lt;/code&gt; 하나만
추가하면 된다. 기본적으로는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Configuration&lt;/code&gt; 클래스에
붙이거나, Spring Boot의 메인 클래스에 붙인다. 이 어노테이션 하나가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditingEntityListener&lt;/code&gt;와 관련 빈들을 자동으로 등록한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Configuration&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EnableJpaAuditing&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    auditorAwareRef &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;auditorProvider&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    dateTimeProviderRef &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;dateTimeProvider&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    modifyOnCreate &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; JpaAuditingConfig &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; AuditorAware&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;auditorProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-11&quot;&gt;&lt;a href=&quot;#cb2-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SecurityContextHolder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getAuthentication&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-12&quot;&gt;&lt;a href=&quot;#cb2-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Authentication&lt;span class=&quot;op&quot;&gt;::&lt;/span&gt;isAuthenticated&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-13&quot;&gt;&lt;a href=&quot;#cb2-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Authentication&lt;span class=&quot;op&quot;&gt;::&lt;/span&gt;getName&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-14&quot;&gt;&lt;a href=&quot;#cb2-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-15&quot;&gt;&lt;a href=&quot;#cb2-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-16&quot;&gt;&lt;a href=&quot;#cb2-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-17&quot;&gt;&lt;a href=&quot;#cb2-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; DateTimeProvider &lt;span class=&quot;fu&quot;&gt;dateTimeProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-18&quot;&gt;&lt;a href=&quot;#cb2-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OffsetDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-19&quot;&gt;&lt;a href=&quot;#cb2-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-20&quot;&gt;&lt;a href=&quot;#cb2-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;속성 세 가지를 알아두면 디버깅이 쉬워진다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;auditorAwareRef&lt;/code&gt;는 &amp;quot;누가 만들었는가&amp;quot;를 제공하는 빈 이름이다.
지정하지 않으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;는
항상 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로 남는다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;dateTimeProviderRef&lt;/code&gt;는 시각
제공자의 이름이고, 지정하지 않으면 Spring Data의 기본
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CurrentDateTimeProvider&lt;/code&gt;가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime.now()&lt;/code&gt;를 쓴다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;modifyOnCreate&lt;/code&gt;는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티가 최초로 persist될 때
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;도 같이 채울
것인가&lt;/strong&gt;다. 기본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;true&lt;/code&gt;일 때 persist 시점에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;가 함께
채워지므로 INSERT 행에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt == updatedAt&lt;/code&gt;이 된다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;false&lt;/code&gt;로 주면 최초 INSERT 시
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로 남고, 첫 UPDATE가
일어나는 시점에야 처음 채워진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;4가지 애너테이션의 의미는 단순하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;애너테이션&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;채워지는 시점&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;값의 출처&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; (최초 저장)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreUpdate&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware.getCurrentAuditor()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PreUpdate&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware.getCurrentAuditor()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉬운 포인트 두 개. 첫째,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt;를
엔티티(또는 공통 BaseEntity)에 반드시 붙여야 한다&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EnableJpaAuditing&lt;/code&gt;만 선언하고 이걸 빼면 애너테이션이 아무
일도 하지 않는다. 스프링은 리스너 등록을 자동으로 해주지만, 엔티티 쪽에
&amp;quot;이 리스너 써라&amp;quot;라는 선언이 없으면 JPA가 호출해주지 않는다. 둘째,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Column(updatable = false)&lt;/code&gt;를 붙여야 한다&lt;/strong&gt;. 그렇지
않으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt; 시점에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt; 로직이
엉뚱한 경로로 타면서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;이 덮어써질 수 있다.
보수적으로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatable = false&lt;/code&gt;를 고정해두는 게 안전하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-auditoraware-구현과-security-컨텍스트&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) AuditorAware 구현과
Security 컨텍스트&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&amp;lt;T&amp;gt;&lt;/code&gt;는 한 줄짜리 인터페이스다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Optional&amp;lt;T&amp;gt; getCurrentAuditor()&lt;/code&gt;를 구현하면 된다.
제네릭 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;T&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt; 필드 타입과
일치해야 한다. 문자열 로그인 ID면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;String&lt;/code&gt;, 사용자 엔티티
참조면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;User&lt;/code&gt;, UUID면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUID&lt;/code&gt;. 프로젝트 컨벤션에
맞게 선택한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Security와 같이 쓰는 경우 구현은 거의 정해진 형태다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; AuditorAware&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;auditorProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ofNullable&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SecurityContextHolder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getAuthentication&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Authentication&lt;span class=&quot;op&quot;&gt;::&lt;/span&gt;isAuthenticated&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;auth &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;!(&lt;/span&gt;auth &lt;span class=&quot;kw&quot;&gt;instanceof&lt;/span&gt; AnonymousAuthenticationToken&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Authentication&lt;span class=&quot;op&quot;&gt;::&lt;/span&gt;getName&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 개의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;filter&lt;/code&gt;가 각자 다른 경계를 막는다. 첫 번째는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Authentication&lt;/code&gt; 자체가 없는
경우(인증 전 경로, 필터 전), 두 번째는 인증 객체는 있지만 비인증 상태인
경우(만료된 세션), 세 번째는 익명 사용자다. 이 세 경우 모두
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Optional.empty()&lt;/code&gt;를 반환하면 Auditor 필드는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;로 남는다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 진짜 함정은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;인증이 없는 정상 경로&lt;/strong&gt;에 있다.
스케줄러, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Async&lt;/code&gt; 스레드, 배치 잡, 메시지 리스너 같은 곳은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt;가 비어 있는 게 정상이다. 이 경로에서
엔티티를 저장하는데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdBy&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;이면 감사
추적이 끊긴다. 해결은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;fallback 값&lt;/strong&gt;을 명시하는 것.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; AuditorAware&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;auditorProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Authentication auth &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; SecurityContextHolder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getAuthentication&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;auth &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;!&lt;/span&gt;auth&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isAuthenticated&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;||&lt;/span&gt; auth &lt;span class=&quot;kw&quot;&gt;instanceof&lt;/span&gt; AnonymousAuthenticationToken&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SYSTEM&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 인증 없는 경로는 SYSTEM으로&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-10&quot;&gt;&lt;a href=&quot;#cb4-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SYSTEM&lt;/code&gt;이라는 특수 문자열을 쓰면, 감사 로그를 뒤질 때
&amp;quot;SYSTEM이 바꾼 건 배치잡이 건드린 것&amp;quot;으로 쉽게 구분할 수 있다. 더
세밀하게 하려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BATCH&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SCHEDULER&lt;/code&gt; 등을 구분해도
된다. 중요한 건 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;을 남기지 않는 것&lt;/strong&gt;이다.
감사 로그에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;은 &amp;quot;이 경로를 아직 생각 못 했다&amp;quot;는 신호로
해석된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Async&lt;/code&gt;나 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Scheduled&lt;/code&gt; 같은 별도 스레드에서
인증 사용자를 그대로 쓰고 싶으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContext&lt;/code&gt; 전파를
별도로 설정해야 한다 — 이 얘기는 &lt;a href=&quot;./21_spring-security-context-propagation.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;21편 Security
컨텍스트 전파&lt;/a&gt;에서 자세히 다뤘다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DelegatingSecurityContextExecutor&lt;/code&gt;로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TaskExecutor&lt;/code&gt;를 감싸거나,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SecurityContextHolder&lt;/code&gt;의 전략을
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MODE_INHERITABLETHREADLOCAL&lt;/code&gt;로 바꾸는 식이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt;는 이 전파가 제대로 되어 있다는 전제 위에서
동작한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;주체(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;who&lt;/code&gt;) 쪽의 주입 경로가 정리됐으면,
시각(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;when&lt;/code&gt;) 쪽도 같은 주입 원칙이 필요하다. 다음 섹션에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Clock&lt;/code&gt; 주입을 본다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-datetimeprovider와-테스트-시계-고정&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) DateTimeProvider와
테스트 시계 고정&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;는 선택 인프라다. 지정하지 않으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CurrentDateTimeProvider&lt;/code&gt;가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime.now()&lt;/code&gt;를 호출한다. 문제는 이 기본값이
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테스트하기 어렵다&lt;/strong&gt;는 점이다. 시각이 매번 달라지니 &amp;quot;2분
전에 만들어진 엔티티&amp;quot;를 검증하려면 시간 비교에 tolerance를 넣어야 하고,
경계값 테스트(&amp;quot;자정 직전에 생성된 엔티티가 자정 이후 쿼리에 잡히는가&amp;quot;)
같은 건 재현이 어렵다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해법은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Clock&lt;/code&gt;을 주입하는
형태다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; DateTimeProvider &lt;span class=&quot;fu&quot;&gt;dateTimeProvider&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Clock clock&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; Optional&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OffsetDateTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clock&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Profile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;!test&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Clock &lt;span class=&quot;fu&quot;&gt;systemClock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Clock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;systemDefaultZone&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-10&quot;&gt;&lt;a href=&quot;#cb5-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-11&quot;&gt;&lt;a href=&quot;#cb5-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-12&quot;&gt;&lt;a href=&quot;#cb5-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-13&quot;&gt;&lt;a href=&quot;#cb5-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Profile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-14&quot;&gt;&lt;a href=&quot;#cb5-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Clock &lt;span class=&quot;fu&quot;&gt;fixedClock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-15&quot;&gt;&lt;a href=&quot;#cb5-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Clock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Instant&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;2026-04-20T00:00:00Z&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; ZoneOffset&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;UTC&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-16&quot;&gt;&lt;a href=&quot;#cb5-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이렇게 해두면 테스트에서는 시간이 항상
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;2026-04-20T00:00:00Z&lt;/code&gt;로 고정된다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;가 박히는 시각이 예측 가능해지니 assertion도
정확해지고, 시점 의존 버그(자정 경계, 월말 경계)도 쉽게 재현할 수 있다.
운영 환경에선 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Profile&lt;/code&gt; 없이
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Clock.systemDefaultZone()&lt;/code&gt;만 주입된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;테스트 중간에 시간을 움직이고 싶으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;MutableClock&lt;/code&gt; 같은
간단한 구현을 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; MutableClock &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Clock &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Instant now&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; ZoneId zone&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;MutableClock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Instant start&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; ZoneId zone&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; start&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;zone&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; zone&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;plusMinutes&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;long&lt;/span&gt; minutes&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;plus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minutes&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; ChronoUnit&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MINUTES&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-12&quot;&gt;&lt;a href=&quot;#cb6-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-13&quot;&gt;&lt;a href=&quot;#cb6-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-14&quot;&gt;&lt;a href=&quot;#cb6-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Instant &lt;span class=&quot;fu&quot;&gt;instant&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; now&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-15&quot;&gt;&lt;a href=&quot;#cb6-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; ZoneId &lt;span class=&quot;fu&quot;&gt;getZone&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; zone&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-16&quot;&gt;&lt;a href=&quot;#cb6-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Clock &lt;span class=&quot;fu&quot;&gt;withZone&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ZoneId zone&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;MutableClock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;now&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; zone&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-17&quot;&gt;&lt;a href=&quot;#cb6-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;테스트 코드에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clock.plusMinutes(10)&lt;/code&gt; 한 줄로 &amp;quot;10분이
지난 것&amp;quot;을 흉내 낼 수 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Scheduled&lt;/code&gt;나 만료 로직
테스트에서 특히 유용하다. 운영 코드가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;new Date()&lt;/code&gt;나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime.now()&lt;/code&gt; 대신 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Clock&lt;/code&gt;을 통과하는
습관이면 시간 관련 모든 테스트가 깔끔해진다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-baseentity-패턴-mappedsuperclass와-entitylisteners&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5)
BaseEntity 패턴: @MappedSuperclass와 EntityListeners&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네 필드를 모든 엔티티에 중복으로 선언하는 건 낭비다. Java 상속의 일반
패턴처럼, 공통 조상 클래스를 두고 나머지가 상속받는다. JPA에서 이 역할을
하는 건 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Getter&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;AuditingEntityListener&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;updatable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; nullable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OffsetDateTime createdAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-9&quot;&gt;&lt;a href=&quot;#cb7-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-10&quot;&gt;&lt;a href=&quot;#cb7-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-11&quot;&gt;&lt;a href=&quot;#cb7-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-12&quot;&gt;&lt;a href=&quot;#cb7-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OffsetDateTime updatedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-13&quot;&gt;&lt;a href=&quot;#cb7-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-14&quot;&gt;&lt;a href=&quot;#cb7-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedBy&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-15&quot;&gt;&lt;a href=&quot;#cb7-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;updatable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; length &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-16&quot;&gt;&lt;a href=&quot;#cb7-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; createdBy&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-17&quot;&gt;&lt;a href=&quot;#cb7-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-18&quot;&gt;&lt;a href=&quot;#cb7-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedBy&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-19&quot;&gt;&lt;a href=&quot;#cb7-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;length &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-20&quot;&gt;&lt;a href=&quot;#cb7-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; updatedBy&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-21&quot;&gt;&lt;a href=&quot;#cb7-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-22&quot;&gt;&lt;a href=&quot;#cb7-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-23&quot;&gt;&lt;a href=&quot;#cb7-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-24&quot;&gt;&lt;a href=&quot;#cb7-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Table&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;orders&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-25&quot;&gt;&lt;a href=&quot;#cb7-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-26&quot;&gt;&lt;a href=&quot;#cb7-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-27&quot;&gt;&lt;a href=&quot;#cb7-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-28&quot;&gt;&lt;a href=&quot;#cb7-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-29&quot;&gt;&lt;a href=&quot;#cb7-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Enumerated&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;EnumType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;STRING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-30&quot;&gt;&lt;a href=&quot;#cb7-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OrderStatus status&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-31&quot;&gt;&lt;a href=&quot;#cb7-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 네 필드 안 써도 됨 — 상속으로 주어짐&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-32&quot;&gt;&lt;a href=&quot;#cb7-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티가 아니라 매핑 정보
모음&lt;/strong&gt;이라는 선언이다. 자체적으로는 테이블이 없고, 상속받은
엔티티의 테이블에 컬럼이 합쳐진다. 즉 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orders&lt;/code&gt; 테이블에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;created_at&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updated_at&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;created_by&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updated_by&lt;/code&gt; 네 컬럼이 자동으로
포함된다. 별도 조인 테이블도 없고, 상속 전략(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JOINED&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SINGLE_TABLE&lt;/code&gt;) 고민도 필요 없다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 애너테이션이 핵심이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;가
없으면 테이블이 두 개로 쪼개지거나 상속 전략이 엉킨다&lt;/strong&gt;. 그리고
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt;가
없으면 Audit 애너테이션이 전부 무시된다&lt;/strong&gt;. 이 둘은 짝이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;40_jpa-auditing-soft-delete-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;40_jpa-auditing-soft-delete-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;한 가지 더. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity&lt;/code&gt;를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;abstract&lt;/code&gt;로 두면
실수로 인스턴스화하는 걸 막을 수 있다. 또 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Getter&lt;/code&gt;만 주고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Setter&lt;/code&gt;는 열지 않는 편이 안전하다. 감사 필드는 외부에서
직접 바꿀 일이 없다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;BaseEntity를 여러 개로 나누는 경우도 있다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TimeEntity&lt;/code&gt;(생성/수정 시각만)와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditEntity&lt;/code&gt;(+생성/수정자)로 분리해서, 사용자 정보가 의미
없는 테이블(예: 로그성 테이블)은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TimeEntity&lt;/code&gt;만 상속하는
식이다. 프로젝트 규모와 규칙에 따라 선택하면 된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-시간-타입-선택-localdatetime-vs-offsetdatetime-vs-instant&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6)
시간 타입 선택: LocalDateTime vs OffsetDateTime vs Instant&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;감사 시각 필드의 타입을 무엇으로 잡을지는 의외로 프로젝트의 운명을
가른다. 세 후보가 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;타입&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;타임존 정보&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 컬럼 타입&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;적합성&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음 (서버 tz 의존)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TIMESTAMP&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;권장 X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OffsetDateTime&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;UTC offset 포함&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TIMESTAMP WITH TIME ZONE&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Instant&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;항상 UTC epoch&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TIMESTAMP&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TIMESTAMPTZ&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt;이 기본으로 많이 쓰이지만 운영에 들어가면
문제가 쌓인다. 서버 JVM의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TimeZone&lt;/code&gt; 설정, OS의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TZ&lt;/code&gt; 환경변수, DB 드라이버의 tz 변환 — 이 셋이 어긋나는 순간
시각이 몇 시간씩 틀어진다. 한국 시간으로 만들어진 엔티티가 미국 리전
서버에서 읽힐 때 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt;은 &amp;quot;누구의 시각인가&amp;quot;라는
정보가 없어서 해석이 달라진다. 특히 감사 로그는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;절대
시각&lt;/strong&gt;이어야 하는데 상대 시각으로 저장되면 사후 추적이
깨진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OffsetDateTime&lt;/code&gt;은 UTC 오프셋을 명시적으로 붙인 타입이다.
이 값이 PostgreSQL의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TIMESTAMP WITH TIME ZONE&lt;/code&gt; 컬럼과 잘
맞고, Hibernate 6은 이걸 1급으로 지원한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;2026-04-20T09:00:00+09:00&lt;/code&gt;처럼 오프셋이 저장되니 어느
리전에서 읽어도 해석이 같다. 실무 추천은 여기다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Instant&lt;/code&gt;는 한 발 더 나가서 &amp;quot;타임존은 DB 안에 안 두고 그냥
UTC epoch milli로 저장한다&amp;quot;는 선택이다. 가장 단순하고, 비교 연산이
빠르고, tz 이슈가 원천적으로 없다. 표시할 때만
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ZonedDateTime.ofInstant(instant, userZone)&lt;/code&gt;로 변환한다.
로그성 데이터, 이벤트 소싱처럼 &amp;quot;찍힌 순간만 중요&amp;quot;한 경우 가장
깔끔하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6의 권장 설정은 이렇다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode yaml&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode yaml&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;spring&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jpa&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;timezone&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;default_storage&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; NORMALIZE_UTC&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;   # DB에 저장할 때 UTC로 정규화&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;jdbc&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;time_zone&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;at&quot;&gt; UTC&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;default_storage=NORMALIZE_UTC&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OffsetDateTime&lt;/code&gt;을 DB에 저장할 때 UTC로 변환해서 넣는다. 저장
시점에 타임존을 일원화하니 쿼리 비교가 단순해지고, 리전이 다른 서버
간에도 일관된다. 기본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DEFAULT&lt;/code&gt; 동작은 DB/드라이버에 따라
달라서 예측이 어렵다 — 운영 들어가기 전에 이 설정을 명시하는 게
안전하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;권장 요약&lt;/strong&gt;: 새 프로젝트는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OffsetDateTime&lt;/code&gt;
또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Instant&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NORMALIZE_UTC&lt;/code&gt; 조합. 기존
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt; 코드라면 당장 바꿀 필요는 없지만, 리전 확장
또는 UTC 정책이 들어오는 시점에 마이그레이션을 계획해야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-hibernate-envers-전면-감사-로그의-비용과-이득&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) Hibernate
Envers: 전면 감사 로그의 비용과 이득&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt;는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;마지막으로 언제 누가 바꿨는지&amp;quot;&lt;/strong&gt; 까지만 기록한다. 그
사이에 몇 번이 바뀌었는지, 이전 값은 무엇이었는지는 알 수 없다. 이
한계를 넘고 싶으면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate Envers&lt;/strong&gt;가 선택지다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Envers는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Audited&lt;/code&gt; 애너테이션이 붙은 엔티티의 모든 변경을
별도 테이블에 히스토리로 쌓는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Audited&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Enumerated&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;EnumType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;STRING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-8&quot;&gt;&lt;a href=&quot;#cb9-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OrderStatus status&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-9&quot;&gt;&lt;a href=&quot;#cb9-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 한 줄을 붙이면 Hibernate가 추가 테이블을 만든다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orders_AUD&lt;/code&gt;(히스토리)와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;REVINFO&lt;/code&gt;(revision 번호
메타)다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orders&lt;/code&gt;에 UPDATE가 나갈 때마다
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orders_AUD&lt;/code&gt;에 이전 행 전체가 쌓이고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;REVINFO&lt;/code&gt;에
revision 번호와 timestamp가 들어간다. 나중에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditReader&lt;/code&gt;로
특정 revision의 엔티티 상태를 재구성할 수 있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;AuditReader reader &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; AuditReaderFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;entityManager&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 어떤 revision들이 있는지&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; revisions &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; reader&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getRevisions&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 특정 revision 시점의 엔티티 상태&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Order snapshot &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; reader&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; revisions&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Envers의 이득은 분명하다. **&amp;quot;6번째 바뀌었을 때 상태&amp;quot;**를 재구성할 수
있고, 누가 몇 번 건드렸는지, 어떤 필드가 시간에 따라 어떻게 변했는지
전부 추적된다. 감사 요구가 강한 도메인 — 금융, 의료, 법적 기록 — 에서는
사실상 필수다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대신 비용도 크다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테이블이 두 배 이상으로 늘어나고&lt;/strong&gt;,
모든 UPDATE마다 INSERT가 추가로 나가고, 스키마 변경 시 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;_AUD&lt;/code&gt;
테이블 마이그레이션도 같이 해야 한다. 성능 영향은 트래픽이 많을수록
체감되며, 특히 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Audited&lt;/code&gt;를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@OneToMany&lt;/code&gt; 컬렉션에
붙이면 컬렉션 수정 시 history 기록이 폭주한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무 권장은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;필요한 범위만 선별 적용&lt;/strong&gt;이다. 주문 상태
변화는 전체 이력이 중요하니 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order&lt;/code&gt;에만
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Audited&lt;/code&gt;, 로그성 테이블은 제외. 감사 요구가 약한 도메인은
Envers 대신 &lt;a href=&quot;./10_spring-application-event-async.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10편의
ApplicationEvent&lt;/a&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Async&lt;/code&gt; 조합으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditLog&lt;/code&gt; 테이블에 직접 쓰는 방식이 가볍다. 변경 이벤트를
발행하고 별도 리스너가 로그를 쌓는 구조는 스키마가 단순하고 성능 튜닝도
쉽다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Envers 대신 이벤트 기반으로 가는 패턴&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EventListener&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Async&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;onOrderStatusChanged&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatusChanged event&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    auditLogRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;st&quot;&gt;&amp;quot;Order&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; event&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;oldStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; event&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;newStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-7&quot;&gt;&lt;a href=&quot;#cb11-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        event&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;changedBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; event&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;changedAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-8&quot;&gt;&lt;a href=&quot;#cb11-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-9&quot;&gt;&lt;a href=&quot;#cb11-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Envers와 이 방식의 선택 기준은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;전 필드 이력이 필요한가, 특정
전이만 이력으로 남기면 되는가&amp;quot;&lt;/strong&gt; 다. 대부분의 서비스는
후자다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-soft-delete-설계-동기와-함정&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8) Soft Delete 설계 동기와
함정&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Soft delete는 &amp;quot;DELETE를 하지 말고 플래그로 상태만 바꾸자&amp;quot;는 패턴이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted&lt;/code&gt; 컬럼을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;true&lt;/code&gt;로 세우거나,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_at&lt;/code&gt;에 시각을 박는 식이다. 물리 삭제(hard delete)와
비교하면 장단이 뚜렷하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;측면&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hard Delete&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Soft Delete&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;데이터 복구&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;불가 (백업 복원 필요)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt;로 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;FK 무결성&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ON DELETE CASCADE&lt;/code&gt; 필요&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;참조 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;스토리지&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;행이 사라짐&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;계속 쌓임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;조회 쿼리&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;단순&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;매번 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE deleted = false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;GDPR/개인정보&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;물리 삭제가 요건과 직결&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;별도 anonymize 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서 soft delete가 유효한 경우는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;삭제됐지만 과거 참조가
유효해야 하는&amp;quot; 도메인&lt;/strong&gt;이다. 주문, 결제, 계약 같은 엔티티는
사용자가 탈퇴해도 이력이 남아야 한다. 반대로 일회성 로그, 세션, 캐시
같은 건 hard delete가 맞다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;모든 엔티티에 일괄 soft delete를
거는 건 과잉&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;설계 동기를 인정하고 나면 함정이 따라온다. 네 개가 자주 나온다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-unique-제약과의-충돌&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) Unique 제약과의 충돌&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;가장 흔한 함정이다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;users.email&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UNIQUE&lt;/code&gt;
제약이 걸려 있으면, soft delete된 사용자도 그 이메일을 점유하고 있다.
같은 이메일로 재가입하려 들면 DB가 거부한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- users 테이블&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; | email              | deleted&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt; | alice@example.com  | &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;    &lt;span class=&quot;co&quot;&gt;-- soft delete됨&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt; &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt; | alice@example.com  | &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;-- 재가입 → UNIQUE 위반&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해결은 두 가지다. PostgreSQL이면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;partial unique
index&lt;/strong&gt;를 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INDEX&lt;/span&gt; users_email_active_uniq&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;ON&lt;/span&gt; users (email)&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; deleted &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이러면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted = false&lt;/code&gt;인 행만 unique 대상이 된다. 삭제된
행은 몇 개든 있어도 괜찮다. MySQL처럼 partial index를 지원하지 않으면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;복합 unique&lt;/strong&gt;로 우회한다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UNIQUE (email, deleted_at)&lt;/code&gt;처럼 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_at&lt;/code&gt;이
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;이 아닌 행끼리만 중복이 의미를 가지게 만든다. 단
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NULL&lt;/code&gt;의 중복 처리는 DB마다 달라서 사전에 확인이
필요하다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-onetomany-cascade와-soft-delete&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) @OneToMany cascade와
soft delete&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;부모가 soft delete될 때 자식은 어떻게 되나.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CascadeType.REMOVE&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove(parent)&lt;/code&gt;에서
트리거되는데, soft delete는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt;를 호출하지 않는다 —
서비스가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;parent.setDeleted(true)&lt;/code&gt;로 플래그만 세운다.
결과적으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;cascade가 발동하지 않는다&lt;/strong&gt;. 자식은 부모가
삭제되어도 계속 조회된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해결은 자식도 soft delete 플래그를 가지고, 도메인 서비스가 부모와
자식을 함께 플래그 처리하는 것. 또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;를 쓰는
경우(§9) &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove(parent)&lt;/code&gt;가 UPDATE로 바뀌고 cascade가
그대로 작동하게 만들 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;8-3-native-query는-sqlrestriction을-무시&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-3) Native query는
@SQLRestriction을 무시&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;은 Hibernate가 자동 생성하는
모든 SQL(JPQL, Criteria, repository 메서드, 연관 lazy 로딩 포함)에
덧붙는다. Native query에는 적용되지 않는다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;entityManager.createNativeQuery(&amp;quot;SELECT ... FROM orders&amp;quot;)&lt;/code&gt;를
직접 쓰면 이 필터가 무시되면서 삭제된 데이터가 조용히 섞여 나온다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Native query를 쓰는 경로에서는 WHERE 절을 직접 써야 한다는 규율이
필요하다. 리포팅/어드민 기능에서 많이 밟는다.&lt;/p&gt;
&lt;h3 id=&quot;8-4-복원undelete이-번거로워진다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-4) 복원(Undelete)이
번거로워진다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;이 조회를 막기 때문에, 복원하려면 먼저
삭제된 엔티티를 읽어야 하는데 읽히지를 않는다. 결국 native query로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE deleted = true&lt;/code&gt;를 강제해서 읽고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt;로
복원한다. 관리자 UI에서 &amp;quot;복원&amp;quot; 버튼이 있는 서비스는 이 경로가 필요하다 —
&lt;a href=&quot;#11-filterdef--filter로-동적-필터&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§11의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@FilterDef&lt;/code&gt;&lt;/a&gt;로 유연하게 처리할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네 가지 함정을 의식하지 않고 soft delete를 도입하면, 몇 달 뒤에
&amp;quot;재가입 안 됨&amp;quot;, &amp;quot;삭제된 데이터가 어드민에 보임&amp;quot;, &amp;quot;복원 기능이 안
만들어진다&amp;quot; 같은 티켓이 줄줄이 올라온다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;도메인별로 soft
delete가 필요한가를 먼저 판단&lt;/strong&gt;하고, 필요한 곳에만 신중하게
적용한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-sqldelete--sqlrestriction-hibernate-63&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) @SQLDelete +
@SQLRestriction (Hibernate 6.3+)&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate는 soft delete를 지원하기 위한 두 어노테이션을 제공한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DELETE&lt;/code&gt; 쿼리를 다른 SQL로
교체하고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;은 모든 조회에 자동으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE&lt;/code&gt; 절을 덧붙인다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Table&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;orders&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SQLDelete&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;sql &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE orders SET deleted = true, deleted_at = NOW() WHERE id = ?&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SQLRestriction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;deleted = false&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;boolean&lt;/span&gt; deleted &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-10&quot;&gt;&lt;a href=&quot;#cb14-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-11&quot;&gt;&lt;a href=&quot;#cb14-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OffsetDateTime deletedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-12&quot;&gt;&lt;a href=&quot;#cb14-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 어노테이션의 분업은 명확하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;어노테이션&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;역할&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작 시점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt;의 SQL을 교체&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPA DELETE 호출 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;모든 SELECT에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE&lt;/code&gt; 자동 추가&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPQL/Criteria 조회 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;?&lt;/code&gt;는 PK 하나의 위치 파라미터다.
복합 키나 optimistic lock의 version 컬럼까지 쓰려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;?&lt;/code&gt;
개수와 순서를 맞춰야 한다. Hibernate 6 기준 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;?&lt;/code&gt;는 엔티티의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt; 컬럼(복합키면 매핑 순서), 그 뒤에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;
컬럼 순으로 위치 바인딩된다. 잘못 쓰면 &amp;quot;삭제는 됐는데 엉뚱한 행이
지워진다&amp;quot; 같은 재앙이 일어난다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;은 Hibernate 6.3에서 도입된 이름이다.
이전에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Where(clause = &amp;quot;deleted = false&amp;quot;)&lt;/code&gt;를 썼는데,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Where&lt;/code&gt;는 deprecated 예정이다. 새 코드는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;으로 쓰고, 레거시는 점진적으로 교체한다.
이름만 바뀌고 의미는 같다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 사용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;      &lt;span class=&quot;co&quot;&gt;// SELECT ... WHERE id=1 AND deleted=false&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                   &lt;span class=&quot;co&quot;&gt;// UPDATE orders SET deleted=true, deleted_at=NOW() WHERE id=?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;서비스 코드는 그대로다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.remove()&lt;/code&gt;라고 쓰지만 실제로는
UPDATE가 나가고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;로 조회하면 삭제된 엔티티는
결과에서 빠진다. 이 투명성이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt;
조합의 핵심 가치다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;주의할 경계 두 가지.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;첫째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;은
JPA 스펙이 아니라 Hibernate 전용&lt;/strong&gt;이다. 다른 JPA
구현체(EclipseLink 등)로 바꾸는 순간 작동하지 않는다. 이 글처럼
Hibernate 고정 환경에서는 문제 없지만, &amp;quot;JPA 호환성을 유지하고 싶다&amp;quot;는
요구가 있으면 이 방식은 선택에서 빠진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;둘째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조인 쿼리에서 이상한 중복이 발생할 수 있다&lt;/strong&gt;.
부모와 자식 양쪽에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;이 걸려 있으면 fetch join
할 때
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE child.deleted = false AND parent.deleted = false&lt;/code&gt;가
붙는데, 이게 LEFT JOIN과 결합되면 의도와 다른 결과가 나올 때가 있다.
복잡한 쿼리는 실제 발행 SQL을 찍어보면서 검증한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6.3 미만에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;이 없으니
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Where&lt;/code&gt;로 써야 한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Hibernate 6.3 이전 fallback&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;clause &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;deleted = false&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Where&lt;/code&gt;는 deprecated 표시가 붙어 있지만 당장 없어지지는
않는다. 다만 새 코드에서 굳이 쓸 이유는 없다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-hibernate-64-softdelete-신규-애너테이션&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) Hibernate 6.4+
@SoftDelete 신규 애너테이션&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6.4에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SoftDelete&lt;/code&gt;&lt;/strong&gt;
어노테이션이 도입됐다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt; 조합을
한 어노테이션으로 대체하는 Hibernate가 공식 지원하는 축약형이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Table&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;orders&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SoftDelete&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;columnName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;deleted&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; SoftDeleteType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;DELETED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// deleted 컬럼은 Hibernate가 자동으로 관리 — 필드 선언 불필요&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 파라미터가 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;columnName&lt;/code&gt;은 soft delete 플래그
컬럼명(기본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted&lt;/code&gt;). &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;strategy&lt;/code&gt;는 플래그의 해석
방향을 정한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;플래그가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;true&lt;/code&gt;면&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;자동으로 붙는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE&lt;/code&gt; 절&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SoftDeleteType.DELETED&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&amp;quot;삭제됨&amp;quot;으로 간주&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE deleted = false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SoftDeleteType.ACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&amp;quot;활성&amp;quot;으로 간주&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE is_active = true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;레거시 스키마에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;is_active&lt;/code&gt; 컬럼을 쓰는 경우
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ACTIVE&lt;/code&gt; 전략이 유용하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 레거시 스키마: is_active 컬럼&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-2&quot;&gt;&lt;a href=&quot;#cb18-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-3&quot;&gt;&lt;a href=&quot;#cb18-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SoftDelete&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;columnName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;is_active&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; SoftDeleteType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ACTIVE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-4&quot;&gt;&lt;a href=&quot;#cb18-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; LegacyEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SoftDelete&lt;/code&gt;의 이득은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;보일러플레이트
감소&lt;/strong&gt;와 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate 공식 지원&lt;/strong&gt;이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;의 SQL을 직접 쓸 필요가 없고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE&lt;/code&gt;
절도 자동으로 붙는다. 복합 키 엔티티에서도 PK 순서 신경쓸 필요 없다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대신 한계가 있다. Hibernate 6.4 기준으로 기능이 아직 성숙 중이라서,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;복잡한 필터 조건은 표현할 수 없다&lt;/strong&gt;. 예를 들어 &amp;quot;deleted가
false이고, tenant_id가 특정 값인 것만&amp;quot; 같은 다중 조건 필터는 여전히
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;이 필요하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_at&lt;/code&gt; 같은
부가 컬럼을 같이 업데이트하고 싶으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt;의 자유도가
더 높다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 복잡한 조건 — @SQLRestriction 유지&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SQLDelete&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;sql &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE orders SET deleted = true, deleted_at = NOW(), deleted_by = current_user WHERE id = ?&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SQLRestriction&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;deleted = false AND tenant_id = current_setting(&amp;#39;app.tenant_id&amp;#39;)::bigint&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;current_user&lt;/code&gt;는 DB 접속 계정이지 애플리케이션
로그인 사용자가 아니다. 감사 대상이 &amp;quot;누가 로그인해서 지웠는가&amp;quot;라면 이
값은 쓸모가 없으니, 애플리케이션 사용자를 기록하려면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete&lt;/code&gt; 대신 엔티티 메서드에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deletedBy&lt;/code&gt;
필드를 세팅하고 normal UPDATE로 내보내는 편이 맞다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;권장&lt;/strong&gt;: 단순한 soft delete 플래그만 있고 추가 로직이
필요 없으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SoftDelete&lt;/code&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_at&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_by&lt;/code&gt; 같은 부가 정보를 같이
관리하거나 멀티 테넌시 필터 같은 조건이 붙으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt; 조합. Hibernate 6.3 이하에서는
선택지가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt; 하나뿐이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-filterdef--filter로-동적-필터&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) @FilterDef + @Filter로
동적 필터&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;항상 적용되는 정적
필터&lt;/strong&gt;다. 켜고 끌 수가 없다. 관리자 UI에서 &amp;quot;삭제된 데이터도 함께
보기&amp;quot;를 지원하려면 별도 경로가 필요한데, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;을
쓰면 이게 불가능하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@FilterDef&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Filter&lt;/code&gt;&lt;/strong&gt; 는 세션 단위로 on/off 가능한 동적 필터를
정의한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@FilterDef&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;deletedFilter&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-4&quot;&gt;&lt;a href=&quot;#cb20-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    parameters &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@ParamDef&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;isDeleted&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; type &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;java.lang.Boolean&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-5&quot;&gt;&lt;a href=&quot;#cb20-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    defaultCondition &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;deleted = :isDeleted&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-6&quot;&gt;&lt;a href=&quot;#cb20-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-7&quot;&gt;&lt;a href=&quot;#cb20-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Filter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;deletedFilter&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-8&quot;&gt;&lt;a href=&quot;#cb20-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-9&quot;&gt;&lt;a href=&quot;#cb20-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-10&quot;&gt;&lt;a href=&quot;#cb20-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-11&quot;&gt;&lt;a href=&quot;#cb20-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-12&quot;&gt;&lt;a href=&quot;#cb20-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;boolean&lt;/span&gt; deleted &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-13&quot;&gt;&lt;a href=&quot;#cb20-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@FilterDef&lt;/code&gt;가 필터의 정의(이름, 파라미터, 조건식),
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Filter&lt;/code&gt;가 이 엔티티에 필터를 적용하겠다는 선언이다. 기본
상태에서는 필터가 비활성이고, 쿼리 실행 전에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Session&lt;/code&gt;에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;enableFilter&lt;/code&gt;를 호출해야 적용된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb21&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb21-1&quot;&gt;&lt;a href=&quot;#cb21-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-2&quot;&gt;&lt;a href=&quot;#cb21-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderQueryService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-3&quot;&gt;&lt;a href=&quot;#cb21-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-4&quot;&gt;&lt;a href=&quot;#cb21-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@PersistenceContext&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-5&quot;&gt;&lt;a href=&quot;#cb21-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-6&quot;&gt;&lt;a href=&quot;#cb21-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-7&quot;&gt;&lt;a href=&quot;#cb21-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findActive&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-8&quot;&gt;&lt;a href=&quot;#cb21-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Session session &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Session&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-9&quot;&gt;&lt;a href=&quot;#cb21-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        session&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;enableFilter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;deletedFilter&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-10&quot;&gt;&lt;a href=&quot;#cb21-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;               &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;isDeleted&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-11&quot;&gt;&lt;a href=&quot;#cb21-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-12&quot;&gt;&lt;a href=&quot;#cb21-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;select o from Order o&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-13&quot;&gt;&lt;a href=&quot;#cb21-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-14&quot;&gt;&lt;a href=&quot;#cb21-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-15&quot;&gt;&lt;a href=&quot;#cb21-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findAllIncludingDeleted&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-16&quot;&gt;&lt;a href=&quot;#cb21-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// 필터 활성화 안 함 — 모든 Order 조회&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-17&quot;&gt;&lt;a href=&quot;#cb21-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;select o from Order o&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-18&quot;&gt;&lt;a href=&quot;#cb21-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-19&quot;&gt;&lt;a href=&quot;#cb21-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 조합의 강점은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;컨텍스트에 따라 필터를 바꿀 수
있다&lt;/strong&gt;는 것이다. 일반 사용자 API는 필터 ON, 관리자 API는 필터
OFF. 트랜잭션 중에 필터를 끄고 삭제된 데이터를 복구한 뒤 다시 켜는 식의
세밀한 제어도 가능하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;의 강제성과 정반대의
유연함이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;권장 사용 패턴&lt;/strong&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;기본 서비스 경로에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;(항상 삭제된 데이터
제외)&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;관리자 경로나 복원 API에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@FilterDef + @Filter&lt;/code&gt;(동적
제어)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 어노테이션을 같이 쓰면 동시 적용 시 두 조건이 AND로 결합되어
의도와 다르게 결과가 빌 수 있으므로 팀 컨벤션상 하나만 선택하는 편이
깔끔하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@FilterDef&lt;/code&gt;만 쓰면 모든 리포지토리 메서드에 필터
활성화 코드를 넣어야 해서 번거롭다. 대부분의 프로젝트는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt; 기본 + 관리자 전용 네이티브 쿼리 조합으로
충분하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-실무-체크리스트&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 실무 체크리스트&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서 Auditing과 Soft Delete를 도입할 때 챙겨야 할 체크리스트를
섹션별로 정리한다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EnableJpaAuditing&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityListeners(AuditingEntityListener.class)&lt;/code&gt; 한 쌍을 항상
함께 확인&lt;/strong&gt;. 둘 중 하나만 있으면 아무 일도 일어나지 않는다.
BaseEntity에 리스너를 붙이고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Configuration&lt;/code&gt; 클래스에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EnableJpaAuditing&lt;/code&gt;을 선언한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt;는 fallback을 반드시 포함&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SYSTEM&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BATCH&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SCHEDULER&lt;/code&gt; 같은
특수 문자열로 인증 없는 경로를 구분한다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;을 반환하게
두지 않는다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt;에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Column(updatable = false)&lt;/code&gt; 고정&lt;/strong&gt;. 생성 정보는
엔티티의 수명 동안 불변이어야 한다. 도메인 규율로만 지키려고 하면 언젠가
깨진다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;시간 타입은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OffsetDateTime&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Instant&lt;/code&gt; 권장&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt;은 레거시
코드에서만 유지하고 새 엔티티에는 쓰지 않는다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.timezone.default_storage=NORMALIZE_UTC&lt;/code&gt;를 같이
설정한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Soft delete는 도메인별 선택&lt;/strong&gt;. 주문/결제/계약 같은
이력이 중요한 도메인에만 적용하고, 로그성/세션성 테이블은 hard delete를
유지한다. &amp;quot;모든 엔티티에 soft delete&amp;quot;는 과잉이다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;을 쓸 때는 unique 제약 전략을
먼저 정한다&lt;/strong&gt;. PostgreSQL은 partial unique index, MySQL은 복합
unique 또는 재가입 허용 정책. 운영 투입 전에 결정하지 않으면 며칠 안에
재가입 불가 티켓이 올라온다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Native query 경로에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE deleted = false&lt;/code&gt;를
직접 쓴다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLRestriction&lt;/code&gt;이 무시되는 걸 코드 리뷰
체크리스트에 박아둔다. 특히 리포팅·어드민·배치 경로에서 누락되기
쉽다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate 6.4+라면 단순 케이스는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SoftDelete&lt;/code&gt;
고려&lt;/strong&gt;. 플래그 외에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_at&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;deleted_by&lt;/code&gt; 같은 부가 정보가
필요하면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt; 조합이 낫다. 선택
기준을 팀 컨벤션으로 문서화.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Envers는 감사 요구가 강한 도메인 한정&lt;/strong&gt;.
금융/의료/법적 기록처럼 전 필드 이력이 필요한 경우에만 쓴다. 일반
서비스는 &lt;a href=&quot;./10_spring-application-event-async.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;이벤트 기반
audit log&lt;/a&gt;가 가볍다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Clock을 통해 시각을 주입하는 습관&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateTimeProvider&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Clock&lt;/code&gt;에 의존하게 두면
테스트에서 시간이 고정되고, 운영에서도 tz 정책을 한 곳에서 제어할 수
있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime.now()&lt;/code&gt; 직접 호출은 점진적으로 제거.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;BaseEntity의 Setter는 열지 않는다&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Getter&lt;/code&gt;만 주고 감사 필드를 외부에서 수정하지 못하게 막는다.
Lombok &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Data&lt;/code&gt;는 &lt;a href=&quot;./34_jpa-association-mapping.md#5-양방향-매핑과-동기화-메서드의-책임&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;34편에서
본 양방향 순환 참조&lt;/a&gt; 리스크까지 겹치니 BaseEntity에는 절대 쓰지
않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;13-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA Auditing과 Soft Delete는 &amp;quot;생성·수정·삭제라는
라이프사이클을 서비스가 아니라 영속성 계층이 책임지도록 내리는&amp;quot; 동일한
설계 결이다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EnableJpaAuditing&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity(@MappedSuperclass + @EntityListeners)&lt;/code&gt; 한 세트를
기본으로 두고, soft delete는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;도메인별로 선택적으로&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SQLDelete + @SQLRestriction&lt;/code&gt;(Hibernate 6.3+) 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SoftDelete&lt;/code&gt;(6.4+)를 붙인다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditorAware&lt;/code&gt;의
fallback과 unique 제약 전략, native query의 필터 누락은 설계 단계에서
결정하지 않으면 반드시 운영 이슈로 돌아온다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: JPA, Spring Data JPA, Hibernate, Auditing,
@CreatedDate, @AuditorAware, Soft Delete, @SQLDelete, @SQLRestriction,
@SoftDelete, Envers&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>@AuditorAware</category>
      <category>@CreatedDate</category>
      <category>@SoftDelete</category>
      <category>@SQLDelete</category>
      <category>@SQLRestriction</category>
      <category>auditing</category>
      <category>Hibernate</category>
      <category>JPA</category>
      <category>soft delete</category>
      <category>spring data jpa</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/425</guid>
      <comments>https://dding-shark.tistory.com/425#entry425comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:28:33 +0900</pubDate>
    </item>
    <item>
      <title>JPA Locking &amp;mdash; @Version Optimistic과 @Lock Pessimistic의 경계</title>
      <link>https://dding-shark.tistory.com/424</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;jpa-locking--version-optimistic과-lock-pessimistic의-경계&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;JPA
Locking — @Version Optimistic과 @Lock Pessimistic의 경계&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;재고 수량을 줄이는 코드를 짰다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;stock.setQuantity(stock.getQuantity() - 1)&lt;/code&gt; 한 줄이다.
동시에 두 개의 요청이 들어오면 둘 다 재고가 10인 줄 알고 각자 -1을 해서
9로 UPDATE한다. DB에는 9가 남지만, 실제로는 두 건이 팔렸으니 8이 맞다.
한 건이 사라진 셈이다. 이걸 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;분실된 갱신(Lost Update)&lt;/strong&gt;
이라고 부르고, 거의 모든 동시성 사고의 출발점이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA는 이 문제를 두 갈래로 풀어준다. 한쪽은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;낙관적
잠금(Optimistic Locking)&lt;/strong&gt; 으로, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 컬럼에
번호를 달아두고 &amp;quot;내가 읽은 이후에 누군가 건드렸으면 예외로 알려줘&amp;quot;라고
DB에게 부탁한다. 다른 한쪽은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;비관적 잠금(Pessimistic
Locking)&lt;/strong&gt; 으로, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;로 DB에 아예
물리적 락을 걸어버린다. 이름 그대로 하나는 &amp;quot;충돌이 잘 안 날 거야&amp;quot;라고
낙관하고, 하나는 &amp;quot;충돌 날 테니 미리 잠가두자&amp;quot;라고 비관한다. 어느 쪽을
어디에 쓸지는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;충돌 빈도와 실패 비용의 비율&lt;/strong&gt;로
결정한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;a href=&quot;./08_spring-transactional-deep-dive.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8편
@Transactional 심화&lt;/a&gt;와 &lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편 영속성
컨텍스트&lt;/a&gt;를 깔고 간다. 트랜잭션 경계 안에서 EM이 무엇을 하는지가
아니라, 그 경계 안에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동시에 들어온 다른 경계와 어떻게
부딪히는지&lt;/strong&gt;를 본다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;을 붙이려다
어노테이션이 안 먹는다&lt;/strong&gt; — 기본 메서드에는 붙일 수 없다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 붙였는데 detached 엔티티를
merge하자마자 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OptimisticLockException&lt;/code&gt;이 난다&lt;/strong&gt; —
버전 숫자가 옛날 값이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;를 걸었더니 데드락이
잦다&lt;/strong&gt; — 락 획득 순서가 제각각이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SKIP LOCKED&lt;/code&gt;로 메시지 큐 소비를 구현하려는데
DB마다 지원이 다르다&lt;/strong&gt; — JPA 표준 힌트로는 한계가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;문제 → 두 전략 → LockModeType → @Lock →
타임아웃/NOWAIT/SKIP LOCKED → 예외 처리 → 데드락 → 경합 심한 도메인 →
함정 → 실무&lt;/strong&gt; 순으로, Spring Boot 3.x / Hibernate 6.x / Jakarta
Persistence 3.x 기준으로 정리한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-lost-update-문제와-locking의-두-갈래&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) Lost Update
문제와 Locking의 두 갈래&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-version과-optimistic-locking-원리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) @Version과
Optimistic Locking 원리&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-version-타입-선택-long을-권장하는-이유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) @Version 타입
선택: Long을 권장하는 이유&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-lockmodetype-6종의-의미&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) LockModeType 6종의
의미&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-lock-어노테이션과-repository-메서드&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) @Lock
어노테이션과 Repository 메서드&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-pessimistic_write와-select--for-update&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6)
PESSIMISTIC_WRITE와 SELECT ... FOR UPDATE&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-락-타임아웃과-nowait--skip-locked&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7) 락 타임아웃과
NOWAIT / SKIP LOCKED&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-optimisticlockexception-재시도-패턴&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8)
OptimisticLockException 재시도 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-데드락-회피-락-순서와-보유-시간&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) 데드락 회피: 락
순서와 보유 시간&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-경합-심한-도메인의-락-선택-stock--counter&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) 경합
심한 도메인의 락 선택: Stock / Counter&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-함정-종합&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 함정 종합&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#13-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-lost-update-문제와-locking의-두-갈래&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) Lost Update 문제와
Locking의 두 갈래&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;동시성 버그는 이름이 붙은 세 가지 유형으로 자주 정리된다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;분실된 갱신(Lost Update)&lt;/strong&gt;, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;더티 리드(Dirty
Read)&lt;/strong&gt;, 그리고 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;반복 불가능한 읽기 / 팬텀
리드(Non-Repeatable / Phantom Read)&lt;/strong&gt; 다. 이 중 Dirty Read와
Non-Repeatable Read는 DB의 격리 수준(Isolation Level) 설정으로 대부분
해결된다 — 상세는 &lt;a href=&quot;./08_spring-transactional-deep-dive.md#4-isolation과-db-기본값&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8편
§4&lt;/a&gt;에서 다뤘다. 격리 수준으로 잘 막히지 않는 게 Lost Update다.
애플리케이션이 &amp;quot;읽고-바꾸고-쓴다&amp;quot;라는 세 단계를 하나의 트랜잭션으로
묶어도, 두 트랜잭션이 각자 같은 값을 읽고 각자 써버리면 한 쪽의 변경이
사라진다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA Locking은 주로 이 Lost Update를 방어하기 위한
장치&lt;/strong&gt;다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 갈래 중에 어디를 선택할지는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;충돌 빈도와 실패 비용의
비율&lt;/strong&gt;로 결정한다. 충돌이 드물고 재시도 비용이 낮으면 낙관적
잠금이 낫고, 충돌이 잦거나 재시도가 비즈니스적으로 곤란하면 비관적
잠금이 낫다. 재고 차감처럼 충돌이 빈번한 곳은 후자가, 사용자 프로필
수정처럼 동시 편집이 거의 없는 곳은 전자가 어울린다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;구분&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Optimistic (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;)&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Pessimistic (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;원리&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;version 컬럼 비교로 충돌 감지&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 락으로 충돌 예방&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 락&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR UPDATE&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR SHARE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;실패 시점&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;commit 시 예외&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;대기 또는 타임아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;충돌 적을 때 우수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;락 오버헤드 + 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음 (분산 환경 유리)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음 (경합 시 병목)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;예외&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OptimisticLockException&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LockTimeoutException&lt;/code&gt; 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;전형적 용도&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;프로필/주문 수정&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;재고 차감·좌석 예약&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 방식은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;같이 쓸 수도 있다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;을
붙여둔 엔티티에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;를 걸면, 락으로 물리적
경합을 막으면서 version으로 버전 불일치까지 이중으로 막는다. 다만
실무에서는 대개 둘 중 하나를 선택하는 쪽이 더 예측 가능하다. 섞어 쓰면
장애 시 원인 추적이 복잡해진다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-version과-optimistic-locking-원리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) @Version과 Optimistic
Locking 원리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;은 엔티티에 정수(또는 시각) 컬럼을 하나 붙여서
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;매 갱신마다 값을 1 증가시키는&lt;/strong&gt; 아주 단순한 장치다.
Hibernate가 이 컬럼을 UPDATE 문의 WHERE 절에 자동으로 끼워 넣는다. 그
결과 UPDATE가 성공하면 &amp;quot;내가 읽은 이후 아무도 안 건드렸다&amp;quot;가 보장되고,
실패(0 rows affected)하면 &amp;quot;누군가 먼저 바꿨다&amp;quot;가 증명된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Version&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; version&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 매 UPDATE마다 +1&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;동작을 분해하면 이렇다. 트랜잭션 A가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order&lt;/code&gt; 엔티티를
조회하면 version=5가 같이 딸려 온다. A가 quantity를 바꾸고 commit하면
Hibernate는 다음과 같이 쿼리를 만든다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;UPDATE&lt;/span&gt; orders&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;   &lt;span class=&quot;kw&quot;&gt;SET&lt;/span&gt; quantity &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ?, version &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;6&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt; &lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ? &lt;span class=&quot;kw&quot;&gt;AND&lt;/span&gt; version &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이때 트랜잭션 B가 먼저 commit해서 version이 6으로 이미 올라가 있으면,
A의 UPDATE는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE version = 5&lt;/code&gt; 조건을 만족하지 못하고
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;0 rows affected&lt;/strong&gt;가 된다. Hibernate는 이 결과를 보고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OptimisticLockException&lt;/code&gt;(Hibernate의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StaleObjectStateException&lt;/code&gt;이 원인)을 던진다. 스프링 래핑을
거치면 최종적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;으로 전파된다. 즉
Optimistic Locking은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;충돌을 방지하지 않는다. 감지할
뿐이다.&lt;/strong&gt; 이 차이는 이후 모든 논의의 출발점이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;39_jpa-locking-optimistic-pessimistic-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;39_jpa-locking-optimistic-pessimistic-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 그림에서 읽어야 할 건 두 가지다. 첫째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;낙관 쪽은 충돌이
commit 시점에 터지고&lt;/strong&gt;, 비관 쪽은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;lock 시점에 대기로
직렬화된다&lt;/strong&gt;. 둘째, 낙관은 재시도 또는 사용자 통지라는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;애플리케이션 측 대응 전략이 필수&lt;/strong&gt;고, 비관은 대신
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대기 시간과 데드락 위험&lt;/strong&gt;을 감수한다. 구조를 바꾸지 않고
단순히 둘을 바꿔 달 수 있는 게 아니라, 서비스 흐름 자체가 다른 방향으로
설계된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 벌크 UPDATE는 version을 자동 증가시키지 않는다.&lt;/strong&gt;
Hibernate 확장 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE VERSIONED Stock s SET ...&lt;/code&gt;를 쓰거나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SET s.version = s.version + 1&lt;/code&gt;을 명시해야 한다. Native
Query도 마찬가지로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;version = version + 1&lt;/code&gt;을 직접 넣지 않으면
이후 엔티티 버전이 DB와 어긋나기 시작한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-version-타입-선택-long을-권장하는-이유&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) @Version 타입 선택:
Long을 권장하는 이유&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드의 타입은 JPA 스펙상 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;int&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;long&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Long&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;short&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Short&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;java.sql.Timestamp&lt;/code&gt;가 허용된다. Hibernate는 여기에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;java.time.Instant&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt; 같은 시각
계열도 지원한다. 겉보기엔 선택지가 많지만, 실무에서 쓸만한 건 사실상
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Long&lt;/code&gt; 하나다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: Long&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Version&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; version&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: LocalDateTime&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Version&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime updatedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 같은 밀리초에 두 UPDATE가 들어오면 충돌 감지 실패&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;숫자 타입과 시각 타입의 차이는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;해상도&lt;/strong&gt;다. 숫자는 항상
1씩 증가하므로 &amp;quot;다른 값인지&amp;quot;를 비교하기만 하면 된다. 시각은 시스템
시계의 정밀도에 의존한다. JVM의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Instant&lt;/code&gt;는 밀리초 또는
마이크로초 단위이고, 같은 밀리초에 두 트랜잭션이 동시에 UPDATE를 날리면
두 쪽 다 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;똑같은 timestamp를 version에 쓰게 되어&lt;/strong&gt; 충돌
감지가 실패할 수 있다. 경합이 심한 자리에 시각 기반 버전을 쓰는 건
위험하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;오버플로도 사람들이 자주 걱정하지만 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Long&lt;/code&gt;이면 사실상
무시해도 된다. 초당 1000번 갱신해도 Long.MAX_VALUE에 도달하려면 2억 년
이상 걸린다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Integer&lt;/code&gt;는 이론적으로 부족할 가능성이 있지만,
대부분의 OLTP 엔티티에서도 충분히 안전하다. 그래도 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Long이 기본
권장&lt;/strong&gt;인 이유는 한 번 정하면 마이그레이션이 번거롭기
때문이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;한편 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드에는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Convert&lt;/code&gt;로
커스텀 Converter를 붙이면 안 된다&lt;/strong&gt;. Hibernate는 필드 원시 값을
직접 비교하는데, Converter가 중간에 끼면 비교 로직이 Converter 경유로
돌아가면서 예기치 않은 동등성 판단이 생길 수 있다. 특히
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt;에 timezone 변환 Converter가 끼면 같은
시각인데 다르게 비교되는 사고가 종종 보고된다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;version 필드는
순수하게 두자&lt;/strong&gt;가 원칙이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-lockmodetype-6종의-의미&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) LockModeType 6종의 의미&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Jakarta Persistence 3.x의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LockModeType&lt;/code&gt;은 겉보기엔 이름이
많지만, 두 갈래(Optimistic / Pessimistic) × 세 변주(기본 /
FORCE_INCREMENT / READ-WRITE 구분)로 정리된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;LockModeType&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;갈래&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 락&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;version 증가&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;용도&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NONE&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;—&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음 (수정 시만)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;기본. 잠금 명시 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낙관&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;버전 재검증만, 증가 없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;읽기만 하지만 시점 보장이 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낙관&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;읽기만 해도 강제 +1&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;연관 엔티티 변경 시 부모 버전도 올리고 싶을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_READ&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비관&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;공유 락 (FOR SHARE)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다른 트랜잭션의 쓰기만 차단, 읽기는 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비관&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;배타 락 (FOR UPDATE)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다른 트랜잭션의 읽기/쓰기 모두 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_FORCE_INCREMENT&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비관&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;배타 락&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;함께 +1&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;락으로 직렬화하면서 version도 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;READ&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WRITE&lt;/code&gt;는 JPA 2.0 시절 alias로 남아
있으나 deprecated, 신규 코드에서는 쓰지 않는다.)&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;두 개씩 짝을 지어 의미를 잡으면 이해가 쉽다.&lt;/p&gt;
&lt;h3 id=&quot;4-1-optimistic-vs-optimistic_force_increment&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) OPTIMISTIC vs
OPTIMISTIC_FORCE_INCREMENT&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC&lt;/code&gt;은 읽기만 했는데도 commit 시점에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;내가
읽은 version이 아직 그대로인지&amp;quot;&lt;/strong&gt; 를 다시 확인한다. 평범한
SELECT는 읽은 뒤에 다른 트랜잭션이 바꿔도 모르는 채로 지나가지만,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC&lt;/code&gt; 모드로 조회하면 commit 시 해당 row의 현재
version과 내가 읽은 version을 비교해서 다르면 예외가 난다. &amp;quot;쓰기는 안
하지만 내가 본 스냅샷 기준으로 결정하고 싶다&amp;quot;는 시나리오에 쓴다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/code&gt;는 한 걸음 더 나간다. 읽기만
했는데도 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;version을 강제로 +1&lt;/strong&gt; 하고 그 변경을 내보낸다.
부모-자식 관계에서 자식만 바꿨을 때 부모 버전도 같이 올려서 &amp;quot;이 집계
엔티티가 바뀌었다&amp;quot;를 외부에 알리고 싶을 때 유용하다. Aggregate 패턴에서
가끔 본다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-pessimistic_read-vs-pessimistic_write&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2) PESSIMISTIC_READ
vs PESSIMISTIC_WRITE&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_READ&lt;/code&gt;는 DB 수준의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;공유 락&lt;/strong&gt;을
건다. 같은 락을 여러 트랜잭션이 동시에 보유할 수 있고, 그 동안에는
아무도 쓰기를 하지 못한다. &amp;quot;읽는 동안 바뀌지 말아라&amp;quot;에 가깝다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;배타 락&lt;/strong&gt;이다. 한
트랜잭션만 보유할 수 있고, 다른 모두는 대기 또는 타임아웃된다. &amp;quot;읽는
순간부터 내가 쓸 때까지 아무도 접근 못 한다&amp;quot;에 해당한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;MySQL InnoDB에서는 구현 매핑이 살짝 다르다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_READ&lt;/code&gt;는 MySQL 8.0 미만에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LOCK IN SHARE MODE&lt;/code&gt;, 8.0 이상에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR SHARE&lt;/code&gt;로
번역된다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR UPDATE&lt;/code&gt;다.
PostgreSQL은 둘 다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR SHARE&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;FOR UPDATE&lt;/code&gt;로
직역된다.&lt;/p&gt;
&lt;h3 id=&quot;4-3-pessimistic_force_increment&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-3)
PESSIMISTIC_FORCE_INCREMENT&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;락으로 직렬화하는데도 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;version까지 함께 올리고&lt;/strong&gt; 싶을
때 쓴다. 실무에서 흔하진 않지만, 비관적 잠금 중에도 &amp;quot;이 엔티티가
수정되었다&amp;quot;는 기록을 명시적으로 남기고 싶을 때 의미가 있다. 예를 들어
재고 차감을 PESSIMISTIC_WRITE로 직렬화하면서, 같은 엔티티를 별도
경로에서 OPTIMISTIC으로 읽는 흐름이 있다면 version이 올라가야 그 경로가
충돌을 감지할 수 있다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-lock-어노테이션과-repository-메서드&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5) @Lock 어노테이션과
Repository 메서드&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA에서 LockModeType을 지정하는 가장 흔한 방법은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt; 어노테이션이다. Repository 메서드 위에 붙여서 &amp;quot;이
쿼리는 이 락 모드로 실행된다&amp;quot;를 선언한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT o FROM Order o WHERE o.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Optional&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;호출부는 평범한 Repository 메서드 호출 그대로다. 내부에서 Hibernate가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;로 쿼리를 만들어 보낸다. 주의할 점은
이 메서드는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;반드시 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; 경계 안에서
호출&lt;/strong&gt;되어야 한다는 것이다. 트랜잭션 밖에서 호출하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TransactionRequiredException&lt;/code&gt;이 나거나, DB가 자동 commit
모드로 락을 걸었다가 즉시 풀어버린다. 결과적으로 의미 없는 락이
된다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-findbyid에는-lock을-붙일-수-없다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) findById에는 @Lock을
붙일 수 없다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;많이들 이 자리에서 막힌다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaRepository&lt;/code&gt;가 기본 제공하는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById(id)&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock(PESSIMISTIC_WRITE)&lt;/code&gt;를
덮어씌우고 싶은데, 어노테이션이 안 먹는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 기본 메서드를 override 하면서 @Lock&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Optional&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 동작은 하지만 호출부에서 락 여부가 드러나지 않음&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이게 애매한 이유는 Spring Data JPA의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SimpleJpaRepository&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;를 자체
구현해버리기 때문이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기술적으로는 override +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt; 조합이 Spring Data JPA에서 동작하지만, 호출부가
평범한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;와 구분되지 않아 락이 걸리는 경로인지
드러나지 않는다. 그래서 실무에서는 별도 메서드를 권장한다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: 별도 메서드&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT o FROM Order o WHERE o.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Optional&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 쪽이 코드 의도도 더 선명하다. &amp;quot;락을 거는 조회&amp;quot;와 &amp;quot;평범한 조회&amp;quot;를
이름으로 구분할 수 있으니 호출부를 봐도 어느 쪽이 직렬화 영역인지 한눈에
보인다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-entitymanager-직접-호출&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) EntityManager 직접 호출&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;을 쓰지 않고 EM API로 락을 거는 방법도 있다.
동적으로 락 모드를 바꿔야 할 때나, 이미 managed 상태인 엔티티에
사후적으로 락을 걸고 싶을 때 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// find 시점에 락 모드 지정&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 이미 managed인 엔티티에 락 추가&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;OPTIMISTIC&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// commit 시 version 재검증&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.lock&lt;/code&gt;의 뒤쪽 사용법은 특히 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OPTIMISTIC&lt;/code&gt;과
궁합이 좋다. 1차 캐시에서 이미 꺼내온 엔티티에도 commit 시점 version
재검증을 추가로 부여할 수 있다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-pessimistic_write와-select--for-update&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6) PESSIMISTIC_WRITE와
SELECT ... FOR UPDATE&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;는 DB에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;배타 락&lt;/strong&gt;을
건다. 대부분의 RDB에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;로 번역된다.
이 쿼리가 도는 동안 같은 row를 읽거나 쓰려는 다른 트랜잭션은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대기&lt;/strong&gt;한다. 트랜잭션이 commit 또는 rollback되면 락이
풀리면서 대기 중이던 트랜잭션이 깨어난다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseStock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; stockId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Stock stock &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stockId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// FOR UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;재고 부족&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 트랜잭션 종료 시 commit → 락 해제&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-10&quot;&gt;&lt;a href=&quot;#cb8-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 흐름의 핵심은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락 획득부터 해제까지가 트랜잭션 경계와
정확히 일치&lt;/strong&gt;한다는 점이다. 트랜잭션 길이가 곧 다른 트랜잭션을
대기시키는 시간이다. 락 안에서 외부 API를 호출하거나, 무거운 계산을
돌리거나, 사용자 입력을 기다리는 순간 직렬화 비용이 폭발한다. &amp;quot;락
안에서는 DB 작업만&amp;quot;이 기본 원칙이다.&lt;/p&gt;
&lt;h3 id=&quot;6-1-readonly-트랜잭션에서의-pessimistic_write&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-1) readOnly
트랜잭션에서의 PESSIMISTIC_WRITE&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt; 안에서
PESSIMISTIC_WRITE를 거는 코드를 가끔 본다. readOnly의 의미는 JPA
쪽에서는 FlushMode=MANUAL이지만 JDBC 드라이버 쪽에서는 구현체에 따라
트랜잭션을 read-only 모드로 세팅할 수 있다 — 상세는 &lt;a href=&quot;./08_spring-transactional-deep-dive.md#6-readonlytrue의-실제-효과&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8편
§6&lt;/a&gt;. PostgreSQL처럼 read-only 트랜잭션에서 FOR UPDATE를 거부하는 DB도
있고, MySQL처럼 무시하고 진행하는 DB도 있다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락이 필요하다면
readOnly를 빼는 게 맞다&lt;/strong&gt;. 의도가 섞이면 나중에 디버깅이
힘들다.&lt;/p&gt;
&lt;h3 id=&quot;6-2-락-범위는-row-단위가-기본&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-2) 락 범위는 row 단위가
기본&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;FOR UPDATE의 락 범위는 DB마다 약간씩 다르지만, InnoDB·PostgreSQL
기준으로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조회된 row 단위&lt;/strong&gt;가 기본이다. WHERE에 인덱스가
없으면 테이블 스캔이 일어나면서 불필요한 row까지 잠기는 참사가 벌어질 수
있다 — MySQL InnoDB에서는 특히 조심해야 한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByIdForUpdate&lt;/code&gt;처럼 PK 조회는 안전하지만,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByStatus(...)&lt;/code&gt; 같은 보조 인덱스 조회에 FOR UPDATE를 거는
패턴은 쿼리 플랜을 반드시 확인해야 한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-락-타임아웃과-nowait--skip-locked&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) 락 타임아웃과 NOWAIT /
SKIP LOCKED&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;비관적 잠금에서 가장 먼저 설정해야 하는 게 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대기 시간
상한&lt;/strong&gt;이다. 무제한 대기는 곧 커넥션 풀 고갈로 이어진다. JPA는
표준 힌트 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jakarta.persistence.lock.timeout&lt;/code&gt;을 제공한다(단위:
ms).&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; StockRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Stock&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@QueryHints&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@QueryHint&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;jakarta.persistence.lock.timeout&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;3000&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT s FROM Stock s WHERE s.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Optional&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Stock&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;다만 이 힌트를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB가 실제로 해석해 주는지는 구현에 따라
다르다&lt;/strong&gt;. PostgreSQL은 세션 변수 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;lock_timeout&lt;/code&gt;을 함께
설정해야 의도대로 동작하고, MySQL InnoDB는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;innodb_lock_wait_timeout&lt;/code&gt;을 기본으로 쓰면서 쿼리 수준
타임아웃을 상대적으로 덜 지원한다. 필요하면 트랜잭션 시작 시점에
명시적으로 세션 변수를 건다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// PostgreSQL: 트랜잭션 스코프에서 lock_timeout 설정&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;someMethod&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createNativeQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SET LOCAL lock_timeout = &amp;#39;3s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;executeUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 이후 모든 락 획득은 3초 타임아웃 적용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;7-1-nowait-즉시-실패&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-1) NOWAIT: 즉시 실패&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOWAIT&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락을 즉시 얻지 못하면 예외&lt;/strong&gt;다.
대기 없이 바로 실패 처리하고 싶을 때 쓴다. 사용자 요청에 즉시 응답해야
하는 API에서 &amp;quot;지금 선점할 수 있나?&amp;quot;라는 질문에 어울린다. Hibernate는
힌트 값 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;-2&lt;/code&gt;를 NOWAIT로 매핑한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@QueryHints&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@QueryHint&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;jakarta.persistence.lock.timeout&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;-2&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT s FROM Seat s WHERE s.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Optional&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Seat&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findByIdNoWait&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;7-2-skip-locked-잠긴-건-건너뛰기&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-2) SKIP LOCKED: 잠긴 건
건너뛰기&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SKIP LOCKED&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;이미 잠긴 row는 조회 결과에서
제외&lt;/strong&gt;한다. 대기도 안 하고 예외도 안 던지고, 그냥 &amp;quot;다른 놈이 잡고
있으니 내가 잡을 수 있는 다른 걸 찾자&amp;quot;라는 의미다. 메시지 큐의 소비자
패턴에 특히 유용하다 — 여러 워커가 같은 테이블에서 작업을 하나씩 꺼낼 때
서로 다른 row를 집어간다. Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;-1&lt;/code&gt;을 SKIP LOCKED로
매핑한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 대기 큐 테이블에서 처리할 작업 하나를 빼 간다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Lock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LockModeType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@QueryHints&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@QueryHint&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;jakarta.persistence.lock.timeout&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;-1&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;SELECT j FROM Job j WHERE j.status = &amp;#39;PENDING&amp;#39; ORDER BY j.createdAt ASC&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Job&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findNextJobsSkipLocked&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;다만 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SKIP LOCKED는 DB별 지원 차이가 크다&lt;/strong&gt;. PostgreSQL
9.5+, MySQL 8.0+, Oracle은 지원한다. MySQL 5.7 이하, 오래된 MariaDB
버전은 지원하지 않는다. Hibernate가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;-1&lt;/code&gt; 힌트를 받아도 DB가
지원하지 않으면 무시하거나 다른 행동을 한다. 이 기능을 쓰기 전에
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 버전과 방언(dialect)을 먼저 확인&lt;/strong&gt;하는 게
안전하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;힌트 값&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;의미&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Hibernate 해석&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;양의 정수 (ms)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;대기 상한&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;지원 DB에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;lock_timeout&lt;/code&gt; 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;대기 없음 (일부 DB에서만)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;즉시 실패 또는 NOWAIT로 번역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;-1&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;SKIP LOCKED&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 지원 시에만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;-2&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;NOWAIT&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 지원 시에만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-optimisticlockexception-재시도-패턴&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8)
OptimisticLockException 재시도 패턴&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Optimistic Locking을 쓰면 필연적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OptimisticLockException&lt;/code&gt; 또는 그 하위
예외(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt; 같은 스프링
래핑)를 다루게 된다. 이 예외는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;누군가 먼저 바꿨으니 다시
시도하거나 사용자에게 알려달라&lt;/strong&gt;는 신호다. 어떻게 처리할지는
도메인 성격에 따라 다르다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-사용자에게-알리는-패턴&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) 사용자에게 알리는 패턴&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실시간 상호작용이 없는 사용자 수정(프로필, 설정 변경 등)에서는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재시도보다 통지&lt;/strong&gt;가 낫다. 사용자가 본 시점의 데이터로
덮어쓰면 다른 사람의 변경이 사라지니, &amp;quot;다른 사용자가 먼저 수정했습니다.
최신 정보를 다시 불러오시겠습니까?&amp;quot;라는 식으로 화면에 안내한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;updateProfile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; userId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; UpdateRequest req&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    User user &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; userRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    user&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// commit 시 OptimisticLockException 가능&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-6&quot;&gt;&lt;a href=&quot;#cb13-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-7&quot;&gt;&lt;a href=&quot;#cb13-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-8&quot;&gt;&lt;a href=&quot;#cb13-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 호출부 (컨트롤러)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-9&quot;&gt;&lt;a href=&quot;#cb13-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-10&quot;&gt;&lt;a href=&quot;#cb13-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    userService&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;updateProfile&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; req&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-11&quot;&gt;&lt;a href=&quot;#cb13-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ObjectOptimisticLockingFailureException e&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-12&quot;&gt;&lt;a href=&quot;#cb13-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;StaleDataException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;다른 사용자가 먼저 수정했습니다.&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-13&quot;&gt;&lt;a href=&quot;#cb13-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;8-2-자동-재시도-패턴&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) 자동 재시도 패턴&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반대로 재시도가 자연스러운 도메인(예: 내부 처리 잡)에서는 Spring
Retry로 자동 재시도를 건다. 단 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재시도 횟수 상한을 반드시
명시&lt;/strong&gt;해야 한다. 무한 재시도는 경합이 심할수록 폭주한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Retryable&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    retryFor &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ObjectOptimisticLockingFailureException&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    maxAttempts &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    backoff &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Backoff&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;delay &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; multiplier &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;processJob&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Job job &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; jobRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    job&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-10&quot;&gt;&lt;a href=&quot;#cb14-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Retryable&lt;/code&gt;은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt;보다 바깥쪽에서 작동해야 한다&lt;/strong&gt;. 같은
메서드에 둘 다 붙이면 순서가 엉켜서 재시도 시점에 트랜잭션이 이미
롤백되어 있지 않을 수 있다. 보통은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Retryable&lt;/code&gt;을 가진 상위
메서드가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional&lt;/code&gt; 메서드를 호출하는 구조로
분리한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: 외부 메서드는 재시도, 내부 메서드가 트랜잭션&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Service&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; JobService &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Retryable&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;retryFor &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; ObjectOptimisticLockingFailureException&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; maxAttempts &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;processJobWithRetry&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;fu&quot;&gt;internalProcessJob&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;internalProcessJob&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; jobId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-12&quot;&gt;&lt;a href=&quot;#cb15-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Job job &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; jobRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-13&quot;&gt;&lt;a href=&quot;#cb15-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        job&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-14&quot;&gt;&lt;a href=&quot;#cb15-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-15&quot;&gt;&lt;a href=&quot;#cb15-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;8-3-수동-재시도&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-3) 수동 재시도&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Retry를 추가하기 싫으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;try-catch&lt;/code&gt; + 반복문으로
충분하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; attempts &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;attempts&lt;span class=&quot;op&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        service&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;processJob&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;jobId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-5&quot;&gt;&lt;a href=&quot;#cb16-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-6&quot;&gt;&lt;a href=&quot;#cb16-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ObjectOptimisticLockingFailureException e&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-7&quot;&gt;&lt;a href=&quot;#cb16-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;attempts &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-8&quot;&gt;&lt;a href=&quot;#cb16-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;bu&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 단순 backoff&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-9&quot;&gt;&lt;a href=&quot;#cb16-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-10&quot;&gt;&lt;a href=&quot;#cb16-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;단순해 보여도 이 패턴은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;무한 루프에 빠지지 않도록
상한&lt;/strong&gt;이 꼭 필요하고, 재시도 사이에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;backoff&lt;/strong&gt;도
필요하다. 바로 재시도하면 같은 경합 상황이 그대로 반복된다. 간격을 살짝
두는 것만으로도 충돌 확률이 크게 떨어진다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-데드락-회피-락-순서와-보유-시간&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) 데드락 회피: 락 순서와
보유 시간&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;비관적 잠금을 쓰기 시작하면 곧 만나는 게 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;데드락&lt;/strong&gt;이다.
트랜잭션 A가 X 락을 쥐고 Y를 기다리고, B가 Y를 쥐고 X를 기다리는 순환.
DB는 데드락을 감지하면 한 쪽을 희생자(victim)로 골라 rollback하고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DeadlockLoserDataAccessException&lt;/code&gt;(스프링 래핑) 또는
드라이버별 데드락 예외를 던진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;회피 원칙은 단순한데 지키기는 까다롭다.&lt;/p&gt;
&lt;h3 id=&quot;9-1-항상-같은-순서로-락을-획득한다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) 항상 같은 순서로 락을
획득한다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여러 row에 락을 걸 일이 있으면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;id 오름차순&lt;/strong&gt;처럼
고정된 순서로 획득한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 호출 순서가 제각각&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; fromId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; toId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Account from &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fromId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Account to &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;toId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// fromId=1, toId=2인 트랜잭션과 fromId=2, toId=1인 트랜잭션이&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 서로 반대 방향으로 락을 쥐면 데드락&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-9&quot;&gt;&lt;a href=&quot;#cb17-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-10&quot;&gt;&lt;a href=&quot;#cb17-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: id 오름차순으로 정렬 후 락&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-11&quot;&gt;&lt;a href=&quot;#cb17-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-12&quot;&gt;&lt;a href=&quot;#cb17-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; fromId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; toId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-13&quot;&gt;&lt;a href=&quot;#cb17-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; firstId &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fromId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; toId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-14&quot;&gt;&lt;a href=&quot;#cb17-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; secondId &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fromId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; toId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-15&quot;&gt;&lt;a href=&quot;#cb17-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;firstId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-16&quot;&gt;&lt;a href=&quot;#cb17-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;secondId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-17&quot;&gt;&lt;a href=&quot;#cb17-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// 이제 실제 작업&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-18&quot;&gt;&lt;a href=&quot;#cb17-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Account from &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fromId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-19&quot;&gt;&lt;a href=&quot;#cb17-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Account to &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; accountRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;toId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-20&quot;&gt;&lt;a href=&quot;#cb17-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    from&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;withdraw&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;amount&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-21&quot;&gt;&lt;a href=&quot;#cb17-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    to&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;deposit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;amount&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-22&quot;&gt;&lt;a href=&quot;#cb17-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;모든 경로에서 같은 규칙을 강제해야 효과가 있다. 일부 경로만 지키면
나머지 경로와의 교차에서 여전히 데드락이 난다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-락-보유-시간을-최소화한다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) 락 보유 시간을
최소화한다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락 안에서는 DB 작업만 한다&lt;/strong&gt;. 외부 HTTP 호출, 메일
발송, 파일 I/O는 트랜잭션 밖으로 뺀다. 이벤트 방식으로 처리하고 싶다면
&lt;a href=&quot;./10_spring-application-event-async.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10편 스프링
이벤트&lt;/a&gt;의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@TransactionalEventListener(AFTER_COMMIT)&lt;/code&gt;가
유용하다 — commit 후에 부수 효과가 따라가도록.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 락 안에서 외부 호출&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-2&quot;&gt;&lt;a href=&quot;#cb18-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-3&quot;&gt;&lt;a href=&quot;#cb18-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;placeOrder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-4&quot;&gt;&lt;a href=&quot;#cb18-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-5&quot;&gt;&lt;a href=&quot;#cb18-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;confirm&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-6&quot;&gt;&lt;a href=&quot;#cb18-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    paymentClient&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;    &lt;span class=&quot;co&quot;&gt;// 외부 API — 수 초 걸릴 수 있음&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-7&quot;&gt;&lt;a href=&quot;#cb18-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    mailSender&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;          &lt;span class=&quot;co&quot;&gt;// 마찬가지&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-8&quot;&gt;&lt;a href=&quot;#cb18-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-9&quot;&gt;&lt;a href=&quot;#cb18-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-10&quot;&gt;&lt;a href=&quot;#cb18-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: 락 안에서는 DB만, 부수 효과는 이벤트로&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-11&quot;&gt;&lt;a href=&quot;#cb18-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-12&quot;&gt;&lt;a href=&quot;#cb18-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;placeOrder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-13&quot;&gt;&lt;a href=&quot;#cb18-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-14&quot;&gt;&lt;a href=&quot;#cb18-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;confirm&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-15&quot;&gt;&lt;a href=&quot;#cb18-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    events&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;publishEvent&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderConfirmedEvent&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-16&quot;&gt;&lt;a href=&quot;#cb18-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-17&quot;&gt;&lt;a href=&quot;#cb18-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 리스너에서 @TransactionalEventListener(AFTER_COMMIT)으로 charge/mail 처리&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;9-3-대기-자체를-회피-skip-locked&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-3) 대기 자체를 회피: SKIP
LOCKED&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;일부 도메인에서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대기 자체가 손해&lt;/strong&gt;다. 대기 큐
소비자는 대기할 시간에 다음 작업을 가져오는 게 낫다. 이럴 때 SKIP
LOCKED로 &amp;quot;다른 워커가 쥔 건 건너뛴다&amp;quot;를 구현하면 데드락 가능성 자체가
없어진다. 대신 동일한 작업을 두 워커가 동시에 처리하려는 경합 자체를 락
논리에서 자연스럽게 회피하는 셈이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-경합-심한-도메인의-락-선택-stock--counter&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) 경합 심한
도메인의 락 선택: Stock / Counter&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서 락을 제대로 이해하고 있는지 가장 많이 시험받는 자리가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재고(Stock) 차감&lt;/strong&gt;이다. 초당 수백 건이 같은 row를
건드리기 때문에 선택에 따라 성능이 10배씩 갈라진다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-선택지-세-가지&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) 선택지 세 가지&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;첫째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt; + 비즈니스 로직을
트랜잭션 안에&lt;/strong&gt;. 가장 이해하기 쉽고 정확하다. 단점은 락 획득
대기가 쌓이면 응답 지연이 급격히 늘어난다는 것. 초당 수백 건을 넘어가면
한계에 부딪힌다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Transactional&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseStock&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; stockId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Stock stock &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; stockRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findByIdForUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stockId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-4&quot;&gt;&lt;a href=&quot;#cb19-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OutOfStockException&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-5&quot;&gt;&lt;a href=&quot;#cb19-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;stock&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-6&quot;&gt;&lt;a href=&quot;#cb19-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;둘째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 단일 원자 UPDATE&lt;/strong&gt;. 읽기-감소-쓰기 3단계를
DB 한 번에 밀어 넣는다. 락 경합이 있어도 InnoDB의 row-level 락이
자동으로 잡아주므로 추가 락 힌트가 필요 없다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE Stock s SET s.quantity = s.quantity - :qty, s.version = s.version + 1 &amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       &lt;span class=&quot;st&quot;&gt;&amp;quot;WHERE s.id = :id AND s.quantity &amp;gt;= :qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-4&quot;&gt;&lt;a href=&quot;#cb20-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseIfEnough&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반환값이 1이면 성공, 0이면 재고 부족이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;벌크 UPDATE는
version을 자동 증가시키지 않는다.&lt;/strong&gt; JPQL에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SET s.version = s.version + 1&lt;/code&gt;을 직접 넣거나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE VERSIONED&lt;/code&gt; 확장을 써야 한다. 이 한 줄을 누락하면 이후
동일 엔티티를 낙관적 잠금 경로에서 다룰 때 버전이 어긋나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StaleObjectStateException&lt;/code&gt;이 터진다. 매우 빠르고 경합
상황에서도 정확하다. 단점은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티 상태와 어긋난다&lt;/strong&gt;는 점
— 1차 캐시에 있던 Stock 엔티티의 quantity 필드는 UPDATE 이후에도 옛날
값이다. 필요하면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;em.clear()&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clearAutomatically = true&lt;/code&gt;를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Modifying&lt;/code&gt;에 붙여
처리한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;셋째, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Optimistic Locking + 재시도&lt;/strong&gt;. 경합이 있어도
일단 진행하고 충돌 시 재시도. 경합이 낮을 때는 비관보다 빠르지만, 경합이
높아지면 재시도 횟수가 폭증해서 비관보다 느려진다. 재고 차감 같은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;hot-spot&lt;/strong&gt;에는 잘 맞지 않는다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;경합 낮음&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;경합 높음&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정확성&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;PESSIMISTIC_WRITE&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;보통&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;대기 누적&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPQL 원자 UPDATE&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가장 빠름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Optimistic + 재시도&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;재시도 폭증&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음(결국 성공)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;10-2-redis-같은-외부-카운터로-우회하는-경우&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2) Redis 같은
외부 카운터로 우회하는 경우&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;글 범위를 벗어나지만 언급은 해둔다. 극도로 높은 TPS가 필요한 재고 /
카운터는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Redis의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DECR&lt;/code&gt; 같은 원자 연산&lt;/strong&gt;으로
offload하는 패턴을 쓴다. RDB는 최종 정합성을 위한 소스 오브 트루스로
남기고, 실시간 차감은 Redis에서 한다. JPA Locking의 범위를 넘어서는
주제라 여기서는 선택지만 언급한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-함정-종합&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 함정 종합&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;지금까지 조각조각 나온 함정을 한자리에 모은다. 디버깅할 때
체크리스트로 쓸 수 있도록.&lt;/p&gt;
&lt;h3 id=&quot;11-1-detached-엔티티를-merge하면-optimisticlockexception&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-1)
detached 엔티티를 merge하면 OptimisticLockException&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;준영속 상태의 엔티티를 클라이언트에서 받아와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;merge&lt;/code&gt;하면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전달받은 version도 옛날 값&lt;/strong&gt;이다. 사용자가 화면에
띄워두고 다른 사람이 수정한 뒤에 저장을 누르면, merge 시 DB의 version이
이미 다음 값이므로 바로 예외가 난다. 이게 의도된 동작이긴 하지만,
클라이언트가 &amp;quot;편집 중&amp;quot;임을 오래 유지하는 UI에서는 자주 발생한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해법은 두 가지다. 하나는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;편집 진입 시점에 최신 version을 함께
전달&lt;/strong&gt;하고 저장 시 비교 로직을 명확히 하는 것. 다른 하나는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;부분 수정은 merge가 아니라 서비스에서 find + setter&lt;/strong&gt;로
처리하는 것 — &lt;a href=&quot;./12_spring-persistence-context-transactional.md#8-persist--merge--save--saveandflush-함정&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편
§8&lt;/a&gt;에서 다룬 바와 같다.&lt;/p&gt;
&lt;h3 id=&quot;11-2-findbyid에-lock-못-붙인다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-2) findById에 @Lock 못
붙인다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이미 &lt;a href=&quot;#5-lock-어노테이션과-repository-메서드&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§5 @Lock
어노테이션과 Repository 메서드&lt;/a&gt;에서 다뤘지만 다시 강조할 만큼 자주
틀린다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaRepository.findById&lt;/code&gt;를 override하면서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;을 얹는 조합은 기술적으로 동작하긴 하지만, 호출부에서
평범한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;와 구분되지 않아 락이 걸리는 경로인지 코드에
드러나지 않는다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도 메서드(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByIdForUpdate&lt;/code&gt;
등)를 만든다&lt;/strong&gt;가 답이다.&lt;/p&gt;
&lt;h3 id=&quot;11-3-벌크-update는-jpql이든-native든-version이-자동이-아니다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-3)
벌크 UPDATE는 JPQL이든 Native든 version이 자동이 아니다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 벌크도 version은 자동이 아니다. JPQL이든 Native든
명시해야 하며, Hibernate의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE VERSIONED&lt;/code&gt; 확장이 JPQL
한정으로 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb21&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb21-1&quot;&gt;&lt;a href=&quot;#cb21-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 위험: JPQL이지만 version이 그대로 남는다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-2&quot;&gt;&lt;a href=&quot;#cb21-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-3&quot;&gt;&lt;a href=&quot;#cb21-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE Stock s SET s.quantity = s.quantity - :qty WHERE s.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-4&quot;&gt;&lt;a href=&quot;#cb21-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseJpql&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-5&quot;&gt;&lt;a href=&quot;#cb21-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-6&quot;&gt;&lt;a href=&quot;#cb21-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 (JPQL + 수동 증가)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-7&quot;&gt;&lt;a href=&quot;#cb21-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-8&quot;&gt;&lt;a href=&quot;#cb21-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE Stock s SET s.quantity = s.quantity - :qty, s.version = s.version + 1 &amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-9&quot;&gt;&lt;a href=&quot;#cb21-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       &lt;span class=&quot;st&quot;&gt;&amp;quot;WHERE s.id = :id AND s.version = :expected&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-10&quot;&gt;&lt;a href=&quot;#cb21-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseJpqlWithVersion&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-11&quot;&gt;&lt;a href=&quot;#cb21-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                            &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-12&quot;&gt;&lt;a href=&quot;#cb21-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                            &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;expected&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; expected&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-13&quot;&gt;&lt;a href=&quot;#cb21-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-14&quot;&gt;&lt;a href=&quot;#cb21-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호 (Hibernate 확장, JPQL 전용)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-15&quot;&gt;&lt;a href=&quot;#cb21-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-16&quot;&gt;&lt;a href=&quot;#cb21-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE VERSIONED Stock s SET s.quantity = s.quantity - :qty WHERE s.id = :id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-17&quot;&gt;&lt;a href=&quot;#cb21-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseVersioned&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-18&quot;&gt;&lt;a href=&quot;#cb21-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-19&quot;&gt;&lt;a href=&quot;#cb21-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 위험: Native — 직접 안 쓰면 version이 안 올라감&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-20&quot;&gt;&lt;a href=&quot;#cb21-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Modifying&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-21&quot;&gt;&lt;a href=&quot;#cb21-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Query&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;UPDATE stock SET quantity = quantity - :qty, version = version + 1 &amp;quot;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-22&quot;&gt;&lt;a href=&quot;#cb21-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;               &lt;span class=&quot;st&quot;&gt;&amp;quot;WHERE id = :id AND version = :expected&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-23&quot;&gt;&lt;a href=&quot;#cb21-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       nativeQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-24&quot;&gt;&lt;a href=&quot;#cb21-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;decreaseNative&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-25&quot;&gt;&lt;a href=&quot;#cb21-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                   &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;qty&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; qty&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-26&quot;&gt;&lt;a href=&quot;#cb21-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                   &lt;span class=&quot;at&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;expected&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; expected&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL이든 Native든, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SET ... version = version + 1&lt;/code&gt;을 직접
쓰지 않으면 version이 그대로 남는다. 이후 이 엔티티를 낙관적 잠금
경로에서 다루는 흐름에서 DB와 version이 어긋나
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StaleObjectStateException&lt;/code&gt;이 터진다. JPQL에는 Hibernate
확장으로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE VERSIONED&lt;/code&gt;가 있어 한 줄 생략이 가능하지만,
Native에는 그런 단축이 없다. WHERE에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AND version = :expected&lt;/code&gt;까지 붙여 두면 반환값 0이 &amp;quot;다른
트랜잭션이 먼저 갱신함&amp;quot;을 뜻하게 되고, 애플리케이션에서 이를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ObjectOptimisticLockingFailureException&lt;/code&gt;으로 전환해 재시도
루프에 태우는 흐름이 자연스러워진다.&lt;/p&gt;
&lt;h3 id=&quot;11-4-readonly-트랜잭션-안의-pessimistic_write&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-4) readOnly
트랜잭션 안의 PESSIMISTIC_WRITE&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;§6-1에서 다룬 내용이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Transactional(readOnly = true)&lt;/code&gt;와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;를 같이 쓰면 DB에 따라 거부당하거나 의도와
다르게 동작한다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락이 필요하면 readOnly를 뺀다&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;11-5-version-필드에-convert-금지&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-5) @Version 필드에 @Convert
금지&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;커스텀 Converter를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드에 붙이면 값 비교 로직이
예기치 않은 방향으로 흘러간다. 특히 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LocalDateTime&lt;/code&gt; +
timezone Converter 조합은 같은 시각을 다르게 비교하는 사고를 쉽게
만든다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;version 필드는 Converter 없이 원시 상태로&lt;/strong&gt;
둔다.&lt;/p&gt;
&lt;h3 id=&quot;11-6-version의-접근-제어자&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-6) @Version의 접근 제어자&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 필드는 가능하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;외부에서 setter로
변경하지 않도록&lt;/strong&gt; 캡슐화한다. Hibernate가 관리하는 값이므로
사용자 코드가 건드리면 상태 관리가 깨진다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb22&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb22-1&quot;&gt;&lt;a href=&quot;#cb22-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: setter 없음 or private&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-2&quot;&gt;&lt;a href=&quot;#cb22-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-3&quot;&gt;&lt;a href=&quot;#cb22-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-4&quot;&gt;&lt;a href=&quot;#cb22-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-5&quot;&gt;&lt;a href=&quot;#cb22-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-6&quot;&gt;&lt;a href=&quot;#cb22-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Version&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-7&quot;&gt;&lt;a href=&quot;#cb22-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; version&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-8&quot;&gt;&lt;a href=&quot;#cb22-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// public setVersion() 만들지 말기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-9&quot;&gt;&lt;a href=&quot;#cb22-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기본은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt; 낙관적 잠금&lt;/strong&gt;. 대부분의
엔티티에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version Long version&lt;/code&gt; 한 줄을 박아두면 Lost
Update를 가장 가볍게 방어한다. DB 락이 없으므로 확장성에 해가 없고,
충돌이 실제로 나는 곳만 재시도·통지 로직을 더하면 된다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;경합이 확실한 자리에만
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;&lt;/strong&gt;. 재고 차감·좌석 예약·중복 처리
방지 같은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;직렬화가 비즈니스적으로 필수&lt;/strong&gt;인 자리에만 비관
잠금을 건다. 그 외엔 낙관으로 충분하다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findById&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;을 붙이는 대신
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByIdForUpdate&lt;/code&gt; 별도 메서드&lt;/strong&gt;를 만든다. 락이
걸리는 경로가 코드에서 한눈에 보이게 한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락 안에서는 외부 호출 금지&lt;/strong&gt;. 결제 API, 이메일, 파일
I/O는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@TransactionalEventListener(AFTER_COMMIT)&lt;/code&gt;으로 commit
이후로 미룬다 — &lt;a href=&quot;./10_spring-application-event-async.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10편
이벤트&lt;/a&gt; 참조.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;데드락 회피의 첫 줄은 락 획득 순서 고정&lt;/strong&gt;. 두 개
이상의 row에 락을 걸 때는 id 오름차순처럼 고정 규칙을 강제한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락 타임아웃은 DB 세션 변수로 보강&lt;/strong&gt;. JPA 힌트만으로는
DB별 차이가 있어, PostgreSQL이라면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SET LOCAL lock_timeout&lt;/code&gt;을, MySQL이라면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;innodb_lock_wait_timeout&lt;/code&gt;을 함께 본다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;경합 심한 카운터는 JPQL 원자 UPDATE&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE ... SET qty = qty - :n WHERE ... AND qty &amp;gt;= :n&lt;/code&gt;은
락 힌트 없이도 빠르고 정확하다. 반환값으로 성공/실패를 판단한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Native Query에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;을 쓸 거면 수동 증가
필수&lt;/strong&gt;. 누락하면 Optimistic 흐름이 망가진다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAndFlush()&lt;/code&gt;는 락 테스트용으로만&lt;/strong&gt;.
프로덕션 경로에 습관적으로 박히면 Write-Behind 이점이 사라진다 — &lt;a href=&quot;./12_spring-persistence-context-transactional.md#8-persist--merge--save--saveandflush-함정&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12편
§8-4&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;13-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA Locking은 Lost Update를 두 방향으로 푼다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;의 낙관은 충돌을 감지해 예외로 알려주고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Lock&lt;/code&gt;의 비관은 DB 락으로 직렬화해 아예 막는다&lt;/strong&gt;.
대부분의 엔티티에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Version&lt;/code&gt;을 얹고 충돌 시 재시도 또는
통지를 더하는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;가벼운 방어가 기본이며&lt;/strong&gt;, 경합이 확실한
재고·좌석 같은 자리에서만 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PESSIMISTIC_WRITE&lt;/code&gt;로 직렬화한다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;락 안에서는 외부 호출을 피하고 락 획득 순서를 고정&lt;/strong&gt;하는
두 습관이, 잠금 설계가 살아남는 가장 큰 차이를 만든다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: JPA, Hibernate, Locking, @Version, @Lock,
Optimistic Locking, Pessimistic Locking, Spring Data JPA, 동시성
제어&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>@Lock</category>
      <category>@version</category>
      <category>Hibernate</category>
      <category>JPA</category>
      <category>Locking</category>
      <category>Optimistic Locking</category>
      <category>Pessimistic Locking</category>
      <category>spring data jpa</category>
      <category>동시성 제어</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/424</guid>
      <comments>https://dding-shark.tistory.com/424#entry424comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:27:54 +0900</pubDate>
    </item>
    <item>
      <title>Hibernate 2차 캐시 &amp;mdash; SessionFactory 스코프의 공유 캐시</title>
      <link>https://dding-shark.tistory.com/423</link>
      <description>&lt;h1&gt;Hibernate 2차 캐시 — SessionFactory 스코프의 공유 캐시&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;JPA를 쓰다 보면 &amp;quot;같은 엔티티를 같은 트랜잭션 안에서 두 번 조회하면 SELECT가 한 번만 나간다&amp;quot;는 걸 먼저 배운다. &lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot;&gt;12편 영속성 컨텍스트와 @Transactional&lt;/a&gt;에서 정리한 &lt;strong&gt;1차 캐시&lt;/strong&gt; 이야기다. 그런데 이 캐시는 트랜잭션이 끝나면 통째로 사라진다. 다음 요청이 들어오면 같은 엔티티를 또 SELECT로 읽는다. 이걸 &lt;strong&gt;애플리케이션 전체로 살려두려는 시도&lt;/strong&gt;가 Hibernate 2차 캐시다. 스코프가 &lt;code&gt;EntityManager&lt;/code&gt;가 아니라 &lt;code&gt;SessionFactory&lt;/code&gt;가 되고, 수명이 트랜잭션이 아니라 &lt;strong&gt;애플리케이션 수명 전체&lt;/strong&gt;가 된다.&lt;/p&gt;
&lt;p&gt;이 글은 Spring Boot 3.x / Hibernate 6.x / JSR-107(JCache) 기준으로, 2차 캐시를 &lt;strong&gt;왜&lt;/strong&gt; 쓰고, &lt;strong&gt;어떻게&lt;/strong&gt; 구성하며, &lt;strong&gt;어디서 터지는지&lt;/strong&gt;를 정리한다. 설정 한 줄로 쉽게 켜지지만 hit rate가 0%로 나오는 전형적 함정과, 분산 환경에서의 stale, 벌크 쿼리의 캐시 우회처럼 실제로 많이 밟는 지뢰를 중심으로 다룬다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2차 캐시를 켰는데 hit rate가 0%다&lt;/strong&gt; — &lt;code&gt;@Cacheable&lt;/code&gt;과 &lt;code&gt;shared-cache-mode&lt;/code&gt; 둘 다 맞춰야 비로소 엔트리가 들어간다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분산 환경에서 로컬 Caffeine으로 실험했더니 노드 간 stale이 심각하다&lt;/strong&gt; — 로컬 캐시는 단일 인스턴스용이다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;벌크 &lt;code&gt;UPDATE&lt;/code&gt; JPQL 한 번에 캐시가 조용히 어긋났다&lt;/strong&gt; — 2차 캐시 자동 invalidation이 우회된다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query Cache만 켰는데 효과가 없다&lt;/strong&gt; — 쿼리 캐시는 PK 목록만 저장해서 Entity Cache 없이는 빈 상자다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글은 &lt;strong&gt;스코프 비교 → 세 가지 캐시 → Provider 설정 → 활성화 애너테이션 → 동시성 전략 → shared-cache-mode → 컬렉션 캐시 → Query Cache → 벌크 쿼리 함정 → 통계 → Spring Cache 대안 → 실무 체크리스트&lt;/strong&gt; 순으로 간다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#1-1%EC%B0%A8-%EC%BA%90%EC%8B%9C-vs-2%EC%B0%A8-%EC%BA%90%EC%8B%9C-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%EC%88%98%EB%AA%85&quot;&gt;1) 1차 캐시 vs 2차 캐시: 스코프와 수명&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-2%EC%B0%A8-%EC%BA%90%EC%8B%9C%EC%9D%98-%EC%84%B8-%EC%96%BC%EA%B5%B4-entity--collection--query&quot;&gt;2) 2차 캐시의 세 얼굴: Entity / Collection / Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-jcache--caffeine--redisson-%EC%84%A4%EC%A0%95&quot;&gt;3) JCache + Caffeine / Redisson 설정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-cacheable%EA%B3%BC-hibernate-cacheusage&quot;&gt;4) @Cacheable과 Hibernate @Cache(usage=...)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-%EB%84%A4-%EA%B0%80%EC%A7%80-concurrency-strategy&quot;&gt;5) 네 가지 Concurrency Strategy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6-shared-cache-mode-enable_selective-%EA%B6%8C%EC%9E%A5&quot;&gt;6) shared-cache-mode: ENABLE_SELECTIVE 권장&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#7-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%BA%90%EC%8B%9C%EC%99%80-%EC%9E%90%EC%8B%9D-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%97%B0%EC%87%84&quot;&gt;7) 컬렉션 캐시와 자식 엔티티 연쇄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#8-query-cache%EC%99%80-updatetimestamps&quot;&gt;8) Query Cache와 UpdateTimestamps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#9-%EB%B2%8C%ED%81%AC-%EC%BF%BC%EB%A6%AC%EC%9D%98-2%EC%B0%A8-%EC%BA%90%EC%8B%9C-%EC%9A%B0%ED%9A%8C-%ED%95%A8%EC%A0%95&quot;&gt;9) 벌크 쿼리의 2차 캐시 우회 함정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#10-%ED%86%B5%EA%B3%84%EB%A1%9C-hit-rate-%EC%B8%A1%EC%A0%95&quot;&gt;10) 통계로 hit rate 측정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#11-%EB%8C%80%EC%95%88-spring-cache--redis&quot;&gt;11) 대안: Spring Cache + Redis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#12-%EC%8B%A4%EB%AC%B4-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot;&gt;12) 실무 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#13-%ED%95%9C-%EC%A4%84-%EC%A0%95%EB%A6%AC&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1) 1차 캐시 vs 2차 캐시: 스코프와 수명&lt;/h2&gt;
&lt;p&gt;JPA의 캐시는 두 층으로 나뉜다. 1차 캐시는 &lt;code&gt;EntityManager&lt;/code&gt; 안쪽, 2차 캐시는 &lt;code&gt;SessionFactory&lt;/code&gt; 바깥쪽. 이름에 &amp;quot;1·2차&amp;quot;가 붙어 있지만 계층 관계보다 &lt;strong&gt;스코프와 수명의 차이&lt;/strong&gt;로 읽는 쪽이 정확하다. 1차 캐시는 트랜잭션과 함께 사라지고, 2차 캐시는 애플리케이션이 살아 있는 한 계속 살아 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;구분&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;1차 캐시&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;2차 캐시&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;스코프&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EntityManager&lt;/code&gt; (=영속성 컨텍스트)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SessionFactory&lt;/code&gt; (=애플리케이션)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수명&lt;/td&gt;
&lt;td&gt;트랜잭션 종료 시 소멸&lt;/td&gt;
&lt;td&gt;애플리케이션 수명 전체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공유&lt;/td&gt;
&lt;td&gt;한 트랜잭션 내부 한정&lt;/td&gt;
&lt;td&gt;여러 트랜잭션 / 여러 EntityManager가 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장 구조&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Map&amp;lt;(타입, @Id), 엔티티&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;region 단위 Map (entity/collection/query region)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;켜기&lt;/td&gt;
&lt;td&gt;항상 켜짐 (끌 수 없음)&lt;/td&gt;
&lt;td&gt;옵션 (&lt;code&gt;use_second_level_cache=true&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주 효과&lt;/td&gt;
&lt;td&gt;같은 트랜잭션 반복 조회 절약&lt;/td&gt;
&lt;td&gt;여러 트랜잭션 반복 조회 절약&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;1차 캐시의 핵심 효과는 &lt;a href=&quot;./12_spring-persistence-context-transactional.md&quot;&gt;12편&lt;/a&gt;에서 본 엔티티 동일성(&lt;code&gt;==&lt;/code&gt; 보장)이다. 2차 캐시는 &lt;strong&gt;동일성을 보장하지 않는다&lt;/strong&gt;. 2차 캐시에서 꺼내올 때는 저장된 상태(dehydrated state, 즉 식별자 + 필드 값의 스냅샷)에서 엔티티를 재구성하므로, 서로 다른 트랜잭션에서 같은 ID를 조회해도 다른 인스턴스가 돌아온다. 대신 &lt;strong&gt;SELECT를 안 보낸다&lt;/strong&gt;는 이득이 남는다. &lt;strong&gt;조회 순서는 1차 → 2차 → DB&lt;/strong&gt;다. 1차 캐시에 있으면 그걸 쓰고, 없으면 2차 캐시를 보고, 2차에도 없으면 그제야 DB로 SELECT가 나간다. 2차에서 hit이 나면 그 결과를 1차 캐시에도 복사해두므로, 같은 트랜잭션에서 두 번째 조회부터는 1차 캐시가 받아준다.&lt;/p&gt;
&lt;p&gt;한 가지 오해를 먼저 정리한다. 2차 캐시는 &lt;strong&gt;&amp;quot;모든 엔티티에 자동 적용&amp;quot;되지 않는다&lt;/strong&gt;. 활성화 플래그 하나만 켜놓고 끝이 아니라, &lt;code&gt;@Cacheable&lt;/code&gt; 애너테이션으로 엔티티 단위로 opt-in해야 한다. &lt;a href=&quot;#6-shared-cache-mode-enable_selective-%EA%B6%8C%EC%9E%A5&quot;&gt;§6 shared-cache-mode&lt;/a&gt;에서 다시 보겠지만, 기본 모드가 &lt;code&gt;ENABLE_SELECTIVE&lt;/code&gt;여서 붙인 것만 대상이 된다. 이 전제를 놓치면 hit rate가 0%로 찍힌다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2) 2차 캐시의 세 얼굴: Entity / Collection / Query&lt;/h2&gt;
&lt;p&gt;&amp;quot;2차 캐시&amp;quot;라는 한 단어로 묶이지만 안에서는 &lt;strong&gt;세 개의 서로 다른 region&lt;/strong&gt;이 돈다. 구조·키·invalidation 규칙이 다 달라서 각각의 이름을 구분해두는 게 나중 디버깅을 훨씬 수월하게 한다.&lt;/p&gt;
&lt;h3&gt;2-1) Entity Cache&lt;/h3&gt;
&lt;p&gt;개별 엔티티를 PK 단위로 저장하는 캐시. 키는 &lt;code&gt;(엔티티 타입, @Id)&lt;/code&gt;, 값은 &lt;strong&gt;엔티티 자체가 아니라 dehydrated state&lt;/strong&gt;(식별자 + 필드 스냅샷, 연관관계는 FK로만)다. 꺼낼 때 이 스냅샷에서 엔티티 인스턴스를 재조립한다. 가장 기본이자 가장 많이 쓰이는 캐시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {
    @Id private Long id;
    private String name;
    private int price;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-2) Collection Cache&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@OneToMany&lt;/code&gt;, &lt;code&gt;@ManyToMany&lt;/code&gt; 같은 컬렉션 관계를 캐시한다. 키는 &lt;code&gt;(부모 엔티티 타입, 부모 @Id, 컬렉션 필드명)&lt;/code&gt;, 값은 &lt;strong&gt;자식 PK 목록&lt;/strong&gt;. 자식 엔티티 실체는 각자 Entity Cache에서 따로 관리된다. 그래서 컬렉션 캐시 단독으로는 효과가 반쪽이다. &lt;a href=&quot;#7-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%BA%90%EC%8B%9C%EC%99%80-%EC%9E%90%EC%8B%9D-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%97%B0%EC%87%84&quot;&gt;§7&lt;/a&gt;에서 자세히 본다.&lt;/p&gt;
&lt;h3&gt;2-3) Query Cache&lt;/h3&gt;
&lt;p&gt;JPQL/Criteria 쿼리 결과를 캐시한다. 키는 &lt;code&gt;(쿼리 문자열 + 파라미터 값 해시)&lt;/code&gt;, 값은 &lt;strong&gt;결과 엔티티의 PK 목록&lt;/strong&gt;. 이것도 Entity Cache 없이는 빈 상자다. 다음에 같은 쿼리가 들어오면 PK 목록만 꺼내고, 각 엔티티는 Entity Cache를 거쳐 조립한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;종류&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;키&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;값&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;동반 필요&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Entity Cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(타입, @Id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dehydrated 엔티티&lt;/td&gt;
&lt;td&gt;단독 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collection Cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(부모타입, 부모@Id, 필드)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;자식 PK 목록&lt;/td&gt;
&lt;td&gt;자식의 Entity Cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query Cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(JPQL + 파라미터)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;결과 PK 목록&lt;/td&gt;
&lt;td&gt;대상 엔티티의 Entity Cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;세 region은 역할이 겹치지 않는다. 예를 들어 &amp;quot;특정 카테고리의 상품 목록을 자주 조회한다&amp;quot;면 Query Cache로 목록을 캐시하고, 실제 &lt;code&gt;Product&lt;/code&gt; 엔티티는 Entity Cache가 재료를 대준다. Query Cache만 켜놓고 &lt;code&gt;Product&lt;/code&gt;에 &lt;code&gt;@Cacheable&lt;/code&gt;을 안 붙이면 매번 PK 목록만 캐시 hit에 그치고 &lt;strong&gt;엔티티 로딩 SELECT는 그대로 나간다&lt;/strong&gt;. 여기서 &amp;quot;쿼리 캐시 효과가 없다&amp;quot;는 착각이 자주 나온다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3) JCache + Caffeine / Redisson 설정&lt;/h2&gt;
&lt;p&gt;Hibernate 6에서는 &lt;strong&gt;JCache (JSR-107)&lt;/strong&gt; 기반 구성이 표준이다. 과거 &lt;code&gt;EhCacheRegionFactory&lt;/code&gt; 같은 직접 바인딩은 deprecated 되고, 이제 region factory 자리에 &lt;code&gt;JCacheRegionFactory&lt;/code&gt;를 꽂고, 그 아래 실제 provider(Caffeine, Redisson, Ehcache 3, Infinispan)를 JCache 규약으로 붙인다.&lt;/p&gt;
&lt;h3&gt;3-1) 공통 의존성 + 활성화&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;dependencies {
    implementation &amp;#39;org.springframework.boot:spring-boot-starter-data-jpa&amp;#39;
    implementation &amp;#39;org.hibernate.orm:hibernate-jcache&amp;#39;
    implementation &amp;#39;javax.cache:cache-api&amp;#39;
    // provider 중 하나
    implementation &amp;#39;com.github.ben-manes.caffeine:jcache&amp;#39;  // 버전은 Spring Boot BOM에서 관리되지 않는 경우 명시
    // 또는 분산 캐시용
    // implementation &amp;#39;org.redisson:redisson-hibernate-6&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          use_query_cache: true
          region:
            factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
        javax:
          cache:
            provider: com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
        generate_statistics: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;use_second_level_cache&lt;/code&gt;가 켜지고, region factory로 &lt;code&gt;JCacheRegionFactory&lt;/code&gt;가 지정되며, 그 아래 provider로 Caffeine이 선택된다. &lt;code&gt;generate_statistics&lt;/code&gt;는 &lt;a href=&quot;#10-%ED%86%B5%EA%B3%84%EB%A1%9C-hit-rate-%EC%B8%A1%EC%A0%95&quot;&gt;§10 통계&lt;/a&gt;에서 다시 쓴다. 한 가지 주의할 게 있다. artifact는 &lt;code&gt;ben-manes&lt;/code&gt;(하이픈)이고 패키지는 &lt;code&gt;benmanes&lt;/code&gt;(하이픈 없음)다. 철자 차이 때문에 provider FQCN을 &lt;code&gt;com.github.ben-manes...&lt;/code&gt;로 적으면 &lt;code&gt;ClassNotFoundException&lt;/code&gt;이 난다.&lt;/p&gt;
&lt;h3&gt;3-2) Provider 선택 기준&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Provider&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;형태&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;적합 상황&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;주의&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caffeine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로컬 인메모리&lt;/td&gt;
&lt;td&gt;단일 인스턴스 서비스&lt;/td&gt;
&lt;td&gt;멀티 노드에서 stale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redisson&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;분산(Redis)&lt;/td&gt;
&lt;td&gt;다중 인스턴스&lt;/td&gt;
&lt;td&gt;네트워크 왕복 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ehcache 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로컬 또는 cluster&lt;/td&gt;
&lt;td&gt;기존 Ehcache 자산&lt;/td&gt;
&lt;td&gt;구버전(2.x)은 deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infinispan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;분산(JGroups)&lt;/td&gt;
&lt;td&gt;JBoss/WildFly 계열&lt;/td&gt;
&lt;td&gt;단독 서비스에선 과함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;단일 인스턴스라면 Caffeine이 제일 가볍고 빠르다. 멀티 인스턴스 서비스에서 Caffeine을 쓰면 &lt;strong&gt;노드 A가 상품을 수정해도 노드 B의 로컬 캐시는 모른다&lt;/strong&gt;. 분산 환경에서는 Redisson 같은 중앙 캐시로 올려야 한다. 이 판단을 안 하고 Caffeine으로 시작하면 실무에서 노드 간 stale이 조용히 쌓인다.&lt;/p&gt;
&lt;h3&gt;3-3) region 별 세부 설정 (Caffeine 예)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# caffeine-jcache 설정 (application.conf 또는 JCache 프로퍼티)
caffeine:
  jcache:
    caches:
      &amp;quot;com.example.Product&amp;quot;:
        policy:
          maximum:
            size: 10000
        expireAfterWrite: 10m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;region 단위로 max size, TTL 같은 정책을 분리한다. &lt;code&gt;@Cache(region = &amp;quot;products-hot&amp;quot;)&lt;/code&gt;으로 별도 region을 지정하면 다른 정책을 적용할 수도 있다. Hibernate 6에서 region prefix 관리가 이전 버전보다 간소해져서 굳이 수동 prefix를 박아 넣을 필요가 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4) @Cacheable과 Hibernate @Cache(usage=...)&lt;/h2&gt;
&lt;p&gt;엔티티 쪽에서 해야 할 작업은 &lt;strong&gt;두 애너테이션을 같이 붙이는 것&lt;/strong&gt;이다. 이름이 비슷해서 헷갈리지만 역할이 다르다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import jakarta.persistence.Cacheable;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Cacheable                                              // (1) JPA 표준: 캐시 대상 표시
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)     // (2) Hibernate 전용: 동시성 전략
public class Product {
    @Id private Long id;
    private String name;
    private int price;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(1) &lt;code&gt;jakarta.persistence.Cacheable&lt;/code&gt;은 &lt;strong&gt;&amp;quot;이 엔티티는 캐시 대상입니다&amp;quot;&lt;/strong&gt;라고 선언만 한다. 동시성 정책 같은 건 없다. (2) &lt;code&gt;org.hibernate.annotations.Cache&lt;/code&gt;는 Hibernate 확장으로, 실제 &lt;strong&gt;어떤 동시성 전략으로 캐시할지&lt;/strong&gt;를 지정한다. &lt;strong&gt;&lt;code&gt;@Cacheable&lt;/code&gt;만 붙이면 동시성 전략이 없어 캐시되지 않는다.&lt;/strong&gt; 전역 기본값이 필요하면 &lt;code&gt;hibernate.cache.default_cache_concurrency_strategy&lt;/code&gt; 프로퍼티로 지정할 수 있지만, 엔티티마다 명시하는 쪽이 명확하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Cacheable&lt;/code&gt;은 boolean 인자를 받는다. &lt;code&gt;@Cacheable(false)&lt;/code&gt;는 &lt;strong&gt;&amp;quot;이 엔티티는 캐시에서 제외&amp;quot;&lt;/strong&gt;라는 의미다. &lt;a href=&quot;#6-shared-cache-mode-enable_selective-%EA%B6%8C%EC%9E%A5&quot;&gt;§6 shared-cache-mode&lt;/a&gt;에서 &lt;code&gt;DISABLE_SELECTIVE&lt;/code&gt; 모드를 쓸 때 의미가 생긴다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;애너테이션&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;출처&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;역할&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;빠지면?&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Cacheable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jakarta.persistence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;캐시 대상 opt-in&lt;/td&gt;
&lt;td&gt;2차 캐시에 안 들어감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Cache(usage=...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;org.hibernate.annotations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;동시성 전략 지정&lt;/td&gt;
&lt;td&gt;전략 불명확 → 캐시 안정성 보장 X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;둘이 같이 있어야 캐시가 제대로 작동한다. 단축된 표현으로 Hibernate &lt;code&gt;@Cache&lt;/code&gt; 하나만 붙여도 Hibernate는 이를 캐시 대상으로 간주하지만, &lt;strong&gt;표준에 맞추고 Spring 에코시스템과의 일관성을 지키려면 둘 다 붙이는 쪽&lt;/strong&gt;이 권장된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5) 네 가지 Concurrency Strategy&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@Cache(usage = ...)&lt;/code&gt;에 들어갈 전략은 네 가지다. 각각 &lt;strong&gt;동시 쓰기를 어떻게 다룰지&lt;/strong&gt;가 다르다. 이 선택이 정합성과 성능의 트레이드오프를 결정한다.&lt;/p&gt;
&lt;h3&gt;5-1) READ_ONLY&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;읽기 전용&lt;/strong&gt;. 캐시된 엔티티를 수정하려 하면 예외가 난다. 가장 단순하고 stale 위험이 없다 — 코드 테이블, 국가·통화 마스터, 불변 메타데이터처럼 &lt;strong&gt;애플리케이션 수명 동안 바뀌지 않는 데이터&lt;/strong&gt;에 적합하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Country { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5-2) NONSTRICT_READ_WRITE&lt;/h3&gt;
&lt;p&gt;쓰기 허용하지만 &lt;strong&gt;strict isolation을 보장하지 않는다&lt;/strong&gt;. invalidation은 트랜잭션 완료 후에 일어나므로, 완료 전 찰나에 stale read가 가능하다. 성능이 최고지만 정합성이 느슨하다. &amp;quot;완전히 최신이 아니어도 큰 문제는 없다&amp;quot;는 도메인(조회 통계, 뉴스 피드 같은)에서만 쓸 수 있다.&lt;/p&gt;
&lt;h3&gt;5-3) READ_WRITE&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;soft lock 기반&lt;/strong&gt;으로 동시성을 관리한다. 쓰기 트랜잭션이 들어오면 해당 캐시 엔트리를 lock 상태로 표시하고, 같은 엔트리에 접근하는 다른 트랜잭션은 DB에서 직접 읽게 한다. 커밋 후 unlock과 함께 캐시가 갱신된다. &lt;code&gt;READ_COMMITTED&lt;/code&gt; 격리 수준과 유사한 안정성을 제공한다. &lt;strong&gt;대부분의 쓰기 가능 엔티티가 이걸 쓴다&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;5-4) TRANSACTIONAL&lt;/h3&gt;
&lt;p&gt;JTA 기반 &lt;strong&gt;분산 트랜잭션에 캐시 자체가 참여&lt;/strong&gt;한다. 캐시와 DB가 같은 2PC 트랜잭션에 포함된다. Infinispan 같은 트랜잭션 지원 provider에서만 의미 있고, 스프링 기본 JPA/로컬 트랜잭션 환경에서는 거의 안 쓴다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;쓰기 허용&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;isolation&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;성능&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;적합 데이터&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;READ_ONLY&lt;/td&gt;
&lt;td&gt;불가 (예외)&lt;/td&gt;
&lt;td&gt;최강&lt;/td&gt;
&lt;td&gt;최고&lt;/td&gt;
&lt;td&gt;불변 마스터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NONSTRICT_READ_WRITE&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;약함 (stale 가능)&lt;/td&gt;
&lt;td&gt;매우 빠름&lt;/td&gt;
&lt;td&gt;통계·비필수 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ_WRITE&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;READ_COMMITTED 수준&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;일반 쓰기 엔티티&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TRANSACTIONAL&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;느림&lt;/td&gt;
&lt;td&gt;JTA 분산 트랜잭션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;실무에서 고를 선택지는 사실상 &lt;strong&gt;READ_ONLY&lt;/strong&gt; 또는 &lt;strong&gt;READ_WRITE&lt;/strong&gt; 둘이다. 전자는 확실히 안 바뀌는 것, 후자는 그 외 대부분. NONSTRICT가 매력적으로 보여도 운영 중 stale이 언제 어디서 튀어나올지 예측이 어려워서 지양한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6) shared-cache-mode: ENABLE_SELECTIVE 권장&lt;/h2&gt;
&lt;p&gt;Jakarta Persistence 표준은 캐시 대상 범위를 &lt;strong&gt;애플리케이션 단위&lt;/strong&gt;로 한 번 더 제어하는 설정을 둔다. 이게 &lt;code&gt;shared-cache-mode&lt;/code&gt;다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  jpa:
    properties:
      jakarta:
        persistence:
          sharedCache:
            mode: ENABLE_SELECTIVE&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;모드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;의미&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;실무 판단&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 엔티티를 캐시&lt;/td&gt;
&lt;td&gt;권장 안 함 (통제 상실)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2차 캐시 전체 비활성&lt;/td&gt;
&lt;td&gt;켜지 않는 것과 같음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ENABLE_SELECTIVE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Cacheable(true)&lt;/code&gt;만 캐시&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hibernate 권장&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DISABLE_SELECTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Cacheable(false)&lt;/code&gt; 제외 전부 캐시&lt;/td&gt;
&lt;td&gt;관리 불편&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNSPECIFIED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;provider 기본값 사용 (Jakarta 스펙 기본)&lt;/td&gt;
&lt;td&gt;애매함, 명시 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Jakarta Persistence 스펙 기본값은 &lt;code&gt;UNSPECIFIED&lt;/code&gt;이고, 이 경우 provider에 따라 해석이 달라진다. 대부분 &lt;code&gt;ENABLE_SELECTIVE&lt;/code&gt;를 명시하고, &lt;code&gt;@Cacheable&lt;/code&gt;을 명시적으로 붙인 엔티티만 opt-in 한다. 이 모드가 아니면 엔티티에 &lt;code&gt;@Cacheable&lt;/code&gt;을 붙였어도 캐시가 안 될 수 있고, 반대로 아무 것도 안 붙였는데 캐시에 들어갈 수도 있다.&lt;/p&gt;
&lt;p&gt;여기서 틀리기 쉽다. &amp;quot;&lt;code&gt;use_second_level_cache=true&lt;/code&gt;만 켜면 알아서 되겠지&amp;quot;라고 두면 &lt;code&gt;ENABLE_SELECTIVE&lt;/code&gt;를 명시했어도 어느 엔티티에도 &lt;code&gt;@Cacheable&lt;/code&gt;이 없으면 &lt;strong&gt;2차 캐시는 실제로 아무 것도 담지 않는다&lt;/strong&gt;. hit rate 0%의 전형적 원인이다. 설정 한 줄만 바꾸면 해결된다는 점에서 허무한 버그다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7) 컬렉션 캐시와 자식 엔티티 연쇄&lt;/h2&gt;
&lt;p&gt;컬렉션 캐시는 앞서 봤듯 &lt;strong&gt;자식 PK 목록만&lt;/strong&gt; 저장한다. 부모 엔티티와 연관된 컬렉션 필드에 &lt;code&gt;@Cache&lt;/code&gt;를 붙이면 대상이 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Order {
    @Id private Long id;

    @OneToMany(mappedBy = &amp;quot;order&amp;quot;)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)   // 컬렉션 자체의 캐시
    private List&amp;lt;OrderItem&amp;gt; items;
}

// 자식도 반드시 같이 캐시 대상이어야 한다
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class OrderItem {
    @Id private Long id;
    @ManyToOne private Order order;
    private String productName;
    private int quantity;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 &lt;code&gt;OrderItem&lt;/code&gt;에 &lt;code&gt;@Cacheable&lt;/code&gt;을 안 붙이면 어떻게 되는가. &lt;code&gt;order.getItems()&lt;/code&gt;를 호출할 때, 컬렉션 캐시는 hit해서 PK 목록 &lt;code&gt;[101, 102, 103]&lt;/code&gt;을 돌려준다. 그런데 각 &lt;code&gt;OrderItem&lt;/code&gt;이 Entity Cache에 없으니, 각 PK마다 SELECT가 나간다. &lt;strong&gt;PK 개수만큼의 N+1 SELECT&lt;/strong&gt;가 발생한다. 이건 컬렉션 캐시를 안 쓴 것보다 오히려 나쁠 수 있다 — 원래라면 &lt;code&gt;JOIN FETCH&lt;/code&gt;로 한 번에 가져왔을 걸 쪼개서 가져오는 셈이 된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;상황&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;컬렉션 캐시만, 자식 Entity Cache 없음&lt;/td&gt;
&lt;td&gt;컬렉션 hit → 자식마다 SELECT (N+1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컬렉션 캐시 없음, 자식 Entity Cache만&lt;/td&gt;
&lt;td&gt;컬렉션은 매번 SELECT, 자식은 캐시 hit 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;둘 다 있음&lt;/td&gt;
&lt;td&gt;양쪽 hit, SELECT 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;따라서 컬렉션 캐시를 쓰려면 &lt;strong&gt;자식 엔티티도 반드시 &lt;code&gt;@Cacheable&lt;/code&gt; + &lt;code&gt;@Cache(usage)&lt;/code&gt;가 있어야 한다&lt;/strong&gt;. 대원칙은 &amp;quot;연관 엔티티도 캐시 대상이어야 컬렉션 캐시가 제값을 한다&amp;quot;로 기억하면 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;8) Query Cache와 UpdateTimestamps&lt;/h2&gt;
&lt;p&gt;Query Cache는 JPQL/Criteria 결과를 캐시한다. 기본으로 꺼져 있고, 쿼리 단위로 hint를 주거나 Spring Data에서 &lt;code&gt;@QueryHints&lt;/code&gt;로 켠다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Plain JPA
TypedQuery&amp;lt;Product&amp;gt; q = em.createQuery(
    &amp;quot;select p from Product p where p.category = :cat&amp;quot;, Product.class);
q.setParameter(&amp;quot;cat&amp;quot;, &amp;quot;BOOK&amp;quot;);
q.setHint(&amp;quot;org.hibernate.cacheable&amp;quot;, true);
List&amp;lt;Product&amp;gt; list = q.getResultList();

// Spring Data JPA
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    @QueryHints(@QueryHint(name = &amp;quot;org.hibernate.cacheable&amp;quot;, value = &amp;quot;true&amp;quot;))
    List&amp;lt;Product&amp;gt; findByCategory(String category);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;키는 쿼리 문자열과 파라미터 해시로 만들어진다. 같은 JPQL에 다른 파라미터면 다른 엔트리다. 값은 &lt;strong&gt;결과 엔티티의 PK 목록&lt;/strong&gt;이다. 실제 엔티티 조립은 Entity Cache 또는 DB에서 한다.&lt;/p&gt;
&lt;h3&gt;8-1) UpdateTimestamps 캐시&lt;/h3&gt;
&lt;p&gt;Query Cache는 &lt;strong&gt;언제 invalid 해야 하는지&lt;/strong&gt;를 스스로 판단해야 한다. Hibernate는 이를 위해 내부에 &lt;code&gt;UpdateTimestampsCache&lt;/code&gt;라는 보조 캐시를 둔다. 테이블별로 &lt;strong&gt;마지막 쓰기 타임스탬프&lt;/strong&gt;를 기록하는 region이다.&lt;/p&gt;
&lt;p&gt;쿼리 캐시 hit 판단 흐름은 이렇다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;쿼리 캐시에서 key로 엔트리 조회 → 캐시된 시점 타임스탬프 &lt;code&gt;T_cache&lt;/code&gt; 확인&lt;/li&gt;
&lt;li&gt;이 쿼리가 건드리는 테이블 목록을 파악&lt;/li&gt;
&lt;li&gt;각 테이블의 &lt;code&gt;UpdateTimestampsCache&lt;/code&gt; 값 &lt;code&gt;T_table&lt;/code&gt; 조회&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T_cache &amp;gt;= max(T_table)&lt;/code&gt;이면 hit (여전히 유효)&lt;/li&gt;
&lt;li&gt;하나라도 &lt;code&gt;T_table &amp;gt; T_cache&lt;/code&gt;면 &lt;strong&gt;stale로 간주하고 DB 재조회&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;덕분에 어떤 쓰기가 일어나면 그 테이블과 연관된 모든 쿼리 캐시 엔트리가 &lt;strong&gt;자연히 무효화&lt;/strong&gt;된다. 엔트리를 삭제하는 게 아니라, 타임스탬프 비교에서 탈락시키는 방식이다. 구현이 단순하고, invalidation을 region 단위가 아니라 테이블 단위로 묶어서 관리 부담을 낮춘다.&lt;/p&gt;
&lt;h3&gt;8-2) Query Cache가 효과를 보려면&lt;/h3&gt;
&lt;p&gt;Query Cache는 &lt;strong&gt;Entity Cache 없이는 거의 무의미하다&lt;/strong&gt;. PK 목록만 돌려줘도, 각 엔티티 로딩에서 SELECT가 나가면 효과가 절반도 안 나온다. 그래서 Query Cache를 켤 때는 &lt;strong&gt;대상 엔티티에 &lt;code&gt;@Cacheable&lt;/code&gt;이 걸려 있는지&lt;/strong&gt;부터 확인한다. 둘이 같이 걸려야 전체 SELECT 0이 된다.&lt;/p&gt;
&lt;p&gt;또 하나 실무 팁. &lt;strong&gt;바뀌는 엔티티를 포함한 쿼리는 캐시 효율이 낮다&lt;/strong&gt;. 쓰기가 잦으면 invalidation이 잦아 hit rate가 바닥이다. Query Cache는 &lt;strong&gt;조회 빈도 높고 쓰기 드문 쿼리&lt;/strong&gt;에만 선택적으로 건다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;9) 벌크 쿼리의 2차 캐시 우회 함정&lt;/h2&gt;
&lt;p&gt;JPA에서 가장 조용하고 무서운 버그 중 하나가 여기서 나온다. JPQL 벌크 &lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt;는 &lt;strong&gt;영속성 컨텍스트를 우회&lt;/strong&gt;하고 DB에 직접 쿼리를 날린다. 같은 이유로 &lt;strong&gt;2차 캐시의 자동 invalidation도 일어나지 않는다&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 피하기: 벌크 쿼리 후 캐시 그대로 두기
@Transactional
public void markAllShipped(Long customerId) {
    em.createQuery(
        &amp;quot;update Order o set o.status = &amp;#39;SHIPPED&amp;#39; where o.customer.id = :cid&amp;quot;)
        .setParameter(&amp;quot;cid&amp;quot;, customerId)
        .executeUpdate();
    // 이 시점: DB는 SHIPPED, 2차 캐시의 Order 엔트리는 여전히 이전 상태
    // 다음 요청에서 order.getStatus() → 캐시 hit → 잘못된 값
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해법은 두 가지다. 명시적으로 해당 region을 비우거나, 벌크 쿼리를 피하고 managed 엔티티를 루프로 수정하거나.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 선호 1: 벌크 후 캐시 수동 evict
@Transactional
public void markAllShipped(Long customerId) {
    em.createQuery(&amp;quot;update Order o set o.status = &amp;#39;SHIPPED&amp;#39; where o.customer.id = :cid&amp;quot;)
        .setParameter(&amp;quot;cid&amp;quot;, customerId)
        .executeUpdate();
    em.getEntityManagerFactory().getCache().evict(Order.class);
    // 또는 특정 ID만: .evict(Order.class, orderId);
    // 또는 전부 날리기(거의 안 씀): .evictAll();
}

// 선호 2: managed 엔티티 루프 (작은 건수에 한해)
@Transactional
public void markAllShipped(Long customerId) {
    List&amp;lt;Order&amp;gt; orders = em.createQuery(
        &amp;quot;select o from Order o where o.customer.id = :cid&amp;quot;, Order.class)
        .setParameter(&amp;quot;cid&amp;quot;, customerId)
        .getResultList();
    for (Order o : orders) o.setStatus(&amp;quot;SHIPPED&amp;quot;);
    // Dirty Checking → 개별 UPDATE → 2차 캐시 자동 invalidation
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 예시처럼 &lt;code&gt;EntityManager&lt;/code&gt;에서 &lt;code&gt;getEntityManagerFactory()&lt;/code&gt;를 꺼내 쓰는 방식도 동작하지만, 실무에서는 &lt;code&gt;@PersistenceUnit&lt;/code&gt;으로 &lt;code&gt;EntityManagerFactory&lt;/code&gt;를 직접 주입해 &lt;code&gt;emf.getCache().evict(Order.class, id)&lt;/code&gt;를 호출하는 쪽이 더 흔하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;방식&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SQL 수&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;2차 캐시 동기화&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;적합 상황&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;벌크 JPQL + 수동 evict&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;개발자 책임&lt;/td&gt;
&lt;td&gt;대량 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;managed 엔티티 루프&lt;/td&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;소량 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native SQL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;자동 안 됨&lt;/td&gt;
&lt;td&gt;성능 최우선, 수동 evict 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;벌크가 필요하지만 양이 크면 evict 경로가 현실적이다. &amp;quot;벌크 쓸 때는 캐시 evict&amp;quot;를 규칙으로 박아두는 게 낫다. 누락되면 &lt;strong&gt;stale이 캐시에 살아남아 다음 트랜잭션까지 오염된다&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Native SQL도 같은 문제를 안고 있다. Hibernate는 native query가 건드리는 테이블을 파싱으로 추론하려 시도하지만 한계가 있어, 안전을 원하면 &lt;code&gt;@org.hibernate.annotations.NamedNativeQuery&lt;/code&gt; 또는 &lt;code&gt;Query.unwrap(NativeQuery.class).addSynchronizedEntityClass(Order.class)&lt;/code&gt;로 명시한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;10) 통계로 hit rate 측정&lt;/h2&gt;
&lt;p&gt;2차 캐시가 &amp;quot;돌아간다&amp;quot;와 &amp;quot;효과가 있다&amp;quot;는 다르다. hit rate를 실제로 측정해야 한다. Hibernate는 &lt;code&gt;SessionFactory.getStatistics()&lt;/code&gt;로 hit/miss/put 카운트를 노출한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  jpa:
    properties:
      hibernate:
        generate_statistics: true&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
@RequiredArgsConstructor
public class CacheStatsReporter {
    private final EntityManagerFactory emf;

    public void report() {
        SessionFactory sf = emf.unwrap(SessionFactory.class);
        Statistics stats = sf.getStatistics();

        long hit = stats.getSecondLevelCacheHitCount();
        long miss = stats.getSecondLevelCacheMissCount();
        long put = stats.getSecondLevelCachePutCount();
        double rate = (hit + miss) == 0 ? 0.0 : (double) hit / (hit + miss);

        log.info(&amp;quot;L2 cache hit={}, miss={}, put={}, hitRate={}&amp;quot;, hit, miss, put, rate);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;region별 세부 통계도 뽑을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;CacheRegionStatistics region = stats.getCacheRegionStatistics(&amp;quot;com.example.Product&amp;quot;);
log.info(&amp;quot;Product region: hit={}, miss={}, elementCount={}&amp;quot;,
    region.getHitCount(), region.getMissCount(), region.getElementCountInMemory());&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10-1) 해석 가이드&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;패턴&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;해석&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;조치&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;hit=0, miss&amp;gt;0, put=0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Cacheable&lt;/code&gt; 미부착 또는 mode 잘못&lt;/td&gt;
&lt;td&gt;§4, §6 재확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hit=0, miss&amp;gt;0, put&amp;gt;0&lt;/td&gt;
&lt;td&gt;캐시에 들어가지만 hit이 안 됨&lt;/td&gt;
&lt;td&gt;TTL 너무 짧거나 쓰기 잦아 invalidation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hit&amp;gt;miss&lt;/td&gt;
&lt;td&gt;정상 동작&lt;/td&gt;
&lt;td&gt;유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;put이 비정상적으로 큼&lt;/td&gt;
&lt;td&gt;쓰기 반복으로 재적재&lt;/td&gt;
&lt;td&gt;해당 엔티티 2차 캐시 부적합&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;hit rate가 0%면 설정 문제&lt;/strong&gt;, &lt;strong&gt;hit rate가 낮으면 캐시 부적합&lt;/strong&gt;. 이 둘을 구분하는 게 첫 번째 디버깅 포인트다.&lt;/p&gt;
&lt;p&gt;Actuator를 통한 Micrometer 연동도 가능하지만, Hibernate 6의 micrometer 바인더는 별도 라이브러리(&lt;code&gt;hibernate-micrometer&lt;/code&gt;)가 필요하다. 이걸 붙이면 &lt;code&gt;/actuator/metrics&lt;/code&gt;에 &lt;code&gt;hibernate.cache.requests&lt;/code&gt;, &lt;code&gt;hibernate.cache.puts&lt;/code&gt; 등이 노출되어 Grafana에 그대로 연결된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;11) 대안: Spring Cache + Redis&lt;/h2&gt;
&lt;p&gt;2차 캐시를 안 쓰고 서비스 계층에서 &lt;strong&gt;Spring Cache&lt;/strong&gt;(&lt;code&gt;@Cacheable&lt;/code&gt; on method)로 Redis에 캐싱하는 선택지도 실무에 많다. JPA 레이어 바깥에서 도는 캐시라서 성격이 완전히 다르다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepository repo;

    @Cacheable(value = &amp;quot;products&amp;quot;, key = &amp;quot;#id&amp;quot;)
    public ProductDto findById(Long id) {
        Product p = repo.findById(id).orElseThrow();
        return ProductDto.from(p);
    }

    @CacheEvict(value = &amp;quot;products&amp;quot;, key = &amp;quot;#id&amp;quot;)
    @Transactional
    public void updateName(Long id, String newName) {
        Product p = repo.findById(id).orElseThrow();
        p.setName(newName);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hibernate 2차 캐시와 Spring Cache + Redis의 차이를 정리하면 이렇다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;비교&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Hibernate 2차 캐시&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Spring Cache + Redis&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;캐시 단위&lt;/td&gt;
&lt;td&gt;엔티티 / 컬렉션 / 쿼리&lt;/td&gt;
&lt;td&gt;메서드 반환값 (DTO 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자동 invalidation&lt;/td&gt;
&lt;td&gt;엔티티 수정 시 자동 (벌크 제외)&lt;/td&gt;
&lt;td&gt;수동 (&lt;code&gt;@CacheEvict&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분산&lt;/td&gt;
&lt;td&gt;provider에 따라 (Redisson 등)&lt;/td&gt;
&lt;td&gt;Redis로 기본 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;영속성 컨텍스트 연계&lt;/td&gt;
&lt;td&gt;있음 (dehydrated state)&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시리얼라이즈 대상&lt;/td&gt;
&lt;td&gt;엔티티 스냅샷&lt;/td&gt;
&lt;td&gt;보통 DTO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPA 지식 요구&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Spring Cache 쪽 장점&lt;/strong&gt;은 명확하다. 비즈니스 단위(조회 메서드)에 직접 캐시를 건다. 반환 DTO를 그대로 캐시하므로 직렬화가 단순하고, Redis 분산 캐시를 표준처럼 붙일 수 있다. &lt;strong&gt;단점&lt;/strong&gt;은 invalidation이 수동이라는 점. 엔티티를 어디서 바꾸든 관련 &lt;code&gt;@CacheEvict&lt;/code&gt;를 빼먹으면 stale이 남는다.&lt;/p&gt;
&lt;p&gt;실무에서는 이 두 개를 섞어 쓴다. 마스터성 데이터(국가, 통화, 상품 카테고리 같은)는 &lt;strong&gt;Hibernate 2차 캐시&lt;/strong&gt;로 엔티티 단위 캐싱, 비즈니스 응답 단위(&lt;code&gt;/api/products/hot&lt;/code&gt;의 상위 100개 DTO 같은)는 &lt;strong&gt;Spring Cache + Redis&lt;/strong&gt;로 메서드 캐싱. 성격에 맞춰 고르면 된다.&lt;/p&gt;
&lt;p&gt;선택 기준을 굳이 한 줄로 요약하면: &lt;strong&gt;&amp;quot;엔티티를 그대로 재활용할 거면 2차 캐시, 조합된 응답 DTO를 재활용할 거면 Spring Cache&amp;quot;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;12) 실무 체크리스트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;2차 캐시는 내 도메인에 맞나&amp;quot;부터 묻는다&lt;/strong&gt;. Read-heavy이면서 변경이 드문 데이터(코드 테이블, 마스터)에만 실효성이 분명하다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;활성화 순서는 ① &lt;code&gt;use_second_level_cache=true&lt;/code&gt; ② provider ③ &lt;code&gt;@Cacheable&lt;/code&gt; + &lt;code&gt;@Cache(usage=...)&lt;/code&gt; ④ &lt;code&gt;shared-cache-mode=ENABLE_SELECTIVE&lt;/code&gt;&lt;/strong&gt;. 하나만 빠져도 hit rate가 0%다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;엔티티의 &lt;code&gt;equals&lt;/code&gt;/&lt;code&gt;hashCode&lt;/code&gt;를 정확히 구현&lt;/strong&gt;. 2차 캐시 재조립 시 인스턴스가 달라 &lt;code&gt;Set&lt;/code&gt; 기반 비교가 깨진다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분산 환경에서 로컬 Caffeine은 재앙&lt;/strong&gt;. 노드마다 stale이 다르게 발생한다. 멀티 인스턴스라면 Redisson 같은 분산 캐시 provider로 가야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;벌크 JPQL 뒤에는 수동 evict를 규칙으로&lt;/strong&gt;. &lt;code&gt;getCache().evict(...)&lt;/code&gt;가 빠지면 stale이 조용히 남는다. PR 리뷰의 고정 체크 항목.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query Cache는 선택적으로&lt;/strong&gt;. 쓰기 적은 조회 쿼리에만 &lt;code&gt;@QueryHints&lt;/code&gt;로 opt-in. 전체 활성화는 invalidation 오버헤드 때문에 지양한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;generate_statistics&lt;/code&gt;로 hit rate를 모니터링&lt;/strong&gt;. hit rate가 20%를 못 넘는 region은 캐시에서 내리는 편이 낫다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hibernate 6부터는 JCache가 표준&lt;/strong&gt;. 새 프로젝트는 &lt;code&gt;JCacheRegionFactory&lt;/code&gt; + Caffeine 또는 Redisson 조합.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;2차 캐시는 &lt;code&gt;SessionFactory&lt;/code&gt; 스코프에서 여러 &lt;code&gt;EntityManager&lt;/code&gt;가 공유하는 애플리케이션 레벨 캐시이고, Entity·Collection·Query 세 region이 각자 다른 키로 돌아간다&lt;/strong&gt;. 활성화 플래그만으로는 부족하고 &lt;strong&gt;&lt;code&gt;@Cacheable&lt;/code&gt; + &lt;code&gt;@Cache(usage)&lt;/code&gt; + &lt;code&gt;shared-cache-mode&lt;/code&gt; + provider 조합 네 가지가 다 맞아야&lt;/strong&gt; 비로소 엔트리가 쌓인다. &lt;strong&gt;Read-heavy·변경 드문 도메인에만 켜고, 벌크 쿼리 뒤 수동 evict를 규칙화하고, 분산 환경에선 로컬 캐시를 피한다&lt;/strong&gt; — 이 세 조건을 지키지 못하면 2차 캐시는 쉽게 버그로 돌아온다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;태그&lt;/strong&gt;: Hibernate, JPA, 2차 캐시, JCache, Caffeine, Redisson, CacheConcurrencyStrategy, shared-cache-mode, Query Cache, Spring Cache&lt;/p&gt;</description>
      <category>CS/Spring</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/423</guid>
      <comments>https://dding-shark.tistory.com/423#entry423comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:27:07 +0900</pubDate>
    </item>
    <item>
      <title>JPA 동적 쿼리 네 가지 &amp;mdash; JPQL&amp;middot;Criteria&amp;middot;Querydsl&amp;middot;Specification</title>
      <link>https://dding-shark.tistory.com/422</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;jpa-동적-쿼리-네-가지--jpqlcriteriaquerydslspecification&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;JPA
동적 쿼리 네 가지 — JPQL·Criteria·Querydsl·Specification&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;검색 화면을 하나 만든다. 조건은 상태, 최소 금액, 기간, 회원 이름,
상품 카테고리 다섯 개. 각 조건은 있으면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;에 추가하고
없으면 뺀다. 처음에는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StringBuilder&lt;/code&gt;로 JPQL을 이어붙인다. 두
번째 요구사항이 들어와 조건이 여덟 개가 되는 순간 메서드가 100줄을
넘기고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AND&lt;/code&gt; 빼먹은 오타가 런타임에서 터진다. 이게
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동적 쿼리 문제&lt;/strong&gt;다. 조건의 개수가 코드 작성 시점에
확정되지 않는다는 딱 한 가지 특성 때문에, JPA는 JPQL 문자열 빌더, JPA
Criteria API, Querydsl, Spring Data Specification 네 가지 도구를 동시에
유지한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 네 도구를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;언제 무엇을 쓰는가&lt;/strong&gt;의 관점에서
정리한다. &amp;quot;Querydsl 쓰면 된다&amp;quot;가 답이긴 한데, 왜 답인지를 설명하려면
나머지 세 가지가 어디서 부러지는지를 봐야 한다. 특히 다음 네 가지는
현장에서 가장 자주 밟는 함정이다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;검색 조건이 5개를 넘어가자 JPQL이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StringBuilder&lt;/code&gt;
지옥이 된다&lt;/strong&gt; — 파라미터 바인딩도 같이 분기돼 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;문
한 덩어리가 두 배로 커진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Criteria API로 도전했는데 가독성이 JPQL의 10배로
떨어졌다&lt;/strong&gt; — 3줄짜리 쿼리가 20줄이 되고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;root.get(&amp;quot;status&amp;quot;)&lt;/code&gt;는 문자열이라 타입 안전도 반쪽짜리다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Querydsl을 도입하니 QClass가 생성 안 돼 빌드가
실패한다&lt;/strong&gt; — Boot 3 + Hibernate 6는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;querydsl-jpa:5.x:jakarta&lt;/code&gt; classifier가 필수다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Specification을 여러 개 합성했는데 JOIN이 중복돼 결과가
부풀었다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;query.distinct(true)&lt;/code&gt; 누락이 거의 항상
원인이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;왜 → 네 도구 각각 → QClass 생성과 호환 → 합성 함정 →
비교와 선택 → Projection/정렬/페이징 → 함정 종합&lt;/strong&gt; 순으로, Spring
Boot 3.x / Hibernate 6.x / Jakarta Persistence 3.x / Querydsl 5.x /
Spring Data JPA 3.x 기준으로 정리한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-동적-쿼리가-필요한-순간&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) 동적 쿼리가 필요한
순간&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-jpql-문자열-빌더-가장-단순하지만-위험&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) JPQL 문자열
빌더: 가장 단순하지만 위험&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-jpa-criteria-api-타입-안전의-대가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) JPA Criteria API:
타입 안전의 대가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-querydsl-사실상-표준이-된-이유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) Querydsl: 사실상
표준이 된 이유&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-qclass-생성과-boot-3--hibernate-6-호환&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) QClass 생성과
Boot 3 + Hibernate 6 호환&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-spring-data-specification-criteria의-spring-래퍼&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6)
Spring Data Specification: Criteria의 Spring 래퍼&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-null-안전한-specification-합성&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7) Null 안전한
Specification 합성&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-querydsl-booleanexpression-분리-패턴&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8) Querydsl
BooleanExpression 분리 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-네-가지-비교와-선택-기준&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) 네 가지 비교와 선택
기준&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-dto-projection과-동적-정렬페이징&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) DTO Projection과
동적 정렬/페이징&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-함정-종합-metamodel--distinct--fetch-join&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 함정
종합: metamodel / distinct / fetch join&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#13-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-동적-쿼리가-필요한-순간&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) 동적 쿼리가 필요한 순간&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;정적 쿼리는 컴파일 시점에 SQL 형태가 확정된다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByStatus(Status)&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where status = ?&lt;/code&gt; 한
줄로 끝난다. 반면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동적 쿼리는 조건의 유무와 개수가 런타임에
결정&lt;/strong&gt;된다. 검색 API의 요청 DTO에 필드 다섯 개가 있고, 각 필드가
null일 수도 있고 아닐 수도 있으면 가능한 조합은 2의 5승, 32가지다. 이걸
32개의 정적 쿼리로 만들면 중복이 폭발하고, 하나로 묶으려면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건을 동적으로 조립해야 한다&lt;/strong&gt;.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA가 제공하는 선택지는 네 가지다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 문자열 빌더 / JPA
Criteria API / Querydsl / Spring Data Specification&lt;/strong&gt;. 각자 다른
시기에 다른 문제를 풀려고 추가됐고, 서로 완전히 대체하지 않기 때문에
지금도 네 가지가 공존한다. 이 순서를 기억하면 좋다 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL은
원조이자 가장 단순한 방법, Criteria는 JPA 표준의 타입 안전 시도,
Querydsl은 Criteria의 가독성 문제에 대한 외부 라이브러리의 답,
Specification은 Spring Data가 Criteria를 다시 감싼 얇은
래퍼&lt;/strong&gt;.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;앞의 두 개는 표준에 속하고 뒤의 두 개는 외부/반(半)표준이다. 실무에서
&amp;quot;뭘 쓰지?&amp;quot;의 답이 이 네 개 사이에서 흔들리는 이유는, 표준이라고 해서
실전 가독성이 좋지도 않고 외부 라이브러리라고 해서 피해야 하는 것도
아니기 때문이다. 기준은 하나다 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건 조합의 복잡도에 비해 어느
도구가 코드를 얼마나 덜 깨뜨리는가&lt;/strong&gt;.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-jpql-문자열-빌더-가장-단순하지만-위험&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) JPQL 문자열 빌더:
가장 단순하지만 위험&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL을 문자열로 직접 만들어 붙이는 방식이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StringBuilder&lt;/code&gt;에 조건마다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AND&lt;/code&gt;를 끼워 넣고,
파라미터 바인딩은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setParameter&lt;/code&gt;로 따로 건다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus status&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; memberId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;StringBuilder&lt;/span&gt; jpql &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;select o from Order o where 1=1&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;    jpql&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot; and o.status = :status&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; jpql&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot; and o.amount &amp;gt;= :min&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  jpql&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot; and o.member.id = :mid&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    TypedQuery&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; q &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;jpql&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;    q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;mid&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; memberId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; q&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;동작은 한다. 단순한 검색 화면에서는 이 정도면 충분하다는 주장도
틀리지 않다. 문제는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건이 늘 때마다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;가 두 배로
늘어난다&lt;/strong&gt;는 점이다. 쿼리 조립에서 한 번, 파라미터 바인딩에서 한
번. 빼먹으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IllegalArgumentException: could not locate named parameter&lt;/code&gt;가
런타임에 터진다.&lt;/p&gt;
&lt;h3 id=&quot;2-1-where-11-관용구&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-1) where 1=1 관용구&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where 1=1&lt;/code&gt;은 첫 조건 앞에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and&lt;/code&gt;를 자연스럽게
놓기 위한 꼼수다. DB 옵티마이저는 대부분 이 상수식을 제거하므로 성능상
의미는 없지만, 리뷰에서 &amp;quot;왜 1=1이냐&amp;quot;는 지적을 받으면 설명하기 귀찮은
코드다. 조건이 세 개 이상이면 이미 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;StringBuilder&lt;/code&gt; 자체가
실수의 근원이 된다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and&lt;/code&gt;를 빼먹어도 컴파일 에러는 없다.&lt;/p&gt;
&lt;h3 id=&quot;2-2-타입-안전-없음&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-2) 타입 안전 없음&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jpql.append(&amp;quot; and o.stauts = :status&amp;quot;)&lt;/code&gt; 같은 오타는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;런타임에 DB가 보기 전까지&lt;/strong&gt; 드러나지 않는다. JPQL은
문자열이고 IDE가 검사하지 않는다. JPA 엔티티 이름 변경도 문자열 쿼리에는
반영되지 않는다. 작은 리팩터가 조용히 쿼리를 깨뜨린다.&lt;/p&gt;
&lt;h3 id=&quot;2-3-언제-쓸-수-있는가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-3) 언제 쓸 수 있는가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;조건이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;2~3개 이하이고 변할 가능성이 낮다&lt;/strong&gt;면 JPQL
문자열 빌더가 가장 빠르고 읽기 쉽다. 네임드 쿼리나 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;
애너테이션으로 고정할 수 있는 경우에는 동적 쿼리 도구를 꺼낼 이유가
없다. 이 섹션의 교훈은 &amp;quot;JPQL을 쓰지 말라&amp;quot;가 아니라, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;동적이 된
순간 StringBuilder는 부채&lt;/strong&gt;&amp;quot;라는 것이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-jpa-criteria-api-타입-안전의-대가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) JPA Criteria API: 타입
안전의 대가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Criteria API는 Jakarta Persistence 3.x 표준이다. 쿼리를 문자열이
아니라 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;객체 그래프&lt;/strong&gt;로 조립한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CriteriaBuilder&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Predicate&lt;/code&gt;를 만들고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CriteriaQuery&lt;/code&gt;가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;from/where/select/orderBy&lt;/code&gt;를
조합한다. JPQL의 모든 기능을 코드로 표현할 수 있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus status&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; memberId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    CriteriaBuilder cb &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getCriteriaBuilder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    CriteriaQuery&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; cq &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Root&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; o &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cq&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; ps &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;    ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; status&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;amount&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;member&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; memberId&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-11&quot;&gt;&lt;a href=&quot;#cb2-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    cq&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Predicate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;]));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-12&quot;&gt;&lt;a href=&quot;#cb2-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cq&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-13&quot;&gt;&lt;a href=&quot;#cb2-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL에서는 5줄이던 쿼리가 Criteria API에서는 20줄이 된다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;타입 안전의 대가가 장황함&lt;/strong&gt;이다. 가독성이 떨어진다는 건
단순한 취향 문제가 아니라, 팀원 리뷰 비용이 올라간다는 뜻이다. 조건이 열
개 넘어가면 메서드 하나가 50줄을 훌쩍 넘긴다.&lt;/p&gt;
&lt;h3 id=&quot;3-1-metamodel--진짜-타입-안전의-조건&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-1) metamodel — 진짜 타입
안전의 조건&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;위 예제의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;o.get(&amp;quot;status&amp;quot;)&lt;/code&gt; 는 실제로는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;문자열
키&lt;/strong&gt;다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&amp;quot;status&amp;quot;&lt;/code&gt;를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&amp;quot;stauts&amp;quot;&lt;/code&gt;로 오타 내면
Criteria도 런타임에서 터진다. Criteria API의 타입 안전성은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;metamodel 클래스&lt;/strong&gt;를 생성해야 비로소 완성된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 빌드 시 APT가 생성: Order_.java&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@StaticMetamodel&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order_ &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;volatile&lt;/span&gt; SingularAttribute&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; OrderStatus&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; status&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;volatile&lt;/span&gt; SingularAttribute&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;volatile&lt;/span&gt; SingularAttribute&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Member&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; member&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// metamodel 기반 Criteria&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order_&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; status&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-11&quot;&gt;&lt;a href=&quot;#cb3-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;ps&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order_&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order_.status&lt;/code&gt;는 필드 참조이므로 오타면 컴파일이 안 된다.
타입도 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderStatus&lt;/code&gt;로 잡혀 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;cb.equal&lt;/code&gt;이 올바른
타입만 받는다. metamodel은 Hibernate JPA Modelgen 같은 APT 플러그인이
빌드 시 생성한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode groovy&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode groovy&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;dependencies &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    annotationProcessor &lt;span class=&quot;st&quot;&gt;&amp;quot;org.hibernate.orm:hibernate-jpamodelgen:6.x&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;metamodel 없이 Criteria를 쓰는 건 타입 안전성을 반만 챙기는
것&lt;/strong&gt;이다. 하지만 metamodel을 붙여도 가독성은 여전히 JPQL의 몇
배다. 이게 Criteria API가 실무에서 밀려난 결정적 이유다.&lt;/p&gt;
&lt;h3 id=&quot;3-2-언제-criteria를-쓰나&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-2) 언제 Criteria를 쓰나&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;외부 의존성을 추가할 수 없는 환경(라이브러리 프로젝트, 엔터프라이즈
규정상 Querydsl 금지)이라면 Criteria가 유일한 타입 안전 선택지다. 또는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동적 조건이 드물고 구조 자체가 복잡한 단발성
쿼리&lt;/strong&gt;(서브쿼리, CASE, 집계가 섞인 리포트 쿼리)에서 Criteria의
표현력이 필요할 때가 있다. 이 두 가지가 아니라면 Querydsl 또는
Specification으로 넘어가는 게 합리적이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-querydsl-사실상-표준이-된-이유&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) Querydsl: 사실상 표준이 된
이유&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl은 외부 라이브러리지만 JPA 동적 쿼리의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;사실상
표준&lt;/strong&gt;이다. 이유는 하나다 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Criteria의 타입 안전성을
유지하면서 JPQL에 가까운 가독성&lt;/strong&gt;을 제공한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus status&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; minAmount&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; memberId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    QOrder o &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    BooleanBuilder where &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;    where&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; where&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;goe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  where&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-8&quot;&gt;&lt;a href=&quot;#cb5-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-9&quot;&gt;&lt;a href=&quot;#cb5-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb5-10&quot;&gt;&lt;a href=&quot;#cb5-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-11&quot;&gt;&lt;a href=&quot;#cb5-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;where&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-12&quot;&gt;&lt;a href=&quot;#cb5-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-13&quot;&gt;&lt;a href=&quot;#cb5-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL의 구조가 그대로 보인다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;selectFrom(o).where(...).fetch()&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;select o from Order o where ...&lt;/code&gt;의 메서드 체인 번역이다.
컴파일 타임에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QOrder&lt;/code&gt;라는 클래스가 생성돼
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;o.status.eq(status)&lt;/code&gt;가 전부 타입 체크된다. 오타는 컴파일
에러, 타입 불일치도 컴파일 에러다.&lt;/p&gt;
&lt;h3 id=&quot;4-1-qclass와-jpaqueryfactory&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) QClass와 JPAQueryFactory&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl은 엔티티마다 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;QClass&lt;/strong&gt;(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QOrder&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QMember&lt;/code&gt;)를 생성한다. 이건 Annotation Processor가 빌드 시
만들어낸다. 쿼리 시작점은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JPAQueryFactory&lt;/code&gt;이고, Spring
Bean으로 등록해 주입받아 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Configuration&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; QuerydslConfig &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@PersistenceContext&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; EntityManager em&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Bean&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; JPAQueryFactory &lt;span class=&quot;fu&quot;&gt;queryFactory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;JPAQueryFactory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;em&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl이 아니라 &amp;quot;Querydsl-JPA&amp;quot;라고 부르는 이유가 여기 있다.
Querydsl은 SQL, JPA, MongoDB, Lucene 등 여러 저장소를 지원하는 상위
프레임워크이고, JPA용 모듈이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;querydsl-jpa&lt;/code&gt;다. 우리가 쓰는 건
이 JPA 모듈이다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-booleanbuilder-vs-where-varargs&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2) BooleanBuilder vs
where varargs&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl에서 조건을 동적으로 조립하는 방법은 두 가지다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanBuilder&lt;/code&gt;는 앞 예제에서 본 것처럼
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and/or&lt;/code&gt;를 누적한다. 다른 방법은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where(...)&lt;/code&gt;에
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;varargs로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanExpression&lt;/code&gt;을 나열&lt;/strong&gt;하고, 각
조건은 조건부로 null을 반환하게 만드는 것이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;fu&quot;&gt;memberIdEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-9&quot;&gt;&lt;a href=&quot;#cb7-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-10&quot;&gt;&lt;a href=&quot;#cb7-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus s&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-11&quot;&gt;&lt;a href=&quot;#cb7-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; s &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-12&quot;&gt;&lt;a href=&quot;#cb7-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;는 인자 중 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;null을 자동으로
무시&lt;/strong&gt;한다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanBuilder&lt;/code&gt;를 직접 조립하는 방식보다
더 선언적이고, 각 조건을 메서드로 분리하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재사용&lt;/strong&gt;도
쉬워진다. 이 패턴은 &lt;a href=&quot;#8-querydsl-booleanexpression-분리-패턴&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§8
BooleanExpression 분리 패턴&lt;/a&gt;에서 자세히 다룬다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-qclass-생성과-boot-3--hibernate-6-호환&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5) QClass 생성과 Boot
3 + Hibernate 6 호환&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl의 가장 흔한 도입 실패는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;QClass가 생성되지 않는
것&lt;/strong&gt;이다. 컴파일이 되는데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QOrder&lt;/code&gt;를 import할 수 없다,
또는 생성은 됐는데 빌드를 다시 하니 사라진다 같은 문제다. 원인은 대부분
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Annotation Processor 설정&lt;/strong&gt;과 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Jakarta classifier
누락&lt;/strong&gt; 두 가지에 있다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-jakarta-classifier는-필수&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) Jakarta classifier는
필수&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Boot 3.x는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;javax.persistence&lt;/code&gt; 대신
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jakarta.persistence&lt;/code&gt;를 쓴다. Hibernate 6도 마찬가지다.
Querydsl은 기본 배포가 여전히 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;javax&lt;/code&gt; 기반이므로,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Boot 3 + Hibernate 6 환경에서는 반드시 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jakarta&lt;/code&gt;
classifier를 붙여야 한다&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode groovy&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode groovy&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;dependencies &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    implementation &lt;span class=&quot;st&quot;&gt;&amp;quot;com.querydsl:querydsl-jpa:5.1.0:jakarta&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    annotationProcessor &lt;span class=&quot;st&quot;&gt;&amp;quot;com.querydsl:querydsl-apt:5.1.0:jakarta&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    annotationProcessor &lt;span class=&quot;st&quot;&gt;&amp;quot;jakarta.annotation:jakarta.annotation-api&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    annotationProcessor &lt;span class=&quot;st&quot;&gt;&amp;quot;jakarta.persistence:jakarta.persistence-api&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;classifier 없이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;com.querydsl:querydsl-jpa:5.1.0&lt;/code&gt;만 쓰면
APT가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;javax.persistence&lt;/code&gt; 타입을 못 찾아 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;빌드
시&lt;/strong&gt; QClass 생성이 실패하거나, 빌드가 통과되더라도 런타임에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NoClassDefFoundError: javax/persistence/Entity&lt;/code&gt;가 터진다.
또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Entity&lt;/code&gt;가 달린 엔티티를 APT가 인식하지 못해 QClass가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;아예 생성되지 않는다&lt;/strong&gt;. 이 증상이 Querydsl 도입 실패
사례의 8할이다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-qclass-생성-위치&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) QClass 생성 위치&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Gradle 기준 QClass는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;build/generated/sources/annotationProcessor/java/main&lt;/code&gt;에
생성된다. IDE가 이 경로를 소스 루트로 인식해야 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QOrder&lt;/code&gt;를
import할 수 있다. IntelliJ는 대부분 자동 인식하지만, 인식 못 하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Mark Directory as → Generated Sources Root&lt;/code&gt;로 수동
설정한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;clean 빌드를 하면 QClass가 사라지는 경우는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;clean&lt;/code&gt;
태스크가 해당 디렉터리를 날리고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;compileJava&lt;/code&gt;가 재생성하지
않는 설정 문제다. 빌드 로그에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Note: Generating QOrder&lt;/code&gt;가
보이는지 확인하고, 안 보이면 APT가 돌지 않은 것이다.&lt;/p&gt;
&lt;h3 id=&quot;5-3-entity-스캔-범위&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-3) @Entity 스캔 범위&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;QClass는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Entity&lt;/code&gt;가 붙은 클래스에 대해서만 생성된다.
MappedSuperclass, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;은 별도 설정이 필요하지
않다(APT가 같이 처리한다). 반면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;멀티 모듈 프로젝트&lt;/strong&gt;에서
엔티티가 다른 모듈에 있으면 해당 모듈에도
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;annotationProcessor&lt;/code&gt; 설정을 넣어줘야 한다. 빌드 모듈마다
독립적이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-spring-data-specification-criteria의-spring-래퍼&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6) Spring
Data Specification: Criteria의 Spring 래퍼&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification&lt;/code&gt;은 Spring Data JPA가 제공하는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaSpecificationExecutor&lt;/code&gt; 인터페이스와 함께 쓰는 도구다.
본질은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Criteria API의 함수형 래퍼&lt;/strong&gt;다. 조건 하나를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification&amp;lt;T&amp;gt;&lt;/code&gt;로 만들고, 여러 개를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;or&lt;/code&gt;로 합성한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;,&lt;/span&gt; JpaSpecificationExecutor&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JpaSpecificationExecutor&lt;/code&gt;를 상속하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findAll(Specification)&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findOne(Specification)&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;count(Specification)&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findAll(Specification, Pageable)&lt;/code&gt; 같은 메서드가 공짜로
추가된다. 개별 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification&lt;/code&gt;은 함수 인터페이스다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderSpecs &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus s&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            s &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-8&quot;&gt;&lt;a href=&quot;#cb10-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; min&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-9&quot;&gt;&lt;a href=&quot;#cb10-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-10&quot;&gt;&lt;a href=&quot;#cb10-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            min &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ge&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;amount&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; min&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-11&quot;&gt;&lt;a href=&quot;#cb10-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-12&quot;&gt;&lt;a href=&quot;#cb10-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건이 null이면 람다 자체가 null을 반환한다&lt;/strong&gt;. Spring
Data는 합성 과정에서 null &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Predicate&lt;/code&gt;를 자동으로 걸러낸다.
이게 Specification이 동적 쿼리에서 빛나는 이유다 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;null 처리가
내장돼 있어 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;문 분기가 사라진다&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;6-1-합성&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-1) 합성&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification&lt;/code&gt;에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;or&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;not&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;
정적 메서드가 있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; spec &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Specification&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;minAmount&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;memberIdEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;memberId&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; result &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;spec&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification.where(null)&lt;/code&gt;도 안전하다. Spring Data가
null을 &amp;quot;조건 없음&amp;quot;으로 처리한다. 결과적으로 JPQL 문자열 빌더의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt; 체인이 선언적인 합성 표현으로 바뀐다.&lt;/p&gt;
&lt;h3 id=&quot;6-2-그래도-criteria의-자손이다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-2) 그래도 Criteria의
자손이다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;편리하지만 Specification의 람다 내부는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;여전히 Criteria
API&lt;/strong&gt;다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;root.get(&amp;quot;status&amp;quot;)&lt;/code&gt;처럼 문자열 키를 쓰면 &lt;a href=&quot;#3-1-metamodel--진짜-타입-안전의-조건&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§3-1 metamodel&lt;/a&gt;에서 본
함정이 그대로 나타난다. metamodel을 같이 쓰면 타입 안전하지만,
Specification 람다의 장황함은 Querydsl 대비 여전하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// metamodel 적용&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus s&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        s &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Order_&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Specification은 &amp;quot;Criteria의 가독성을 약간 개선한 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;합성
도구&lt;/strong&gt;&amp;quot;다. 타입 안전성과 표현력은 Criteria 그대로고, 이점은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;null 조건 자동 무시와 메서드 합성&lt;/strong&gt;에 집중돼 있다. 조건
수가 그리 많지 않고 Querydsl을 추가하기 부담스러운 프로젝트에서 괜찮은
타협점이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-null-안전한-specification-합성&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) Null 안전한 Specification
합성&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Specification의 &amp;quot;null이면 자동 무시&amp;quot; 동작은 편리하지만 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정확히
어디까지 자동인지&lt;/strong&gt;를 알아야 실수가 없다. 규칙은 간단하다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification&lt;/code&gt; 람다가 null
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Predicate&lt;/code&gt;를 반환&lt;/strong&gt;하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;and&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;or&lt;/code&gt; 합성에서 제외된다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;**&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification.where(null)&lt;/code&gt;**은 빈 조건으로
시작한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;lambda 안에서 예외&lt;/strong&gt;를 던지면 그 조건만 무시되는 게
아니라 쿼리 전체가 터진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서 깔끔한 패턴은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건마다 null 가드를 람다 첫 줄에 두는
것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;createdBetween&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDate from&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; LocalDate to&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; to &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;lessThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;createdAt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; to&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atTime&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;to &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;greaterThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;createdAt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; from&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atStartOfDay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-6&quot;&gt;&lt;a href=&quot;#cb13-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;createdAt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; from&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atStartOfDay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; to&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atTime&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-7&quot;&gt;&lt;a href=&quot;#cb13-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-8&quot;&gt;&lt;a href=&quot;#cb13-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이런 복잡한 조건도 Specification 하나로 감싸면 호출부는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;specs.and(createdBetween(from, to))&lt;/code&gt; 한 줄이다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건의 복잡도가 호출부로 새지 않는다&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;7-1-여러-specification-조합&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-1) 여러 Specification 조합&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여러 조건이 있는 검색은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;서비스 레이어에서
합성&lt;/strong&gt;한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Page&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSearchCondition cond&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; spec &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Specification&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;minAmount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createdBetween&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSpecs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;memberNameLike&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;memberName&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;spec&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;조건 객체(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderSearchCondition&lt;/code&gt;)가 null이어도 각
Specification의 null 가드가 처리한다. 호출부에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;가 한 줄도
없다. 여기서 틀리기 쉬운 건
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Specification.where(a).and(b)&lt;/code&gt;의 순서&lt;/strong&gt;다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;합성된 WHERE 조건은 합성 순서대로 AND로 이어진다. 다만 실제 조건
평가 순서는 DB 옵티마이저가 재정렬하므로 쿼리 계획상 특정 순서를
가정하지 말 것.&lt;/strong&gt;&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-querydsl-booleanexpression-분리-패턴&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8) Querydsl
BooleanExpression 분리 패턴&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl에서 동적 조건을 가장 깔끔하게 쓰는 관용구가
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanExpression&lt;/code&gt; 분리&lt;/strong&gt;다. 각 조건을
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;private BooleanExpression&lt;/code&gt;을 반환하는 메서드로 분리하고,
조건이 null이면 그냥 null을 반환한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Repository&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderQueryRepository &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; JPAQueryFactory queryFactory&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSearchCondition cond&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        QOrder o &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-12&quot;&gt;&lt;a href=&quot;#cb15-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-13&quot;&gt;&lt;a href=&quot;#cb15-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-14&quot;&gt;&lt;a href=&quot;#cb15-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;minAmount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-15&quot;&gt;&lt;a href=&quot;#cb15-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;createdBetween&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-16&quot;&gt;&lt;a href=&quot;#cb15-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                &lt;span class=&quot;fu&quot;&gt;memberIdEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;memberId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-17&quot;&gt;&lt;a href=&quot;#cb15-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-18&quot;&gt;&lt;a href=&quot;#cb15-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-19&quot;&gt;&lt;a href=&quot;#cb15-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-20&quot;&gt;&lt;a href=&quot;#cb15-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-21&quot;&gt;&lt;a href=&quot;#cb15-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus s&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-22&quot;&gt;&lt;a href=&quot;#cb15-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; s &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-23&quot;&gt;&lt;a href=&quot;#cb15-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-24&quot;&gt;&lt;a href=&quot;#cb15-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-25&quot;&gt;&lt;a href=&quot;#cb15-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;amountGoe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; min&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-26&quot;&gt;&lt;a href=&quot;#cb15-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; min &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;goe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;min&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-27&quot;&gt;&lt;a href=&quot;#cb15-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-28&quot;&gt;&lt;a href=&quot;#cb15-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-29&quot;&gt;&lt;a href=&quot;#cb15-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;memberIdEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-30&quot;&gt;&lt;a href=&quot;#cb15-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; id &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-31&quot;&gt;&lt;a href=&quot;#cb15-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-32&quot;&gt;&lt;a href=&quot;#cb15-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-33&quot;&gt;&lt;a href=&quot;#cb15-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;createdBetween&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalDate from&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; LocalDate to&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-34&quot;&gt;&lt;a href=&quot;#cb15-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; to &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-35&quot;&gt;&lt;a href=&quot;#cb15-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;loe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;to&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atTime&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-36&quot;&gt;&lt;a href=&quot;#cb15-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;to &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;goe&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atStartOfDay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-37&quot;&gt;&lt;a href=&quot;#cb15-37&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; QOrder&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;from&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atStartOfDay&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(),&lt;/span&gt; to&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;atTime&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;LocalTime&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-38&quot;&gt;&lt;a href=&quot;#cb15-38&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-39&quot;&gt;&lt;a href=&quot;#cb15-39&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 패턴의 장점은 세 가지다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-null-자동-무시&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) null 자동 무시&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where(a, b, c)&lt;/code&gt;의 varargs에 null이 섞이면 Querydsl은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;해당 조건을 조용히 버린다&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanBuilder&lt;/code&gt;로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;를 쓰는 방식과 결과는
같지만, 코드가 선언적이고 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;if&lt;/code&gt;가 한 줄도 없다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-재사용&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) 재사용&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;memberIdEq&lt;/code&gt; 같은 작은 조건 메서드는 다른 쿼리에서도 쓸 수
있다. 여러 검색 API에 공통으로 들어가는 조건은 별도
클래스(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderPredicates&lt;/code&gt;)로 빼두면 쿼리 레포가
깔끔해진다.&lt;/p&gt;
&lt;h3 id=&quot;8-3-합성도-쉽다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-3) 합성도 쉽다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanExpression&lt;/code&gt;끼리는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;.and()&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;.or()&lt;/code&gt;로 합칠 수 있다. 다만 null 체크를 넣어야 안전하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; BooleanExpression &lt;span class=&quot;fu&quot;&gt;statusActiveOrHold&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus s&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ACTIVE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;statusEq&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// s가 null이면 statusEq(s)가 null → NullPointerException 위험&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanExpression.or(null)&lt;/code&gt;은 예외로 터진다 (구현 경로에
따라 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NullPointerException&lt;/code&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IllegalArgumentException&lt;/code&gt;). varargs 레벨의 &amp;quot;null 자동
무시&amp;quot;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Querydsl이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;에서 직접 처리&lt;/strong&gt;하는
것이지, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BooleanExpression&lt;/code&gt;의 연산자에는 적용되지 않는다.
메서드 내부에서 합성할 때는 null 체크를 직접 해야 한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. &amp;quot;Querydsl은 null 안전하다&amp;quot;는 말은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;의 varargs 한정이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-네-가지-비교와-선택-기준&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) 네 가지 비교와 선택 기준&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;네 도구의 성격을 한 화면에 정리한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;도구&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;타입 안전&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;가독성&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;코드 생성기&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;학습 비용&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;외부 의존성&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 빌더&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음 (단순한 경우)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;불필요&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Criteria API&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O (metamodel 필요)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;매우 낮음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Hibernate JPA Modelgen&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음 (표준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Querydsl&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;querydsl-apt (QClass)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;querydsl-jpa&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Specification&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;O (metamodel 필요)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;선택&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음 (Spring Data 내장)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&amp;quot;타입 안전&amp;quot;이라고 해서 전부 같은 수준이 아니다. Criteria와
Specification은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;metamodel을 붙여야 진짜 타입 안전&lt;/strong&gt;이고,
Querydsl은 기본적으로 타입 안전하다. 가독성 차이는 코드 줄 수로 체감된다
— 같은 쿼리를 Criteria로 쓰면 Querydsl 대비 2~3배 길어진다.&lt;/p&gt;
&lt;h3 id=&quot;9-1-의사결정-플로우&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) 의사결정 플로우&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;37_jpa-dynamic-query-jpql-criteria-querydsl-specification-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;37_jpa-dynamic-query-jpql-criteria-querydsl-specification-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 플로우가 말하는 건 하나다. **&amp;quot;외부 의존성 OK + 조건이 복잡 =
Querydsl&amp;quot;**이 현대 JPA의 기본값이다. 그 외의 경우에만 나머지 세 개를
고른다. Criteria API가 표준임에도 불구하고 플로우에서 말단으로 밀리는
이유는 가독성 때문이다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-실무에서-가장-흔한-조합&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) 실무에서 가장 흔한 조합&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;현장에서 가장 많이 보이는 패턴은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;Spring Data JPA 메서드 파생
+ Querydsl 커스텀&amp;quot;&lt;/strong&gt; 조합이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 단순 조회는 메서드 파생&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepository &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; JpaRepository&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;,&lt;/span&gt; OrderRepositoryCustom &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;findByStatus&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderStatus status&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 복잡한 동적 쿼리는 Querydsl 커스텀&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;interface&lt;/span&gt; OrderRepositoryCustom &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;searchComplex&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSearchCondition cond&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-9&quot;&gt;&lt;a href=&quot;#cb17-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-10&quot;&gt;&lt;a href=&quot;#cb17-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-11&quot;&gt;&lt;a href=&quot;#cb17-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@RequiredArgsConstructor&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-12&quot;&gt;&lt;a href=&quot;#cb17-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderRepositoryImpl &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; OrderRepositoryCustom &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-13&quot;&gt;&lt;a href=&quot;#cb17-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;final&lt;/span&gt; JPAQueryFactory queryFactory&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-14&quot;&gt;&lt;a href=&quot;#cb17-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-15&quot;&gt;&lt;a href=&quot;#cb17-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-16&quot;&gt;&lt;a href=&quot;#cb17-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;searchComplex&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSearchCondition cond&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-17&quot;&gt;&lt;a href=&quot;#cb17-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// Querydsl 로직&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-18&quot;&gt;&lt;a href=&quot;#cb17-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-19&quot;&gt;&lt;a href=&quot;#cb17-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Spring Data JPA는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;xxxRepository&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;xxxRepositoryCustom&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;xxxRepositoryImpl&lt;/code&gt; 네이밍
관례를 인식해 두 인터페이스를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;하나의 Repository로
합친다&lt;/strong&gt;. 호출부에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orderRepository&lt;/code&gt;만 주입받으면
메서드 파생과 커스텀 Querydsl을 다 쓸 수 있다. 이 조합이 사실상
표준이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-dto-projection과-동적-정렬페이징&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) DTO Projection과 동적
정렬/페이징&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;동적 쿼리 도구를 골랐다고 끝이 아니다. 실무에서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Projection,
정렬, 페이징&lt;/strong&gt;이 같이 얽힌다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-dto-projection&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) DTO Projection&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티 그대로가 아니라 응답 DTO로 바로 받고 싶으면 Querydsl에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Projections&lt;/code&gt;를 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderSummary&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;BigDecimal&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; memberName&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-2&quot;&gt;&lt;a href=&quot;#cb18-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-3&quot;&gt;&lt;a href=&quot;#cb18-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;OrderSummary&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; result &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb18-4&quot;&gt;&lt;a href=&quot;#cb18-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Projections&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSummary&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-5&quot;&gt;&lt;a href=&quot;#cb18-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-6&quot;&gt;&lt;a href=&quot;#cb18-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-7&quot;&gt;&lt;a href=&quot;#cb18-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; m&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-8&quot;&gt;&lt;a href=&quot;#cb18-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-9&quot;&gt;&lt;a href=&quot;#cb18-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Projections.constructor&lt;/code&gt;는 생성자 호출이다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Projections.fields&lt;/code&gt;는 리플렉션으로 필드에 직접
주입(세터 불필요)&lt;/strong&gt;, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Projections.bean&lt;/code&gt;은
자바빈즈 세터(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setXxx&lt;/code&gt;) 주입&lt;/strong&gt;. 세 가지 중
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;constructor&lt;/code&gt;를 기본으로&lt;/strong&gt; 쓴다. record와
조합이 제일 깔끔하고, 필드 순서만 맞으면 컴파일 시점에 검사된다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPQL에서도 같은 패턴이 된다 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;select new com.x.OrderSummary(o.id, o.amount, o.member.name) from Order o ...&lt;/code&gt;.
Constructor Expression이라고 부른다.&lt;/p&gt;
&lt;h3 id=&quot;10-2-동적-정렬&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2) 동적 정렬&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Sort.by(&amp;quot;createdAt&amp;quot;).descending()&lt;/code&gt;을 Querydsl에 전달하는
방법은 두 가지다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 1) OrderSpecifier 직접 만들기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;OrderSpecifier&lt;span class=&quot;op&quot;&gt;&amp;lt;?&amp;gt;&lt;/span&gt; order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; cond&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;sortByAmountDesc&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-4&quot;&gt;&lt;a href=&quot;#cb19-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-5&quot;&gt;&lt;a href=&quot;#cb19-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-6&quot;&gt;&lt;a href=&quot;#cb19-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;queryFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 2) Spring Data Sort → Querydsl OrderSpecifier 변환&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// com.querydsl.core.types.Order 기준 (엔티티 Order와 이름 충돌 주의)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Sort sort &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; Sort&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;by&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;createdAt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;descending&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-4&quot;&gt;&lt;a href=&quot;#cb20-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;JPAQuery&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; query &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; queryFactory&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-5&quot;&gt;&lt;a href=&quot;#cb20-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;cf&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;Sort&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Order&lt;/span&gt; s &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; sort&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-6&quot;&gt;&lt;a href=&quot;#cb20-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    PathBuilder&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; path &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; PathBuilder&lt;span class=&quot;op&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;order&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-7&quot;&gt;&lt;a href=&quot;#cb20-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    query&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderSpecifier&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-8&quot;&gt;&lt;a href=&quot;#cb20-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        s&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;isAscending&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;?&lt;/span&gt; com&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;querydsl&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;ASC&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; com&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;querydsl&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-9&quot;&gt;&lt;a href=&quot;#cb20-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        path&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())));&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-10&quot;&gt;&lt;a href=&quot;#cb20-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;첫 번째가 타입 안전하고 깔끔하지만, 정렬 키 목록이 대량이면 두 번째
변환이 필요하다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PathBuilder&lt;/code&gt;는 문자열 경로를 Querydsl
표현식으로 바꿔주는 도구다. 컴파일 타임 안전성은 잃는 대신 유연성을
얻는다.&lt;/p&gt;
&lt;h3 id=&quot;10-3-페이징&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-3) 페이징&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Querydsl + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Pageable&lt;/code&gt; 조합은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PageableExecutionUtils&lt;/code&gt;가 표준 관용구다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb21&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb21-1&quot;&gt;&lt;a href=&quot;#cb21-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; Page&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderSearchCondition cond&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Pageable&lt;/span&gt; pageable&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-2&quot;&gt;&lt;a href=&quot;#cb21-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; content &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb21-3&quot;&gt;&lt;a href=&quot;#cb21-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;selectFrom&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-4&quot;&gt;&lt;a href=&quot;#cb21-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-5&quot;&gt;&lt;a href=&quot;#cb21-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getOffset&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-6&quot;&gt;&lt;a href=&quot;#cb21-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;pageable&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getPageSize&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-7&quot;&gt;&lt;a href=&quot;#cb21-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-8&quot;&gt;&lt;a href=&quot;#cb21-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-9&quot;&gt;&lt;a href=&quot;#cb21-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    JPAQuery&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; countQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb21-10&quot;&gt;&lt;a href=&quot;#cb21-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-11&quot;&gt;&lt;a href=&quot;#cb21-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-12&quot;&gt;&lt;a href=&quot;#cb21-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-13&quot;&gt;&lt;a href=&quot;#cb21-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-14&quot;&gt;&lt;a href=&quot;#cb21-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; PageableExecutionUtils&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getPage&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; pageable&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; countQuery&lt;span class=&quot;op&quot;&gt;::&lt;/span&gt;fetchOne&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-15&quot;&gt;&lt;a href=&quot;#cb21-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PageableExecutionUtils.getPage&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;count 쿼리를
항상 실행하지 않는다&lt;/strong&gt;. 페이지가 첫 페이지이면서 content 크기가
페이지 크기보다 작으면 count가 불필요하므로 자동으로 생략한다. 이 최적화
덕분에 count 쿼리는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;메서드
참조&lt;/strong&gt;(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;countQuery::fetchOne&lt;/code&gt;)로 넘겨 게으르게
평가한다.&lt;/p&gt;
&lt;h3 id=&quot;10-4-count-쿼리는-분리가-유리&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-4) count 쿼리는 분리가
유리&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;count 쿼리는 본 쿼리와 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;구조가 다를 때 성능이 가장
좋다&lt;/strong&gt;. JOIN이 필요 없고, fetch join도 제거하고, DTO Projection도
없다. content 쿼리와 같은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;where&lt;/code&gt;만 공유하고
JOIN/Projection은 벗긴 별도 쿼리로 작성하면 대형 테이블에서 체감 차이가
크다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb22&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb22-1&quot;&gt;&lt;a href=&quot;#cb22-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;JPAQuery&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; countQuery &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; queryFactory&lt;/span&gt;
&lt;span id=&quot;cb22-2&quot;&gt;&lt;a href=&quot;#cb22-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-3&quot;&gt;&lt;a href=&quot;#cb22-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-4&quot;&gt;&lt;a href=&quot;#cb22-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// content 쿼리에 있던 fetch join 제거&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-5&quot;&gt;&lt;a href=&quot;#cb22-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;statusEq&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; amountGoe&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// where만 동일&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;count와 content가 똑같은 JOIN을 쓰면 DB는 불필요한 조인을 두 번
돌린다. 분리하는 게 규율이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-함정-종합-metamodel--distinct--fetch-join&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 함정 종합:
metamodel / distinct / fetch join&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;도구별로 자주 터지는 함정 네 가지를 한 번에 정리한다.&lt;/p&gt;
&lt;h3 id=&quot;11-1-criteriaspecification의-문자열-키&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-1)
Criteria/Specification의 문자열 키&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;root.get(&amp;quot;status&amp;quot;)&lt;/code&gt;는 타입 안전이 아니다. 오타가
런타임에서 터진다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;metamodel을 생성&lt;/strong&gt;해
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;root.get(Order_.status)&lt;/code&gt;로 쓰는 게 Criteria/Specification의
올바른 사용법이다. metamodel 없이 Criteria를 쓰면 &amp;quot;JPQL 문자열 쿼리를
객체 API로 옮겨 장황하게 만든 것&amp;quot; 이상의 이점이 없다.&lt;/p&gt;
&lt;h3 id=&quot;11-2-specification--join--distinct&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-2) Specification + JOIN +
distinct&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Specification에서 컬렉션 연관을 JOIN하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;결과가 부모 ×
자식만큼 늘어난다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;distinct&lt;/code&gt;를 안 걸면 같은 부모가
여러 번 나와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;content.size()&lt;/code&gt;와 실제 고유 개수가 다르다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb23&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb23-1&quot;&gt;&lt;a href=&quot;#cb23-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: distinct 누락&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-2&quot;&gt;&lt;a href=&quot;#cb23-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;hasItemCategory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; category&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-3&quot;&gt;&lt;a href=&quot;#cb23-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-4&quot;&gt;&lt;a href=&quot;#cb23-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Join&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; OrderItem&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; items &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;items&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-5&quot;&gt;&lt;a href=&quot;#cb23-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;items&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; category&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-6&quot;&gt;&lt;a href=&quot;#cb23-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-7&quot;&gt;&lt;a href=&quot;#cb23-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb24&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb24-1&quot;&gt;&lt;a href=&quot;#cb24-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: distinct 명시&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-2&quot;&gt;&lt;a href=&quot;#cb24-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;hasItemCategory&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; category&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-3&quot;&gt;&lt;a href=&quot;#cb24-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-4&quot;&gt;&lt;a href=&quot;#cb24-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        query&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 중복 제거&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-5&quot;&gt;&lt;a href=&quot;#cb24-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        Join&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; OrderItem&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; items &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;items&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-6&quot;&gt;&lt;a href=&quot;#cb24-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;items&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;category&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;),&lt;/span&gt; category&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-7&quot;&gt;&lt;a href=&quot;#cb24-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb24-8&quot;&gt;&lt;a href=&quot;#cb24-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;query.distinct(true)&lt;/code&gt;는 Specification 람다 내부의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CriteriaQuery&lt;/code&gt;를 받아 호출한다. 여러 Specification이 JOIN을
걸 수 있는 경우 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;어느 한 곳에서라도 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;distinct(true)&lt;/code&gt;를
걸어두는 게 안전&lt;/strong&gt;하다. Querydsl에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;selectDistinct(o)&lt;/code&gt; 또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;.distinct()&lt;/code&gt;로 같은
의도를 표현한다.&lt;/p&gt;
&lt;h3 id=&quot;11-3-specification--fetch-join&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-3) Specification + fetch
join&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Specification 안에서 fetch join을 쓰려면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;root.fetch(&amp;quot;items&amp;quot;)&lt;/code&gt;로 명시한다. 단, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;count
쿼리에서는 fetch가 금지&lt;/strong&gt;된다. Pageable과 같이 쓰는 경우
Specification이 count 쿼리에서도 fetch를 걸어 에러가 나는 경우가
있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb25&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb25-1&quot;&gt;&lt;a href=&quot;#cb25-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;static&lt;/span&gt; Specification&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;fetchItems&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-2&quot;&gt;&lt;a href=&quot;#cb25-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-3&quot;&gt;&lt;a href=&quot;#cb25-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;co&quot;&gt;// count 쿼리에서는 fetch 건너뛰기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-4&quot;&gt;&lt;a href=&quot;#cb25-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultType&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;!=&lt;/span&gt; query&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultType&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-5&quot;&gt;&lt;a href=&quot;#cb25-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            root&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;items&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; JoinType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LEFT&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-6&quot;&gt;&lt;a href=&quot;#cb25-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-7&quot;&gt;&lt;a href=&quot;#cb25-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 조건은 없고 fetch만&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-8&quot;&gt;&lt;a href=&quot;#cb25-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb25-9&quot;&gt;&lt;a href=&quot;#cb25-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;query.getResultType()&lt;/code&gt;이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Long.class&lt;/code&gt;이면
count 쿼리라는 뜻이다. Specification이 이 트릭을 쓰지 않으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list&lt;/code&gt;가
터진다.&lt;/p&gt;
&lt;h3 id=&quot;11-4-querydsl-qclass-미생성&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;11-4) Querydsl QClass 미생성&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;가장 흔한 빌드 실패. 원인은 &lt;a href=&quot;#5-qclass-생성과-boot-3--hibernate-6-호환&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§5&lt;/a&gt;에서 본
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Jakarta classifier 누락&lt;/strong&gt; 또는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;annotationProcessor&lt;/code&gt; 의존성 미설정. 증상은 &amp;quot;컴파일은 되는데
IDE에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;QOrder&lt;/code&gt;를 인식 못 함&amp;quot; 또는 &amp;quot;빌드 시 QClass 자체가
생성 안 됨&amp;quot;. 해결은 build 파일의 의존성 재점검 후 clean 빌드.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;멀티 모듈에서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티가 있는 모듈마다&lt;/strong&gt; APT 설정이
필요하다. entity 모듈 따로, service 모듈 따로라면 entity 모듈에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;annotationProcessor&lt;/code&gt;를 넣고, service 모듈은 entity 모듈을
의존하면 생성된 QClass를 쓴다. APT 설정이 service 모듈에 있으면 엔티티가
없어 아무것도 생성되지 않는다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;동적 조건이 2~3개 이하로 고정이면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Query&lt;/code&gt;에
JPQL&lt;/strong&gt;로 끝내라. 도구를 꺼낼 이유가 없다. 동적 쿼리 도구는 문자열
빌더의 고통이 실제로 있을 때만 꺼낸다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;외부 의존성 추가가 가능하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Querydsl을 기본&lt;/strong&gt;으로
간다. Criteria API는 표준이지만 가독성에서 밀리고, Specification은
Criteria의 래퍼라 같은 장황함을 공유한다. Querydsl의 유일한 장벽은
QClass 생성 설정이다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Boot 3 + Hibernate 6면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;querydsl-jpa:5.x:jakarta&lt;/code&gt;
classifier는 반드시&lt;/strong&gt; 확인. 도입 실패의 8할이 여기다. APT
의존성도 같이 넣어야 QClass가 생성된다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Spring Data만 쓰는 프로젝트에서 외부 라이브러리를 피해야 한다면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Specification + metamodel&lt;/strong&gt;이 최선. metamodel 없이 쓰는
Specification은 타입 안전을 반만 챙긴 상태다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;실무 관용구는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;Spring Data 메서드 파생 + Querydsl
커스텀&lt;/strong&gt;. 단순 조회는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;findByStatus&lt;/code&gt;로, 복잡한 검색은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderRepositoryCustom&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Impl&lt;/code&gt;로 분리. 한
Repository로 합쳐져 호출부는 깔끔하다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;페이징은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;PageableExecutionUtils.getPage&lt;/code&gt; + count
쿼리 분리&lt;/strong&gt; 관용구를 기본으로. content와 같은 JOIN을 count가
돌리는 건 낭비다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Specification에서 JOIN이 들어가면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;query.distinct(true)&lt;/code&gt; 습관적으로&lt;/strong&gt; 체크. 중복
결과가 부풀어 집계 오류가 나는 사고가 실제로 많다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;Criteria/Specification을 쓴다면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;metamodel 생성&lt;/strong&gt;을
빌드 파이프라인에 넣어둔다. 문자열 키로 타협하면 리팩터 한 번에 쿼리가
조용히 깨진다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;13-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;13) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA 동적 쿼리의 현실적 기본값은 Querydsl&lt;/strong&gt;이고,
Criteria/Specification은 외부 의존성이 막힌 환경의 차선책이며, JPQL
빌더는 조건이 2~3개 이하일 때만 정당화된다. Boot 3 + Hibernate 6에서
Querydsl을 쓰려면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jakarta&lt;/code&gt; classifier와 APT
설정&lt;/strong&gt;이 필수이고, 이걸 놓치면 QClass 자체가 생성되지 않는다.
도구가 네 개여도 선택 기준은 하나 — &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건의 복잡도에 비해 어느
도구가 코드를 가장 덜 깨뜨리는가&lt;/strong&gt;.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: JPA, Querydsl, Spring Data JPA, Criteria API,
Specification, JPQL, 동적 쿼리, Hibernate, Spring Boot 3, metamodel&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>criteria api</category>
      <category>Hibernate</category>
      <category>JPA</category>
      <category>JPQL</category>
      <category>metamodel</category>
      <category>querydsl</category>
      <category>Specification</category>
      <category>Spring Boot 3</category>
      <category>spring data jpa</category>
      <category>동적 쿼리</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/422</guid>
      <comments>https://dding-shark.tistory.com/422#entry422comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:25:26 +0900</pubDate>
    </item>
    <item>
      <title>JPA 상속 매핑 &amp;mdash; SINGLE_TABLE&amp;middot;JOINED&amp;middot;TABLE_PER_CLASS와 @MappedSuperclass</title>
      <link>https://dding-shark.tistory.com/421</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;jpa-상속-매핑--single_tablejoinedtable_per_class와-mappedsuperclass&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;JPA
상속 매핑 — SINGLE_TABLE·JOINED·TABLE_PER_CLASS와 @MappedSuperclass&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;객체 지향 코드에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Payment&lt;/code&gt;를 추상 클래스로 두고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BankTransfer&lt;/code&gt;가 상속받는 구조는
자연스럽다. 문제는 이걸 관계형 DB로 옮기는 순간부터다. 테이블 하나에 다
욱여넣을지, 부모-자식으로 쪼갤지, 아예 자식마다 독립 테이블을 팔지.
선택마다 트레이드오프가 명확해서, 첫 설계에서 잘못 고르면 쿼리 튜닝이든
스키마 변경이든 몇 달 뒤에 비용이 크게 돌아온다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA는 이 고민을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt; 전략 셋 —
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SINGLE_TABLE&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JOINED&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE_PER_CLASS&lt;/code&gt; — 으로 풀고, 여기에 &amp;quot;상속은 아니지만 공통
필드만 공유하고 싶다&amp;quot;는 요구를 위해 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;를
따로 얹었다. 이름이 비슷한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;까지 끼면 세 축의
역할이 한 번에 헷갈린다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SINGLE_TABLE에서 서브 컬럼을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOT NULL&lt;/code&gt;로 못
건다&lt;/strong&gt; — 검증 책임이 애플리케이션으로 넘어온다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JOINED는 정규화지만 쿼리마다 JOIN이 붙어 프로파일링이
필요하다&lt;/strong&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;를
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;처럼 쓰려다 다형성 쿼리가 안
먹는다&lt;/strong&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;TABLE_PER_CLASS + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IDENTITY&lt;/code&gt;는 그냥 동작하지
않는다&lt;/strong&gt; — 시퀀스 전략 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상속 → 세 전략 → 선택 기준 → @MappedSuperclass →
@Embeddable 구분 → 최적화 → 실전 함정&lt;/strong&gt; 순으로 Spring Boot 3.x /
Hibernate 6.x / Jakarta Persistence 3.x 기준으로 정리한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-상속을-db로-옮길-때의-고민&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) 상속을 DB로 옮길 때의
고민&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-single_table-기본-전략의-장단&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) SINGLE_TABLE: 기본
전략의 장단&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-discriminatorcolumn과-타입-판별&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3)
@DiscriminatorColumn과 타입 판별&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-joined-정규화의-대가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) JOINED: 정규화의 대가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-table_per_class-왜-피하는가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) TABLE_PER_CLASS: 왜
피하는가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-전략-선택-의사결정-표&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6) 전략 선택 의사결정 표&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-mappedsuperclass-엔티티-아닌-공통-필드&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7)
@MappedSuperclass: 엔티티 아닌 공통 필드&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-mappedsuperclass-vs-inheritance-vs-embeddable&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8)
@MappedSuperclass vs @Inheritance vs @Embeddable&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-dynamic-insertupdate로-single_table-최적화&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) Dynamic
Insert/Update로 SINGLE_TABLE 최적화&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-실전-함정-종합&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) 실전 함정 종합&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-상속을-db로-옮길-때의-고민&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) 상속을 DB로 옮길 때의 고민&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;객체 세계에서 상속은 &amp;quot;공통을 묶고, 특수를 확장한다&amp;quot;는 한 마디로
끝난다. 하지만 관계형 DB에는 상속 개념이 없다. 테이블은 평평한 레코드
집합이고, &amp;quot;이 row는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment&lt;/code&gt;이고 저 row는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BankTransfer&lt;/code&gt;다&amp;quot;를 표현하려면 어딘가에 타입 정보를 넣든,
테이블을 분리하든, 결국 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;구조적 선택&lt;/strong&gt;을 해야 한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA는 이 선택을 세 가지로 정형화한다. 모두 부모 쪽에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance(strategy = ...)&lt;/code&gt;를 붙이고, 자식들이 이를
물려받는 형식이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SINGLE_TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;// 또는 JOINED, TABLE_PER_CLASS&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime paidAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; cardNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; approvalCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BankTransfer &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; bankCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; accountNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-20&quot;&gt;&lt;a href=&quot;#cb1-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테이블 개수&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JOIN&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다형성 쿼리&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SINGLE_TABLE&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;1개 (공용)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;단일 테이블 스캔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JOINED&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;부모 + 자식 N개&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;필수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;LEFT JOIN N회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE_PER_CLASS&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자식 N개 (부모 없음)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;UNION ALL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;를 아예 안 붙이면 기본값은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SINGLE_TABLE&lt;/code&gt;이다. 부모를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;abstract&lt;/code&gt;로 둘지
말지는 전략과 무관하지만, 도메인상 부모를 직접 인스턴스화할 일이 없다면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;abstract&lt;/code&gt;로 두는 편이 안전하다. 구체 클래스로 두면 부모 자체
row가 만들어질 수 있어 타입 판별이 지저분해진다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 전략의 차이는 결국 **&amp;quot;다형성 쿼리의 비용을 어디에 몰아넣을
것인가&amp;quot;**로 귀결된다. SINGLE_TABLE은 스키마 유연성(NOT NULL 못 검)을
희생해서 조회 속도를 챙기고, JOINED는 조회 JOIN 비용을 감수하고 정규화를
얻고, TABLE_PER_CLASS는 단일 자식 조회는 빠르지만 다형성 쿼리에서 UNION
ALL이라는 큰 비용을 치른다. 실무 선택의 90% 이상이 SINGLE_TABLE 아니면
JOINED 둘 중 하나로 수렴하는 이유다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;36_jpa-inheritance-mapping-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;36_jpa-inheritance-mapping-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-single_table-기본-전략의-장단&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) SINGLE_TABLE: 기본 전략의
장단&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SINGLE_TABLE&lt;/code&gt;은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;모든 서브클래스의 컬럼을 한
테이블에 전부 욱여넣는다&lt;/strong&gt;. 부모의 공통 컬럼과 모든 자식의 특수
컬럼이 한 row 안에 병렬로 존재하고, 타입
구분자(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;, 기본 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DTYPE&lt;/code&gt;) 한
컬럼으로 &amp;quot;이 row는 어느 자식이냐&amp;quot;를 표현한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;를 생략하면 이게 기본이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SINGLE_TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; discriminatorType &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; DiscriminatorType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;STRING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; length &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-5&quot;&gt;&lt;a href=&quot;#cb2-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-6&quot;&gt;&lt;a href=&quot;#cb2-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-7&quot;&gt;&lt;a href=&quot;#cb2-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-8&quot;&gt;&lt;a href=&quot;#cb2-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime paidAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-9&quot;&gt;&lt;a href=&quot;#cb2-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-10&quot;&gt;&lt;a href=&quot;#cb2-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-11&quot;&gt;&lt;a href=&quot;#cb2-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-12&quot;&gt;&lt;a href=&quot;#cb2-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;CARD&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-13&quot;&gt;&lt;a href=&quot;#cb2-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-14&quot;&gt;&lt;a href=&quot;#cb2-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; cardNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-15&quot;&gt;&lt;a href=&quot;#cb2-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; approvalCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-16&quot;&gt;&lt;a href=&quot;#cb2-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-17&quot;&gt;&lt;a href=&quot;#cb2-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-18&quot;&gt;&lt;a href=&quot;#cb2-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-19&quot;&gt;&lt;a href=&quot;#cb2-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;BANK&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-20&quot;&gt;&lt;a href=&quot;#cb2-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BankTransfer &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-21&quot;&gt;&lt;a href=&quot;#cb2-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; bankCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-22&quot;&gt;&lt;a href=&quot;#cb2-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; accountNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-23&quot;&gt;&lt;a href=&quot;#cb2-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DDL은 이렇게 나온다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; payment (&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    amount BIGINT,&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    paid_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    payment_type &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;20&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    card_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),     &lt;span class=&quot;co&quot;&gt;-- CardPayment 전용, NULL 허용 강제&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    approval_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),   &lt;span class=&quot;co&quot;&gt;-- CardPayment 전용, NULL 허용 강제&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    bank_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),       &lt;span class=&quot;co&quot;&gt;-- BankTransfer 전용, NULL 허용 강제&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-9&quot;&gt;&lt;a href=&quot;#cb3-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    account_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;)   &lt;span class=&quot;co&quot;&gt;-- BankTransfer 전용, NULL 허용 강제&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-10&quot;&gt;&lt;a href=&quot;#cb3-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;2-1-장점-join-제로-쿼리-단순&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-1) 장점: JOIN 제로, 쿼리
단순&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;장점은 한 줄로 요약된다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JOIN이 없다&lt;/strong&gt;.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT p FROM Payment p WHERE p.amount &amp;gt; 10000&lt;/code&gt;은 단일
테이블 인덱스 스캔으로 끝난다. 다형성 쿼리가 많은 읽기 위주 서비스라면
성능상 가장 유리하다. 자식별
조회(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT c FROM CardPayment c&lt;/code&gt;)도
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE payment_type = &amp;#39;CARD&amp;#39;&lt;/code&gt;가 자동으로 붙어 역시 한 테이블
스캔이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;INSERT도 한 번에 끝난다. JOINED처럼 부모/자식 테이블에 각각 INSERT를
날릴 필요가 없다.&lt;/p&gt;
&lt;h3 id=&quot;2-2-단점-not-null-불가와-스키마-팽창&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;2-2) 단점: NOT NULL 불가와
스키마 팽창&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;단점도 구조적이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;서브클래스 전용 컬럼은
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOT NULL&lt;/code&gt;을 걸 수 없다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment&lt;/code&gt;가
저장될 때 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;bank_code&lt;/code&gt; 컬럼은 NULL일 수밖에 없고, 반대도
마찬가지다. DDL 레벨에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOT NULL&lt;/code&gt;을 선언하는 순간 다른 자식
저장이 전부 실패한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. Bean Validation의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@NotNull&lt;/code&gt;은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;필드 단위로 엔티티별로 걸 수 있다&lt;/strong&gt; — 즉
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment.cardNumber&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@NotNull&lt;/code&gt;을 붙이는 건
가능하다. 다만 이건 애플리케이션 레벨 검증이지 DB 제약이 아니다. 누군가
SQL로 직접 INSERT하거나 배치가 잘못되면 검증을 우회한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;또 하나, 서브클래스가 늘어날수록 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테이블 스키마가
팽창&lt;/strong&gt;한다. 자식이 10개고 각각 10개씩 특수 컬럼이 있으면 테이블은
100개 컬럼을 껴안게 되고, 대부분이 NULL인 row가 수두룩 쌓인다. 디스크
효율도 떨어지고, DDL 변경 비용도 커진다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;장점&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단점&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;쿼리 성능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JOIN 없음, 단일 테이블 스캔&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;서브 컬럼이 많으면 row 넓어짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다형성 쿼리&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가장 간단, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;WHERE type IN (...)&lt;/code&gt; 한 번&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;무결성&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;—&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;서브 전용 컬럼 NOT NULL 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;디스크&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;—&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;NULL 컬럼 다수, 스키마 팽창&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;1회&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;서브클래스 특화 컬럼이 적거나 자식 개수가 2~3개 수준이면
SINGLE_TABLE이 가장 무난하다. 반대로 자식별로 특수 컬럼이 5개 이상
쌓이면 JOINED를 검토하는 편이 낫다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-discriminatorcolumn과-타입-판별&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) @DiscriminatorColumn과
타입 판별&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;SINGLE_TABLE에서 같은 테이블의 각 row가 어느 자식인지 구분하는 장치가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;이다. 부모 쪽에 컬럼을 선언하고, 자식
쪽에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorValue&lt;/code&gt;로 값을 지정한다. 둘 다 생략하면
기본값은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DTYPE VARCHAR(31)&lt;/code&gt;에 자식 클래스의 단순 이름이
값으로 들어간다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SINGLE_TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    discriminatorType &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; DiscriminatorType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;STRING&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    length &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-10&quot;&gt;&lt;a href=&quot;#cb4-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-11&quot;&gt;&lt;a href=&quot;#cb4-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;CARD&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 생략하면 &amp;quot;CardPayment&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-12&quot;&gt;&lt;a href=&quot;#cb4-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;discriminatorType&lt;/code&gt;은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;STRING&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CHAR&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;INTEGER&lt;/code&gt; 셋 중 하나. 실무에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;STRING&lt;/code&gt;이 가독성·확장성 면에서 제일 무난하다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;INTEGER&lt;/code&gt;는 디스크를 아낀다는 이유로 제안되기도 하지만, DB를
직접 들여다볼 때 &amp;quot;1이면 카드, 2면 이체&amp;quot;를 매번 기억해야 해서 운영 비용이
더 크다.&lt;/p&gt;
&lt;h3 id=&quot;3-1-수동-조작-불가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-1) 수동 조작 불가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 한 번 더 짚고 갈 것.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;은 Hibernate가 관리하는
컬럼&lt;/strong&gt;이라 엔티티 필드로 매핑해 직접 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setXxx()&lt;/code&gt;로
바꾸는 건 허용되지 않는다. 정 필드로 노출하고 싶으면 다음처럼 읽기
전용으로만 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SINGLE_TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; insertable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; updatable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-6&quot;&gt;&lt;a href=&quot;#cb5-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; paymentType&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// 읽기 전용. 직접 쓰려고 하면 무시됨&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-7&quot;&gt;&lt;a href=&quot;#cb5-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;insertable=false updatable=false&lt;/code&gt;로 선언해도 Hibernate가
내부적으로 값을 채운다. 개발자가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;setPaymentType(...)&lt;/code&gt;를 직접
호출해도 flush 시점에 Hibernate가 자식 타입에 맞게 덮어써서
무의미해진다. 타입 바꾸기는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;애초에 DB 레벨에서 지원되지 않는
연산&lt;/strong&gt;이라고 생각하는 편이 맞다.&lt;/p&gt;
&lt;h3 id=&quot;3-2-joined에서도-쓸-수-있다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-2) JOINED에서도 쓸 수 있다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;은 이름과 달리 SINGLE_TABLE 전용이
아니다. JOINED에도 붙일 수 있고, Hibernate 6부터는 JOINED에
디스크리미네이터를 두면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;부모 테이블 하나만 조회해도 타입을 즉시
알 수 있다&lt;/strong&gt;. 없으면 어떤 자식인지 판단하려고 LEFT JOIN으로 자식
테이블들을 다 훑어야 한다. JOINED에서 부모만 조회하는 경우가 잦다면
디스크리미네이터를 같이 얹는 쪽이 성능상 유리하다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;JOINED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// JOINED에도 OK&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;TABLE_PER_CLASS에는 의미가 없다. 각 자식이 독립 테이블을 쓰므로 &amp;quot;어느
테이블에서 온 row냐&amp;quot;가 곧 타입 정보다. 스펙상 지정해도 무시된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-joined-정규화의-대가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) JOINED: 정규화의 대가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JOINED&lt;/code&gt;는 SINGLE_TABLE의 반대편이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;부모
테이블에는 공통 컬럼만&lt;/strong&gt;, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;자식 테이블에는 자식 특수
컬럼만&lt;/strong&gt; 두고, 자식 테이블의 PK가 동시에 부모 테이블의 FK 역할을
한다. 전형적인 3정규화 스키마와 모양이 같다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;JOINED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime paidAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-9&quot;&gt;&lt;a href=&quot;#cb7-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-10&quot;&gt;&lt;a href=&quot;#cb7-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-11&quot;&gt;&lt;a href=&quot;#cb7-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-12&quot;&gt;&lt;a href=&quot;#cb7-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@PrimaryKeyJoinColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// 자식 PK(=부모 FK) 컬럼 이름 지정&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-13&quot;&gt;&lt;a href=&quot;#cb7-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-14&quot;&gt;&lt;a href=&quot;#cb7-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// NOT NULL 가능&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-15&quot;&gt;&lt;a href=&quot;#cb7-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; cardNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-16&quot;&gt;&lt;a href=&quot;#cb7-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;nullable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-17&quot;&gt;&lt;a href=&quot;#cb7-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; approvalCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-18&quot;&gt;&lt;a href=&quot;#cb7-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DDL은 이렇게 풀린다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; payment (&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    amount BIGINT,&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    paid_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    payment_type &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;31&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; card_payment (&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    payment_id BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-10&quot;&gt;&lt;a href=&quot;#cb8-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    card_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,   &lt;span class=&quot;co&quot;&gt;-- NOT NULL 가능&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-11&quot;&gt;&lt;a href=&quot;#cb8-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    approval_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-12&quot;&gt;&lt;a href=&quot;#cb8-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt; (payment_id) &lt;span class=&quot;kw&quot;&gt;REFERENCES&lt;/span&gt; payment(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb8-13&quot;&gt;&lt;a href=&quot;#cb8-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;
&lt;span id=&quot;cb8-14&quot;&gt;&lt;a href=&quot;#cb8-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-15&quot;&gt;&lt;a href=&quot;#cb8-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; bank_transfer (&lt;/span&gt;
&lt;span id=&quot;cb8-16&quot;&gt;&lt;a href=&quot;#cb8-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    payment_id BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-17&quot;&gt;&lt;a href=&quot;#cb8-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    bank_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-18&quot;&gt;&lt;a href=&quot;#cb8-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    account_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb8-19&quot;&gt;&lt;a href=&quot;#cb8-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt; (payment_id) &lt;span class=&quot;kw&quot;&gt;REFERENCES&lt;/span&gt; payment(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb8-20&quot;&gt;&lt;a href=&quot;#cb8-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;4-1-장점-정규화와-무결성&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) 장점: 정규화와 무결성&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JOINED의 이점은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 스키마가 객체 모델과 일대일로
대응한다&lt;/strong&gt;는 점이다. 자식 전용 컬럼에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOT NULL&lt;/code&gt; 걸기,
디스크 공간 효율, 자식별 인덱스 독립 관리 — 전부 자연스럽게 된다. DBA
관점에서 가장 편한 구조다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-단점-join-오버헤드&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2) 단점: JOIN 오버헤드&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;단점은 하나지만 묵직하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조회마다 JOIN이 붙는다&lt;/strong&gt;.
자식 엔티티 한 건을 조회하려면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT ... FROM card_payment c INNER JOIN payment p ON c.payment_id = p.id&lt;/code&gt;.
부모 엔티티를 조회해도 타입 판별을 위해 자식 테이블로 LEFT JOIN이 필요할
수 있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- SELECT p FROM Payment p (다형성 쿼리, Hibernate 6 기준)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; p.&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, p.amount, p.paid_at, p.payment_type,&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       c.card_number, c.approval_code,&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;       b.bank_code, b.account_number&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; payment p&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;JOIN&lt;/span&gt; card_payment c &lt;span class=&quot;kw&quot;&gt;ON&lt;/span&gt; p.&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; c.payment_id&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;JOIN&lt;/span&gt; bank_transfer b &lt;span class=&quot;kw&quot;&gt;ON&lt;/span&gt; p.&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; b.payment_id&lt;/span&gt;
&lt;span id=&quot;cb9-8&quot;&gt;&lt;a href=&quot;#cb9-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;WHERE&lt;/span&gt; p.payment_type &lt;span class=&quot;kw&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;st&quot;&gt;&amp;#39;CARD&amp;#39;&lt;/span&gt;, &lt;span class=&quot;st&quot;&gt;&amp;#39;BANK&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;자식 테이블이 늘어날수록 LEFT JOIN 개수가 선형으로 증가하고,
옵티마이저가 넓적한 플랜을 못 짜면 전체 테이블 스캔과 JOIN 조합이 나와
성능이 들쑥날쑥해진다. 이 쿼리가 핫패스라면 프로파일링이 반드시
필요하다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;또 하나, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;INSERT가 두 번 나간다&lt;/strong&gt;. 부모 테이블에 먼저
INSERT, 그다음 자식 테이블에 INSERT. 트랜잭션 하나 안에서 자동으로
묶이긴 해도 SQL 왕복이 두 번이다. 배치 INSERT를 써도 테이블별로 따로
묶이므로 SINGLE_TABLE만큼 효율이 안 나온다.&lt;/p&gt;
&lt;h3 id=&quot;4-3-primarykeyjoincolumn과-이름-튜닝&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-3)
@PrimaryKeyJoinColumn과 이름 튜닝&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;자식 테이블의 FK(=PK) 컬럼 이름을 바꾸고 싶으면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrimaryKeyJoinColumn&lt;/code&gt;을 쓴다. 기본은 부모 PK와 같은
이름(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id&lt;/code&gt;)이 자식 테이블에도 그대로 들어간다. 관례상
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;{parent_table}_id&lt;/code&gt; 형식이 가독성이 좋다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@PrimaryKeyJoinColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// → card_payment.payment_id (PK, FK → payment.id)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;4-4-성능-튜닝-entitygraph와-디스크리미네이터&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-4) 성능 튜닝:
@EntityGraph와 디스크리미네이터&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JOINED에서 N+1을 줄이려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityGraph&lt;/code&gt;를 쓰는 게
기본이다. 부모만 필요하면 부모만 로딩하고, 특정 자식 경로만 필요하면
그것만 fetch. 디스크리미네이터 컬럼이 부모 테이블에 있으면 자식 테이블
LEFT JOIN을 건너뛸 수 있다 — Hibernate 6이 이 최적화를 많이
개선했다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;반대로 &amp;quot;어떤 자식인지까지 알고 필드도 다 써야 하는&amp;quot; 경우면 결국
JOIN을 피할 수 없다. 이 때문에 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JOINED는 쓰기 위주 + 무결성이
중요한 도메인&lt;/strong&gt;에 맞고, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다형성 읽기가 핫패스인
도메인&lt;/strong&gt;에는 SINGLE_TABLE이 더 맞는 경향이 있다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-table_per_class-왜-피하는가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5) TABLE_PER_CLASS: 왜
피하는가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE_PER_CLASS&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;각 구체 서브클래스마다 독립
테이블&lt;/strong&gt;을 파고, 공통 컬럼도 각 테이블에 전부 중복해 넣는
전략이다. 부모가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;abstract&lt;/code&gt;면 부모 테이블 자체는 생성되지
않는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TABLE_PER_CLASS&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// IDENTITY 불가&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-7&quot;&gt;&lt;a href=&quot;#cb11-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-8&quot;&gt;&lt;a href=&quot;#cb11-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime paidAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-9&quot;&gt;&lt;a href=&quot;#cb11-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-10&quot;&gt;&lt;a href=&quot;#cb11-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-11&quot;&gt;&lt;a href=&quot;#cb11-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-12&quot;&gt;&lt;a href=&quot;#cb11-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; CardPayment &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-13&quot;&gt;&lt;a href=&quot;#cb11-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; cardNumber&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-14&quot;&gt;&lt;a href=&quot;#cb11-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; approvalCode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-15&quot;&gt;&lt;a href=&quot;#cb11-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DDL.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- payment 테이블 없음 (abstract이므로)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; card_payment (&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    amount BIGINT,                &lt;span class=&quot;co&quot;&gt;-- 중복&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-6&quot;&gt;&lt;a href=&quot;#cb12-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    paid_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,            &lt;span class=&quot;co&quot;&gt;-- 중복&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-7&quot;&gt;&lt;a href=&quot;#cb12-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    card_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),&lt;/span&gt;
&lt;span id=&quot;cb12-8&quot;&gt;&lt;a href=&quot;#cb12-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    approval_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb12-9&quot;&gt;&lt;a href=&quot;#cb12-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;
&lt;span id=&quot;cb12-10&quot;&gt;&lt;a href=&quot;#cb12-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-11&quot;&gt;&lt;a href=&quot;#cb12-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; bank_transfer (&lt;/span&gt;
&lt;span id=&quot;cb12-12&quot;&gt;&lt;a href=&quot;#cb12-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb12-13&quot;&gt;&lt;a href=&quot;#cb12-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    amount BIGINT,                &lt;span class=&quot;co&quot;&gt;-- 중복&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-14&quot;&gt;&lt;a href=&quot;#cb12-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    paid_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,            &lt;span class=&quot;co&quot;&gt;-- 중복&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-15&quot;&gt;&lt;a href=&quot;#cb12-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    bank_code &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),&lt;/span&gt;
&lt;span id=&quot;cb12-16&quot;&gt;&lt;a href=&quot;#cb12-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    account_number &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb12-17&quot;&gt;&lt;a href=&quot;#cb12-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;5-1-장점-단일-자식-조회는-빠르다&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-1) 장점: 단일 자식 조회는
빠르다&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;특정 자식 하나만 조회하면 해당 테이블 스캔으로 끝난다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT c FROM CardPayment c&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;card_payment&lt;/code&gt;
테이블만 읽는다. JOIN도 없고 디스크리미네이터 필터도 없다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-단점-다형성-쿼리의-union-all&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-2) 단점: 다형성 쿼리의 UNION
ALL&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT p FROM Payment p&lt;/code&gt;를 날리면
Hibernate는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;모든 자식 테이블을 UNION ALL로 합친다&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, amount, paid_at, card_number, approval_code, &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; bank_code, &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; account_number, &lt;span class=&quot;st&quot;&gt;&amp;#39;CARD&amp;#39;&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; clazz_&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; card_payment&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;ALL&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, amount, paid_at, &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;, &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;, bank_code, account_number, &lt;span class=&quot;st&quot;&gt;&amp;#39;BANK&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;FROM&lt;/span&gt; bank_transfer&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;자식이 3개면 UNION ALL 3번, 10개면 10번. 각 테이블 풀스캔에 결과를
합치는 비용까지 얹힌다. 페이징·정렬이 들어가면 옵티마이저가 짜는 플랜이
더 복잡해진다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다형성 쿼리를 조금이라도 쓸 거면 이 전략은 거의
못 쓴다&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;5-3-pk-전략-제약&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;5-3) PK 전략 제약&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;공통 PK 시퀀스를 모든 자식 테이블이 공유해야 한다. 그래야
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment&lt;/code&gt;의 id=1과 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BankTransfer&lt;/code&gt;의 id=1이
충돌하지 않는다. 이 때문에
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType.IDENTITY&lt;/code&gt;는 쓸 수 없다&lt;/strong&gt;.
IDENTITY는 테이블별 auto-increment라 자식 간 충돌이 불가피하다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt;나 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt; 전략을 강제로 써야 하고,
MySQL처럼 시퀀스가 없는 DB에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt; 전략으로 우회해야
한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: IDENTITY는 TABLE_PER_CLASS에서 동작하지 않음&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: SEQUENCE 또는 TABLE&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서 TABLE_PER_CLASS를 채택하는 사례는 드물다. &amp;quot;상속이긴 한데
자식들을 완전히 별개의 엔티티처럼만 쓰고 다형성 쿼리가 정말 없다&amp;quot;는
조건이 맞으면 고려 대상이지만, 그 정도 분리가 되어 있다면 애초에 상속
구조 자체를 걷어내고 독립 엔티티 N개로 두는 편이 더 깔끔하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-전략-선택-의사결정-표&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6) 전략 선택 의사결정 표&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;세 전략 중 실전에서 고민은 주로 SINGLE_TABLE과 JOINED 사이에서
일어난다. 판단 기준은 크게 네 가지.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;조건&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SINGLE_TABLE&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JOINED&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;TABLE_PER_CLASS&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자식 개수 2~3개, 특화 컬럼 적음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;최적&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;과함&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비추천&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자식별 특화 컬럼 많음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;스키마 팽창&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;적합&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비추천&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다형성 쿼리 자주&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;최적&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;무난&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비추천 (UNION ALL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다형성 쿼리 거의 없음&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;무난&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JOIN 비용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;고려 대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;자식 전용 컬럼 NOT NULL 필요&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;불가&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;쓰기 성능 중요&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;최적 (INSERT 1회)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;부담 (INSERT 2회)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;무난&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DBA 관점 스키마 선호&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;비표준 스키마&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;정규화 스키마&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;중복 컬럼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;대부분의 경우 **&amp;quot;SINGLE_TABLE을 기본으로 두고, 자식별 특화 컬럼이
많아지거나 DB 무결성이 특별히 중요하면 JOINED로 이동&amp;quot;**이 합리적인
디폴트다. 성능만 보면 SINGLE_TABLE이 유리하고, 데이터 품질을 DB 레벨에서
보증하려면 JOINED가 유리하다. TABLE_PER_CLASS는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;선택지에서
제외&lt;/strong&gt;하고 고민을 시작하는 편이 시간을 아낀다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;한 가지 더. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;중간에 전략을 바꾸는 비용은 생각보다
크다&lt;/strong&gt;. 운영 중인 테이블을 SINGLE_TABLE에서 JOINED로 옮기려면
데이터 마이그레이션 스크립트를 별도로 짜야 하고, 그 사이 애플리케이션
다운타임이나 듀얼 라이트가 필요하다. 초기 설계에서 충분히 고민하는 편이
장기적으로 싸다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-mappedsuperclass-엔티티-아닌-공통-필드&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) @MappedSuperclass:
엔티티 아닌 공통 필드&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;지금까지의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;도메인
상속&lt;/strong&gt;이었다. 그런데 실무에서 더 자주 마주치는 건 &amp;quot;모든 엔티티에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;createdAt&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updatedAt&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id&lt;/code&gt;가 공통으로
들어간다&amp;quot;는 단순한 코드 중복 제거 요구다. 이건 상속이라기보다
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;공통 필드 재사용&lt;/strong&gt;이고, 다형성 쿼리도 필요 없다. 여기
맞는 도구가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;AuditingEntityListener&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime createdAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime updatedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-12&quot;&gt;&lt;a href=&quot;#cb15-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-13&quot;&gt;&lt;a href=&quot;#cb15-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-14&quot;&gt;&lt;a href=&quot;#cb15-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-15&quot;&gt;&lt;a href=&quot;#cb15-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Order &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-16&quot;&gt;&lt;a href=&quot;#cb15-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; userId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-17&quot;&gt;&lt;a href=&quot;#cb15-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; amount&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-18&quot;&gt;&lt;a href=&quot;#cb15-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-19&quot;&gt;&lt;a href=&quot;#cb15-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-20&quot;&gt;&lt;a href=&quot;#cb15-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-21&quot;&gt;&lt;a href=&quot;#cb15-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Product &lt;span class=&quot;kw&quot;&gt;extends&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-22&quot;&gt;&lt;a href=&quot;#cb15-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-23&quot;&gt;&lt;a href=&quot;#cb15-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; price&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-24&quot;&gt;&lt;a href=&quot;#cb15-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;DDL은 이렇게 나온다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity&lt;/code&gt; 테이블은
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;생성되지 않고&lt;/strong&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Product&lt;/code&gt;
각각의 테이블에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;created_at&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;updated_at&lt;/code&gt;이
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;복사되어&lt;/strong&gt; 들어간다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- base_entity 테이블 없음&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; orders (&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-5&quot;&gt;&lt;a href=&quot;#cb16-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    created_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-6&quot;&gt;&lt;a href=&quot;#cb16-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    updated_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-7&quot;&gt;&lt;a href=&quot;#cb16-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    user_id BIGINT,&lt;/span&gt;
&lt;span id=&quot;cb16-8&quot;&gt;&lt;a href=&quot;#cb16-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    amount BIGINT&lt;/span&gt;
&lt;span id=&quot;cb16-9&quot;&gt;&lt;a href=&quot;#cb16-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;
&lt;span id=&quot;cb16-10&quot;&gt;&lt;a href=&quot;#cb16-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-11&quot;&gt;&lt;a href=&quot;#cb16-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; product (&lt;/span&gt;
&lt;span id=&quot;cb16-12&quot;&gt;&lt;a href=&quot;#cb16-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt; BIGINT &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-13&quot;&gt;&lt;a href=&quot;#cb16-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    created_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-14&quot;&gt;&lt;a href=&quot;#cb16-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    updated_at &lt;span class=&quot;dt&quot;&gt;TIMESTAMP&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb16-15&quot;&gt;&lt;a href=&quot;#cb16-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    name &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;255&lt;/span&gt;),&lt;/span&gt;
&lt;span id=&quot;cb16-16&quot;&gt;&lt;a href=&quot;#cb16-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    price BIGINT&lt;/span&gt;
&lt;span id=&quot;cb16-17&quot;&gt;&lt;a href=&quot;#cb16-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;7-1-핵심-특징&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-1) 핵심 특징&lt;/h3&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티가 아니다.&lt;/strong&gt; &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Entity&lt;/code&gt;가 없고,
테이블이 생성되지 않는다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;쿼리 대상이 아니다.&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT b FROM BaseEntity b&lt;/code&gt;는 컴파일은 되어도 실행 시
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity is not mapped&lt;/code&gt;로 실패한다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;필드가 자식 테이블에 복사된다.&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity.id&lt;/code&gt;는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order.id&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Product.id&lt;/code&gt;로 각각 매핑된다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt;를 가질 수 있다.&lt;/strong&gt; 이게
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;를 쓰는 가장 흔한 패턴이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;7-2-전형적-용도-감사auditing-필드&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-2) 전형적 용도:
감사(auditing) 필드&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;스프링 데이터의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedDate&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedDate&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@CreatedBy&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@LastModifiedBy&lt;/code&gt;를 한 데 묶는 베이스 클래스로 거의
필수적으로 쓴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;AuditingEntityListener&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; AuditableEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;updatable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime createdAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedDate&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-9&quot;&gt;&lt;a href=&quot;#cb17-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; LocalDateTime updatedAt&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-10&quot;&gt;&lt;a href=&quot;#cb17-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-11&quot;&gt;&lt;a href=&quot;#cb17-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@CreatedBy&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-12&quot;&gt;&lt;a href=&quot;#cb17-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;updatable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-13&quot;&gt;&lt;a href=&quot;#cb17-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; createdBy&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-14&quot;&gt;&lt;a href=&quot;#cb17-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-15&quot;&gt;&lt;a href=&quot;#cb17-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@LastModifiedBy&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-16&quot;&gt;&lt;a href=&quot;#cb17-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; updatedBy&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb17-17&quot;&gt;&lt;a href=&quot;#cb17-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 패턴을 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance(SINGLE_TABLE)&lt;/code&gt;로 흉내 내면 오히려
이상해진다. 공통 감사 필드를 위해 모든 엔티티가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;auditable_entity&lt;/code&gt;라는 한 테이블을 공유하는 꼴이 된다. 엔티티
간 PK가 충돌하고, 엔티티 수가 늘 때마다 테이블이 더 넓어진다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;공통 필드를 공유하고 싶을 뿐&amp;quot;이면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;&lt;/strong&gt;, **&amp;quot;도메인상 자식들이 부모의
일종&amp;quot;이면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;**가 맞는 판단 기준이다.&lt;/p&gt;
&lt;h3 id=&quot;7-3-연관관계-매핑도-가능&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-3) 연관관계 매핑도 가능&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@OneToMany&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@ManyToOne&lt;/code&gt;을 두는 것도 가능하다.
자식 엔티티가 이 연관관계를 물려받는다. 다만 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;자식별로 FK 컬럼이
다른 테이블에 생기므로&lt;/strong&gt;, 베이스 클래스 쪽에 하나의 연관 엔티티를
두고 &amp;quot;모든 자식이 같은 컬렉션을 가진다&amp;quot;는 의미는 아니다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-mappedsuperclass-vs-inheritance-vs-embeddable&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8)
@MappedSuperclass vs @Inheritance vs @Embeddable&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;셋 다 &amp;quot;재사용&amp;quot;이라는 키워드에 걸려 한 번씩 헷갈린다. 차원이 다르다는
걸 표로 정리하면 선명해진다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;구분&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;@MappedSuperclass&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;@Inheritance&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;@Embeddable&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티인가?&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;아님 (abstract 권장)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 (부모)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;아님 (값 타입)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테이블 생성&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;안 됨&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;전략에 따라 (SINGLE/JOINED/PER_CLASS)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;쿼리 대상&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;아님&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;다형성 쿼리 가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt; 소유&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;부모가 가진다&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;불가 (식별자 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;사용 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;extends&lt;/code&gt; (상속)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;extends&lt;/code&gt; (상속)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embedded&lt;/code&gt;로 필드처럼 내장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;주 용도&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;공통 필드 코드 재사용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;도메인 상속 (Payment → CardPayment)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;값 타입 구조 재사용 (Address, Money)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;8-1-embeddable-값-타입-구조-공유&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) @Embeddable: 값 타입 구조
공유&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;여러 엔티티가 같은 구조의 컬럼
뭉치를 가지고 싶을 때&lt;/strong&gt; 쓴다. 상속이 아니고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embedded&lt;/code&gt;로 필드처럼 내장한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Embeddable&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-2&quot;&gt;&lt;a href=&quot;#cb18-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Address &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-3&quot;&gt;&lt;a href=&quot;#cb18-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; zipcode&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-4&quot;&gt;&lt;a href=&quot;#cb18-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; city&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-5&quot;&gt;&lt;a href=&quot;#cb18-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; street&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-6&quot;&gt;&lt;a href=&quot;#cb18-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-7&quot;&gt;&lt;a href=&quot;#cb18-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-8&quot;&gt;&lt;a href=&quot;#cb18-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-9&quot;&gt;&lt;a href=&quot;#cb18-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; User &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-10&quot;&gt;&lt;a href=&quot;#cb18-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-11&quot;&gt;&lt;a href=&quot;#cb18-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-12&quot;&gt;&lt;a href=&quot;#cb18-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; name&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-13&quot;&gt;&lt;a href=&quot;#cb18-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-14&quot;&gt;&lt;a href=&quot;#cb18-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Embedded&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-15&quot;&gt;&lt;a href=&quot;#cb18-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Address address&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// user 테이블에 zipcode, city, street 컬럼이 생김&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-16&quot;&gt;&lt;a href=&quot;#cb18-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-17&quot;&gt;&lt;a href=&quot;#cb18-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-18&quot;&gt;&lt;a href=&quot;#cb18-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-19&quot;&gt;&lt;a href=&quot;#cb18-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Company &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-20&quot;&gt;&lt;a href=&quot;#cb18-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-21&quot;&gt;&lt;a href=&quot;#cb18-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-22&quot;&gt;&lt;a href=&quot;#cb18-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-23&quot;&gt;&lt;a href=&quot;#cb18-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Embedded&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-24&quot;&gt;&lt;a href=&quot;#cb18-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Address address&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// company 테이블에도 같은 zipcode, city, street 컬럼&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-25&quot;&gt;&lt;a href=&quot;#cb18-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;User&lt;/code&gt;도 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Company&lt;/code&gt;도 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Address&lt;/code&gt;의 세
컬럼을 각자 자기 테이블에 품는다. 상속 관계는 아니고, &amp;quot;같은 컬럼 구조를
필드처럼 끼워 넣었다&amp;quot;에 가깝다. 값 타입이므로 식별자(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt;)가
없고, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt;로 동치성을 판단한다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-선택-기준&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) 선택 기준&lt;/h3&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;같은 필드 몇 개를 여러 엔티티에 반복해서 쓰는데, 값 자체로
의미가 있다&amp;quot;&lt;/strong&gt; → &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt; (예:
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Address&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Money&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;DateRange&lt;/code&gt;)&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;모든 엔티티가 공통으로 가져야 할 기술적 공통 필드 (id,
audit)&amp;quot;&lt;/strong&gt; → &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&amp;quot;자식들이 부모의 일종이며 다형성 쿼리·공유 FK가
필요하다&amp;quot;&lt;/strong&gt; → &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;혼동 사례 하나. &amp;quot;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Address&lt;/code&gt;를 여러 엔티티에서 공유한다&amp;quot;는
요구에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass AbstractAddressOwner&lt;/code&gt;를 만들고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;User&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Company&lt;/code&gt;가 상속하게 만드는 설계를 가끔
본다. 상속 관계가 전혀 없는 도메인(회사와 사용자는 부모-자식이 아니다)을
기술적 이유로 상속으로 묶는 셈이다. 이런 경우엔
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;이 맞다. 도메인 모델링이 기술 장치에 끌려가면 안
된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-dynamic-insertupdate로-single_table-최적화&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) Dynamic
Insert/Update로 SINGLE_TABLE 최적화&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;SINGLE_TABLE의 단점 중 하나인 &amp;quot;자식별 컬럼이 많아 NULL이 수두룩&amp;quot;을
달래는 도구가 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicInsert&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;다.
둘 다 Hibernate 고유 어노테이션으로, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;jakarta.persistence&lt;/code&gt;
표준은 아니다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;DynamicInsert&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;DynamicUpdate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-4&quot;&gt;&lt;a href=&quot;#cb19-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-5&quot;&gt;&lt;a href=&quot;#cb19-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SINGLE_TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-6&quot;&gt;&lt;a href=&quot;#cb19-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DynamicInsert&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// NULL인 컬럼은 INSERT SQL에서 제외&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-7&quot;&gt;&lt;a href=&quot;#cb19-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DynamicUpdate&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 변경되지 않은 컬럼은 UPDATE SQL에서 제외&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-8&quot;&gt;&lt;a href=&quot;#cb19-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;9-1-동작-방식&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) 동작 방식&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;기본값(정적 SQL)이면 Hibernate는 애플리케이션 시작 시점에 &amp;quot;이
엔티티의 INSERT SQL&amp;quot;을 모든 컬럼을 포함한 형태로 미리 만들어둔다. 한 번
만든 SQL을 모든 row에 재활용한다. 이 방식이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PreparedStatement
캐시 재사용&lt;/strong&gt;에 유리해서 DB 쪽 파싱 캐시를 잘 쓴다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicInsert&lt;/code&gt;를 붙이면 매 INSERT마다 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;실제
NULL이 아닌 컬럼만 포함하는 SQL을 동적으로 조립&lt;/strong&gt;한다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CardPayment&lt;/code&gt; 저장 시에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;bank_code&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;account_number&lt;/code&gt; 컬럼이 SQL에서 아예
빠진다. SQL 전송량이 줄고, DB 쪽에서도 불필요한 NULL 처리가
없어진다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- 정적 INSERT (기본)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INTO&lt;/span&gt; payment (&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, amount, paid_at, payment_type,&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                     card_number, approval_code, bank_code, account_number)&lt;/span&gt;
&lt;span id=&quot;cb20-4&quot;&gt;&lt;a href=&quot;#cb20-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (?, ?, ?, ?, ?, ?, ?, ?);&lt;/span&gt;
&lt;span id=&quot;cb20-5&quot;&gt;&lt;a href=&quot;#cb20-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-6&quot;&gt;&lt;a href=&quot;#cb20-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;-- @DynamicInsert 적용 (CardPayment 저장 시)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb20-7&quot;&gt;&lt;a href=&quot;#cb20-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INTO&lt;/span&gt; payment (&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, amount, paid_at, payment_type,&lt;/span&gt;
&lt;span id=&quot;cb20-8&quot;&gt;&lt;a href=&quot;#cb20-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;                     card_number, approval_code)&lt;/span&gt;
&lt;span id=&quot;cb20-9&quot;&gt;&lt;a href=&quot;#cb20-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;VALUES&lt;/span&gt; (?, ?, ?, ?, ?, ?);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;9-2-트레이드오프&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) 트레이드오프&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉽다. 동적 SQL은 매번 SQL 모양이 달라진다는 뜻이고,
그러면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PreparedStatement 캐시 재사용률이 떨어진다&lt;/strong&gt;. 대량
INSERT에서는 이 손실이 NULL 컬럼 제외의 이득보다 클 수 있다. 반대로
INSERT 빈도가 낮고 컬럼 수가 아주 많은 경우엔 이득이 크다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;도 같은 이야기다. 변경된 필드만 UPDATE에
포함하므로 &amp;quot;많은 컬럼 중 한두 개만 바꾸는&amp;quot; 시나리오에서 유리하고, 모든
컬럼을 일괄 갱신하는 배치라면 정적 UPDATE가 더 효율적이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상황&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;권장&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;SINGLE_TABLE + 자식별 특화 컬럼 10개 이상&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicInsert&lt;/code&gt; 검토&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;대량 배치 INSERT (초당 수천 건)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;정적 (기본) — PreparedStatement 캐시 이점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;와이드 테이블이고 실제 변경 필드 소수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;컬럼 수 적음 (10개 미만)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;기본 유지 — 효과 미미&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;실무에서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기본값으로 두다가 프로파일링으로 병목이 확인되면
적용&lt;/strong&gt;하는 순서가 맞다. &amp;quot;혹시 모르니 붙여두자&amp;quot;는 조기 최적화고,
오히려 배치 성능을 깎을 수 있다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-실전-함정-종합&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) 실전 함정 종합&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;설계 중·운영 중에 반복적으로 마주치는 함정을 모아둔다. 앞 섹션에서
흩어져 언급된 것도 한 번에 본다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-single_table에서-자식별-not-null&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) SINGLE_TABLE에서
자식별 NOT NULL&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이미 여러 번 나왔지만 다시. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 레벨 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;NOT NULL&lt;/code&gt;은
불가&lt;/strong&gt;, Bean Validation의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@NotNull&lt;/code&gt;은
가능(애플리케이션 레벨). 규모가 커지면 Bean Validation만으로는 부족하다
싶을 때 JOINED 전환이 진지한 선택지가 된다. SQL로 직접 INSERT하는 배치
잡이 있다면 Bean Validation이 우회되므로, 이 경로에서 데이터 정합성을
잃을 여지가 있다.&lt;/p&gt;
&lt;h3 id=&quot;10-2-joined에서-부모만-조회할-때-left-join-폭증&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2)
JOINED에서 부모만 조회할 때 LEFT JOIN 폭증&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT p FROM Payment p&lt;/code&gt; 같은 부모 다형성 쿼리가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;LEFT JOIN card_payment LEFT JOIN bank_transfer LEFT JOIN ...&lt;/code&gt;으로
늘어난다. 자식이 많으면 플랜이 복잡해진다. 해결책은 두 가지.&lt;/p&gt;
&lt;ol type=&quot;1&quot; style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;을 부모에 둔다&lt;/strong&gt; —
타입 판별만 필요하면 자식 JOIN을 건너뛸 수 있다 (Hibernate 6).&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EntityGraph&lt;/code&gt;로 필요한 자식만 fetch&lt;/strong&gt;한다
— 전체 자식을 뒤지지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb21&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb21-1&quot;&gt;&lt;a href=&quot;#cb21-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-2&quot;&gt;&lt;a href=&quot;#cb21-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;JOINED&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-3&quot;&gt;&lt;a href=&quot;#cb21-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@DiscriminatorColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_type&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-4&quot;&gt;&lt;a href=&quot;#cb21-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-5&quot;&gt;&lt;a href=&quot;#cb21-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-6&quot;&gt;&lt;a href=&quot;#cb21-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 부모만 필요하면 부모 테이블 스캔으로 끝날 수 있다&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-7&quot;&gt;&lt;a href=&quot;#cb21-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;&amp;lt;&lt;/span&gt;Payment&lt;span class=&quot;op&quot;&gt;&amp;gt;&lt;/span&gt; all &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-8&quot;&gt;&lt;a href=&quot;#cb21-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;quot;select p from Payment p where p.amount &amp;gt; :amt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; Payment&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-9&quot;&gt;&lt;a href=&quot;#cb21-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setParameter&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;amt&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;10000L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb21-10&quot;&gt;&lt;a href=&quot;#cb21-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;10-3-discriminatorcolumn을-필드로-수동-조작&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-3)
@DiscriminatorColumn을 필드로 수동 조작&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;insertable=false updatable=false&lt;/code&gt;로 읽기 전용 필드를 두는
건 가능하지만, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;값을 바꾸려는 시도는 Hibernate가 덮어쓰므로
무의미&lt;/strong&gt;하다. 타입 변경은 DB 레벨에서 지원되지 않는다. 타입을
바꾸고 싶으면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;새 자식 엔티티로 INSERT 후 기존 row
삭제&lt;/strong&gt;가 유일한 경로다.&lt;/p&gt;
&lt;h3 id=&quot;10-4-table_per_class--identity-조합-불가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-4) TABLE_PER_CLASS
+ IDENTITY 조합 불가&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb22&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb22-1&quot;&gt;&lt;a href=&quot;#cb22-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기: 애플리케이션 시작 시 실패하거나 런타임에 충돌&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-2&quot;&gt;&lt;a href=&quot;#cb22-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-3&quot;&gt;&lt;a href=&quot;#cb22-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Inheritance&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; InheritanceType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TABLE_PER_CLASS&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-4&quot;&gt;&lt;a href=&quot;#cb22-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; Payment &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-5&quot;&gt;&lt;a href=&quot;#cb22-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// X&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-6&quot;&gt;&lt;a href=&quot;#cb22-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-7&quot;&gt;&lt;a href=&quot;#cb22-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-8&quot;&gt;&lt;a href=&quot;#cb22-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-9&quot;&gt;&lt;a href=&quot;#cb22-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 선호: SEQUENCE&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-10&quot;&gt;&lt;a href=&quot;#cb22-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-11&quot;&gt;&lt;a href=&quot;#cb22-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;payment_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb22-12&quot;&gt;&lt;a href=&quot;#cb22-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;TABLE_PER_CLASS는 자식 테이블 간 PK 유일성을 애플리케이션이 보장해야
하는데, IDENTITY는 테이블별로 따로 증가하므로 보장할 방법이 없다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt;(또는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt;)가 강제된다.&lt;/p&gt;
&lt;h3 id=&quot;10-5-mappedsuperclass로-다형성-쿼리-시도&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-5)
@MappedSuperclass로 다형성 쿼리 시도&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb23&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb23-1&quot;&gt;&lt;a href=&quot;#cb23-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@MappedSuperclass&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-2&quot;&gt;&lt;a href=&quot;#cb23-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; BaseEntity &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-3&quot;&gt;&lt;a href=&quot;#cb23-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-4&quot;&gt;&lt;a href=&quot;#cb23-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 실패: BaseEntity는 엔티티가 아님&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-5&quot;&gt;&lt;a href=&quot;#cb23-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;select b from BaseEntity b&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; BaseEntity&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb23-6&quot;&gt;&lt;a href=&quot;#cb23-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// → IllegalArgumentException: BaseEntity is not mapped&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;는 쿼리 대상이 아니다. 다형성 쿼리가
필요하면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;로 바꿔야 한다. 거꾸로 다형성 쿼리가
필요 없는데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;를 쓰면 불필요한 다형성
오버헤드(DTYPE 컬럼, LEFT JOIN 등)가 붙는다.&lt;/p&gt;
&lt;h3 id=&quot;10-6-embeddable로-식별자-공유-시도&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-6) @Embeddable로 식별자
공유 시도&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;은 값 타입이라 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt;를 가질 수
없다. &amp;quot;ID를 공유하고 싶다&amp;quot;가 목적이라면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;의
영역이다. 복합 키는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;로 별개 패턴이 있지만 이건
상속과는 다른 이야기다.&lt;/p&gt;
&lt;h3 id=&quot;10-7-자식-추가-시-single_table의-마이그레이션&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-7) 자식 추가
시 SINGLE_TABLE의 마이그레이션&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;새 자식을 하나 추가하면 해당 자식 특화 컬럼들이 기존 테이블에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;ALTER TABLE ADD COLUMN&lt;/code&gt;으로 붙는다. 운영 테이블이 크면 이
DDL이 오래 걸리거나 락을 잡는다. 온라인 DDL이 지원되는 DB(MySQL 8
등)라도 주의가 필요하다. 자식이 꾸준히 늘어날 도메인이라면 JOINED가 더
편하다 — 새 자식은 새 테이블로 생기므로 기존 테이블에 영향이 없다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;기본은 SINGLE_TABLE로 시작하고, 자식별 특화 컬럼이 5개를
넘기거나 DB NOT NULL이 필요해지면 JOINED로 옮기는 지점을
고민하자&lt;/strong&gt;. 초기 자식 수가 2~3개 정도라면 SINGLE_TABLE이 가장
손이 덜 간다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;TABLE_PER_CLASS는 선택지에서 제외하고 시작하자&lt;/strong&gt;.
다형성 쿼리가 단 한 줄이라도 섞이면 UNION ALL 비용이 즉시 튀어나온다.
완전히 독립된 엔티티들이라면 상속을 걷어내는 게 맞다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DiscriminatorColumn&lt;/code&gt;은 JOINED에도
붙이자&lt;/strong&gt;. Hibernate 6에서 부모만 조회할 때 자식 LEFT JOIN을
건너뛰게 해준다. 운영 쿼리의 상당수가 부모 조회라면 체감 차이가
크다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BaseEntity&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AuditableEntity&lt;/code&gt; 용도로만
쓰자&lt;/strong&gt;. 도메인 상속으로 확장하려는 순간 혼선이 시작된다. 도메인
상속은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Inheritance&lt;/code&gt;, 공통 기술 필드는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;로 구분선을 명확히.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicInsert&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@DynamicUpdate&lt;/code&gt;는
프로파일링 이후에&lt;/strong&gt;. 조기 최적화는 PreparedStatement 캐시 효율을
떨어뜨려 오히려 배치 성능을 깎는다. 자식별 NULL 컬럼이 10개 이상 쌓이는
SINGLE_TABLE에서나 의미가 있다.&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략 변경은 비싸다&lt;/strong&gt;. 운영 중 SINGLE_TABLE → JOINED
이동은 데이터 마이그레이션 + 듀얼 라이트 + 다운타임 조합이 필요하다. 첫
설계에서 &amp;quot;앞으로 자식이 몇 개로 늘어날지, 특화 컬럼은 몇 개가 될지&amp;quot;를 한
번 더 추정해두는 편이 싸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA 상속 매핑의 핵심은 &amp;quot;다형성 쿼리의 비용을 테이블
구조·JOIN·UNION ALL 셋 중 어디에 몰아넣을지&amp;quot;의 선택이다&lt;/strong&gt;.
SINGLE_TABLE은 조회 속도를 얻는 대신 NOT NULL을 포기하고, JOINED는
정규화를 얻는 대신 JOIN 비용을 받아들이며, TABLE_PER_CLASS는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다형성 쿼리에서 UNION ALL이 터지므로 실무에서 피하는
편&lt;/strong&gt;이다. 여기에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MappedSuperclass&lt;/code&gt;는 엔티티가 아닌
공통 필드 재사용 용도, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;은 값 타입 구조 재사용
용도로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;차원이 다른 도구&lt;/strong&gt;라는 걸 구분해두면 설계가
흔들리지 않는다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: JPA, Hibernate, 상속 매핑, SINGLE_TABLE,
JOINED, TABLE_PER_CLASS, @Inheritance, @DiscriminatorColumn,
@MappedSuperclass, @Embeddable, DynamicInsert, Spring Boot&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>@discriminatorcolumn</category>
      <category>@Embeddable</category>
      <category>@inheritance</category>
      <category>@MappedSuperclass</category>
      <category>Hibernate</category>
      <category>joined</category>
      <category>JPA</category>
      <category>SINGLE_TABLE</category>
      <category>TABLE_PER_CLASS</category>
      <category>상속 매핑</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/421</guid>
      <comments>https://dding-shark.tistory.com/421#entry421comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:24:54 +0900</pubDate>
    </item>
    <item>
      <title>JPA ID 생성 전략과 복합 키 &amp;mdash; IDENTITY&amp;middot;SEQUENCE&amp;middot;UUID&amp;middot;@EmbeddedId</title>
      <link>https://dding-shark.tistory.com/420</link>
      <description>&lt;div style=&quot;font-family:Pretendard,-apple-system,BlinkMacSystemFont,&quot;Apple SD Gothic Neo&quot;,&quot;Noto Sans KR&quot;,&quot;Segoe UI&quot;,sans-serif;font-size:16px;line-height:1.75;color:#1f2328;max-width:820px;margin:0 auto;padding:20px 0;word-break:keep-all;overflow-wrap:break-word;&quot;&gt;
&lt;h1 id=&quot;jpa-id-생성-전략과-복합-키--identitysequenceuuidembeddedid&quot; style=&quot;font-size:2.1rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.4em;border-bottom:2px solid #d1d9e0;margin-top:0;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;JPA
ID 생성 전략과 복합 키 — IDENTITY·SEQUENCE·UUID·@EmbeddedId&lt;/h1&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;들어가며&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;들어가며&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;새 엔티티 하나 만들 때 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id @GeneratedValue&lt;/code&gt; 한 줄을
별생각 없이 붙여본 경험은 누구나 있다. 그런데 그 한 줄이 무슨 전략인지,
왜 MySQL은 IDENTITY가 기본이고 PostgreSQL은 SEQUENCE가 기본인지,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll(10000건)&lt;/code&gt;이 왜 batch insert로 안 묶이는지를 묻기
시작하면 답이 막힌다. ID 전략은 &amp;quot;PK 어떻게 만들지&amp;quot;에 그치지 않고
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch insert 가능 여부, 인덱스 페이지 분할, 분산 환경의 충돌,
ORM의 영속화 시점&lt;/strong&gt;까지 한꺼번에 결정한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 JPA의 4가지 기본 전략(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IDENTITY&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt;)에 더해
Hibernate 6에서 정식화된 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUID&lt;/code&gt; 전략, 그리고 복합 키를 다루는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@IdClass&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;까지 한 화면에 정리한다.
도구 나열이 아니라 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;언제 무엇을 고르는가&lt;/strong&gt;가 핵심이다.
특히 다음 네 가지는 실무에서 가장 많이 밟는다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;IDENTITY를 쓰면 JDBC batch insert가 구조적으로
비활성화된다&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll(10000건)&lt;/code&gt;이 INSERT를 10000번
찍는 이유&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;MySQL PK에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUID.randomUUID()&lt;/code&gt;를 그대로 붙이면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;인덱스가 망가진다&lt;/strong&gt; — v4의 무작위성이 B+Tree와
충돌한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE allocationSize=50&lt;/code&gt;인데 ID가 중간중간
비어 있다&lt;/strong&gt; — 이게 정상 동작이고 버그가 아니라는 걸 설명할 수
있어야 한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt;를 안 만들면 영속성 컨텍스트가 같은 키를 다른
객체로 본다&lt;/strong&gt; — 1차 캐시가 깨진 것처럼 보이는 증상이 여기서
나온다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 글은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;왜 ID 전략이 설계 결정인가 → 4전략 개요 → IDENTITY의
batch 제약 → SEQUENCE 튜닝 → TABLE은 왜 피하나 → UUID(v4 vs v7/TSID) →
MySQL UUID 저장 → 복합 키(@IdClass vs @EmbeddedId) → @MapsId → 자연 키
vs 대리 키 → 실무 체크리스트&lt;/strong&gt; 순으로 정리한다. 환경은 Spring
Boot 3.x / Hibernate 6.x / Jakarta Persistence 3.x / Java 17+
기준이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;목차&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;목차&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#1-왜-id-전략이-설계-결정인가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;1) 왜 ID 전략이 설계
결정인가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#2-generatedvalue-네-전략-개요&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;2) @GeneratedValue 네 전략
개요&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-identity의-batch-insert-불가-구조적-제약&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3) IDENTITY의
Batch Insert 불가: 구조적 제약&lt;/a&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#3-3-table_per_class--identity는-금지&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;3-3) TABLE_PER_CLASS
+ IDENTITY는 금지&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#4-sequence--allocationsize--pooled-optimizer&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;4) SEQUENCE
+ allocationSize + pooled optimizer&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#5-table-전략은-왜-피하나&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;5) TABLE 전략은 왜
피하나&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#6-uuid-전략-v4-vs-v7tsid&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;6) UUID 전략: v4 vs
v7/TSID&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#7-mysql-uuid-저장-전략-varchar36이-왜-위험한가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;7) MySQL
UUID 저장 전략: VARCHAR(36)이 왜 위험한가&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#8-idclass-vs-embeddedid-복합-키-두-방식&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;8) @IdClass vs
@EmbeddedId: 복합 키 두 방식&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#9-mapsid로-파생-식별자-구성&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;9) @MapsId로 파생 식별자
구성&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#10-자연-키-vs-대리-키-그리고-시퀀스-이름-공유&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;10) 자연 키
vs 대리 키, 그리고 시퀀스 이름 공유&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;11) 실무에서 이렇게 읽고
쓴다&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;a href=&quot;#12-한-줄-정리&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;12) 한 줄 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;1-왜-id-전략이-설계-결정인가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;1) 왜 ID 전략이 설계 결정인가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;ID 전략은 단순히 &amp;quot;PK 값을 어디서 받아오는가&amp;quot;의 문제가 아니다. 그 결정
하나로 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다음 네 가지가 동시에 결정된다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch insert 가능 여부&lt;/strong&gt; — IDENTITY는 INSERT 실행
후에야 PK를 알 수 있어 batch로 묶을 수 없다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;분산 환경에서의 충돌 가능성&lt;/strong&gt; — DB 시퀀스는 단일 서버
의존, UUID/TSID는 분산 친화&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;인덱스 효율&lt;/strong&gt; — PK가 단조 증가하면 B+Tree 인덱스의
끝부분에만 삽입돼 페이지 분할이 적다. 무작위면 페이지 전반에 흩어져
분할·재배치가 잦다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA 영속화 시점&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt;가 호출되는
순간 PK가 정해지는지, 아니면 flush 시점에 정해지는지가 전략마다
다르다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 네 축은 서로 얽혀 있다. 예를 들어 &amp;quot;분산이 필요해 UUID를 쓰자&amp;quot;가
그대로 &amp;quot;MySQL InnoDB 인덱스가 망가진다&amp;quot;로 이어지고, 그 해법이 &amp;quot;UUIDv7로
단조 증가성을 회복한다&amp;quot;인 식이다. 그래서 ID 전략은 새 엔티티 만들 때
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별생각 없이 IDENTITY 붙이는 게 아니라&lt;/strong&gt;, 도메인 특성을
보고 골라야 한다. 이 글의 나머지는 그 선택지를 하나씩 푼다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;2-generatedvalue-네-전략-개요&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;2) @GeneratedValue 네 전략
개요&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA가 정의한 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType&lt;/code&gt;은 4가지다. Jakarta
Persistence 3.1이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;GenerationType.UUID&lt;/code&gt;를 표준에 추가했고,
Hibernate 6.2+가 이를 구현한다. Hibernate 6.0~6.1에서는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@UuidGenerator&lt;/code&gt;(Hibernate 고유 어노테이션)만 썼다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 의존&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK 확정 시점&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch insert&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대표 DB&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;IDENTITY&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;AUTO_INCREMENT / SERIAL&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT 실행 후&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;불가&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;MySQL, MariaDB, SQL Server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB 시퀀스 객체&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT 전 (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;PostgreSQL, Oracle, H2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;별도 PK 카운트 테이블&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;락 획득 후&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능하나 락 경합&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;모든 DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt; (기본)&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;벤더에 위임&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;벤더에 따라&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;벤더에 따라&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;모든 DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUID&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;없음 (앱에서 생성)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;persist 시점&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;모든 DB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO&lt;/code&gt;는 Hibernate 6 기준으로 대부분의 경우
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SequenceStyleGenerator&lt;/code&gt;로 풀린다. MySQL처럼 시퀀스가 없는
DB에서는 IDENTITY로 떨어진다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;명시적으로 골라 쓰는 편이
안전&lt;/strong&gt;하다. AUTO에 의존하면 DB를 바꿀 때 예고 없이 동작이
바뀐다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 명시적 — 권장&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 묵시적 — 비권장 (DB 따라 동작 달라짐)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;각 전략의 세부는 다음 섹션에서 풀지만, 결론부터 말하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;선택의
축은 두 개&lt;/strong&gt;다. &amp;quot;batch insert가 중요한가&amp;quot;와 &amp;quot;분산 환경인가&amp;quot;. 이
두 질문에 따라 IDENTITY / SEQUENCE / UUID 중 하나로 좁혀진다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;3-identity의-batch-insert-불가-구조적-제약&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;3) IDENTITY의 Batch
Insert 불가: 구조적 제약&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;IDENTITY는 가장 익숙한 전략이다. MySQL을 쓰면 디폴트가 이쪽이고, 별
설정 없이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;AUTO_INCREMENT&lt;/code&gt; 컬럼이 알아서 PK를 증가시키니
편하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단, 대량 insert가 섞이는 도메인에서는 첫 번째로 의심해야
할 선택&lt;/strong&gt;이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb2&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb2-1&quot;&gt;&lt;a href=&quot;#cb2-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기 (대량 insert가 잦은 도메인에서)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-2&quot;&gt;&lt;a href=&quot;#cb2-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-3&quot;&gt;&lt;a href=&quot;#cb2-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb2-4&quot;&gt;&lt;a href=&quot;#cb2-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;문제는 &lt;a href=&quot;#3-1-왜-batch가-안-묶이는가&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;§3-1&lt;/a&gt;에서 보듯 batch
insert가 구조적으로 안 된다는 점이다. 이 제약은 &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편 N+1 글의
§12&lt;/a&gt;에서 한 번 다뤘는데, ID 전략 관점에서 다시 한번 정리한다.&lt;/p&gt;
&lt;h3 id=&quot;3-1-왜-batch가-안-묶이는가&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-1) 왜 batch가 안 묶이는가&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;JPA의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;EntityManager.persist()&lt;/code&gt;는 호출된 순간 엔티티를
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;영속 상태&lt;/strong&gt;로 전이시켜야 한다. 영속 상태가 되려면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;1차 캐시(영속성 컨텍스트)에 PK를 키로 등록&lt;/strong&gt;해야 하는데,
IDENTITY는 PK를 알아내려면 INSERT를 실제로 실행해야 한다. 결과적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 한 번 = INSERT 한 번 = 네트워크 왕복 한 번이 1:1로
묶인다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch로 묶을 틈이 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb3&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb3-1&quot;&gt;&lt;a href=&quot;#cb3-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 1만 건 saveAll&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-2&quot;&gt;&lt;a href=&quot;#cb3-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;memberRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;saveAll&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;members&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-3&quot;&gt;&lt;a href=&quot;#cb3-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-4&quot;&gt;&lt;a href=&quot;#cb3-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 실제 발생하는 SQL (IDENTITY 기준)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-5&quot;&gt;&lt;a href=&quot;#cb3-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// INSERT INTO member (...) VALUES (...);  -- 1번&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-6&quot;&gt;&lt;a href=&quot;#cb3-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// INSERT INTO member (...) VALUES (...);  -- 2번&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-7&quot;&gt;&lt;a href=&quot;#cb3-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb3-8&quot;&gt;&lt;a href=&quot;#cb3-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// INSERT INTO member (...) VALUES (...);  -- 10000번&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate.jdbc.batch_size=50&lt;/code&gt;을 켜둬도 IDENTITY
전략에서는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;로그가 변하지 않는다&lt;/strong&gt;. 이걸 모르고
batch_size를 100, 200으로 올려보다가 &amp;quot;batch가 안 먹힌다&amp;quot;는 이슈로
넘어오는 경우가 많다. 원인은 batch_size가 아니라 PK 전략이다.&lt;/p&gt;
&lt;h3 id=&quot;3-2-해결-방향&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-2) 해결 방향&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;IDENTITY를 유지해야 한다면 batch insert는 포기하고 &lt;a href=&quot;./13_spring-jpa-performance-n-plus-one.md&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;13편의 §13&lt;/a&gt;
마지막에서 말한 대로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;JdbcTemplate.batchUpdate&lt;/code&gt;로 내려가는
편이 빠르다. PK 전략을 바꿀 수 있다면 SEQUENCE로 옮기는 게 정공법이다.
PostgreSQL/Oracle은 즉시 가능하고, MySQL 8.0 이후라면 시퀀스 시뮬레이션
테이블을 만들거나 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;UUIDv7 / TSID&lt;/strong&gt;를 PK로 쓰는 우회로가
있다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉬운 건 &amp;quot;IDENTITY가 항상 나쁘다&amp;quot;는 결론으로 비약하는
것이다. 읽기 위주 + 단건 insert가 대부분인 시스템(관리 페이지,
게시판류)에서는 IDENTITY가 가장 단순하고 충분하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch
insert가 필요한 경로가 있는가&lt;/strong&gt;가 판단 기준이다.&lt;/p&gt;
&lt;h3 id=&quot;3-3-table_per_class--identity는-금지&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;3-3) TABLE_PER_CLASS +
IDENTITY는 금지&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;상속 매핑에서 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;InheritanceType.TABLE_PER_CLASS&lt;/code&gt;를 쓰면
자식 테이블마다 PK 컬럼이 별도로 생기지만, &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK 공간 자체는 모든
자식 테이블이 공유&lt;/strong&gt;한다. 이때 IDENTITY를 붙이면 자식 테이블의
AUTO_INCREMENT가 각자 독립적으로 증가하므로 서브클래스 간 PK가 충돌하고,
Hibernate는 시작 시점에 런타임 예외로 거부한다. TABLE_PER_CLASS에서는
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SEQUENCE 또는 TABLE 전략만 가능&lt;/strong&gt;하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;4-sequence--allocationsize--pooled-optimizer&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;4) SEQUENCE +
allocationSize + pooled optimizer&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;SEQUENCE는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB의 시퀀스 객체&lt;/strong&gt;(PostgreSQL/Oracle
등)에서 PK를 미리 받아온다. INSERT 실행 전에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT nextval(&amp;#39;member_seq&amp;#39;)&lt;/code&gt;로 PK를 확보하므로 영속화
시점에 이미 PK를 알고 있고, 따라서 batch insert가 자유롭다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;핵심은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize&lt;/code&gt;다. 이름 그대로 &amp;quot;한 번의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; 호출로 미리 예약할 PK 개수&amp;quot;다.&lt;/p&gt;
&lt;h3 id=&quot;4-1-allocationsize의-동작-원리&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-1) allocationSize의 동작
원리&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize=50&lt;/code&gt;이면 Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;을
호출해 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;50개의 ID 범위&lt;/strong&gt;를 통째로 받아온다. 그 뒤에는
메모리에서 1, 2, 3, ... 50을 차례로 꺼내 쓰고, 51번째 INSERT가 필요할 때
다시 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;을 호출해 다음 50개를 받는다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;img role=&quot;img&quot; aria-label=&quot;35_jpa-id-generation-composite-key-01&quot; src=&quot;data:image/png;base64,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&quot; alt=&quot;35_jpa-id-generation-composite-key-01&quot; style=&quot;max-width:100%;height:auto;display:block;margin:1.5em auto;border-radius:6px;background:#fff;padding:8px;border:1px solid #d1d9e0;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize=1&lt;/code&gt;이면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;매 INSERT마다
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;&lt;/strong&gt; 이 발생해 네트워크 왕복이 N번 늘어난다.
1만 건 insert면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;이 1만 번 나간다. 50으로만 잡아도
200번으로 줄고, 100이면 100번이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;권장은
50~100&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3 id=&quot;4-2-hibernate-6의-pooled-optimizer&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-2) Hibernate 6의 pooled
optimizer&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;optimizer&lt;/code&gt; 옵션으로 시퀀스 사용 방식을
고른다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;optimizer&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동작&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 시퀀스 INCREMENT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;none&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;매 INSERT마다 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; (= allocationSize=1과 동일)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hilo&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;hi/lo 알고리즘. 옛 Hibernate 5 호환용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;pooled&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; 결과를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;상한&lt;/strong&gt;(hi)으로 해석, 사용
범위 = &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;[val-inc+1, val]&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;allocationSize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;pooled-lo&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; 결과를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;하한&lt;/strong&gt;(lo)으로 해석, 사용
범위 = &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;[val, val+inc-1]&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;allocationSize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize &amp;gt; 1&lt;/code&gt;일 때 pooled 계열
optimizer를 자동 선택한다. 어느 쪽이든 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 시퀀스 자체의
INCREMENT를 allocationSize와 같게&lt;/strong&gt; 설정해야 한다.
PostgreSQL이라면 다음과 같이 시퀀스를 만든다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;SEQUENCE&lt;/span&gt; member_seq &lt;span class=&quot;kw&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INCREMENT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SequenceGenerator(allocationSize=50)&lt;/code&gt;만 쓰고 DB 시퀀스를
INCREMENT 1로 만들어두면 Hibernate가 받아온 값을 잘못 해석해 PK 충돌이
날 수 있다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;앱 설정과 DB 시퀀스 설정이 일치해야 한다&lt;/strong&gt;는
점은 마이그레이션 스크립트 작성 시 반드시 확인할 항목이다.&lt;/p&gt;
&lt;h3 id=&quot;4-3-id가-비어-있다는-오해&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;4-3) &amp;quot;ID가 비어 있다&amp;quot;는 오해&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize=50&lt;/code&gt;을 쓰면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK가 중간중간
빈다&lt;/strong&gt;. 이건 정상 동작이다. 이유는 두 가지다.&lt;/p&gt;
&lt;ol type=&quot;1&quot; style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;앱 재시작&lt;/strong&gt; — 1번 인스턴스가 [1..50]을 받아 23번까지
쓰고 종료되면, 24~50은 그대로 버려진다. 다음 인스턴스는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt;로 100을 받아 [51..100]을 쓴다. 24~50이 영원히 빈
ID로 남는다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;트랜잭션 롤백&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 후 PK가
할당됐어도 트랜잭션이 롤백되면 그 PK는 DB에 INSERT되지 않는다.
메모리에서만 소비된 채 사라진다&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이게 감사/운영 담당자에게 &amp;quot;ID가 왜 11, 12, 13 다음에 65로 뛰냐&amp;quot;는
질문으로 돌아온다. 답은 단순하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK는 오직 식별자일 뿐,
연속성을 의미하지 않는다.&lt;/strong&gt; &amp;quot;1번 회원, 2번 회원, ... N번 회원&amp;quot;
같은 비즈니스 의미가 필요하다면 그건 PK가 아니라 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도의 순번
컬럼&lt;/strong&gt;으로 분리해야 한다. PK에 비즈니스 의미를 얹는 순간 이런
문제가 시작된다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;5-table-전략은-왜-피하나&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;5) TABLE 전략은 왜 피하나&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;TABLE&lt;/code&gt; 전략은 PK를 받아오기 위한 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도
테이블&lt;/strong&gt;을 둔다. 이름과 마지막 값만 저장하는 단순한 테이블이고,
JPA가 표준으로 정의한 가장 이식성 좋은 방법이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TABLE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_table_gen&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@TableGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_table_gen&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    table &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;id_generator&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    pkColumnName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;gen_name&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    valueColumnName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;gen_val&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    pkColumnValue &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이 방식은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;모든 DB에서 동일하게 작동&lt;/strong&gt;한다는 장점이
있다. MySQL도, PostgreSQL도, H2도, 시퀀스가 없는 DB도 다 된다. 이식성만
보면 최강이다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단, 성능은 최악&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이유는 단순하다. PK 한 번 받으려면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id_generator&lt;/code&gt; 테이블에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT FOR UPDATE&lt;/code&gt; → &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt;를 쳐야 한다. 이게
모든 INSERT의 직렬화 지점이 된다. 한 번에 여러 트랜잭션이 같은 PK를
받으려고 줄 서서 락을 기다린다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;전략&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK 1개 비용&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;동시성&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;IDENTITY&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;INSERT의 일부 (오버헤드 없음)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;DB AUTO_INCREMENT가 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;SEQUENCE&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;nextval&lt;/code&gt; 1회 (allocationSize로 분산)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;시퀀스 객체가 비차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;TABLE&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT FOR UPDATE&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UPDATE&lt;/code&gt; (락 획득)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;테이블 행 락 직렬화&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize&lt;/code&gt;로 어느 정도 완화는 되지만, 그래도
시퀀스보다 무겁다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;실무에서는 거의 쓰지 않는다.&lt;/strong&gt; 진짜로
모든 DB를 지원하는 라이브러리를 만드는 게 아니라면 후보에서 빼는 게
맞다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;6-uuid-전략-v4-vs-v7tsid&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;6) UUID 전략: v4 vs v7/TSID&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;UUID는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB 의존 없이 앱에서 PK를 생성&lt;/strong&gt;하는 전략이다.
분산 환경, 멀티 리전, 외부 시스템과의 키 교환 등에서 자연스러운
선택이다. Hibernate 6 (Jakarta Persistence 3.1)부터
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@UuidGenerator&lt;/code&gt; 어노테이션이 정식화됐다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Hibernate 6+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@UuidGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;style &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; UuidGenerator&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Style&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;RANDOM&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// v4&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;UUID&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// Hibernate 6+&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-8&quot;&gt;&lt;a href=&quot;#cb7-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@UuidGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;style &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; UuidGenerator&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Style&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TIME&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;co&quot;&gt;// 시간 정렬 (Hibernate 고유 포맷)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-9&quot;&gt;&lt;a href=&quot;#cb7-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;UUID&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Style.TIME&lt;/code&gt;은 RFC 표준 v6/v7이 아니라 Hibernate 자체의
시간 정렬 UUID 포맷이다(v1 Gregorian timestamp를 상위 비트로 재배치).
IETF 표준 v7이 필요하면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;uuid-creator&lt;/code&gt; 같은 외부 라이브러리로
생성해 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@PrePersist&lt;/code&gt;에서 주입한다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;UUID는 분산 친화적이라는 명백한 장점이 있다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;단, 어떤 버전을
쓰느냐로 인덱스 효율이 완전히 달라진다.&lt;/strong&gt; 이게 UUID 도입의 첫
함정이다.&lt;/p&gt;
&lt;h3 id=&quot;6-1-uuidv4의-인덱스-함정&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-1) UUIDv4의 인덱스 함정&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUIDv4&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;완전 무작위&lt;/strong&gt;다. 버전·variant
고정 비트 6개를 빼면 122비트가 난수다. PK로 쓰면 새 row가 들어올 때마다
B+Tree 인덱스의 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;임의 위치&lt;/strong&gt;에 삽입된다. MySQL InnoDB는
클러스터드 인덱스라서 PK 순서로 row를 물리적으로 정렬하는데, 무작위 PK는
다음 두 가지를 동시에 일으킨다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;페이지 분할(page split)&lt;/strong&gt; — 빈 자리가 부족한 페이지에
끼워 넣으려면 페이지를 둘로 쪼개야 한다. I/O 비용이 직접적으로 든다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;버퍼 풀 적중률 저하&lt;/strong&gt; — 새 row가 모든 페이지에 흩어져
들어가므로, 자주 쓰는 페이지가 캐시에서 자꾸 밀려난다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;PostgreSQL은 클러스터드 인덱스가 아니라 영향이 덜하지만(heap에
append하고 인덱스는 별도), 인덱스 자체의 페이지 분할 비용은 마찬가지로
발생한다.&lt;/p&gt;
&lt;h3 id=&quot;6-2-uuidv7--v6의-시간-정렬&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-2) UUIDv7 / v6의 시간 정렬&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해법은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;시간 정렬 UUID&lt;/strong&gt;다. 상위 비트에 timestamp를
두면 새 PK가 항상 기존 PK보다 크게 생성되고, B+Tree의 끝부분에만 삽입돼
페이지 분할이 거의 없다. IETF가 표준화한 게 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUIDv6&lt;/code&gt;,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;UUIDv7&lt;/code&gt;이다. Hibernate의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@UuidGenerator(style = TIME)&lt;/code&gt;은 IETF 표준 v6/v7이 아닌
Hibernate 자체의 시간 정렬 포맷을 생성하므로, 표준 v7이 필요하면 외부
라이브러리(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;uuid-creator&lt;/code&gt; 등)를 쓴다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;버전&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;생성 방식&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정렬 가능?&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL InnoDB 적합도&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;v1&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;timestamp + MAC&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;부분적 (timestamp 비트 위치 때문)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;v4&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;완전 무작위&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;아니오&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;매우 낮음&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;v6&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;v1 비트 재배치 (timestamp 상위)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;예&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;v7&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;Unix timestamp ms + 난수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;예&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;새 시스템에서 UUID를 PK로 쓴다면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;v7이 사실상의
권장&lt;/strong&gt;이다. v6는 v1 호환을 위한 과도기 표준이고, v7이 가장
단순하면서 정렬 가능성을 보장한다.&lt;/p&gt;
&lt;h3 id=&quot;6-3-tsid--uuid의-가벼운-대안&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;6-3) TSID — UUID의 가벼운
대안&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;TSID(Time-Sorted ID)는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;64비트 long&lt;/strong&gt; 한 개로 시간 정렬
ID를 만든다. UUID(128비트)의 절반 크기로 같은 효과를 낸다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;크기&lt;/strong&gt;: 64비트 = MySQL &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BIGINT&lt;/code&gt; 한
컬럼&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;정렬&lt;/strong&gt;: timestamp 상위 → 자연 정렬&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;분산&lt;/strong&gt;: node ID + sequence 비트로 충돌 회피&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;가독성&lt;/strong&gt;: Crockford Base32로 13자 문자열 변환
가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;라이브러리는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;com.github.f4b6a3:tsid-creator&lt;/code&gt;가 표준이다.
Hibernate 표준 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@UuidGenerator&lt;/code&gt;로는 TSID를 만들 수 없으므로,
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;앱 레벨에서 직접 생성해 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;persist&lt;/code&gt; 전에
set&lt;/strong&gt;한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@PrePersist&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;assignId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; TsidCreator&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getTsid&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@GeneratedValue&lt;/code&gt; 없이 앱에서 직접 PK를 박는 패턴이다. JPA
입장에서는 &amp;quot;이미 PK가 있는 엔티티&amp;quot;로 보이므로 batch insert도 자유롭다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;분산 + batch + 인덱스 효율을 동시에 만족하는 가장 가벼운
조합&lt;/strong&gt;이 TSID다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기서 틀리기 쉬운 건 &amp;quot;UUID와 TSID 둘 중 무조건 하나가 좋다&amp;quot;는
단순화다. 외부 시스템과의 키 교환이 잦거나 표준 호환이 중요하면 UUIDv7이
낫다. PK 크기를 줄이고 BIGINT 그대로 다루고 싶으면 TSID가 낫다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;요구사항이 가른다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;7-mysql-uuid-저장-전략-varchar36이-왜-위험한가&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;7) MySQL UUID
저장 전략: VARCHAR(36)이 왜 위험한가&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;UUID를 PK로 골랐다면 다음 결정은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;DB에 어떻게 저장할
것인가&lt;/strong&gt;다. PostgreSQL은 네이티브 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;uuid&lt;/code&gt; 타입이 있어
16바이트 그대로 저장된다. MySQL은 네이티브 타입이 없어서 보통 두 가지 중
하나를 고른다.&lt;/p&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;저장 방식&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;컬럼 타입&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;크기&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;인덱스 페이지당 행 수&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;VARCHAR(36)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;문자열 (&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&amp;#39;550e8400-e29b-...&amp;#39;&lt;/code&gt;)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;36바이트 + 길이 prefix&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;적음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY(16)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;16바이트 raw&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;16바이트&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;대략 2배 수준&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;VARCHAR(36)&lt;/code&gt;로 저장하면 인덱스 한 노드에 들어갈 수 있는
PK 개수가 대략 절반 수준으로 줄어든다. 인덱스 깊이가 깊어지고, 같은
메모리에 캐시할 수 있는 인덱스 페이지도 줄어든다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY(16)&lt;/code&gt;이 항상 우선&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3 id=&quot;7-1-hibernate-6의-매핑-제어&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-1) Hibernate 6의 매핑 제어&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6은 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@JdbcTypeCode&lt;/code&gt;로 UUID의 JDBC 타입을 명시할
수 있다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;JdbcTypeCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;im&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;hibernate&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;im&quot;&gt;SqlTypes&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@UuidGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;style &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; UuidGenerator&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;Style&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;TIME&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@JdbcTypeCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;SqlTypes&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;BINARY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// BINARY(16)으로 저장&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;length &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-8&quot;&gt;&lt;a href=&quot;#cb9-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;UUID&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SqlTypes.CHAR&lt;/code&gt;로 두면 문자열 매핑이 되고,
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SqlTypes.BINARY&lt;/code&gt;로 두면 바이너리 매핑이 된다.
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL이라면 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY&lt;/code&gt;가 기본값이어야 한다&lt;/strong&gt;.
PostgreSQL은 별도 지정 없이 네이티브 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;uuid&lt;/code&gt;로 매핑되므로 신경
쓸 일이 없다.&lt;/p&gt;
&lt;h3 id=&quot;7-2-가독성-문제&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;7-2) 가독성 문제&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY(16)&lt;/code&gt;은 인덱스에 좋지만 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;콘솔에서 바로 읽기
어렵다&lt;/strong&gt;. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;mysql&amp;gt;&lt;/code&gt; 프롬프트에서 SELECT하면 16바이트
raw가 깨진 문자로 나온다. 운영 편의를 위해서는 view나 generated
column으로 문자열 변환을 노출해두는 편이 좋다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;member&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;COLUMN&lt;/span&gt; id_str &lt;span class=&quot;dt&quot;&gt;VARCHAR&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;36&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;AS&lt;/span&gt; (&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;fu&quot;&gt;LOWER&lt;/span&gt;(CONCAT_WS(&lt;span class=&quot;st&quot;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        HEX(SUBSTRING(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;4&lt;/span&gt;)),&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        HEX(SUBSTRING(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;5&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;)),&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        HEX(SUBSTRING(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;)),&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        HEX(SUBSTRING(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;9&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;2&lt;/span&gt;)),&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        HEX(SUBSTRING(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;11&lt;/span&gt;, &lt;span class=&quot;dv&quot;&gt;6&lt;/span&gt;))&lt;/span&gt;
&lt;span id=&quot;cb10-8&quot;&gt;&lt;a href=&quot;#cb10-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    ))&lt;/span&gt;
&lt;span id=&quot;cb10-9&quot;&gt;&lt;a href=&quot;#cb10-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;) VIRTUAL;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이렇게 하면
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SELECT * FROM member WHERE id_str = &amp;#39;550e8400-...&amp;#39;&lt;/code&gt;로
가독성을 챙기면서 실제 PK는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY(16)&lt;/code&gt;을 유지할 수 있다.
인덱스 효율과 사람의 눈을 둘 다 챙기는 절충안이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;8-idclass-vs-embeddedid-복합-키-두-방식&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;8) @IdClass vs
@EmbeddedId: 복합 키 두 방식&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;여기까지가 단일 PK 이야기였다면, 이제 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;여러 컬럼이 모여 PK를
이루는&lt;/strong&gt; 복합 키로 넘어간다. JPA가 정의한 표준은 두 가지다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-idclass--엔티티에-id-여러-개&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-1) @IdClass — 엔티티에 @Id
여러 개&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티 안에 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Id&lt;/code&gt;를 여러 번 붙이고, 그 조합을 표현하는
별도 클래스를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@IdClass&lt;/code&gt;로 지정한다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb11&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb11-1&quot;&gt;&lt;a href=&quot;#cb11-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-2&quot;&gt;&lt;a href=&quot;#cb11-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@IdClass&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;OrderItemId&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-3&quot;&gt;&lt;a href=&quot;#cb11-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderItem &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-4&quot;&gt;&lt;a href=&quot;#cb11-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-5&quot;&gt;&lt;a href=&quot;#cb11-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-6&quot;&gt;&lt;a href=&quot;#cb11-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-7&quot;&gt;&lt;a href=&quot;#cb11-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-8&quot;&gt;&lt;a href=&quot;#cb11-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-9&quot;&gt;&lt;a href=&quot;#cb11-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-10&quot;&gt;&lt;a href=&quot;#cb11-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-11&quot;&gt;&lt;a href=&quot;#cb11-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-12&quot;&gt;&lt;a href=&quot;#cb11-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// getter·setter 생략&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-13&quot;&gt;&lt;a href=&quot;#cb11-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-14&quot;&gt;&lt;a href=&quot;#cb11-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-15&quot;&gt;&lt;a href=&quot;#cb11-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderItemId &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-16&quot;&gt;&lt;a href=&quot;#cb11-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-17&quot;&gt;&lt;a href=&quot;#cb11-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-18&quot;&gt;&lt;a href=&quot;#cb11-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-19&quot;&gt;&lt;a href=&quot;#cb11-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-20&quot;&gt;&lt;a href=&quot;#cb11-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-21&quot;&gt;&lt;a href=&quot;#cb11-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-22&quot;&gt;&lt;a href=&quot;#cb11-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderId&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-23&quot;&gt;&lt;a href=&quot;#cb11-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;productId&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-24&quot;&gt;&lt;a href=&quot;#cb11-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-25&quot;&gt;&lt;a href=&quot;#cb11-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-26&quot;&gt;&lt;a href=&quot;#cb11-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-27&quot;&gt;&lt;a href=&quot;#cb11-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Object&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-28&quot;&gt;&lt;a href=&quot;#cb11-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-29&quot;&gt;&lt;a href=&quot;#cb11-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(!(&lt;/span&gt;o &lt;span class=&quot;kw&quot;&gt;instanceof&lt;/span&gt; OrderItemId that&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-30&quot;&gt;&lt;a href=&quot;#cb11-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; that&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-31&quot;&gt;&lt;a href=&quot;#cb11-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; that&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;productId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-32&quot;&gt;&lt;a href=&quot;#cb11-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-33&quot;&gt;&lt;a href=&quot;#cb11-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-34&quot;&gt;&lt;a href=&quot;#cb11-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-35&quot;&gt;&lt;a href=&quot;#cb11-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-36&quot;&gt;&lt;a href=&quot;#cb11-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-37&quot;&gt;&lt;a href=&quot;#cb11-37&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb11-38&quot;&gt;&lt;a href=&quot;#cb11-38&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@IdClass&lt;/code&gt;는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티의 필드 구조를 그대로 펼쳐 PK로
쓴다&lt;/strong&gt;. 엔티티 입장에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orderId&lt;/code&gt;와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;productId&lt;/code&gt;가 평범한 필드로 보이고, 별도의 PK 클래스는 키 값
비교 용도로만 쓰인다.&lt;/p&gt;
&lt;h3 id=&quot;8-2-embeddedid--별도-pk-클래스-통째로&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-2) @EmbeddedId — 별도
PK 클래스 통째로&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;PK를 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;하나의 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt; 클래스&lt;/strong&gt;로 묶어
엔티티에 박는다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb12&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb12-1&quot;&gt;&lt;a href=&quot;#cb12-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-2&quot;&gt;&lt;a href=&quot;#cb12-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderItem &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-3&quot;&gt;&lt;a href=&quot;#cb12-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@EmbeddedId&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-4&quot;&gt;&lt;a href=&quot;#cb12-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OrderItemId id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-5&quot;&gt;&lt;a href=&quot;#cb12-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-6&quot;&gt;&lt;a href=&quot;#cb12-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-7&quot;&gt;&lt;a href=&quot;#cb12-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-8&quot;&gt;&lt;a href=&quot;#cb12-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItem&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-9&quot;&gt;&lt;a href=&quot;#cb12-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-10&quot;&gt;&lt;a href=&quot;#cb12-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItem&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-11&quot;&gt;&lt;a href=&quot;#cb12-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-12&quot;&gt;&lt;a href=&quot;#cb12-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-13&quot;&gt;&lt;a href=&quot;#cb12-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-14&quot;&gt;&lt;a href=&quot;#cb12-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-15&quot;&gt;&lt;a href=&quot;#cb12-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-16&quot;&gt;&lt;a href=&quot;#cb12-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Embeddable&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-17&quot;&gt;&lt;a href=&quot;#cb12-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderItemId &lt;span class=&quot;kw&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-18&quot;&gt;&lt;a href=&quot;#cb12-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-19&quot;&gt;&lt;a href=&quot;#cb12-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-20&quot;&gt;&lt;a href=&quot;#cb12-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-21&quot;&gt;&lt;a href=&quot;#cb12-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-22&quot;&gt;&lt;a href=&quot;#cb12-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-23&quot;&gt;&lt;a href=&quot;#cb12-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-24&quot;&gt;&lt;a href=&quot;#cb12-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderId&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-25&quot;&gt;&lt;a href=&quot;#cb12-25&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;productId&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-26&quot;&gt;&lt;a href=&quot;#cb12-26&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-27&quot;&gt;&lt;a href=&quot;#cb12-27&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-28&quot;&gt;&lt;a href=&quot;#cb12-28&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-29&quot;&gt;&lt;a href=&quot;#cb12-29&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bu&quot;&gt;Object&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-30&quot;&gt;&lt;a href=&quot;#cb12-30&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; o&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-31&quot;&gt;&lt;a href=&quot;#cb12-31&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;(!(&lt;/span&gt;o &lt;span class=&quot;kw&quot;&gt;instanceof&lt;/span&gt; OrderItemId that&lt;span class=&quot;op&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-32&quot;&gt;&lt;a href=&quot;#cb12-32&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; that&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orderId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-33&quot;&gt;&lt;a href=&quot;#cb12-33&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;            &lt;span class=&quot;op&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; that&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;productId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-34&quot;&gt;&lt;a href=&quot;#cb12-34&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-35&quot;&gt;&lt;a href=&quot;#cb12-35&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-36&quot;&gt;&lt;a href=&quot;#cb12-36&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-37&quot;&gt;&lt;a href=&quot;#cb12-37&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-38&quot;&gt;&lt;a href=&quot;#cb12-38&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;        &lt;span class=&quot;cf&quot;&gt;return&lt;/span&gt; Objects&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-39&quot;&gt;&lt;a href=&quot;#cb12-39&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb12-40&quot;&gt;&lt;a href=&quot;#cb12-40&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티의 PK는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id&lt;/code&gt; 한 필드이고, 그 안에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orderId&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;productId&lt;/code&gt;가 들어 있다.
객체지향적으로 더 자연스럽고, PK 클래스를 다른 곳에서도 재사용하기
쉽다.&lt;/p&gt;
&lt;h3 id=&quot;8-3-비교&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-3) 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse:collapse;width:100%;margin:1.4em 0;font-size:0.94rem;border:1px solid #d1d9e0;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;항목&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;@IdClass&lt;/strong&gt;&lt;/th&gt;
&lt;th style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;font-weight:700;color:#0a0a0a;background:#f6f8fa;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;@EmbeddedId&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK 위치&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;엔티티 필드에 분산&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt; 한 객체에 캡슐화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPQL 접근&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;select o.orderId from OrderItem o&lt;/code&gt; (직관적)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;select o.id.orderId from OrderItem o&lt;/code&gt; (중첩)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;객체지향성&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;낮음 (필드 평탄화)&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;높음&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;재사용&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;PK 클래스가 엔티티와 분리된 별개의 표현&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt; 자체를 다른 엔티티에서도 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;표준 권장&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;JPA 1.0 호환용&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;현대 JPA의 권장 방식&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;필수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Serializable&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;필수&lt;/td&gt;
&lt;td style=&quot;padding:10px 14px;text-align:left;border:1px solid #d1d9e0;vertical-align:top;&quot;&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;새 코드는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;로 가는 게 맞다.&lt;/strong&gt;
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@IdClass&lt;/code&gt;는 레거시 DB 매핑이나 JPA 1.0 호환이 필요한
경우만이다.&lt;/p&gt;
&lt;h3 id=&quot;8-4-equals--hashcode가-필수인-이유&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;8-4) equals / hashCode가
필수인 이유&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;복합 키 클래스에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt;와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt;를 안 만들면 영속성 컨텍스트가 깨진 것처럼
보인다&lt;/strong&gt;. 1차 캐시는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;(엔티티 클래스 + PK)&lt;/code&gt; 조합을
key로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;HashMap&lt;/code&gt;에 저장한다. 같은 PK인데 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt;가
default(Object의 reference equality)면 두 번째 조회에서
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;null&lt;/code&gt;이 나오고, JPA는 DB로 다시 쿼리를 날린다. 또는 같은
식별자인 두 인스턴스가 영속성 컨텍스트에 동시에 존재하는 모순이
생긴다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb13&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb13-1&quot;&gt;&lt;a href=&quot;#cb13-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 같은 PK이지만 equals/hashCode가 없으면 false&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-2&quot;&gt;&lt;a href=&quot;#cb13-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;OrderItemId k1 &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;100L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-3&quot;&gt;&lt;a href=&quot;#cb13-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;OrderItemId k2 &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;100L&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-4&quot;&gt;&lt;a href=&quot;#cb13-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-5&quot;&gt;&lt;a href=&quot;#cb13-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;k1&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;k2&lt;span class=&quot;op&quot;&gt;));&lt;/span&gt;        &lt;span class=&quot;co&quot;&gt;// false (Object.equals)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-6&quot;&gt;&lt;a href=&quot;#cb13-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;bu&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;k1&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;==&lt;/span&gt; k2&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;  &lt;span class=&quot;co&quot;&gt;// false&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-7&quot;&gt;&lt;a href=&quot;#cb13-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-8&quot;&gt;&lt;a href=&quot;#cb13-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 결과: em.find(OrderItem.class, k1)와 em.find(OrderItem.class, k2)가&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb13-9&quot;&gt;&lt;a href=&quot;#cb13-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;//       1차 캐시에서 다른 객체로 인식되거나, 캐시 미스로 DB 쿼리가 추가 발생&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해결은 단순하다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK 클래스에는 무조건 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Serializable&lt;/code&gt;을 박는다.&lt;/strong&gt;
자동 생성에 의존해도 되고, Java 17+의 record로 만들어도 된다(record는
자동으로 갖춰진다). 다만 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;에 record를 쓰려면
Hibernate가 정확히 해석할 수 있는지 버전을 확인해야 한다 — Hibernate
6.x는 record &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;을 공식 지원한다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;9-mapsid로-파생-식별자-구성&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;9) @MapsId로 파생 식별자 구성&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;복합 키의 일부가 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;다른 엔티티의 PK를 그대로 가져오는&lt;/strong&gt;
경우가 흔하다. 예를 들어 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderItem&lt;/code&gt;의 PK가
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;(orderId, productId)&lt;/code&gt;인데, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;orderId&lt;/code&gt;는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order&lt;/code&gt; 엔티티의 PK다. 이 관계를 표현하는 게
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb14&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb14-1&quot;&gt;&lt;a href=&quot;#cb14-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-2&quot;&gt;&lt;a href=&quot;#cb14-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; OrderItem &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-3&quot;&gt;&lt;a href=&quot;#cb14-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-4&quot;&gt;&lt;a href=&quot;#cb14-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@EmbeddedId&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-5&quot;&gt;&lt;a href=&quot;#cb14-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; OrderItemId id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-6&quot;&gt;&lt;a href=&quot;#cb14-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-7&quot;&gt;&lt;a href=&quot;#cb14-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@MapsId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;orderId&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;            &lt;span class=&quot;co&quot;&gt;// id.orderId 필드를 이 연관에서 파생&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-8&quot;&gt;&lt;a href=&quot;#cb14-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; FetchType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-9&quot;&gt;&lt;a href=&quot;#cb14-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;order_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-10&quot;&gt;&lt;a href=&quot;#cb14-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Order order&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-11&quot;&gt;&lt;a href=&quot;#cb14-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-12&quot;&gt;&lt;a href=&quot;#cb14-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@MapsId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;productId&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-13&quot;&gt;&lt;a href=&quot;#cb14-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@ManyToOne&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;fetch &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; FetchType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;LAZY&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-14&quot;&gt;&lt;a href=&quot;#cb14-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@JoinColumn&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;product_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-15&quot;&gt;&lt;a href=&quot;#cb14-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; Product product&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-16&quot;&gt;&lt;a href=&quot;#cb14-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-17&quot;&gt;&lt;a href=&quot;#cb14-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;dt&quot;&gt;int&lt;/span&gt; quantity&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb14-18&quot;&gt;&lt;a href=&quot;#cb14-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId(&amp;quot;orderId&amp;quot;)&lt;/code&gt;는 &amp;quot;이 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@ManyToOne&lt;/code&gt; 연관의
PK를 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id.orderId&lt;/code&gt;로 매핑하라&amp;quot;는 의미다. 결과적으로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderItem&lt;/code&gt;의 PK인 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;(orderId, productId)&lt;/code&gt;는 두
연관 엔티티의 PK에서 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;자동으로 파생&lt;/strong&gt;된다. 별도로
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;id.setOrderId(...)&lt;/code&gt;를 호출할 필요가 없다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb15&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb15-1&quot;&gt;&lt;a href=&quot;#cb15-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// @MapsId 사용 (선호)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-2&quot;&gt;&lt;a href=&quot;#cb15-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Order order &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-3&quot;&gt;&lt;a href=&quot;#cb15-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;Product product &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; productRepository&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;op&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-4&quot;&gt;&lt;a href=&quot;#cb15-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-5&quot;&gt;&lt;a href=&quot;#cb15-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;OrderItem item &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItem&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-6&quot;&gt;&lt;a href=&quot;#cb15-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItemId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;// 빈 PK&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-7&quot;&gt;&lt;a href=&quot;#cb15-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setOrder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;            &lt;span class=&quot;co&quot;&gt;// @MapsId가 id.orderId = order.id 자동 채움&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-8&quot;&gt;&lt;a href=&quot;#cb15-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setProduct&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;product&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;        &lt;span class=&quot;co&quot;&gt;// @MapsId가 id.productId = product.id 자동 채움&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-9&quot;&gt;&lt;a href=&quot;#cb15-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setQuantity&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-10&quot;&gt;&lt;a href=&quot;#cb15-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb15-11&quot;&gt;&lt;a href=&quot;#cb15-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;em&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;persist&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;9-1-mapsid-없이-처리하면&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-1) @MapsId 없이 처리하면&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt; 없이 같은 구조를 만들려면 PK 필드와 연관 필드를
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;둘 다 세팅&lt;/strong&gt;해야 한다. 안 그러면 PK는 비어 있고 연관만
박혀 있는 상태가 된다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb16&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb16-1&quot;&gt;&lt;a href=&quot;#cb16-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 피하기&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-2&quot;&gt;&lt;a href=&quot;#cb16-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;OrderItem item &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;OrderItem&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-3&quot;&gt;&lt;a href=&quot;#cb16-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setOrderId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;     &lt;span class=&quot;co&quot;&gt;// PK 수동 세팅&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-4&quot;&gt;&lt;a href=&quot;#cb16-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setProductId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;product&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-5&quot;&gt;&lt;a href=&quot;#cb16-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setOrder&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;                        &lt;span class=&quot;co&quot;&gt;// 연관 세팅 (중복 작업)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb16-6&quot;&gt;&lt;a href=&quot;#cb16-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;item&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;setProduct&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;product&lt;span class=&quot;op&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;같은 값을 두 번 세팅하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;불일치 가능성&lt;/strong&gt;이 생긴다.
PK는 1번 주문, 연관은 2번 주문을 가리키는 모순이 가능해진다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;는 이 불일치를 컴파일 단계는 아니지만
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;런타임에서 자동 보정&lt;/strong&gt;해 준다.&lt;/p&gt;
&lt;h3 id=&quot;9-2-pk-컬럼과-fk-컬럼-공유&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;9-2) PK 컬럼과 FK 컬럼 공유&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;를 쓰면 PK 컬럼과 FK 컬럼이 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;물리적으로
같은 컬럼&lt;/strong&gt;이 된다. 즉 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;OrderItem&lt;/code&gt; 테이블에는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;order_id&lt;/code&gt;, &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;product_id&lt;/code&gt; 두 컬럼만 있고, 이 두
컬럼이 동시에 PK이자 FK다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb17&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb17-1&quot;&gt;&lt;a href=&quot;#cb17-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;TABLE&lt;/span&gt; order_item (&lt;/span&gt;
&lt;span id=&quot;cb17-2&quot;&gt;&lt;a href=&quot;#cb17-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    order_id    BIGINT &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb17-3&quot;&gt;&lt;a href=&quot;#cb17-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    product_id  BIGINT &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb17-4&quot;&gt;&lt;a href=&quot;#cb17-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    quantity    &lt;span class=&quot;dt&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;NULL&lt;/span&gt;,&lt;/span&gt;
&lt;span id=&quot;cb17-5&quot;&gt;&lt;a href=&quot;#cb17-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt; (order_id, product_id),&lt;/span&gt;
&lt;span id=&quot;cb17-6&quot;&gt;&lt;a href=&quot;#cb17-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt; (order_id)   &lt;span class=&quot;kw&quot;&gt;REFERENCES&lt;/span&gt; orders(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;),&lt;/span&gt;
&lt;span id=&quot;cb17-7&quot;&gt;&lt;a href=&quot;#cb17-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;KEY&lt;/span&gt; (product_id) &lt;span class=&quot;kw&quot;&gt;REFERENCES&lt;/span&gt; product(&lt;span class=&quot;kw&quot;&gt;id&lt;/span&gt;)&lt;/span&gt;
&lt;span id=&quot;cb17-8&quot;&gt;&lt;a href=&quot;#cb17-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;스키마 입장에서도 자연스럽고, 인덱스 중복도 없다. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;연관
테이블/조인 테이블에서는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;가 거의 항상
정답&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;10-자연-키-vs-대리-키-그리고-시퀀스-이름-공유&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;10) 자연 키 vs
대리 키, 그리고 시퀀스 이름 공유&lt;/h2&gt;
&lt;h3 id=&quot;10-1-자연-키는-pk로-쓰지-마라&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-1) 자연 키는 PK로 쓰지
마라&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;자연 키(natural key)는 비즈니스 의미가 있는 식별자다 — 이메일,
주민등록번호, 상품 코드, ISBN 등. &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;PK로 쓰면 안 된다&lt;/strong&gt;는
게 현대 DB 설계의 기본 합의다. 이유는 단순하다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;변경 비용&lt;/strong&gt; — 이메일은 바뀐다. 상품 코드는 정책에
따라 재발급된다. PK가 바뀌면 모든 FK를 갱신해야 한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;외부 노출&lt;/strong&gt; — PK는 JOIN 키로 모든 테이블에 박힌다.
비즈니스 의미가 노출되면 보안/프라이버시 문제로 번진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;타입의 자유도&lt;/strong&gt; — 자연 키는 가변 길이 문자열이 많다.
인덱스 효율이 떨어진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;해법은 **대리 키(surrogate key)**다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Long&lt;/code&gt; 또는
UUID/TSID로 의미 없는 식별자를 만들고, 자연 키는 별도 컬럼에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;unique&lt;/code&gt; 제약을 거는 식이다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb18&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb18-1&quot;&gt;&lt;a href=&quot;#cb18-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@Entity&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-2&quot;&gt;&lt;a href=&quot;#cb18-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Member&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-3&quot;&gt;&lt;a href=&quot;#cb18-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Id&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-4&quot;&gt;&lt;a href=&quot;#cb18-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;SEQUENCE&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; generator &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-5&quot;&gt;&lt;a href=&quot;#cb18-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-6&quot;&gt;&lt;a href=&quot;#cb18-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;                        &lt;span class=&quot;co&quot;&gt;// 대리 키&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-7&quot;&gt;&lt;a href=&quot;#cb18-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-8&quot;&gt;&lt;a href=&quot;#cb18-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;at&quot;&gt;@Column&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;unique &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; nullable &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-9&quot;&gt;&lt;a href=&quot;#cb18-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;String&lt;/span&gt; email&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;                   &lt;span class=&quot;co&quot;&gt;// 자연 키, unique 제약&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb18-10&quot;&gt;&lt;a href=&quot;#cb18-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;이렇게 두면 이메일이 바뀌어도 PK는 그대로다. FK 갱신이 필요 없고,
외부에는 PK만 노출하면 된다.&lt;/p&gt;
&lt;h3 id=&quot;10-2-시퀀스-이름은-엔티티별로&quot; style=&quot;font-size:1.25rem;font-weight:700;line-height:1.3;color:#1f2328;margin-top:1.8em;margin-bottom:0.6em;&quot;&gt;10-2) 시퀀스 이름은
엔티티별로&lt;/h3&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 5는 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SequenceGenerator&lt;/code&gt;를 명시하지 않으면 모든
엔티티가 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hibernate_sequence&lt;/code&gt;&lt;/strong&gt; 라는 단일
시퀀스를 공유했다. 결과적으로 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Member&lt;/code&gt;와 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order&lt;/code&gt;가
같은 시퀀스에서 PK를 받아 ID 값이 뒤섞였다. 운영하면서 &amp;quot;회원 1, 2, 3
다음에 7, 8이 나오는 이유는 4, 5, 6이 주문에 할당돼서&amp;quot;라는 사실을 모르고
의아해하는 경우가 많았다.&lt;/p&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;Hibernate 6은 이 동작을 바꿨다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@GeneratedValue(strategy = SEQUENCE)&lt;/code&gt;만 있고
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@SequenceGenerator&lt;/code&gt;가 없으면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티별 개별
시퀀스&lt;/strong&gt;(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&amp;lt;entity&amp;gt;_SEQ&lt;/code&gt;)를 사용한다. 다만
명시적으로 이름을 주는 편이 의도가 분명하고 마이그레이션 스크립트와
매칭하기도 쉽다.&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb19&quot;&gt;&lt;pre class=&quot;sourceCode java&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode java&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb19-1&quot;&gt;&lt;a href=&quot;#cb19-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// 권장&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-2&quot;&gt;&lt;a href=&quot;#cb19-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;at&quot;&gt;@SequenceGenerator&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-3&quot;&gt;&lt;a href=&quot;#cb19-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    name &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-4&quot;&gt;&lt;a href=&quot;#cb19-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    sequenceName &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;member_seq&amp;quot;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-5&quot;&gt;&lt;a href=&quot;#cb19-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;    allocationSize &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb19-6&quot;&gt;&lt;a href=&quot;#cb19-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;op&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb20&quot;&gt;&lt;pre class=&quot;sourceCode sql&quot; style=&quot;background:#f6f8fa;border:1px solid #d1d9e0;border-radius:6px;padding:16px 20px;overflow-x:auto;line-height:1.55;margin:1.2em 0;&quot;&gt;&lt;code style=&quot;background:none;padding:0;border:0;font-size:0.88rem;color:#1f2328; class=&quot;sourceCode sql&quot; style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;&lt;span id=&quot;cb20-1&quot;&gt;&lt;a href=&quot;#cb20-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;SEQUENCE&lt;/span&gt; member_seq &lt;span class=&quot;kw&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INCREMENT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;;&lt;/span&gt;
&lt;span id=&quot;cb20-2&quot;&gt;&lt;a href=&quot;#cb20-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;SEQUENCE&lt;/span&gt; order_seq  &lt;span class=&quot;kw&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INCREMENT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;;&lt;/span&gt;
&lt;span id=&quot;cb20-3&quot;&gt;&lt;a href=&quot;#cb20-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; style=&quot;color:#0969da;text-decoration:none;&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;SEQUENCE&lt;/span&gt; product_seq &lt;span class=&quot;kw&quot;&gt;START&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;INCREMENT&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;50&lt;/span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;엔티티별 시퀀스는 다음 두 가지 이점이 있다.&lt;/p&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;의미 분리&lt;/strong&gt; — &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Member.id&lt;/code&gt;와
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Order.id&lt;/code&gt;가 독립된 카운터를 갖는다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;장애 격리&lt;/strong&gt; — 한 시퀀스가 비정상 상태가 돼도 다른
엔티티에 영향이 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;전사 단일 시퀀스는 분산 ID 생성 같은 특수 목적이 아니면 쓸 일이 없다.
새 엔티티 만들 때마다 시퀀스 한 개 만드는 걸 규약으로 삼는 편이
안전하다.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;11-실무에서-이렇게-읽고-쓴다&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;11) 실무에서 이렇게 읽고 쓴다&lt;/h2&gt;
&lt;ul style=&quot;margin:0.8em 0 1.1em;padding-left:1.8em;line-height:1.75;&quot;&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;새 엔티티 만들 때 &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@GeneratedValue&lt;/code&gt; 한 줄을 그냥 붙이지
말고 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;batch insert가 필요한가 / 분산 환경인가&lt;/strong&gt; 두 질문을
먼저 한다. 이 질문에 따라 IDENTITY / SEQUENCE / UUID·TSID 중 하나로
좁혀진다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;MySQL + IDENTITY&lt;/strong&gt;는 batch insert가 안 된다는 구조적
제약을 안고 있다. &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;saveAll(10000건)&lt;/code&gt;이 느리면 PK 전략부터
의심한다. PostgreSQL이면 SEQUENCE + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;allocationSize=50&lt;/code&gt;이
무난한 디폴트&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;UUID를 쓴다면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;반드시 v7 또는 TSID&lt;/strong&gt;다. v4는 인덱스
페이지 분할로 성능을 갉아먹는다. MySQL에 UUID를 저장하면
&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;BINARY(16)&lt;/code&gt;&lt;/strong&gt;, 절대
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;VARCHAR(36)&lt;/code&gt;으로 두지 말 것.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@JdbcTypeCode(SqlTypes.BINARY)&lt;/code&gt;로 명시&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;SEQUENCE allocationSize&lt;/code&gt;로 ID 갭이 생기는 건 정상이다.
운영 담당자에게 &amp;quot;PK는 식별자일 뿐 연속성을 의미하지 않는다&amp;quot;는 점을
설명할 수 있어야 한다. 비즈니스 순번이 필요하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;별도
컬럼&lt;/strong&gt;으로 분리한다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;복합 키는 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;연관 테이블 / 조인 테이블에만&lt;/strong&gt; 쓰고, 일반
엔티티는 대리 키 + 자연 키 unique 제약이 정공법이다. 복합 키를 일반
엔티티에 박으면 페이징, 정렬, 외부 참조 모든 곳에서 비용이
따라붙는다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt;를 쓰면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt; /
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt; / &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Serializable&lt;/code&gt; 3종은
무조건&lt;/strong&gt;이다. 빠지면 1차 캐시가 동작하지 않는 것처럼 보이는
기괴한 증상이 나온다. Java 17+ record &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@Embeddable&lt;/code&gt;이 가장
안전한 패턴&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@IdClass&lt;/code&gt;는 레거시 호환용. 새 코드는
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt; + &lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;로 가는 게 표준이다.
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;는 &amp;quot;PK와 FK가 같은 컬럼&amp;quot;이라는 자연스러운 매핑을
어노테이션 한 줄로 표현해 준다&lt;/li&gt;
&lt;li style=&quot;margin:0.25em 0;line-height:1.75;&quot;&gt;시퀀스 이름은 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;엔티티별로 명시&lt;/strong&gt;한다. 디폴트 공유
시퀀스에 의존하지 말 것. 마이그레이션
스크립트(&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Flyway&lt;/code&gt;/&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Liquibase&lt;/code&gt;)에
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;CREATE SEQUENCE ... INCREMENT BY 50&lt;/code&gt;을 함께 적어두는 걸
규약으로 삼는다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;h2 id=&quot;12-한-줄-정리&quot; style=&quot;font-size:1.55rem;font-weight:700;line-height:1.3;color:#1f2328;padding-bottom:0.3em;border-bottom:1px solid #d1d9e0;margin-top:2.5em;margin-bottom:0.6em;letter-spacing:-0.01em;&quot;&gt;12) 한 줄 정리&lt;/h2&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;JPA의 ID 전략은 batch insert 가능 여부와 분산 친화성으로
갈린다&lt;/strong&gt;. MySQL+IDENTITY의 batch 제약과 UUIDv4의 인덱스 함정은
도입 전에 반드시 알아야 할 두 함정이다. 복합 키는 가능하면 피하되
필요하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@EmbeddedId&lt;/code&gt; +
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;@MapsId&lt;/code&gt;&lt;/strong&gt; 조합으로 가고, PK 클래스의
&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;equals&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;hashCode&lt;/code&gt;·&lt;code style=&quot;font-family:&quot;JetBrains Mono&quot;,&quot;SF Mono&quot;,Menlo,Consolas,&quot;D2Coding&quot;,monospace;font-size:0.88em;&quot;&gt;Serializable&lt;/code&gt;은
협상 불가다. ID는 그저 식별자일 뿐 연속성도 비즈니스 의미도 담지
않는다는 원칙을 유지하면 &lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;운영 중 ID 갭이나 자연 키 변경 같은
사고를 사전에 잘라낼 수 있다&lt;/strong&gt;.&lt;/p&gt;
&lt;hr style=&quot;border:0;height:1px;background:#d1d9e0;margin:2.5em 0;&quot; /&gt;
&lt;p style=&quot;margin:0.6em 0 1.4em;text-indent:1em;line-height:1.75;color:#1f2328;&quot;&gt;&lt;strong style=&quot;color:#0a0a0a;font-weight:700;&quot;&gt;태그&lt;/strong&gt;: JPA, Hibernate, IDENTITY, SEQUENCE, UUID,
TSID, EmbeddedId, MapsId, 복합키&lt;/p&gt;
&lt;/div&gt;</description>
      <category>CS/Spring</category>
      <category>EmbeddedId</category>
      <category>Hibernate</category>
      <category>identity</category>
      <category>JPA</category>
      <category>MapsId</category>
      <category>SEQUENCE</category>
      <category>TSID</category>
      <category>UUID</category>
      <category>복합키</category>
      <author>dding-shark</author>
      <guid isPermaLink="true">https://dding-shark.tistory.com/420</guid>
      <comments>https://dding-shark.tistory.com/420#entry420comment</comments>
      <pubDate>Tue, 21 Apr 2026 20:24:17 +0900</pubDate>
    </item>
  </channel>
</rss>