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