001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.core;
018
019import java.sql.Connection;
020import java.sql.PreparedStatement;
021import java.sql.ResultSet;
022import java.sql.SQLException;
023import java.sql.Types;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Set;
030
031import org.springframework.dao.InvalidDataAccessApiUsageException;
032import org.springframework.lang.Nullable;
033
034/**
035 * Helper class that efficiently creates multiple {@link PreparedStatementCreator}
036 * objects with different parameters based on an SQL statement and a single
037 * set of parameter declarations.
038 *
039 * @author Rod Johnson
040 * @author Thomas Risberg
041 * @author Juergen Hoeller
042 */
043public class PreparedStatementCreatorFactory {
044
045        /** The SQL, which won't change when the parameters change. */
046        private final String sql;
047
048        /** List of SqlParameter objects (may not be {@code null}). */
049        private final List<SqlParameter> declaredParameters;
050
051        private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
052
053        private boolean updatableResults = false;
054
055        private boolean returnGeneratedKeys = false;
056
057        @Nullable
058        private String[] generatedKeysColumnNames;
059
060
061        /**
062         * Create a new factory. Will need to add parameters via the
063         * {@link #addParameter} method or have no parameters.
064         * @param sql the SQL statement to execute
065         */
066        public PreparedStatementCreatorFactory(String sql) {
067                this.sql = sql;
068                this.declaredParameters = new LinkedList<>();
069        }
070
071        /**
072         * Create a new factory with the given SQL and JDBC types.
073         * @param sql the SQL statement to execute
074         * @param types int array of JDBC types
075         */
076        public PreparedStatementCreatorFactory(String sql, int... types) {
077                this.sql = sql;
078                this.declaredParameters = SqlParameter.sqlTypesToAnonymousParameterList(types);
079        }
080
081        /**
082         * Create a new factory with the given SQL and parameters.
083         * @param sql the SQL statement to execute
084         * @param declaredParameters list of {@link SqlParameter} objects
085         */
086        public PreparedStatementCreatorFactory(String sql, List<SqlParameter> declaredParameters) {
087                this.sql = sql;
088                this.declaredParameters = declaredParameters;
089        }
090
091
092        /**
093         * Return the SQL statement to execute.
094         * @since 5.1.3
095         */
096        public final String getSql() {
097                return this.sql;
098        }
099
100        /**
101         * Add a new declared parameter.
102         * <p>Order of parameter addition is significant.
103         * @param param the parameter to add to the list of declared parameters
104         */
105        public void addParameter(SqlParameter param) {
106                this.declaredParameters.add(param);
107        }
108
109        /**
110         * Set whether to use prepared statements that return a specific type of ResultSet.
111         * @param resultSetType the ResultSet type
112         * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
113         * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
114         * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
115         */
116        public void setResultSetType(int resultSetType) {
117                this.resultSetType = resultSetType;
118        }
119
120        /**
121         * Set whether to use prepared statements capable of returning updatable ResultSets.
122         */
123        public void setUpdatableResults(boolean updatableResults) {
124                this.updatableResults = updatableResults;
125        }
126
127        /**
128         * Set whether prepared statements should be capable of returning auto-generated keys.
129         */
130        public void setReturnGeneratedKeys(boolean returnGeneratedKeys) {
131                this.returnGeneratedKeys = returnGeneratedKeys;
132        }
133
134        /**
135         * Set the column names of the auto-generated keys.
136         */
137        public void setGeneratedKeysColumnNames(String... names) {
138                this.generatedKeysColumnNames = names;
139        }
140
141
142        /**
143         * Return a new PreparedStatementSetter for the given parameters.
144         * @param params list of parameters (may be {@code null})
145         */
146        public PreparedStatementSetter newPreparedStatementSetter(@Nullable List<?> params) {
147                return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList());
148        }
149
150        /**
151         * Return a new PreparedStatementSetter for the given parameters.
152         * @param params the parameter array (may be {@code null})
153         */
154        public PreparedStatementSetter newPreparedStatementSetter(@Nullable Object[] params) {
155                return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList());
156        }
157
158        /**
159         * Return a new PreparedStatementCreator for the given parameters.
160         * @param params list of parameters (may be {@code null})
161         */
162        public PreparedStatementCreator newPreparedStatementCreator(@Nullable List<?> params) {
163                return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList());
164        }
165
166        /**
167         * Return a new PreparedStatementCreator for the given parameters.
168         * @param params the parameter array (may be {@code null})
169         */
170        public PreparedStatementCreator newPreparedStatementCreator(@Nullable Object[] params) {
171                return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList());
172        }
173
174        /**
175         * Return a new PreparedStatementCreator for the given parameters.
176         * @param sqlToUse the actual SQL statement to use (if different from
177         * the factory's, for example because of named parameter expanding)
178         * @param params the parameter array (may be {@code null})
179         */
180        public PreparedStatementCreator newPreparedStatementCreator(String sqlToUse, @Nullable Object[] params) {
181                return new PreparedStatementCreatorImpl(
182                                sqlToUse, params != null ? Arrays.asList(params) : Collections.emptyList());
183        }
184
185
186        /**
187         * PreparedStatementCreator implementation returned by this class.
188         */
189        private class PreparedStatementCreatorImpl
190                        implements PreparedStatementCreator, PreparedStatementSetter, SqlProvider, ParameterDisposer {
191
192                private final String actualSql;
193
194                private final List<?> parameters;
195
196                public PreparedStatementCreatorImpl(List<?> parameters) {
197                        this(sql, parameters);
198                }
199
200                public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) {
201                        this.actualSql = actualSql;
202                        this.parameters = parameters;
203                        if (parameters.size() != declaredParameters.size()) {
204                                // Account for named parameters being used multiple times
205                                Set<String> names = new HashSet<>();
206                                for (int i = 0; i < parameters.size(); i++) {
207                                        Object param = parameters.get(i);
208                                        if (param instanceof SqlParameterValue) {
209                                                names.add(((SqlParameterValue) param).getName());
210                                        }
211                                        else {
212                                                names.add("Parameter #" + i);
213                                        }
214                                }
215                                if (names.size() != declaredParameters.size()) {
216                                        throw new InvalidDataAccessApiUsageException(
217                                                        "SQL [" + sql + "]: given " + names.size() +
218                                                        " parameters but expected " + declaredParameters.size());
219                                }
220                        }
221                }
222
223                @Override
224                public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
225                        PreparedStatement ps;
226                        if (generatedKeysColumnNames != null || returnGeneratedKeys) {
227                                if (generatedKeysColumnNames != null) {
228                                        ps = con.prepareStatement(this.actualSql, generatedKeysColumnNames);
229                                }
230                                else {
231                                        ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS);
232                                }
233                        }
234                        else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) {
235                                ps = con.prepareStatement(this.actualSql);
236                        }
237                        else {
238                                ps = con.prepareStatement(this.actualSql, resultSetType,
239                                        updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
240                        }
241                        setValues(ps);
242                        return ps;
243                }
244
245                @Override
246                public void setValues(PreparedStatement ps) throws SQLException {
247                        // Set arguments: Does nothing if there are no parameters.
248                        int sqlColIndx = 1;
249                        for (int i = 0; i < this.parameters.size(); i++) {
250                                Object in = this.parameters.get(i);
251                                SqlParameter declaredParameter;
252                                // SqlParameterValue overrides declared parameter meta-data, in particular for
253                                // independence from the declared parameter position in case of named parameters.
254                                if (in instanceof SqlParameterValue) {
255                                        SqlParameterValue paramValue = (SqlParameterValue) in;
256                                        in = paramValue.getValue();
257                                        declaredParameter = paramValue;
258                                }
259                                else {
260                                        if (declaredParameters.size() <= i) {
261                                                throw new InvalidDataAccessApiUsageException(
262                                                                "SQL [" + sql + "]: unable to access parameter number " + (i + 1) +
263                                                                " given only " + declaredParameters.size() + " parameters");
264
265                                        }
266                                        declaredParameter = declaredParameters.get(i);
267                                }
268                                if (in instanceof Iterable && declaredParameter.getSqlType() != Types.ARRAY) {
269                                        Iterable<?> entries = (Iterable<?>) in;
270                                        for (Object entry : entries) {
271                                                if (entry instanceof Object[]) {
272                                                        Object[] valueArray = (Object[]) entry;
273                                                        for (Object argValue : valueArray) {
274                                                                StatementCreatorUtils.setParameterValue(ps, sqlColIndx++, declaredParameter, argValue);
275                                                        }
276                                                }
277                                                else {
278                                                        StatementCreatorUtils.setParameterValue(ps, sqlColIndx++, declaredParameter, entry);
279                                                }
280                                        }
281                                }
282                                else {
283                                        StatementCreatorUtils.setParameterValue(ps, sqlColIndx++, declaredParameter, in);
284                                }
285                        }
286                }
287
288                @Override
289                public String getSql() {
290                        return sql;
291                }
292
293                @Override
294                public void cleanupParameters() {
295                        StatementCreatorUtils.cleanupParameters(this.parameters);
296                }
297
298                @Override
299                public String toString() {
300                        return "PreparedStatementCreator: sql=[" + sql + "]; parameters=" + this.parameters;
301                }
302        }
303
304}