PreparedStatement를 여러 번 재사용


97

풀없이 단일 공통 연결로 PreparedStatement를 사용하는 경우 준비된 명령문의 기능을 유지하는 모든 dml / sql 작업에 대해 인스턴스를 다시 만들 수 있습니까?

내말은:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

대신에:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

제 질문은 제가이 코드를 다중 스레드 환경에 넣고 싶다는 사실로 인해 발생합니다. 조언을 해주실 수 있습니까? 감사


그래서 귀하의 쿼리 sql는 루프에서 변경되지 않습니까? 해당 쿼리가 루프의 각 반복마다 변경되지 않는 경우 왜 새로운PreparedStatement 무엇입니까 (첫 번째 코드 스 니펫에서)? 그렇게하는 이유가 있습니까?
Sabir Khan

쿼리가 변경되면 두 번째 접근 방식이 더 낫습니다. 단점이 있습니까?
Stunner

답변:


144

두 번째 방법은 좀 더 효율적이지만 훨씬 더 좋은 방법은 일괄 적으로 실행하는 것입니다.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

그러나 한 번에 실행할 수있는 배치 수는 JDBC 드라이버 구현에 따라 다릅니다. 예를 들어 1000 배치마다 실행할 수 있습니다.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

멀티 스레드 환경의 경우 다음과 같이 try-with-resources 문을 사용하여 일반 JDBC 관용구에 따라 동일한 메서드 블록 내에서 가능한 가장 짧은 범위의 연결 및 문을 획득하고 닫으면 이에 대해 걱정할 필요가 없습니다 . 스 니펫 위.

이러한 일괄 처리가 트랜잭션 인 경우 연결 자동 커밋을 해제하고 모든 일괄 처리가 완료 될 때만 트랜잭션을 커밋하려고합니다. 그렇지 않으면 첫 번째 일괄 처리가 성공할 때 데이터베이스가 더티 데이터베이스가 될 수 있습니다.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}

inside the same method block-모든 스레드는 자체 스택을 가지며 이러한 연결 및 명령문은 한쪽에서 스택에 있고 다른 데이터 소스에서 모든 새 executeFunction (== every thread) 연결 인스턴스를 호출 할 때마다 제공됩니다. 내가 바로 당신을 이해합니까? "
Pavel_K

첫 번째 방법을 수행하고 있지만 SQL 프로파일 러에서 모니터링하는 동안 하나가 아닌 여러 준비된 명령문이 반복되는 것을 볼 수 있습니다. 나는 그것이 여러 문장을 보여주는 이유를 알 수 없었다. 도움이 필요합니다.
Rogue 젊은이

귀하의 대답은 쿼리가 루프에서 변경되지 않는 경우 좋습니다. 예를 들어 쿼리가 변경된 경우에 쿼리가 변경되면 어떻게됩니까 .. 나는 여전히 두 번째 접근 방식이 더 낫다고 가정합니다. 확인하십시오
Stunner

13

코드의 루프는 지나치게 단순화 된 예일뿐입니다.

만드는 것이 더 나을 것입니다 PreparedStatement한 번만 하고 루프에서 반복해서 재사용하는 것이 좋습니다.

그것이 불가능한 상황에서는 (프로그램 흐름이 너무 복잡하기 때문에), 여전히 a를 사용하는 것이 좋습니다. PreparedStatement 서버 측 작업 (SQL 구문 분석 및 실행 캐싱 계획), 여전히 감소됩니다.

Java 측을 재사용하려는 상황을 해결하기 위해 PreparedStatement일부 JDBC 드라이버 (예 : Oracle)에는 캐싱 기능이 있습니다.PreparedStatement 동일한 연결에서 동일한 SQL에 대해 하면 동일한 (캐시 된 ) 인스턴스.

멀티 스레딩에 대하여 : 어쨌든 JDBC 연결이 여러 스레드에서 공유 될 수 있다고 생각하지 않습니다 (즉, 여러 스레드에서 동시에 사용). 모든 스레드는 풀에서 자신의 연결을 가져 와서 사용하고 다시 풀로 반환해야합니다.


1
실제로 연결에는 배타적 스레드가 있고 모든 문이 그 안에서 실행되지만 준비된 문 스택을 통해 해당 스레드에 액세스합니다. 그래서 다른 동시 스레드는 처음에만 모든 준비가 문을 구축하는 데 필요한 PARAMS 통과,하지만 그들은 동시에 PARAMS을 수정할 수 있습니다
강철 깃털
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.