- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
139 lines
8.3 KiB
Markdown
139 lines
8.3 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a05-f018-sql
|
|
title: 쿼리 이름을 SQL 로 옮기는 두 경로가 양쪽 끝만 있고 가운데가 없다
|
|
topic: runtime-reachability-and-composition
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:a05-f018-sql
|
|
evidenceCapturedOn: 2026-09-02
|
|
assets:
|
|
- key: a05-f018-sql
|
|
file: ../../../final/evidence/rendered/a05-f018-sql.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a05-f018-sql.txt
|
|
source:
|
|
- 원본 분석 절은 `final/document.md#a05` §55 의 backlog 안, 「Cross-scope P1/P2 — query SQL naming/observability composition 부재」 항목이다. 검사기의 런타임 등록이 0 이라는 판정, SQL 계층으로 잇는 다리가 확인되지 않았다는 판정, 그리고 최종 판정을 트랜잭션 관측 배선 공백과 함께 관측·설정 범위로 미룬다는 기록이 거기 있다.
|
|
- 다리가 확인되지 않았다는 판정은 맞다. `QueryNameContext` 가 이름을 스레드에 묶고 검사기가 그것을 읽지만, 어떤 SessionFactory 도 그 검사기를 설치하지 않으므로 `inspect()` 는 호출되지 않는다. 코드에 있는 것은 다리가 아니라 양쪽 끝이다. 두 번째 경로인 힌트도 마찬가지다.
|
|
---
|
|
|
|
# 쿼리 이름을 SQL 로 옮기는 두 경로가 양쪽 끝만 있고 가운데가 없다
|
|
|
|
쿼리 이름을 SQL 주석으로 실어 나르는 문장 검사기가 있다. 어떤 SessionFactory 도 그것을 설치하지 않아 `inspect()` 가 호출되지 않는다. 두 번째 경로인 `org.hibernate.comment` 힌트도 그것을 SQL 로 내보내는 설정이 꺼져 있다. 이름을 넣는 프로덕션 클래스 셋은 자기 단위 테스트 밖에서 쓰이지 않는다.
|
|
|
|
## 관계
|
|
|
|
- **@Bean이 있다는 것은 조립 증거가 아니다**
|
|
구현의 존재를 조립의 증거로 읽는다는 점이 같다.
|
|
- **이름은 값이 아니라 registry key다**
|
|
쿼리 이름이 등록된 식별자로 쓰이는 맥락이다.
|
|
- **카디널리티 경계를 타입으로 표현하기**
|
|
그 이름이 메트릭 태그가 되는 경계다.
|
|
|
|
## 문제
|
|
|
|
주석에 이름이 실려야 DBA 가 눈앞의 문장을 어느 유스케이스에서 나온 것인지 되짚는다. 검사기 javadoc 이 그 이유를 적고, 없으면 어느 엔드포인트가 이 쿼리를 내는지를 SQL 조각으로 코드베이스를 뒤져 답하게 된다고 덧붙인다.
|
|
|
|
## 결론
|
|
|
|
inspect() 는 다 쓰여 있다. 현재 스레드에 묶인 이름을 꺼내 /* 이름 */ 을 앞에 붙이고, 이름에 주석 종료자가 있으면 원본을 돌려준다. 그 분기는 도달하지 않는다. QueryName 의 형식 [a-z][a-z0-9.-]{2,95} 가 이미 * 와 / 를 막고, javadoc 도 그 검사를 additionally 라고 적는다.
|
|
|
|
단위 테스트는 없다. 검사기에도 컨텍스트에도 테스트 파일이 없다.
|
|
|
|
SessionFactory 가 설치해 주지 않으면 inspect() 는 불리지 않는다. 네 경로는 저장소 루트에서 확장자 제한 없이 훑어 얻었다. 설정 키 0, HibernatePropertiesCustomizer 와 SessionFactoryBuilder 와 ServiceRegistry 와 Integrator 0, persistence.xml 0, 런타임 리소스의 FQCN 0 이다.
|
|
|
|
이름을 넣는 쪽은 배선되어 있다. QueryNameContext.with(...) 를 부르는 프로덕션 코드가 셋이다. 그 컨텍스트를 읽는 쪽은 검사기 하나뿐이다.
|
|
|
|
SQL 로 이름을 옮기는 길이 그것 말고 또 있다. org.hibernate.comment 힌트를 거는 것은 리포지토리 조각 지원과 Querydsl 지원이다. 그 힌트도 스위치 하나에 달려 있는데 저장소에 그 키가 0 이다.
|
|
|
|
두 경로 다 이름을 넣는 코드와 그것을 읽는 자리는 있는데, 그 사이를 잇는 설치와 스위치가 없다.
|
|
|
|
넣는 세 클래스도 실행되지 않는다. 각 타입을 쓰는 파일은 자기 자신과 자기 단위 테스트뿐이고, 리포지토리 조각 지원을 상속하는 유일한 코드도 그 테스트 안의 중첩 클래스다.
|
|
|
|
## 검증 환경
|
|
|
|
OpenJDK : 21.0.12
|
|
확인 방식 : 설치 경로 넷을 저장소 루트에서 계수, 이름을 넣고 읽는 지점 추적, 두 번째 경로의 활성 설정 확인
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
1. 검사기 구현과 javadoc, 그리고 QueryName 의 형식 정규식을 읽는다.
|
|
2. 저장소 루트에서 확장자 제한 없이 설치 경로 넷을 각각 센다.
|
|
3. 그 클래스의 단위 테스트가 있는지 찾는다.
|
|
4. 이름을 넣는 호출과 읽는 호출을 각각 센다.
|
|
5. org.hibernate.comment 힌트를 거는 곳과, use_sql_comments 를 켜는 곳을 센다.
|
|
6. 넣는 세 클래스를 쓰는 파일과 상속하는 코드를 전부 나열한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
쿼리 이름이 SQL 주석으로 실려야 DBA 가 `pg_stat_activity` 나 느린 쿼리 로그의 문장을 유스케이스로 되돌릴 수 있다. 검사기 javadoc 이 그 이유를 적는다.
|
|
|
|
## inspect() 가 하는 일
|
|
|
|
:::evidence key="a05-f018-sql" alt="문장 검사기의 javadoc 앞부분과 클래스 본문, 쿼리 이름의 형식 정규식, 하이버네이트가 이 구현을 설치하는 경로 넷을 저장소 루트에서 확장자 제한 없이 센 결과와 그 클래스의 단위 테스트 수, 이름을 컨텍스트에 넣는 세 곳과 읽는 곳, 이름을 SQL 로 옮기는 두 번째 경로와 그것을 활성화하는 설정의 수, 그리고 넣는 세 클래스를 쓰는 파일과 상속하는 코드를 출력한 터미널 기록." caption="검사기 javadoc 과 본문 · 이름 형식이 이미 * 와 / 를 막음 · 설치 경로 넷 전부 0 · 단위 테스트 0 · 넣는 곳 셋과 읽는 곳 하나 · 두 번째 경로의 use_sql_comments 0 · 세 클래스는 자기 테스트만 — 69줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
```java
|
|
public String inspect(String sql) {
|
|
if (sql == null) {
|
|
return null;
|
|
}
|
|
Optional<QueryName> queryName = QueryNameContext.current();
|
|
if (queryName.isEmpty()) {
|
|
return sql;
|
|
}
|
|
String value = queryName.get().value();
|
|
if (value.contains(COMMENT_TERMINATOR)) {
|
|
return sql;
|
|
}
|
|
return "/* " + value + " */ " + sql;
|
|
}
|
|
```
|
|
|
|
현재 스레드에 묶인 이름을 꺼내 주석으로 붙인다. 이름에 `*/` 가 있으면 원본을 돌려주는 분기가 하나 더 있는데, `QueryName` 의 형식 `[a-z][a-z0-9.-]{2,95}` 가 이미 `*` 와 `/` 를 막으므로 도달하지 않는다. javadoc 도 그 검사를 additionally 라고 적는다.
|
|
|
|
이 클래스에는 단위 테스트가 없다. 컨텍스트 쪽도 없다.
|
|
|
|
## 검사기를 설치하는 설정이 없다
|
|
|
|
하이버네이트가 `inspect()` 를 부르려면 SessionFactory 가 이 구현을 설치해야 한다. 설치 경로 넷을 저장소 루트에서 확장자 제한 없이 셌다.
|
|
|
|
```text
|
|
statement_inspector 설정 키 : 0
|
|
HibernatePropertiesCustomizer / SessionFactoryBuilder / Integrator : 0
|
|
persistence.xml : 0
|
|
런타임 리소스의 FQCN : 0
|
|
```
|
|
|
|
## 이름을 컨텍스트에 넣는 세 곳
|
|
|
|
`JpaStreamExecutor:63`, `JpaKeysetQuerySupport:49`, `JpaRepositoryFragmentSupport:72` 가 작업을 감싸며 이름을 컨텍스트에 넣는다. 그 컨텍스트를 읽는 코드는 검사기 하나다.
|
|
|
|
이름을 SQL 로 옮기는 경로는 하나가 더 있다. 리포지토리 조각 지원과 Querydsl 지원이 `org.hibernate.comment` 힌트를 건다.
|
|
|
|
```java
|
|
return entityManager.createQuery(jpql, resultType).setHint(COMMENT_HINT, name.value());
|
|
```
|
|
|
|
그 힌트가 SQL 로 나오려면 `hibernate.use_sql_comments` 가 켜져야 한다. 저장소에 그 키는 0 이다.
|
|
|
|
두 경로 모두 양쪽 끝만 있고 가운데가 없다.
|
|
|
|
## 세 클래스의 프로덕션 참조
|
|
|
|
각 타입을 쓰는 파일은 자기 자신과 자기 단위 테스트뿐이다. 리포지토리 조각 지원을 상속하는 유일한 코드도 그 테스트 안의 중첩 클래스다.
|
|
|
|
스프링 데이터의 조각 규약으로 연결될 여지도 없다. 그 클래스는 인터페이스가 아니라 추상 클래스이고, `repositoryBaseClass` 도 `@NoRepositoryBean` 도 저장소에 없다.
|
|
|
|
이름은 실행 경로에 오르지 않는다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
애플리케이션을 부팅해 실제 문장에 주석이 붙지 않는 것을 관측하지 않았다. 설치 경로와 호출 지점을 센 것까지가 확인 범위다.
|
|
|
|
<!-- body:end -->
|