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}