/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.internal.databaseaccess; import java.io.StringWriter; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.eclipse.persistence.descriptors.DescriptorQueryManager; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.OptimisticLockException; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.queries.ModifyQuery; import org.eclipse.persistence.sessions.SessionProfiler; /** * INTERNAL: * DynamicSQLBatchWritingMechanism is a private class, used by the DatabaseAccessor. * It provides the required behavior for batching statements, for write, with parameter binding turned off.<p> */ public class DynamicSQLBatchWritingMechanism extends BatchWritingMechanism { /** * This variable is used to store the SQLStrings that are being batched */ protected List<String> sqlStrings; /** * Stores the statement indexes for statements that are using optimistic locking. This allows us to check individual * statement results on supported platforms */ /** * This attribute is used to store the maximum length of all strings batched together */ protected long batchSize; /** * Records if this batch uses optimistic locking. */ protected boolean usesOptimisticLocking; protected DatabaseCall lastCallAppended; public DynamicSQLBatchWritingMechanism(DatabaseAccessor databaseAccessor) { this.databaseAccessor = databaseAccessor; this.sqlStrings = new ArrayList(); this.batchSize = 0; this.maxBatchSize = this.databaseAccessor.getLogin().getPlatform().getMaxBatchWritingSize(); if (this.maxBatchSize == 0) { // the max size was not set on the platform - use default this.maxBatchSize = DatabasePlatform.DEFAULT_MAX_BATCH_WRITING_SIZE; } } /** * INTERNAL: * This method is called by the DatabaseAccessor to add this statement to the list of statements * being batched. This call may result in the Mechanism executing the batched statements and * possibly, switching out the mechanisms */ public void appendCall(AbstractSession session, DatabaseCall dbCall) { if (!dbCall.hasParameters()) { if ((this.batchSize + dbCall.getSQLString().length()) > this.maxBatchSize) { executeBatchedStatements(session); } if (this.usesOptimisticLocking != dbCall.hasOptimisticLock) { executeBatchedStatements(session); } this.sqlStrings.add(dbCall.getSQLString()); this.lastCallAppended = dbCall; this.batchSize += dbCall.getSQLString().length(); this.usesOptimisticLocking = dbCall.hasOptimisticLock; this.statementCount++; // Store the largest queryTimeout on a single call for later use by the single statement in prepareJDK12BatchStatement if (dbCall != null) { cacheQueryTimeout(session, dbCall); } // feature for bug 4104613, allows users to force statements to flush on execution if (((ModifyQuery) dbCall.getQuery()).forceBatchStatementExecution()) { executeBatchedStatements(session); } } else { executeBatchedStatements(session); switchMechanisms(session, dbCall); } } /** * INTERNAL: * This method is used to clear the batched statements without the need to execute the statements first * This is used in the case of rollback. */ public void clear() { //Bug#419326 : A clone may be holding a reference to this.parameters. //So, instead of clearing the parameters, just initialize with a new reference. this.sqlStrings = new ArrayList(); this.statementCount = executionCount = 0; this.usesOptimisticLocking = false; this.batchSize = 0; this.queryTimeoutCache = DescriptorQueryManager.NoTimeout; this.lastCallAppended = null; } /** * INTERNAL: * This method is used by the DatabaseAccessor to execute and clear the batched statements in the * case that a non batchable statement is being executed */ public void executeBatchedStatements(AbstractSession session) { if (this.sqlStrings.isEmpty()) { return; } //Bug#419326 : Added below clone, clear and clone.executeBatch(session) //Cloning the mechanism and clearing the current mechanism ensures that the current batch //is not visible to recursive calls to executeBatchedStatements(session). DynamicSQLBatchWritingMechanism currentBatch = (DynamicSQLBatchWritingMechanism) this.clone(); this.clear(); currentBatch.executeBatch(session); } /** * INTERNAL: * This method is added to execute and clear the batched statements on the cloned batch mechanism which * is created in executeBatchedStatements(session). * * Introduced in fix for bug#419326. */ private void executeBatch(AbstractSession session) { if (this.sqlStrings.size() == 1) { // If only one call, just execute normally. try { int rowCount = (Integer)this.databaseAccessor.basicExecuteCall(this.lastCallAppended, null, session, false); if (this.usesOptimisticLocking) { if (rowCount != 1) { throw OptimisticLockException.batchStatementExecutionFailure(); } } } finally { clear(); } return; } try { this.databaseAccessor.writeStatementsCount++; this.databaseAccessor.incrementCallCount(session);// Decrement occurs in close. if (session.shouldLog(SessionLog.FINE, SessionLog.SQL)) { session.log(SessionLog.FINER, SessionLog.SQL, "begin_batch_statements", null, this.databaseAccessor); for (String sql : this.sqlStrings) { session.log(SessionLog.FINE, SessionLog.SQL, sql, null, this.databaseAccessor, false); } session.log(SessionLog.FINER, SessionLog.SQL, "end_batch_statements", null, this.databaseAccessor); } if (!session.getPlatform().usesJDBCBatchWriting()) { PreparedStatement statement = prepareBatchStatement(session); this.databaseAccessor.executeBatchedStatement(statement, session); } else { //lets add optimistic locking support. Statement statement = prepareJDK12BatchStatement(session); this.executionCount = this.databaseAccessor.executeJDK12BatchStatement(statement, null, session, false); if (this.usesOptimisticLocking && (executionCount != statementCount)) { throw OptimisticLockException.batchStatementExecutionFailure(); } } } finally { // Reset the batched sql string clear(); } } /** * INTERNAL: * This method is used to switch from this mechanism to the alternate automatically */ protected void switchMechanisms(AbstractSession session, DatabaseCall dbCall) { this.databaseAccessor.setActiveBatchWritingMechanismToParameterizedSQL(); this.databaseAccessor.getActiveBatchWritingMechanism(session).appendCall(session, dbCall); } /** * INTERNAL: * This method is used to build the batch statement by concatenating the strings * together. */ protected PreparedStatement prepareBatchStatement(AbstractSession session) throws DatabaseException { PreparedStatement statement = null; boolean isDelimiterStringNeeded = false; StringWriter writer = new StringWriter(); DatabasePlatform platform = session.getPlatform(); writer.write(platform.getBatchBeginString()); for (String sql : this.sqlStrings) { if (isDelimiterStringNeeded) { writer.write(platform.getBatchDelimiterString()); } writer.write(sql); isDelimiterStringNeeded = true; } writer.write(platform.getBatchDelimiterString()); writer.write(platform.getBatchEndString()); try { session.startOperationProfile(SessionProfiler.SqlPrepare, null, SessionProfiler.ALL); try { statement = this.databaseAccessor.getConnection().prepareStatement(writer.toString()); } finally { session.endOperationProfile(SessionProfiler.SqlPrepare, null, SessionProfiler.ALL); } } catch (SQLException exception) { //If this is a connection from an external pool then closeStatement will close the connection. //we must test the connection before that happens. DatabaseException exceptionToThrow = this.databaseAccessor.processExceptionForCommError(session, exception, null); try {// Ensure that the statement is closed, but still ensure that the real exception is thrown. this.databaseAccessor.closeStatement(statement, session, null); } catch (SQLException closeException) { } if (exceptionToThrow == null){ throw DatabaseException.sqlException(exception, this.databaseAccessor, session, false); } throw exceptionToThrow; } catch (RuntimeException exception) { try {// Ensure that the statement is closed, but still ensure that the real exception is thrown. this.databaseAccessor.closeStatement(statement, session, null); } catch (SQLException closeException) { } throw exception; } return statement; } /** * INTERNAL: * This method is used to build the batch statement for the JDBC2.0 specification */ protected Statement prepareJDK12BatchStatement(AbstractSession session) throws DatabaseException { Statement statement = null; try { session.startOperationProfile(SessionProfiler.SqlPrepare, null, SessionProfiler.ALL); try { statement = this.databaseAccessor.getConnection().createStatement(); for (String sql : this.sqlStrings) { statement.addBatch(sql); } // Set the query timeout that was cached during the multiple calls to appendCall if (this.queryTimeoutCache > DescriptorQueryManager.NoTimeout) { statement.setQueryTimeout(this.queryTimeoutCache); } } finally { session.endOperationProfile(SessionProfiler.SqlPrepare, null, SessionProfiler.ALL); } } catch (SQLException exception) { //If this is a connection from an external pool then closeStatement will close the connection. //we must test the connection before that happens. RuntimeException exceptionToThrow = this.databaseAccessor.processExceptionForCommError(session, exception, null); try {// Ensure that the statement is closed, but still ensure that the real exception is thrown. this.databaseAccessor.closeStatement(statement, session, null); } catch (SQLException closeException) { } if (exceptionToThrow == null){ throw DatabaseException.sqlException(exception, this.databaseAccessor, session, false); } throw exceptionToThrow; } catch (RuntimeException exception) { try {// Ensure that the statement is closed, but still ensure that the real exception is thrown. this.databaseAccessor.closeStatement(statement, session, null); } catch (SQLException closeException) { } throw exception; } return statement; } }