feat: 가상화 문서들 추가

This commit is contained in:
DongHyeonka
2026-09-10 08:54:05 +09:00
parent e9f6a93327
commit 43e1aadef0
695 changed files with 153404 additions and 12754 deletions
@@ -0,0 +1,24 @@
# EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
## Alternative text
loadFeed 매핑, Hibernate, PostgreSQL 사이에서 루트 SELECT, EAGER user·page 2차 SELECT, getHighlights 접근, LAZY highlights SELECT가 차례로 일어나는 시퀀스.
## Long description
세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다. 이때 나가는 SQL 원문은 SELECT * FROM highlights WHERE feed_item_id = ? 이다.
## Elements and evidence
- **loadFeed DTO 매핑** (participant): FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. Evidence: L356L356, L519L522.
- **Hibernate** (participant): 파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. Evidence: L354L356.
- **PostgreSQL** (participant): Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. Evidence: L480L487, L515L515.
## Relationships
- **loadFeed DTO 매핑 → Hibernate:** findAllBy(...). Evidence: L354L354.
- **Hibernate → PostgreSQL:** SELECT feed_items. Evidence: L354L354.
- **Hibernate → PostgreSQL:** SELECT user / page · EAGER 2차. Evidence: L352L355.
- **Hibernate → loadFeed DTO 매핑:** EAGER 연관이 채워진 FeedItem 반환. Evidence: L352L354.
- **loadFeed DTO 매핑 → Hibernate:** 매핑 중 getHighlights() 접근. Evidence: L356L356, L519L522.
- **Hibernate → PostgreSQL:** SELECT highlights · LAZY 초기화. Evidence: L356L356, L493L495.
@@ -0,0 +1,18 @@
# EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
# Question: 루트 피드 조회부터 EAGER ToOne과 LAZY highlights 조회까지 SQL은 어떤 순서로 발생하는가?
direction: right
n0: "loadFeed DTO 매핑" {
shape: rectangle
}
n1: "Hibernate" {
shape: rectangle
}
n2: "PostgreSQL" {
shape: rectangle
}
n0 -> n1: "findAllBy(...)"
n1 -> n2: "SELECT feed_items"
n1 -> n2: "SELECT user / page · EAGER 2차"
n1 -> n0: "EAGER 연관이 채워진 FeedItem 반환"
n0 -> n1: "매핑 중 getHighlights() 접근"
n1 -> n2: "SELECT highlights · LAZY 초기화"
@@ -0,0 +1,14 @@
digraph techviz {
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
n0 [label="loadFeed DTO 매핑", shape=box, style="rounded,filled"];
n1 [label="Hibernate", shape=box, style="rounded,filled"];
n2 [label="PostgreSQL", shape=box, style="rounded,filled"];
n0 -> n1 [label="findAllBy(...)", style=solid];
n1 -> n2 [label="SELECT feed_items", style=solid];
n1 -> n2 [label="SELECT user / page · EAGER 2차", style=solid];
n1 -> n0 [label="EAGER 연관이 채워진 FeedItem 반환", style=solid];
n0 -> n1 [label="매핑 중 getHighlights() 접근", style=solid];
n1 -> n2 [label="SELECT highlights · LAZY 초기화", style=solid];
}
@@ -5,14 +5,14 @@
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="n_load-feed-mapping" value="loadFeed DTO 매핑" tooltip="FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. | Evidence: L322-L322, L449-L451" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxCell id="n_load-feed-mapping" value="loadFeed DTO 매핑" tooltip="FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. | Evidence: L356-L356, L519-L522" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="45.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="n_hibernate" value="Hibernate" tooltip="파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. | Evidence: L320-L322" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxCell id="n_hibernate" value="Hibernate" tooltip="파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. | Evidence: L354-L356" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="255.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="n_postgresql" value="PostgreSQL" tooltip="Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. | Evidence: L417-L422, L447-L447" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
<mxGeometry x="465.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
<mxCell id="n_postgresql" value="PostgreSQL" tooltip="Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. | Evidence: L480-L487, L515-L515" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
<mxGeometry x="465.0" y="35.0" width="150.0" height="78.0" as="geometry"/>
</mxCell>
<mxCell id="e_m1-find-all" value="findAllBy(...)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_load-feed-mapping" target="n_hibernate">
<mxGeometry relative="1" as="geometry">
@@ -39,7 +39,7 @@
<mxPoint x="225.0" y="376.0" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_m6-lazy-highlights-select" value="SELECT highlights WHERE feed_item_id = ?" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hibernate" target="n_postgresql">
<mxCell id="e_m6-lazy-highlights-select" value="SELECT highlights · LAZY 초기화" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hibernate" target="n_postgresql">
<mxGeometry relative="1" as="geometry">
<mxPoint x="435.0" y="438.0" as="offset"/>
</mxGeometry>
@@ -0,0 +1,766 @@
{
"type": "excalidraw",
"version": 2,
"source": "techviz-harness",
"elements": [
{
"id": "edge-m1-find-all",
"type": "arrow",
"x": 120.0,
"y": 140.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1466915521,
"version": 1,
"versionNonce": 619922749,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
210.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-load-feed-mapping",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m1-find-all",
"type": "text",
"x": 169.0,
"y": 116.0,
"width": 112,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1222640664,
"version": 1,
"versionNonce": 97953758,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "findAllBy(...)",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "findAllBy(...)",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-m2-root-select",
"type": "arrow",
"x": 330.0,
"y": 202.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1220139771,
"version": 1,
"versionNonce": 672496334,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
210.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-postgresql",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m2-root-select",
"type": "text",
"x": 367.0,
"y": 178.0,
"width": 136,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1575837228,
"version": 1,
"versionNonce": 421553536,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "SELECT feed_items",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "SELECT feed_items",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-m3-eager-secondary-selects",
"type": "arrow",
"x": 330.0,
"y": 264.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 463896237,
"version": 1,
"versionNonce": 630479025,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
210.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-postgresql",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m3-eager-secondary-selects",
"type": "text",
"x": 319.0,
"y": 240.0,
"width": 232,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 341136983,
"version": 1,
"versionNonce": 137161213,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "SELECT user / page · EAGER 2차",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "SELECT user / page · EAGER 2차",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-m4-return-eager-loaded-items",
"type": "arrow",
"x": 120.0,
"y": 326.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 452217845,
"version": 1,
"versionNonce": 1156366167,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
210.0,
0.0
],
[
0.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-load-feed-mapping",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m4-return-eager-loaded-items",
"type": "text",
"x": 125.0,
"y": 302.0,
"width": 200,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 949125269,
"version": 1,
"versionNonce": 180813121,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "EAGER 연관이 채워진 FeedItem 반환",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "EAGER 연관이 채워진 FeedItem 반환",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-m5-access-highlights",
"type": "arrow",
"x": 120.0,
"y": 388.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 179055340,
"version": 1,
"versionNonce": 1368520467,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
210.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-load-feed-mapping",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m5-access-highlights",
"type": "text",
"x": 133.0,
"y": 364.0,
"width": 184,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 217145704,
"version": 1,
"versionNonce": 908548325,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "매핑 중 getHighlights() 접근",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "매핑 중 getHighlights() 접근",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-m6-lazy-highlights-select",
"type": "arrow",
"x": 330.0,
"y": 450.0,
"width": 210.0,
"height": 0.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1936623542,
"version": 1,
"versionNonce": 1780861516,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
210.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-hibernate",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-postgresql",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-m6-lazy-highlights-select",
"type": "text",
"x": 323.0,
"y": 426.0,
"width": 224,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 723720611,
"version": 1,
"versionNonce": 367100783,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "SELECT highlights · LAZY 초기화",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "SELECT highlights · LAZY 초기화",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-load-feed-mapping",
"type": "rectangle",
"x": 45.0,
"y": 35.0,
"width": 150.0,
"height": 64.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1069507714,
"version": 1,
"versionNonce": 344023964,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-load-feed-mapping",
"type": "text",
"x": 55.0,
"y": 45.0,
"width": 130.0,
"height": 44.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 349239,
"version": 1,
"versionNonce": 1687869941,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "loadFeed DTO 매핑",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "loadFeed DTO 매핑",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-hibernate",
"type": "rectangle",
"x": 255.0,
"y": 35.0,
"width": 150.0,
"height": 64.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1178876136,
"version": 1,
"versionNonce": 283384724,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-hibernate",
"type": "text",
"x": 265.0,
"y": 45.0,
"width": 130.0,
"height": 44.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1294638406,
"version": 1,
"versionNonce": 238144117,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Hibernate",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Hibernate",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-postgresql",
"type": "rectangle",
"x": 465.0,
"y": 35.0,
"width": 150.0,
"height": 78.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 51288073,
"version": 1,
"versionNonce": 91686637,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-postgresql",
"type": "text",
"x": 475.0,
"y": 45.0,
"width": 130.0,
"height": 58.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 308492034,
"version": 1,
"versionNonce": 936735075,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "PostgreSQL",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "PostgreSQL",
"autoResize": true,
"lineHeight": 1.25
}
],
"appState": {
"gridSize": 10,
"viewBackgroundColor": "#ffffff",
"currentItemFontFamily": 5
},
"files": {}
}
@@ -0,0 +1,32 @@
{
"harness_version": "0.2.0",
"spec_id": "eager-lazy-query-sequence",
"spec_version": "1.1",
"spec_sha256": "8ffd28206d556cb3e726ccb5e28f21ecaf9ceee63b2427d2a9cfb0d900b06898",
"source_context": {
"document": "docs/n+1liner/final/document.md",
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
"anchor": {
"kind": "heading",
"value": "5.4 왜 추가 쿼리가 나가나 — EAGER는 \"로딩 시점\" 계약이지 JOIN 보장이 아니다",
"line": 347
}
},
"outputs": [
"eager-lazy-query-sequence.svg",
"eager-lazy-query-sequence.drawio",
"eager-lazy-query-sequence.mmd",
"eager-lazy-query-sequence.d2",
"eager-lazy-query-sequence.dot",
"eager-lazy-query-sequence.excalidraw",
"eager-lazy-query-sequence.alt.md"
],
"lint_issue_count": 0,
"assumption_count": 0,
"assumptions_allowed": false,
"composition_profile": "sequence",
"reference_ids": [
"payment-approval-sequence"
],
"diagram_only": true
}
@@ -0,0 +1,12 @@
%% EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
%% question: 루트 피드 조회부터 EAGER ToOne과 LAZY highlights 조회까지 SQL은 어떤 순서로 발생하는가?
sequenceDiagram
participant n0 as loadFeed DTO 매핑
participant n1 as Hibernate
participant n2 as PostgreSQL
n0->>n1: findAllBy(...)
n1->>n2: SELECT feed_items
n1->>n2: SELECT user / page · EAGER 2차
n1->>n0: EAGER 연관이 채워진 FeedItem 반환
n0->>n1: 매핑 중 getHighlights() 접근
n1->>n2: SELECT highlights · LAZY 초기화
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="680" height="542" viewBox="0 0 680 542" role="img" aria-labelledby="diagram-title diagram-description">
<title id="diagram-title">EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다</title>
<desc id="diagram-description">세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;eager-lazy-query-sequence&quot;,&quot;profile&quot;:&quot;sequence&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md&quot;,&quot;document_sha256&quot;:&quot;1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;marker&quot;,&quot;value&quot;:&quot;eager-lazy-query-sequence&quot;,&quot;line&quot;:324}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;:true}</metadata>
<desc id="diagram-description">세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다. 이때 나가는 SQL 원문은 SELECT * FROM highlights WHERE feed_item_id = ? 이다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;eager-lazy-query-sequence&quot;,&quot;profile&quot;:&quot;sequence&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/n+1liner/final/document.md&quot;,&quot;document_sha256&quot;:&quot;f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;5.4 왜 추가 쿼리가 나가나 — EAGER는 \&quot;로딩 시점\&quot; 계약이지 JOIN 보장이 아니다&quot;,&quot;line&quot;:347}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;:true}</metadata>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
@@ -50,31 +50,31 @@
</style>
</defs>
<rect class="canvas" width="680" height="542" />
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="322-322,449-451" x="45.0" y="35.0" width="150.0" height="64.0" rx="7" />
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="356-356,519-522" x="45.0" y="35.0" width="150.0" height="64.0" rx="7" />
<text class="node-label" x="120.0" y="65.0">loadFeed DTO 매핑</text>
<line class="lifeline" x1="120.0" y1="99.0" x2="120.0" y2="512.0" />
<rect class="node-shape kind-participant emphasis-primary role-participant" data-evidence="320-322" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
<rect class="node-shape kind-participant emphasis-primary role-participant" data-evidence="354-356" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
<text class="node-label" x="330.0" y="65.0">Hibernate</text>
<line class="lifeline" x1="330.0" y1="99.0" x2="330.0" y2="512.0" />
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="417-422,447-447" x="465.0" y="45.7" width="150.0" height="42.7" /><ellipse class="node-shape kind-participant emphasis-normal role-participant" cx="540.0" cy="45.7" rx="75.0" ry="10.7" /><path class="storage-bottom" d="M 465.0 88.3 A 75.0 10.7 0 0 0 615.0 88.3" />
<text class="node-label" x="540.0" y="65.0">PostgreSQL</text>
<line class="lifeline" x1="540.0" y1="99.0" x2="540.0" y2="512.0" />
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="320-320" />
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="480-487,515-515" x="465.0" y="48.0" width="150.0" height="52.0" /><ellipse class="node-shape kind-participant emphasis-normal role-participant" cx="540.0" cy="48.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 465.0 100.0 A 75.0 13.0 0 0 0 615.0 100.0" />
<text class="node-label" x="540.0" y="75.0">PostgreSQL</text>
<line class="lifeline" x1="540.0" y1="113.0" x2="540.0" y2="512.0" />
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="354-354" />
<rect class="edge-label-bg" x="159.1" y="114.0" width="131.9" height="22" rx="3" />
<text class="edge-label" x="225.0" y="129.0">1. findAllBy(...)</text>
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,202.0 540.0,202.0" data-evidence="320-320" />
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,202.0 540.0,202.0" data-evidence="354-354" />
<rect class="edge-label-bg" x="359.0" y="176.0" width="152.0" height="22" rx="3" />
<text class="edge-label" x="435.0" y="191.0">2. SELECT feed_items</text>
<polyline class="edge kind-data style-solid emphasis-normal" points="330.0,264.0 540.0,264.0" data-evidence="318-321" />
<polyline class="edge kind-data style-solid emphasis-normal" points="330.0,264.0 540.0,264.0" data-evidence="352-355" />
<rect class="edge-label-bg" x="318.8" y="238.0" width="232.4" height="22" rx="3" />
<text class="edge-label" x="435.0" y="253.0">3. SELECT user / page · EAGER 2차</text>
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,326.0 120.0,326.0" data-evidence="318-320" />
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,326.0 120.0,326.0" data-evidence="352-354" />
<rect class="edge-label-bg" x="122.2" y="300.0" width="205.6" height="22" rx="3" />
<text class="edge-label" x="225.0" y="315.0">4. EAGER 연관이 채워진 FeedItem 반환</text>
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,388.0 330.0,388.0" data-evidence="322-322,449-451" />
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,388.0 330.0,388.0" data-evidence="356-356,519-522" />
<rect class="edge-label-bg" x="128.9" y="362.0" width="192.2" height="22" rx="3" />
<text class="edge-label" x="225.0" y="377.0">5. 매핑 중 getHighlights() 접근</text>
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,450.0 540.0,450.0" data-evidence="322-322,429-430" />
<rect class="edge-label-bg" x="281.9" y="424.0" width="306.1" height="22" rx="3" />
<text class="edge-label" x="435.0" y="439.0">6. SELECT highlights WHERE feed_item_id = ?</text>
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,450.0 540.0,450.0" data-evidence="356-356,493-495" />
<rect class="edge-label-bg" x="322.1" y="424.0" width="225.7" height="22" rx="3" />
<text class="edge-label" x="435.0" y="439.0">6. SELECT highlights · LAZY 초기화</text>
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB