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

com.github.kagkarlsson.shaded.jdbc.JdbcRunner Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) Gustav Karlsson
 *
 * 

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 com.github.kagkarlsson.shaded.jdbc; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JdbcRunner { private static final Logger LOG = LoggerFactory.getLogger(JdbcRunner.class); private final ConnectionSupplier connectionSupplier; private final TransactionContextProvider transactionContextProvider; public JdbcRunner(DataSource dataSource) { this(dataSource, false); } public JdbcRunner(DataSource dataSource, boolean commitWhenAutocommitDisabled) { this( new DataSourceConnectionSupplier(dataSource, commitWhenAutocommitDisabled), new ThreadLocalTransactionContextProvider()); } public JdbcRunner( DataSource dataSource, boolean commitWhenAutocommitDisabled, TransactionContextProvider transactionContextProvider) { this( new DataSourceConnectionSupplier(dataSource, commitWhenAutocommitDisabled), transactionContextProvider); } public JdbcRunner( ConnectionSupplier connectionSupplier, TransactionContextProvider transactionContextProvider) { this.connectionSupplier = connectionSupplier; this.transactionContextProvider = transactionContextProvider; } /** * Creates a transactional JdbcRunner that can be used to execute operations in a single * transaction. Will currently not detect externally managed transactions (e.g. * Spring-transactions), only prevent nested transactions using inTransaction(..). * Will always commit or rollback. * * @param doInTransaction * @return * @param */ public T inTransaction(Function doInTransaction) { return new TransactionManager(connectionSupplier, transactionContextProvider) .inTransaction( c -> { final JdbcRunner jdbc = new JdbcRunner(new ExternallyManagedConnection(c), transactionContextProvider); return doInTransaction.apply(jdbc); }); } public int execute(String query, PreparedStatementSetter setParameters) { return execute( query, setParameters, PreparedStatementExecutor.EXECUTE, new AfterExecution.ReturnStatementUpdateCount<>()); } public List query( String query, PreparedStatementSetter setParameters, RowMapper rowMapper) { return execute( query, setParameters, PreparedStatementExecutor.EXECUTE, (p, executeResult) -> mapResultSet(p, rowMapper)); } public T query( String query, PreparedStatementSetter setParameters, ResultSetMapper resultSetMapper) { return execute( query, setParameters, PreparedStatementExecutor.EXECUTE, (p, executeResult) -> mapResultSet(p, resultSetMapper)); } public T execute( String query, PreparedStatementSetter setParameters, AfterExecution afterExecution) { return execute(query, setParameters, PreparedStatementExecutor.EXECUTE, afterExecution); } public int[] executeBatch( String query, List batchValues, BatchPreparedStatementSetter setParameters) { PreparedStatementSetter setAllBatches = preparedStatement -> { for (U batchValue : batchValues) { setParameters.setParametersForRow(batchValue, preparedStatement); preparedStatement.addBatch(); } }; return execute( query, setAllBatches, PreparedStatementExecutor.EXECUTE_BATCH, (executedPreparedStatement, executeResult) -> executeResult); } private T execute( String query, PreparedStatementSetter setParameters, PreparedStatementExecutor executePreparedStatement, AfterExecution afterExecution) { return withConnection( c -> { PreparedStatement preparedStatement = null; try { try { preparedStatement = c.prepareStatement(query); } catch (SQLException e) { throw new SQLRuntimeException("Error when preparing statement.", e); } try { LOG.trace("Setting parameters of prepared statement."); setParameters.setParameters(preparedStatement); } catch (SQLException e) { throw new SQLRuntimeException(e); } try { LOG.trace("Executing prepared statement"); U executeResult = executePreparedStatement.execute(preparedStatement); return afterExecution.doAfterExecution(preparedStatement, executeResult); } catch (SQLException e) { throw translateException(e); } } finally { nonThrowingClose(preparedStatement); } }); } private void commitIfNecessary(Connection c) { try { if (shouldManageTransaction(c)) { c.commit(); } } catch (SQLException e) { throw new SQLRuntimeException("Failed to commit.", e); } } private RuntimeException rollbackIfNecessary(Connection c, RuntimeException originalException) { try { if (shouldManageTransaction(c)) { c.rollback(); } return originalException; } catch (SQLException e) { SQLRuntimeException rollbackException = new SQLRuntimeException("Failed to rollback.", e); rollbackException.addSuppressed(originalException); return rollbackException; } } private boolean shouldManageTransaction(Connection c) throws SQLException { if (connectionSupplier.isExternallyManagedConnection()) { // Do not commit/rollback since connection (and therefore transaction-lifecycle), // is managed externally return false; } if (c.getAutoCommit()) { // AUTO-COMMIT=true // Do not commit/rollback when auto-commit is enabled. // Statements are auto-committed after they are run. return false; } else { // AUTO-COMMIT=false if (!connectionSupplier.commitWhenAutocommitDisabled()) { // When auto-commit is disabled, we assume the transaction is externally managed // unless otherwise specified return false; } else { // Commit/rollback when auto-commit is disabled but the user has specified that // they always want commit/rollback, even though their DataSource is giving out // connections where auto-commit=false. // This has been requested by users, but the use-case is not very clear return true; } } } private SQLRuntimeException translateException(SQLException ex) { if (ex instanceof SQLIntegrityConstraintViolationException) { return new IntegrityConstraintViolation(ex); } else { return new SQLRuntimeException(ex); } } private T withConnection(Function doWithConnection) { Connection c; try { LOG.trace("Getting connection from datasource"); c = connectionSupplier.getConnection(); } catch (SQLException e) { throw new SQLRuntimeException("Unable to open connection", e); } try { final T result = doWithConnection.apply(c); commitIfNecessary(c); return result; } catch (RuntimeException e) { throw rollbackIfNecessary(c, e); } finally { // Do not close when connection is managed by TransactionManager if (!connectionSupplier.isExternallyManagedConnection()) { nonThrowingClose(c); } } } private List mapResultSet( PreparedStatement executedPreparedStatement, RowMapper rowMapper) { return withResultSet( executedPreparedStatement, (ResultSet rs) -> { List results = new ArrayList<>(); while (rs.next()) { results.add(rowMapper.map(rs)); } return results; }); } private T mapResultSet( PreparedStatement executedPreparedStatement, ResultSetMapper resultSetMapper) { return withResultSet(executedPreparedStatement, (ResultSet rs) -> resultSetMapper.map(rs)); } private T withResultSet( PreparedStatement executedPreparedStatement, DoWithResultSet doWithResultSet) { ResultSet rs = null; try { try { rs = executedPreparedStatement.getResultSet(); } catch (SQLException e) { throw new SQLRuntimeException(e); } try { return doWithResultSet.withResultSet(rs); } catch (SQLException e) { throw new SQLRuntimeException(e); } } finally { nonThrowingClose(rs); } } private void nonThrowingClose(AutoCloseable toClose) { if (toClose == null) { return; } try { LOG.trace("Closing " + toClose.getClass().getSimpleName()); toClose.close(); } catch (Exception e) { LOG.warn("Exception on close of " + toClose.getClass().getSimpleName(), e); } } interface AfterExecution { T doAfterExecution(PreparedStatement executedPreparedStatement, U executeResult) throws SQLException; class ReturnStatementUpdateCount implements AfterExecution { @Override public Integer doAfterExecution(PreparedStatement executedPreparedStatement, U executeResult) throws SQLException { return executedPreparedStatement.getUpdateCount(); } } } interface DoWithResultSet { T withResultSet(ResultSet rs) throws SQLException; } }