카테고리 없음

Hikari autoCommit false 설정

kjw81024 2026. 6. 4. 16:39

개요

Hikari 라는 의미와... Hibernate 라는 단어를 잘 모르고 Test 를 진행하려니까 뭐하는건지 모르겠어서 .... 대충이라도 정리

 

HikariCP (hikari Connection Pool) : 커넥션을 매번 새로 만ㄷ르면 비용이 크니까 여러개 만들어두고 빌려줬다가 반납받는 형식의 pool 관리자

이때 커넥션이라는건 실제 DB 와의 연결 1개를 의미함 HikariCP 가 connection 을 관리한다고 보면 됨 

(이 때 속성으로 SQL 이 실행하자마자 즉시 커밋할건지, 명시적 commit rollback 이 있어야 반영할건지 결정하는게 autoCommit)

 

Hibernate 는 JPA interface 의 실제 구현체 == SQL 생성, CP 에서 커넥션 빌려서 실제로 쿼리를 보내는 역할 

그래서 query 실행시키면 hibernate.xxx.xxx 이런식으로 뜸

 

즉 동작순서가 : Hibernate 가 connection 을 DataSource (얘도 인터페이스) 에게 요청하면 HikariCP (DataSource 구현체, 풀 관리) 가 꺼내주는 형태 

 

SpringBoot 는 Hibernate, HikariCP 등을 자동으로 설정해주는 역할 


문제 상황

기존에 생각했던 DB connection 동작은 Lazy connection acquisition 

 

SpringBoot 의 기본 값은 아래와 같기 때문에 @Transacion 진입 시점에는 connection 을 잡지 않고 첫 DB 문장 실행 시점에 Connection 을 점유하는 것으로 알고 있었음 

hibernate.connection.handling_mode = DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION

 

-> 이 가정을 토대로... DB 접근 전에 Lock 이 실패하면 Connection 낭비가 없다는 테스트를 작성 

 

@SpringBootTest
@ActiveProfiles("test")
class TransactionConnectionWasteTest {

    @Autowired private ConnectionWasteProbeService probe;
    @Autowired private HikariDataSource hikariDataSource;

    @Test
    @DisplayName("[A] @Transactional 진입만으론 커넥션 미획득")
    void connection_not_acquired_when_lock_fails_before_db_access() {
        HikariPoolMXBean pool = hikariDataSource.getHikariPoolMXBean();
        int baseline = pool.getActiveConnections();
        AtomicInteger activeAtFailure = new AtomicInteger(-1);

        assertThatThrownBy(() -> probe.failLockWithoutDbAccess(activeAtFailure))
                .isInstanceOf(SimulatedLockFailure.class);

        // 기대: baseline 그대로 (DB 접근 0회이므로 커넥션 미점유)
        assertThat(activeAtFailure.get()).isEqualTo(baseline);
    }

    static class ConnectionWasteProbeService {
        @Transactional
        public void failLockWithoutDbAccess(AtomicInteger activeRecorder) {
            // DB 접근 없이 측정 후 throw
            activeRecorder.set(dataSource.getHikariPoolMXBean().getActiveConnections());
            throw new SimulatedLockFailure();
        }
    }
}

하지만 실패 

activeAtFailure == baseline 이라고 가정했는데 +1 이 됨 

== DB 문장을 하나도 실행 안 했는데 커넥션이 점유되어있다 


해결 과정 

 

왜... 이렇게 되었는지 검색해봤다 

 

Hikari 는 기본적으로 auto-commit = true 로 커넥션을 발급 

Hibernate 는 JDBC 트랜잭션 시작하려면 autocommit = false 로 토글해야하고 토글 == 커넥션을 일단 잡아야함 

autoCommit=true SQL 한 줄 실행하면 그 즉시 DB 에 자동 commit
autoCommit=false SQL 실행해도 commit 안 됨. 누군가가 명시적으로 connection.commit() 호출해야 반영

이걸 해주는게 @Transaction 
Spring 의 JpaTransactionManager 가 메서드 정상 종료 시 connection.commit() 을 호출하고 
예외 발생 시 connection.rollback() 을 호출해줌 

 

-> hibernate.connection.handling_mode 가 lazy 여도 트랜잭션 진입 타이밍에 autocommit 토글용 커넥션이 점유됨

-> 그럼 connection acquisition 은 언제 진짜 lazy 인가? => autocommit 토글이 필요 없을때만 

 

이 문제를 고쳐주지 않으면 DB 를 건드리지 않고 예외가 나도 (ex Lock 실패) 커넥션을 잡았다가 놓음 

-> 부하를 줬을 때 pool 이 빠르게 없어짐 

-> Lock 은 무조건 .... 트랜잭션 바깥으로 빼기 

 

application.yml 에서 이렇게 수정해줘야 진짜 lazy 가 됨 

spring:
  datasource:
    hikari:
      auto-commit: false                # 풀이 발급하는 커넥션을 autocommit=false 로 고정
  jpa:
    properties:
      hibernate:
        connection:
          provider_disables_autocommit: true   # Hibernate 가 pool 의 autocommit 관리를 신뢰

test.yml 을 고쳐도 되지만 일단 여기선 아래처럼 추가 

@TestPropertySource(properties = {
        "spring.datasource.hikari.auto-commit=false",
        "spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true"
})

 

 

-> pool 이 autocommit = false 인 connection 을 발급하면 Hibernate 는 토글할 필요 없이 첫 SQL 까지 커넥션 획득 보류 (lazy)


 

회고

 

SpringBoot 기본값이 lazy 지만... autocommit 가정이 깨지면 안 써짐 

이거 꺼두면 트랜잭션 내부에서 Lock 실패만 해도 Connection 1개를 무의미하게 점유했다는 의미 

 

HikariCP 와 Hibernate 양쪽 공식 문서에서 Spring 환경이라면 hikari.auto-commit = false 를 켜라고 권장함 

auto-commit = false 가 좀 무섭긴 한데 
@Transactional 은 autoCOmmit 플래그랑 무관하게 명시적 commit, rollback 하므로 서비스 코드는 그대로 둬도 됨