001/*
002 * Copyright 2002-2018 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.object;
018
019import java.util.Map;
020
021import javax.sql.DataSource;
022
023import org.springframework.dao.DataAccessException;
024import org.springframework.dao.InvalidDataAccessApiUsageException;
025import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
026import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
027import org.springframework.jdbc.core.namedparam.NamedParameterUtils;
028import org.springframework.jdbc.core.namedparam.ParsedSql;
029import org.springframework.jdbc.support.KeyHolder;
030
031/**
032 * Reusable operation object representing an SQL update.
033 *
034 * <p>This class provides a number of {@code update} methods,
035 * analogous to the {@code execute} methods of query objects.
036 *
037 * <p>This class is concrete. Although it can be subclassed (for example
038 * to add a custom update method) it can easily be parameterized by setting
039 * SQL and declaring parameters.
040 *
041 * <p>Like all {@code RdbmsOperation} classes that ship with the Spring
042 * Framework, {@code SqlQuery} instances are thread-safe after their
043 * initialization is complete. That is, after they are constructed and configured
044 * via their setter methods, they can be used safely from multiple threads.
045 *
046 * @author Rod Johnson
047 * @author Thomas Risberg
048 * @author Juergen Hoeller
049 * @see SqlQuery
050 */
051public class SqlUpdate extends SqlOperation {
052
053        /**
054         * Maximum number of rows the update may affect. If more are
055         * affected, an exception will be thrown. Ignored if 0.
056         */
057        private int maxRowsAffected = 0;
058
059        /**
060         * An exact number of rows that must be affected.
061         * Ignored if 0.
062         */
063        private int requiredRowsAffected = 0;
064
065
066        /**
067         * Constructor to allow use as a JavaBean. DataSource and SQL
068         * must be supplied before compilation and use.
069         * @see #setDataSource
070         * @see #setSql
071         */
072        public SqlUpdate() {
073        }
074
075        /**
076         * Constructs an update object with a given DataSource and SQL.
077         * @param ds the DataSource to use to obtain connections
078         * @param sql the SQL statement to execute
079         */
080        public SqlUpdate(DataSource ds, String sql) {
081                setDataSource(ds);
082                setSql(sql);
083        }
084
085        /**
086         * Construct an update object with a given DataSource, SQL
087         * and anonymous parameters.
088         * @param ds the DataSource to use to obtain connections
089         * @param sql the SQL statement to execute
090         * @param types the SQL types of the parameters, as defined in the
091         * {@code java.sql.Types} class
092         * @see java.sql.Types
093         */
094        public SqlUpdate(DataSource ds, String sql, int[] types) {
095                setDataSource(ds);
096                setSql(sql);
097                setTypes(types);
098        }
099
100        /**
101         * Construct an update object with a given DataSource, SQL,
102         * anonymous parameters and specifying the maximum number of rows
103         * that may be affected.
104         * @param ds the DataSource to use to obtain connections
105         * @param sql the SQL statement to execute
106         * @param types the SQL types of the parameters, as defined in the
107         * {@code java.sql.Types} class
108         * @param maxRowsAffected the maximum number of rows that may
109         * be affected by the update
110         * @see java.sql.Types
111         */
112        public SqlUpdate(DataSource ds, String sql, int[] types, int maxRowsAffected) {
113                setDataSource(ds);
114                setSql(sql);
115                setTypes(types);
116                this.maxRowsAffected = maxRowsAffected;
117        }
118
119
120        /**
121         * Set the maximum number of rows that may be affected by this update.
122         * The default value is 0, which does not limit the number of rows affected.
123         * @param maxRowsAffected the maximum number of rows that can be affected by
124         * this update without this class's update method considering it an error
125         */
126        public void setMaxRowsAffected(int maxRowsAffected) {
127                this.maxRowsAffected = maxRowsAffected;
128        }
129
130        /**
131         * Set the <i>exact</i> number of rows that must be affected by this update.
132         * The default value is 0, which allows any number of rows to be affected.
133         * <p>This is an alternative to setting the <i>maximum</i> number of rows
134         * that may be affected.
135         * @param requiredRowsAffected the exact number of rows that must be affected
136         * by this update without this class's update method considering it an error
137         */
138        public void setRequiredRowsAffected(int requiredRowsAffected) {
139                this.requiredRowsAffected = requiredRowsAffected;
140        }
141
142        /**
143         * Check the given number of affected rows against the
144         * specified maximum number or required number.
145         * @param rowsAffected the number of affected rows
146         * @throws JdbcUpdateAffectedIncorrectNumberOfRowsException
147         * if the actually affected rows are out of bounds
148         * @see #setMaxRowsAffected
149         * @see #setRequiredRowsAffected
150         */
151        protected void checkRowsAffected(int rowsAffected) throws JdbcUpdateAffectedIncorrectNumberOfRowsException {
152                if (this.maxRowsAffected > 0 && rowsAffected > this.maxRowsAffected) {
153                        throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(resolveSql(), this.maxRowsAffected, rowsAffected);
154                }
155                if (this.requiredRowsAffected > 0 && rowsAffected != this.requiredRowsAffected) {
156                        throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(resolveSql(), this.requiredRowsAffected, rowsAffected);
157                }
158        }
159
160
161        /**
162         * Generic method to execute the update given parameters.
163         * All other update methods invoke this method.
164         * @param params array of parameters objects
165         * @return the number of rows affected by the update
166         */
167        public int update(Object... params) throws DataAccessException {
168                validateParameters(params);
169                int rowsAffected = getJdbcTemplate().update(newPreparedStatementCreator(params));
170                checkRowsAffected(rowsAffected);
171                return rowsAffected;
172        }
173
174        /**
175         * Method to execute the update given arguments and
176         * retrieve the generated keys using a KeyHolder.
177         * @param params array of parameter objects
178         * @param generatedKeyHolder the KeyHolder that will hold the generated keys
179         * @return the number of rows affected by the update
180         */
181        public int update(Object[] params, KeyHolder generatedKeyHolder) throws DataAccessException {
182                if (!isReturnGeneratedKeys() && getGeneratedKeysColumnNames() == null) {
183                        throw new InvalidDataAccessApiUsageException(
184                                        "The update method taking a KeyHolder should only be used when generated keys have " +
185                                        "been configured by calling either 'setReturnGeneratedKeys' or " +
186                                        "'setGeneratedKeysColumnNames'.");
187                }
188                validateParameters(params);
189                int rowsAffected = getJdbcTemplate().update(newPreparedStatementCreator(params), generatedKeyHolder);
190                checkRowsAffected(rowsAffected);
191                return rowsAffected;
192        }
193
194        /**
195         * Convenience method to execute an update with no parameters.
196         */
197        public int update() throws DataAccessException {
198                return update(new Object[0]);
199        }
200
201        /**
202         * Convenient method to execute an update given one int arg.
203         */
204        public int update(int p1) throws DataAccessException {
205                return update(new Object[] {p1});
206        }
207
208        /**
209         * Convenient method to execute an update given two int args.
210         */
211        public int update(int p1, int p2) throws DataAccessException {
212                return update(new Object[] {p1, p2});
213        }
214
215        /**
216         * Convenient method to execute an update given one long arg.
217         */
218        public int update(long p1) throws DataAccessException {
219                return update(new Object[] {p1});
220        }
221
222        /**
223         * Convenient method to execute an update given two long args.
224         */
225        public int update(long p1, long p2) throws DataAccessException {
226                return update(new Object[] {p1, p2});
227        }
228
229        /**
230         * Convenient method to execute an update given one String arg.
231         */
232        public int update(String p) throws DataAccessException {
233                return update(new Object[] {p});
234        }
235
236        /**
237         * Convenient method to execute an update given two String args.
238         */
239        public int update(String p1, String p2) throws DataAccessException {
240                return update(new Object[] {p1, p2});
241        }
242
243        /**
244         * Generic method to execute the update given named parameters.
245         * All other update methods invoke this method.
246         * @param paramMap a Map of parameter name to parameter object,
247         * matching named parameters specified in the SQL statement
248         * @return the number of rows affected by the update
249         */
250        public int updateByNamedParam(Map<String, ?> paramMap) throws DataAccessException {
251                validateNamedParameters(paramMap);
252                ParsedSql parsedSql = getParsedSql();
253                MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
254                String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
255                Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
256                int rowsAffected = getJdbcTemplate().update(newPreparedStatementCreator(sqlToUse, params));
257                checkRowsAffected(rowsAffected);
258                return rowsAffected;
259        }
260
261        /**
262         * Method to execute the update given arguments and
263         * retrieve the generated keys using a KeyHolder.
264         * @param paramMap a Map of parameter name to parameter object,
265         * matching named parameters specified in the SQL statement
266         * @param generatedKeyHolder the KeyHolder that will hold the generated keys
267         * @return the number of rows affected by the update
268         */
269        public int updateByNamedParam(Map<String, ?> paramMap, KeyHolder generatedKeyHolder) throws DataAccessException {
270                validateNamedParameters(paramMap);
271                ParsedSql parsedSql = getParsedSql();
272                MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
273                String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
274                Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
275                int rowsAffected = getJdbcTemplate().update(newPreparedStatementCreator(sqlToUse, params), generatedKeyHolder);
276                checkRowsAffected(rowsAffected);
277                return rowsAffected;
278        }
279
280}