File: ParameterizedSQLBatchWritingMechanism.java

package info (click to toggle)
eclipselink 2.7.15-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 44,708 kB
  • sloc: java: 476,807; xml: 547; makefile: 21
file content (286 lines) | stat: -rw-r--r-- 12,840 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/*
 * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2022 IBM Corporation. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// 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.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:
 * ParameterizedSQLBatchWritingMechanism is a private class, used by the DatabaseAccessor. it provides the required
 * behavior for batching statements, for write, with parameter binding turned on.<p>
 *
 * @since OracleAS TopLink 10<i>g</i> (9.0.4)
 */
public class ParameterizedSQLBatchWritingMechanism extends BatchWritingMechanism {

    /**
     * This member variable is used to keep track of the last SQL string that was executed
     * by this mechanism.  If the current string and previous string match then simply
     * bind in the arguments, otherwise end previous batch and start a new batch
     */
    protected DatabaseCall previousCall;

    /**
     * This variable contains a list of the parameters list passed into the query
     */
    protected List<List> parameters;
    protected DatabaseCall lastCallAppended;

    public ParameterizedSQLBatchWritingMechanism() {
        super();
    }

    public ParameterizedSQLBatchWritingMechanism(DatabaseAccessor databaseAccessor) {
        this.databaseAccessor = databaseAccessor;
        this.parameters = new ArrayList();
        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_PARAMETERIZED_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()) {
            //make an equality check on the String, because if we are caching statements then
            //we will not have to perform the string comparison multiple times.
            if (this.previousCall == null) {
                this.previousCall = dbCall;
                this.parameters.add(dbCall.getParameters());
            } else {
                if (this.previousCall.getSQLString().equals(dbCall.getSQLString()) && (this.parameters.size() < this.maxBatchSize)) {
                    this.parameters.add(dbCall.getParameters());
                } else {
                    executeBatchedStatements(session);
                    this.previousCall = dbCall;
                    this.parameters.add(dbCall.getParameters());
                }
            }
            // Store the largest queryTimeout on a single call for later use by the single statement in prepareBatchStatements
            if (dbCall != null) {
                cacheQueryTimeout(session, dbCall);
            }
            this.lastCallAppended = 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() {
        this.previousCall = null;
        //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.parameters = new ArrayList();
        this.statementCount = 0;
        this.executionCount  = 0;
        this.queryTimeoutCache = DescriptorQueryManager.NoTimeout;
        // bug 229831 : BATCH WRITING CAUSES MEMORY LEAKS WITH UOW
        this.lastCallAppended = null;
    }

    /**
     * INTERNAL:
     * This method is used by the DatabaseAccessor to clear the batched statements in the
     * case that a non batchable statement is being executed
     */
    public void executeBatchedStatements(AbstractSession session) {
        if (this.parameters.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).
        ParameterizedSQLBatchWritingMechanism currentBatch = (ParameterizedSQLBatchWritingMechanism) 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.parameters.size() == 1) {
            // If only one call, just execute normally.
            try {
                Object rowCount = this.databaseAccessor.basicExecuteCall(this.previousCall, null, session, false);
                if (this.previousCall.hasOptimisticLock() && rowCount instanceof Integer) {
                    if ((Integer)rowCount != 1) {
                        throw OptimisticLockException.batchStatementExecutionFailure();
                    }
                }
            } finally {
                clear();
            }
            return;
        }

        try {
            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);
                session.log(SessionLog.FINE, SessionLog.SQL, this.previousCall.getSQLString(), null, this.databaseAccessor, false);
                // took this logging part from SQLCall
                for (List callParameters : this.parameters) {
                    StringWriter writer = new StringWriter();
                    DatabaseCall.appendLogParameters(callParameters, this.databaseAccessor, writer, session);
                    session.log(SessionLog.FINE, SessionLog.SQL, writer.toString(), null, this.databaseAccessor, false);
                }
                session.log(SessionLog.FINER, SessionLog.SQL, "end_batch_statements", null, this.databaseAccessor);
            }

            //bug 4241441: need to keep track of rows modified and throw opti lock exception if needed
            PreparedStatement statement = prepareBatchStatements(session);
            // += is used as native batch writing can return a row count before execution.
            this.executionCount += this.databaseAccessor.executeJDK12BatchStatement(statement, this.lastCallAppended, session, true);
            this.databaseAccessor.writeStatementsCount++;

            if (this.previousCall.hasOptimisticLock() && (this.executionCount != this.statementCount)) {
                throw OptimisticLockException.batchStatementExecutionFailure();
            }
        } finally {
            // Reset the batched sql string
            //we MUST clear the mechanism here in order to append the new statement.
            this.clear();
        }
    }

    /**
     * INTERNAL:
     * Swaps out the Mechanism for the other Mechanism
     */
    protected void switchMechanisms(AbstractSession session, DatabaseCall dbCall) {
        this.databaseAccessor.setActiveBatchWritingMechanismToDynamicSQL();
        this.databaseAccessor.getActiveBatchWritingMechanism(session).appendCall(session, dbCall);
    }

    /**
     * INTERNAL:
     * This method is used to build the parameterized batch statement for the JDBC2.0 specification
     */
    protected PreparedStatement prepareBatchStatements(AbstractSession session) throws DatabaseException {
        PreparedStatement statement = null;
        try {
            session.startOperationProfile(SessionProfiler.SqlPrepare, null, SessionProfiler.ALL);
            try {
                DatabasePlatform platform = session.getPlatform();
                boolean shouldUnwrapConnection = platform.usesNativeBatchWriting();
                statement = (PreparedStatement)this.databaseAccessor.prepareStatement(this.previousCall, session, shouldUnwrapConnection);
                // Perform platform specific preparations
                platform.prepareBatchStatement(statement, this.maxBatchSize);
                   if (this.queryTimeoutCache > DescriptorQueryManager.NoTimeout) {
                    // Set the query timeout that was cached during the multiple calls to appendCall
                       statement.setQueryTimeout(this.queryTimeoutCache);
                }

                // Iterate over the parameter lists that were batched.
                int statementSize = this.parameters.size();
                for (int statementIndex = 0; statementIndex < statementSize; ++statementIndex) {
                    List parameterList = this.parameters.get(statementIndex);
                    int size = parameterList.size();
                    for (int index = 0; index < size; index++) {
                        platform.setParameterValueInDatabaseCall(parameterList.get(index), statement, index+1, session);
                    }

                    // Batch the parameters to the statement.
                    this.statementCount++;
                    this.executionCount += platform.addBatch(statement);
                }
            } 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, lastCallAppended);
            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;
    }

    public DatabaseCall getPreviousCall() {
        return previousCall;
    }

    public void setPreviousCall(DatabaseCall previousCall) {
        this.previousCall = previousCall;
    }

    public List<List> getParameters() {
        return parameters;
    }

    public void setParameters(List<List> parameters) {
        this.parameters = parameters;
    }

    public DatabaseCall getLastCallAppended() {
        return lastCallAppended;
    }

    public void setLastCallAppended(DatabaseCall lastCallAppended) {
        this.lastCallAppended = lastCallAppended;
    }
}