BACKEND

[MyBatis] flushStatements

handcraft 2025. 8. 14. 13:44

실무 [트러블슈팅 노트]

 

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로 나오는 이유

  1. 배치 모드가 아닌 세션 사용
    • 기본 모드(SIMPLE, REUSE)일 경우 SQL이 즉시 실행 → flushStatements() 결과가 항상 1건.
  2. 반복문에서 매번 커밋
    • 반복문에서 insert 후 commit하면 SQL이 바로 DB에 반영 → Statement Queue가 비어 있음 → flushStatements() 시 1건만 반환.
  3. 트랜잭션 관리 문제
    • 트랜잭션이 올바르게 설정되지 않았거나 auto-commit이 켜져 있으면, 배치가 아닌 즉시 실행 모드가 됨.
  4. Mapper 내부 SQL 처리 문제
    • Mapper SQL이 내부적으로 별도의 Statement로 처리되거나 즉시 실행되도록 구성되어 있을 수 있음.

 

4️⃣ 문제 해결 방안

  1. 배치 모드 확인
log.info("ExecutorType: {}", batchSqlSession.getConfiguration().getDefaultExecutorType());
 
  1. 트랜잭션 확인
    • auto-commit 비활성화
    • 반복문에서 커밋하지 않음
  2. Statement 쌓임 확인
log.info("Pending statements before flush: {}", batchSqlSession.flushStatements().size());
 
  1. 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️⃣ 동작 원리

  1. Statement 누적
    • 반복문에서 Delete/Insert를 호출할 때, SQL이 바로 DB로 가지 않고 배치 모드의 Queue에 쌓임
    • 같은 종류의 Statement가 연속되면 MyBatis가 묶어서(batch) 관리
  2. flushStatements() 호출
    • Queue에 쌓인 Statement를 DB로 전송
    • MyBatis는 중복되거나 동일 Statement는 하나로 합치고 실행 결과를 반환
    • 예시에서는 Delete/Insert 순서가 반복되지만, 동일 Statement 그룹별로 3개의 BatchResult가 생성
  3. 반환값 의미
    • results.size() == 3 → flush 시점에 실행된 Statement 그룹 3건
    • 각 BatchResult 안에는 실행된 SQL과 영향 받은 row 수가 들어 있음

즉, 반복문 호출 횟수 ≠ flushStatements() 결과 건수

  • MyBatis는 배치 내 Statement 종류별로 묶어서 관리
  • Delete, Insert 등 다른 Statement가 섞이면 그 그룹별로 BatchResult 반환