1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.jdbc.core.simple;
18
19 import java.sql.Connection;
20 import java.sql.PreparedStatement;
21 import java.sql.ResultSet;
22 import java.sql.SQLException;
23 import java.sql.Statement;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import javax.sql.DataSource;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 import org.springframework.dao.DataAccessException;
36 import org.springframework.dao.DataIntegrityViolationException;
37 import org.springframework.dao.InvalidDataAccessApiUsageException;
38 import org.springframework.dao.InvalidDataAccessResourceUsageException;
39 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
40 import org.springframework.jdbc.core.ConnectionCallback;
41 import org.springframework.jdbc.core.JdbcTemplate;
42 import org.springframework.jdbc.core.PreparedStatementCreator;
43 import org.springframework.jdbc.core.SqlTypeValue;
44 import org.springframework.jdbc.core.StatementCreatorUtils;
45 import org.springframework.jdbc.core.metadata.TableMetaDataContext;
46 import org.springframework.jdbc.core.namedparam.SqlParameterSource;
47 import org.springframework.jdbc.support.GeneratedKeyHolder;
48 import org.springframework.jdbc.support.JdbcUtils;
49 import org.springframework.jdbc.support.KeyHolder;
50 import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
51 import org.springframework.util.Assert;
52
53
54
55
56
57
58
59
60
61
62 public abstract class AbstractJdbcInsert {
63
64
65 protected final Log logger = LogFactory.getLog(getClass());
66
67
68 private final JdbcTemplate jdbcTemplate;
69
70
71 private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
72
73
74 private final List<String> declaredColumns = new ArrayList<String>();
75
76
77 private String[] generatedKeyNames = new String[0];
78
79
80
81
82
83 private boolean compiled = false;
84
85
86 private String insertString;
87
88
89 private int[] insertTypes;
90
91
92
93
94
95
96 protected AbstractJdbcInsert(DataSource dataSource) {
97 this.jdbcTemplate = new JdbcTemplate(dataSource);
98 }
99
100
101
102
103
104 protected AbstractJdbcInsert(JdbcTemplate jdbcTemplate) {
105 Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
106 this.jdbcTemplate = jdbcTemplate;
107 setNativeJdbcExtractor(jdbcTemplate.getNativeJdbcExtractor());
108 }
109
110
111
112
113
114
115
116
117
118 public JdbcTemplate getJdbcTemplate() {
119 return this.jdbcTemplate;
120 }
121
122
123
124
125 public void setTableName(String tableName) {
126 checkIfConfigurationModificationIsAllowed();
127 this.tableMetaDataContext.setTableName(tableName);
128 }
129
130
131
132
133 public String getTableName() {
134 return this.tableMetaDataContext.getTableName();
135 }
136
137
138
139
140 public void setSchemaName(String schemaName) {
141 checkIfConfigurationModificationIsAllowed();
142 this.tableMetaDataContext.setSchemaName(schemaName);
143 }
144
145
146
147
148 public String getSchemaName() {
149 return this.tableMetaDataContext.getSchemaName();
150 }
151
152
153
154
155 public void setCatalogName(String catalogName) {
156 checkIfConfigurationModificationIsAllowed();
157 this.tableMetaDataContext.setCatalogName(catalogName);
158 }
159
160
161
162
163 public String getCatalogName() {
164 return this.tableMetaDataContext.getCatalogName();
165 }
166
167
168
169
170 public void setColumnNames(List<String> columnNames) {
171 checkIfConfigurationModificationIsAllowed();
172 this.declaredColumns.clear();
173 this.declaredColumns.addAll(columnNames);
174 }
175
176
177
178
179 public List<String> getColumnNames() {
180 return Collections.unmodifiableList(this.declaredColumns);
181 }
182
183
184
185
186 public void setGeneratedKeyName(String generatedKeyName) {
187 checkIfConfigurationModificationIsAllowed();
188 this.generatedKeyNames = new String[] {generatedKeyName};
189 }
190
191
192
193
194 public void setGeneratedKeyNames(String... generatedKeyNames) {
195 checkIfConfigurationModificationIsAllowed();
196 this.generatedKeyNames = generatedKeyNames;
197 }
198
199
200
201
202 public String[] getGeneratedKeyNames() {
203 return this.generatedKeyNames;
204 }
205
206
207
208
209
210 public void setAccessTableColumnMetaData(boolean accessTableColumnMetaData) {
211 this.tableMetaDataContext.setAccessTableColumnMetaData(accessTableColumnMetaData);
212 }
213
214
215
216
217
218 public void setOverrideIncludeSynonymsDefault(boolean override) {
219 this.tableMetaDataContext.setOverrideIncludeSynonymsDefault(override);
220 }
221
222
223
224
225 public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
226 this.tableMetaDataContext.setNativeJdbcExtractor(nativeJdbcExtractor);
227 }
228
229
230
231
232 public String getInsertString() {
233 return this.insertString;
234 }
235
236
237
238
239 public int[] getInsertTypes() {
240 return this.insertTypes;
241 }
242
243
244
245
246
247
248
249
250
251
252
253
254
255 public synchronized final void compile() throws InvalidDataAccessApiUsageException {
256 if (!isCompiled()) {
257 if (getTableName() == null) {
258 throw new InvalidDataAccessApiUsageException("Table name is required");
259 }
260 try {
261 this.jdbcTemplate.afterPropertiesSet();
262 }
263 catch (IllegalArgumentException ex) {
264 throw new InvalidDataAccessApiUsageException(ex.getMessage());
265 }
266 compileInternal();
267 this.compiled = true;
268 if (logger.isDebugEnabled()) {
269 logger.debug("JdbcInsert for table [" + getTableName() + "] compiled");
270 }
271 }
272 }
273
274
275
276
277
278
279 protected void compileInternal() {
280 this.tableMetaDataContext.processMetaData(
281 getJdbcTemplate().getDataSource(), getColumnNames(), getGeneratedKeyNames());
282 this.insertString = this.tableMetaDataContext.createInsertString(getGeneratedKeyNames());
283 this.insertTypes = this.tableMetaDataContext.createInsertTypes();
284 if (logger.isDebugEnabled()) {
285 logger.debug("Compiled insert object: insert string is [" + getInsertString() + "]");
286 }
287 onCompileInternal();
288 }
289
290
291
292
293
294 protected void onCompileInternal() {
295 }
296
297
298
299
300
301 public boolean isCompiled() {
302 return this.compiled;
303 }
304
305
306
307
308
309
310 protected void checkCompiled() {
311 if (!isCompiled()) {
312 logger.debug("JdbcInsert not compiled before execution - invoking compile");
313 compile();
314 }
315 }
316
317
318
319
320
321 protected void checkIfConfigurationModificationIsAllowed() {
322 if (isCompiled()) {
323 throw new InvalidDataAccessApiUsageException(
324 "Configuration can't be altered once the class has been compiled or used");
325 }
326 }
327
328
329
330
331
332
333
334
335
336
337
338 protected int doExecute(Map<String, ?> args) {
339 checkCompiled();
340 List<Object> values = matchInParameterValuesWithInsertColumns(args);
341 return executeInsertInternal(values);
342 }
343
344
345
346
347
348
349 protected int doExecute(SqlParameterSource parameterSource) {
350 checkCompiled();
351 List<Object> values = matchInParameterValuesWithInsertColumns(parameterSource);
352 return executeInsertInternal(values);
353 }
354
355
356
357
358 private int executeInsertInternal(List<?> values) {
359 if (logger.isDebugEnabled()) {
360 logger.debug("The following parameters are used for insert " + getInsertString() + " with: " + values);
361 }
362 return getJdbcTemplate().update(getInsertString(), values.toArray(), getInsertTypes());
363 }
364
365
366
367
368
369
370
371 protected Number doExecuteAndReturnKey(Map<String, ?> args) {
372 checkCompiled();
373 List<Object> values = matchInParameterValuesWithInsertColumns(args);
374 return executeInsertAndReturnKeyInternal(values);
375 }
376
377
378
379
380
381
382
383 protected Number doExecuteAndReturnKey(SqlParameterSource parameterSource) {
384 checkCompiled();
385 List<Object> values = matchInParameterValuesWithInsertColumns(parameterSource);
386 return executeInsertAndReturnKeyInternal(values);
387 }
388
389
390
391
392
393
394
395 protected KeyHolder doExecuteAndReturnKeyHolder(Map<String, ?> args) {
396 checkCompiled();
397 List<Object> values = matchInParameterValuesWithInsertColumns(args);
398 return executeInsertAndReturnKeyHolderInternal(values);
399 }
400
401
402
403
404
405
406
407 protected KeyHolder doExecuteAndReturnKeyHolder(SqlParameterSource parameterSource) {
408 checkCompiled();
409 List<Object> values = matchInParameterValuesWithInsertColumns(parameterSource);
410 return executeInsertAndReturnKeyHolderInternal(values);
411 }
412
413
414
415
416 private Number executeInsertAndReturnKeyInternal(final List<?> values) {
417 KeyHolder kh = executeInsertAndReturnKeyHolderInternal(values);
418 if (kh != null && kh.getKey() != null) {
419 return kh.getKey();
420 }
421 else {
422 throw new DataIntegrityViolationException(
423 "Unable to retrieve the generated key for the insert: " + getInsertString());
424 }
425 }
426
427
428
429
430 private KeyHolder executeInsertAndReturnKeyHolderInternal(final List<?> values) {
431 if (logger.isDebugEnabled()) {
432 logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values);
433 }
434 final KeyHolder keyHolder = new GeneratedKeyHolder();
435 if (this.tableMetaDataContext.isGetGeneratedKeysSupported()) {
436 getJdbcTemplate().update(
437 new PreparedStatementCreator() {
438 @Override
439 public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
440 PreparedStatement ps = prepareStatementForGeneratedKeys(con);
441 setParameterValues(ps, values, getInsertTypes());
442 return ps;
443 }
444 },
445 keyHolder);
446 }
447 else {
448 if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) {
449 throw new InvalidDataAccessResourceUsageException(
450 "The getGeneratedKeys feature is not supported by this database");
451 }
452 if (getGeneratedKeyNames().length < 1) {
453 throw new InvalidDataAccessApiUsageException("Generated Key Name(s) not specified. " +
454 "Using the generated keys features requires specifying the name(s) of the generated column(s)");
455 }
456 if (getGeneratedKeyNames().length > 1) {
457 throw new InvalidDataAccessApiUsageException(
458 "Current database only supports retrieving the key for a single column. There are " +
459 getGeneratedKeyNames().length + " columns specified: " + Arrays.asList(getGeneratedKeyNames()));
460 }
461
462
463
464 final String keyQuery = this.tableMetaDataContext.getSimulationQueryForGetGeneratedKey(
465 this.tableMetaDataContext.getTableName(), getGeneratedKeyNames()[0]);
466 Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null");
467 if (keyQuery.toUpperCase().startsWith("RETURNING")) {
468 Long key = getJdbcTemplate().queryForObject(getInsertString() + " " + keyQuery,
469 values.toArray(new Object[values.size()]), Long.class);
470 Map<String, Object> keys = new HashMap<String, Object>(1);
471 keys.put(getGeneratedKeyNames()[0], key);
472 keyHolder.getKeyList().add(keys);
473 }
474 else {
475 getJdbcTemplate().execute(new ConnectionCallback<Object>() {
476 @Override
477 public Object doInConnection(Connection con) throws SQLException, DataAccessException {
478
479 PreparedStatement ps = null;
480 try {
481 ps = con.prepareStatement(getInsertString());
482 setParameterValues(ps, values, getInsertTypes());
483 ps.executeUpdate();
484 }
485 finally {
486 JdbcUtils.closeStatement(ps);
487 }
488
489 Statement keyStmt = null;
490 ResultSet rs = null;
491 Map<String, Object> keys = new HashMap<String, Object>(1);
492 try {
493 keyStmt = con.createStatement();
494 rs = keyStmt.executeQuery(keyQuery);
495 if (rs.next()) {
496 long key = rs.getLong(1);
497 keys.put(getGeneratedKeyNames()[0], key);
498 keyHolder.getKeyList().add(keys);
499 }
500 }
501 finally {
502 JdbcUtils.closeResultSet(rs);
503 JdbcUtils.closeStatement(keyStmt);
504 }
505 return null;
506 }
507 });
508 }
509 return keyHolder;
510 }
511 return keyHolder;
512 }
513
514
515
516
517
518
519 private PreparedStatement prepareStatementForGeneratedKeys(Connection con) throws SQLException {
520 if (getGeneratedKeyNames().length < 1) {
521 throw new InvalidDataAccessApiUsageException("Generated Key Name(s) not specified. " +
522 "Using the generated keys features requires specifying the name(s) of the generated column(s).");
523 }
524 PreparedStatement ps;
525 if (this.tableMetaDataContext.isGeneratedKeysColumnNameArraySupported()) {
526 if (logger.isDebugEnabled()) {
527 logger.debug("Using generated keys support with array of column names.");
528 }
529 ps = con.prepareStatement(getInsertString(), getGeneratedKeyNames());
530 }
531 else {
532 if (logger.isDebugEnabled()) {
533 logger.debug("Using generated keys support with Statement.RETURN_GENERATED_KEYS.");
534 }
535 ps = con.prepareStatement(getInsertString(), Statement.RETURN_GENERATED_KEYS);
536 }
537 return ps;
538 }
539
540
541
542
543
544
545 protected int[] doExecuteBatch(Map<String, ?>... batch) {
546 checkCompiled();
547 List<List<Object>> batchValues = new ArrayList<List<Object>>(batch.length);
548 for (Map<String, ?> args : batch) {
549 batchValues.add(matchInParameterValuesWithInsertColumns(args));
550 }
551 return executeBatchInternal(batchValues);
552 }
553
554
555
556
557
558
559 protected int[] doExecuteBatch(SqlParameterSource... batch) {
560 checkCompiled();
561 List<List<Object>> batchValues = new ArrayList<List<Object>>(batch.length);
562 for (SqlParameterSource parameterSource : batch) {
563 batchValues.add(matchInParameterValuesWithInsertColumns(parameterSource));
564 }
565 return executeBatchInternal(batchValues);
566 }
567
568
569
570
571 private int[] executeBatchInternal(final List<List<Object>> batchValues) {
572 if (logger.isDebugEnabled()) {
573 logger.debug("Executing statement " + getInsertString() + " with batch of size: " + batchValues.size());
574 }
575 return getJdbcTemplate().batchUpdate(getInsertString(),
576 new BatchPreparedStatementSetter() {
577 @Override
578 public void setValues(PreparedStatement ps, int i) throws SQLException {
579 setParameterValues(ps, batchValues.get(i), getInsertTypes());
580 }
581 @Override
582 public int getBatchSize() {
583 return batchValues.size();
584 }
585 });
586 }
587
588
589
590
591
592
593 private void setParameterValues(PreparedStatement preparedStatement, List<?> values, int... columnTypes)
594 throws SQLException {
595
596 int colIndex = 0;
597 for (Object value : values) {
598 colIndex++;
599 if (columnTypes == null || colIndex > columnTypes.length) {
600 StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, SqlTypeValue.TYPE_UNKNOWN, value);
601 }
602 else {
603 StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, columnTypes[colIndex - 1], value);
604 }
605 }
606 }
607
608
609
610
611
612
613
614 protected List<Object> matchInParameterValuesWithInsertColumns(SqlParameterSource parameterSource) {
615 return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource);
616 }
617
618
619
620
621
622
623
624 protected List<Object> matchInParameterValuesWithInsertColumns(Map<String, ?> args) {
625 return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args);
626 }
627
628 }