All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scriptella.jdbc.StatementCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2006-2012 The Scriptella Project Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package scriptella.jdbc;

import scriptella.util.IOUtils;
import scriptella.util.LRUMap;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;

import static scriptella.util.CollectionUtils.isEmpty;

/**
 * Statements cache for {@link JdbcConnection}.
 * TODO Extract statement handling policy interface and provide 2 implementations for normal and batched mode. (+1 for testing)
 * TODO Use wrapper class for Jdbc ConnectionParameters to store typesafe parameters and overridable factories
 *
 * @author Fyodor Kupolov
 * @version 1.0
 */
class StatementCache implements Closeable {
    private Map map;
    private final Connection connection;
    private final JdbcTypesConverter converter;
    private int batchSize;
    private StatementWrapper.Batched sharedBatchedStatement; //see getter for description
    private int fetchSize;

    /**
     * Creates a statement cache for specified connection.
     *
     * @param connection connection to create cache for.
     * @param size       cache size, 0 or negative means disable cache.
     * @param batchSize  size of prepared statements batch.
     * @param fetchSize  see {@link java.sql.Statement#setFetchSize(int)}
     */
    public StatementCache(Connection connection, final int size, final int batchSize, final int fetchSize) {
        this.connection = connection;
        this.batchSize = batchSize;
        this.converter = new JdbcTypesConverter();
        this.fetchSize = fetchSize;
        if (size > 0) { //if cache is enabled
            map = new CacheMap(size);
        }
    }

    /**
     * Prepares a statement.
     * 

The sql is used as a key to lookup a {@link StatementWrapper}, * if cache miss the statement is created and put to cache. * * @param sql statement SQL. * @param params parameters for SQL. * @return a wrapper for specified SQL. * @throws SQLException if DB reports an error * @see StatementWrapper */ public StatementWrapper prepare(final String sql, final List params) throws SQLException { //In batch mode always use Batched statement for sql without parameters if (isBatchMode() && isEmpty(params)) { StatementWrapper.Batched batchedSt = getSharedBatchStatement(); batchedSt.setSql(sql); return batchedSt; } StatementWrapper sw = map == null ? null : map.get(sql); if (sw == null) { //If not cached if (isEmpty(params)) { sw = create(sql); } else { sw = prepare(sql); } put(sql, sw); } else if (sw instanceof StatementWrapper.Simple) { //if simple statement is obtained second time - use prepared to improve performance sw.close(); //closing unused statement put(sql, sw = prepare(sql)); } sw.setParameters(params); return sw; } /** * Testable template method to create simple statement */ protected StatementWrapper create(final String sql) throws SQLException { Statement statement = connection.createStatement(); if (fetchSize != 0) { statement.setFetchSize(fetchSize); } return new StatementWrapper.Simple(statement, sql, converter); } /** * Testable template method to create prepared statement */ protected StatementWrapper.Prepared prepare(final String sql) throws SQLException { PreparedStatement preparedStatement = connection.prepareStatement(sql); if (fetchSize != 0) { preparedStatement.setFetchSize(fetchSize); } if (isBatchMode()) { return new StatementWrapper.BatchedPrepared(preparedStatement, converter, batchSize); } else { return new StatementWrapper.Prepared(preparedStatement, converter); } } private boolean isBatchMode() { return batchSize > 0; } /** * Returns an instance of StatementWrapper.Batched shared on the instance level. *

Since each ETL element has its own cache, we are using a shared statement. * This is critical in batch mode to allow grouping different statements in one batch. * * @return instance of shared statement. * @throws SQLException if error occurs */ protected StatementWrapper.Batched getSharedBatchStatement() throws SQLException { if (sharedBatchedStatement == null) { sharedBatchedStatement = new StatementWrapper.Batched(connection.createStatement(), converter, batchSize); } return sharedBatchedStatement; } private void put(String key, StatementWrapper entry) { if (map != null) { map.put(key, entry); } } /** * Notifies cache that specified statement is no longer in use. * Close method is invoked on statements pending release after removing from cache. * * @param sw released statement. */ public void releaseStatement(StatementWrapper sw) { if (sw == null) { throw new IllegalArgumentException("Released statement cannot be null"); } //if caching disabled or simple statement - close it if (map == null) { sw.close(); } else { sw.clear(); } } public void close() { if (map != null) { //closing statements IOUtils.closeSilently(map.values()); map = null; } } /** * Flushes pending batches. * * @throws SQLException if DB error occurs. */ public void flush() throws SQLException { if (isBatchMode()) { if (sharedBatchedStatement != null) { sharedBatchedStatement.flush(); } if (map != null) { for (StatementWrapper sw : map.values()) { sw.flush(); } } } } /** * LRU Map implementation for statement cache. */ private static class CacheMap extends LRUMap { private static final long serialVersionUID = 1; public CacheMap(int size) { super(size); } protected void onEldestEntryRemove(Map.Entry eldest) { eldest.getValue().close(); } } }