실무 [트러블슈팅 노트]
RESTAPI 테스트를하는중 기존의 환경에서 보낸 결과 반환 값과 내 작업 결과 반환값이 달라 코드를 분석하였다.
flushStatements 가 문제 였는데
flushStatements
1️⃣ 기본 개념
flushStatements()는 MyBatis의 SqlSession에서 실행 대기 중인 SQL 문(statement)을 DB에 보내고, 그 결과를 반환하는 메서드입니다.
- MyBatis는 기본적으로 **배치 모드(batch mode)**를 지원합니다.
- 배치 모드에서는 insert, update, delete 같은 DML 문이 모아서 한 번에 DB로 보내집니다.
- 이때 아직 DB에 보내지 않은 SQL들을 즉시 실행하고 싶은 경우 flushStatements()를 사용합니다.
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
for (User user : userList) {
sqlSession.insert("UserMapper.insertUser", user);
// 100개마다 DB로 flush
if (count % 100 == 0) {
List<BatchResult> results = sqlSession.flushStatements();
// results에는 배치 실행 결과가 담겨 있음
}
}
sqlSession.commit();
sqlSession.close();
2️⃣ flushStatements()의 역할
- 현재까지 메모리에 쌓인 SQL Statement를 DB로 전송.
- 반환값: List<BatchResult> → 각 Statement 그룹별 실행 결과를 담고 있음.
- 주의: commit과는 별개. flush만 하고 트랜잭션은 계속 유지.
List<BatchResult> results = batchSqlSession.flushStatements();
3️⃣ 결과 리스트 크기가 1로 나오는 이유
- 배치 모드가 아닌 세션 사용
- 기본 모드(SIMPLE, REUSE)일 경우 SQL이 즉시 실행 → flushStatements() 결과가 항상 1건.
- 반복문에서 매번 커밋
- 반복문에서 insert 후 commit하면 SQL이 바로 DB에 반영 → Statement Queue가 비어 있음 → flushStatements() 시 1건만 반환.
- 트랜잭션 관리 문제
- 트랜잭션이 올바르게 설정되지 않았거나 auto-commit이 켜져 있으면, 배치가 아닌 즉시 실행 모드가 됨.
- Mapper 내부 SQL 처리 문제
- Mapper SQL이 내부적으로 별도의 Statement로 처리되거나 즉시 실행되도록 구성되어 있을 수 있음.
4️⃣ 문제 해결 방안
- 배치 모드 확인
log.info("ExecutorType: {}", batchSqlSession.getConfiguration().getDefaultExecutorType());
- 트랜잭션 확인
- auto-commit 비활성화
- 반복문에서 커밋하지 않음
- Statement 쌓임 확인
log.info("Pending statements before flush: {}", batchSqlSession.flushStatements().size());
- Mapper SQL 방식 점검
- SQL이 내부에서 즉시 실행되는지 확인
- 배치에 적합하게 Mapper 구성
문제 이유
“batchSqlSession.flushStatements()" 은 myBatis에서 배치로 처리 중인 SQL 명령어의 갯수 만큼 저장이 됩니다.
1️⃣ 상황
- row.keySet()을 기준으로 반복문을 돌리면서 여러 SQL을 실행
- 예시 순서:
Delete → Insert → Delete → Insert → Insert → …
- batchSqlSession.flushStatements() 호출 시 변경되는 Statement를 감지하고 실행 결과를 반환
- 결과적으로 flushStatements()에서 반환되는 BatchResult 리스트는 3건 정도로 나타남
2️⃣ 예시 코드
SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
Map<String, Data> row = ... // 21개의 데이터
for (String key : row.keySet()) {
Data data = row.get(key);
// 조건에 따라 Delete
if (data.shouldDelete()) {
batchSqlSession.delete("Mapper.deleteData", data);
}
// 조건에 따라 Insert
if (data.shouldInsert()) {
batchSqlSession.insert("Mapper.insertData", data);
}
}
// 중간에 flushStatements() 호출
List<BatchResult> results = batchSqlSession.flushStatements();
System.out.println("Flush 결과 건수: " + results.size());
batchSqlSession.commit();
batchSqlSession.close();
3️⃣ 동작 원리
- Statement 누적
- 반복문에서 Delete/Insert를 호출할 때, SQL이 바로 DB로 가지 않고 배치 모드의 Queue에 쌓임
- 같은 종류의 Statement가 연속되면 MyBatis가 묶어서(batch) 관리
- flushStatements() 호출
- Queue에 쌓인 Statement를 DB로 전송
- MyBatis는 중복되거나 동일 Statement는 하나로 합치고 실행 결과를 반환
- 예시에서는 Delete/Insert 순서가 반복되지만, 동일 Statement 그룹별로 3개의 BatchResult가 생성
- 반환값 의미
- results.size() == 3 → flush 시점에 실행된 Statement 그룹 3건
- 각 BatchResult 안에는 실행된 SQL과 영향 받은 row 수가 들어 있음
즉, 반복문 호출 횟수 ≠ flushStatements() 결과 건수
- MyBatis는 배치 내 Statement 종류별로 묶어서 관리
- Delete, Insert 등 다른 Statement가 섞이면 그 그룹별로 BatchResult 반환
'BACKEND' 카테고리의 다른 글
| [JAVA] toJson vs toPrettyJson (0) | 2025.08.19 |
|---|---|
| [DB]차집합 (LEFT OUTER JOIN + IS NULL) (0) | 2025.08.19 |
| [DB] 옵티마이저 힌트(Optimizer Hint) (6) | 2025.08.14 |
| [DB] OracleSGA Shared Pool 공유풀 사용 원리 (5) | 2025.08.14 |
| [DB]Oracle ORA-01427 : 단일 행 하위 질의에 2개 이상의 행이 리턴되었습니다 (2) | 2025.08.14 |