diff --git a/org.springframework.jdbc/build.xml b/org.springframework.jdbc/build.xml
new file mode 100644
index 0000000000..c8b6069577
--- /dev/null
+++ b/org.springframework.jdbc/build.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/org.springframework.jdbc/ivy.xml b/org.springframework.jdbc/ivy.xml
new file mode 100644
index 0000000000..2aceb75ad3
--- /dev/null
+++ b/org.springframework.jdbc/ivy.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.jdbc/pom.xml b/org.springframework.jdbc/pom.xml
new file mode 100644
index 0000000000..b4cee14adc
--- /dev/null
+++ b/org.springframework.jdbc/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+ org.springframework
+ org.springframework.core
+ jar
+ Spring Core Abstractions and Utilities
+ 3.0.0.M1
+
+
+ com.springsource.repository.bundles.external
+ SpringSource Enterprise Bundle Repository - External Bundle Releases
+ http://repository.springsource.com/maven/bundles/external
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.5
+ 1.5
+
+
+
+
+
+
+ org.apache.commons
+ com.springsource.org.apache.commons.logging
+ 1.1.1
+
+
+ org.apache.log4j
+ com.springsource.org.apache.log4j
+ 1.2.15
+ true
+
+
+ org.apache.commons
+ com.springsource.org.apache.commons.collections
+ 3.2.0
+ true
+
+
+ org.aspectj
+ com.springsource.org.aspectj.weaver
+ 1.6.2.RELEASE
+ true
+
+
+ org.objectweb.asm
+ com.springsource.org.objectweb.asm.commons
+ 2.2.3
+ true
+
+
+
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/BadSqlGrammarException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/BadSqlGrammarException.java
new file mode 100644
index 0000000000..4e3389298b
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/BadSqlGrammarException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.sql.SQLException;
+
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+
+/**
+ * Exception thrown when SQL specified is invalid. Such exceptions always have
+ * a java.sql.SQLException root cause.
+ *
+ *
It would be possible to have subclasses for no such table, no such column etc.
+ * A custom SQLExceptionTranslator could create such more specific exceptions,
+ * without affecting code using this class.
+ *
+ * @author Rod Johnson
+ * @see InvalidResultSetAccessException
+ */
+public class BadSqlGrammarException extends InvalidDataAccessResourceUsageException {
+
+ private String sql;
+
+
+ /**
+ * Constructor for BadSqlGrammarException.
+ * @param task name of current task
+ * @param sql the offending SQL statement
+ * @param ex the root cause
+ */
+ public BadSqlGrammarException(String task, String sql, SQLException ex) {
+ super(task + "; bad SQL grammar [" + sql + "]", ex);
+ this.sql = sql;
+ }
+
+
+ /**
+ * Return the wrapped SQLException.
+ */
+ public SQLException getSQLException() {
+ return (SQLException) getCause();
+ }
+
+ /**
+ * Return the SQL that caused the problem.
+ */
+ public String getSql() {
+ return this.sql;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/CannotGetJdbcConnectionException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/CannotGetJdbcConnectionException.java
new file mode 100644
index 0000000000..186a4d88b2
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/CannotGetJdbcConnectionException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.sql.SQLException;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+
+/**
+ * Fatal exception thrown when we can't connect to an RDBMS using JDBC.
+ *
+ * @author Rod Johnson
+ */
+public class CannotGetJdbcConnectionException extends DataAccessResourceFailureException {
+
+ /**
+ * Constructor for CannotGetJdbcConnectionException.
+ * @param msg the detail message
+ * @param ex SQLException root cause
+ */
+ public CannotGetJdbcConnectionException(String msg, SQLException ex) {
+ super(msg, ex);
+ }
+
+ /**
+ * Constructor for CannotGetJdbcConnectionException.
+ * @param msg the detail message
+ * @param ex ClassNotFoundException root cause
+ * @deprecated since Spring 2.5, in favor of throwing an
+ * IllegalStateException in case of the driver not being found
+ */
+ public CannotGetJdbcConnectionException(String msg, ClassNotFoundException ex) {
+ super(msg, ex);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/IncorrectResultSetColumnCountException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/IncorrectResultSetColumnCountException.java
new file mode 100644
index 0000000000..a11eee5b7c
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/IncorrectResultSetColumnCountException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import org.springframework.dao.DataRetrievalFailureException;
+
+/**
+ * Data access exception thrown when a result set did not have the correct column count,
+ * for example when expecting a single column but getting 0 or more than 1 columns.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see org.springframework.dao.IncorrectResultSizeDataAccessException
+ */
+public class IncorrectResultSetColumnCountException extends DataRetrievalFailureException {
+
+ private int expectedCount;
+
+ private int actualCount;
+
+
+ /**
+ * Constructor for IncorrectResultSetColumnCountException.
+ * @param expectedCount the expected column count
+ * @param actualCount the actual column count
+ */
+ public IncorrectResultSetColumnCountException(int expectedCount, int actualCount) {
+ super("Incorrect column count: expected " + expectedCount + ", actual " + actualCount);
+ this.expectedCount = expectedCount;
+ this.actualCount = actualCount;
+ }
+
+ /**
+ * Constructor for IncorrectResultCountDataAccessException.
+ * @param msg the detail message
+ * @param expectedCount the expected column count
+ * @param actualCount the actual column count
+ */
+ public IncorrectResultSetColumnCountException(String msg, int expectedCount, int actualCount) {
+ super(msg);
+ this.expectedCount = expectedCount;
+ this.actualCount = actualCount;
+ }
+
+
+ /**
+ * Return the expected column count.
+ */
+ public int getExpectedCount() {
+ return this.expectedCount;
+ }
+
+ /**
+ * Return the actual column count.
+ */
+ public int getActualCount() {
+ return this.actualCount;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/InvalidResultSetAccessException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/InvalidResultSetAccessException.java
new file mode 100644
index 0000000000..d62e1c1fa2
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/InvalidResultSetAccessException.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.sql.SQLException;
+
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+
+/**
+ * Exception thrown when a ResultSet has been accessed in an invalid fashion.
+ * Such exceptions always have a java.sql.SQLException root cause.
+ *
+ *
This typically happens when an invalid ResultSet column index or name
+ * has been specified. Also thrown by disconnected SqlRowSets.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see BadSqlGrammarException
+ * @see org.springframework.jdbc.support.rowset.SqlRowSet
+ */
+public class InvalidResultSetAccessException extends InvalidDataAccessResourceUsageException {
+
+ private String sql;
+
+
+ /**
+ * Constructor for InvalidResultSetAccessException.
+ * @param task name of current task
+ * @param sql the offending SQL statement
+ * @param ex the root cause
+ */
+ public InvalidResultSetAccessException(String task, String sql, SQLException ex) {
+ super(task + "; invalid ResultSet access for SQL [" + sql + "]", ex);
+ this.sql = sql;
+ }
+
+ /**
+ * Constructor for InvalidResultSetAccessException.
+ * @param ex the root cause
+ */
+ public InvalidResultSetAccessException(SQLException ex) {
+ super(ex.getMessage(), ex);
+ }
+
+
+ /**
+ * Return the wrapped SQLException.
+ */
+ public SQLException getSQLException() {
+ return (SQLException) getCause();
+ }
+
+ /**
+ * Return the SQL that caused the problem.
+ * @return the offending SQL, if known
+ */
+ public String getSql() {
+ return this.sql;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/JdbcUpdateAffectedIncorrectNumberOfRowsException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/JdbcUpdateAffectedIncorrectNumberOfRowsException.java
new file mode 100644
index 0000000000..4c5592bdb7
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/JdbcUpdateAffectedIncorrectNumberOfRowsException.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
+
+/**
+ * Exception thrown when a JDBC update affects an unexpected number of rows.
+ * Typically we expect an update to affect a single row, meaning it's an
+ * error if it affects multiple rows.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ */
+public class JdbcUpdateAffectedIncorrectNumberOfRowsException extends IncorrectUpdateSemanticsDataAccessException {
+
+ /** Number of rows that should have been affected */
+ private int expected;
+
+ /** Number of rows that actually were affected */
+ private int actual;
+
+
+ /**
+ * Constructor for JdbcUpdateAffectedIncorrectNumberOfRowsException.
+ * @param sql SQL we were tring to execute
+ * @param expected the expected number of rows affected
+ * @param actual the actual number of rows affected
+ */
+ public JdbcUpdateAffectedIncorrectNumberOfRowsException(String sql, int expected, int actual) {
+ super("SQL update '" + sql + "' affected " + actual + " rows, not " + expected + " as expected");
+ this.expected = expected;
+ this.actual = actual;
+ }
+
+
+ /**
+ * Return the number of rows that should have been affected.
+ */
+ public int getExpectedRowsAffected() {
+ return this.expected;
+ }
+
+ /**
+ * Return the number of rows that have actually been affected.
+ */
+ public int getActualRowsAffected() {
+ return this.actual;
+ }
+
+ public boolean wasDataUpdated() {
+ return (getActualRowsAffected() > 0);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/LobRetrievalFailureException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/LobRetrievalFailureException.java
new file mode 100644
index 0000000000..f80ab4fbef
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/LobRetrievalFailureException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.io.IOException;
+
+import org.springframework.dao.DataRetrievalFailureException;
+
+/**
+ * Exception to be thrown when a LOB could not be retrieved.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ */
+public class LobRetrievalFailureException extends DataRetrievalFailureException {
+
+ /**
+ * Constructor for LobRetrievalFailureException.
+ * @param msg the detail message
+ */
+ public LobRetrievalFailureException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructor for LobRetrievalFailureException.
+ * @param msg the detail message
+ * @param ex IOException root cause
+ */
+ public LobRetrievalFailureException(String msg, IOException ex) {
+ super(msg, ex);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/SQLWarningException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/SQLWarningException.java
new file mode 100644
index 0000000000..68a8d25c39
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/SQLWarningException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.sql.SQLWarning;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+
+/**
+ * Exception thrown when we're not ignoring {@link java.sql.SQLWarning SQLWarnings}.
+ *
+ *
If a SQLWarning is reported, the operation completed, so we will need
+ * to explicitly roll it back if we're not happy when looking at the warning.
+ * We might choose to ignore (and log) the warning, or to wrap and throw it
+ * in the shape of this SQLWarningException instead.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see org.springframework.jdbc.core.JdbcTemplate#setIgnoreWarnings
+ */
+public class SQLWarningException extends UncategorizedDataAccessException {
+
+ /**
+ * Constructor for SQLWarningException.
+ * @param msg the detail message
+ * @param ex the JDBC warning
+ */
+ public SQLWarningException(String msg, SQLWarning ex) {
+ super(msg, ex);
+ }
+
+ /**
+ * Return the underlying SQLWarning.
+ */
+ public SQLWarning SQLWarning() {
+ return (SQLWarning) getCause();
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/UncategorizedSQLException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/UncategorizedSQLException.java
new file mode 100644
index 0000000000..eccba39f5d
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/UncategorizedSQLException.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc;
+
+import java.sql.SQLException;
+
+import org.springframework.dao.UncategorizedDataAccessException;
+
+/**
+ * Exception thrown when we can't classify a SQLException into
+ * one of our generic data access exceptions.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ */
+public class UncategorizedSQLException extends UncategorizedDataAccessException {
+
+ /** SQL that led to the problem */
+ private final String sql;
+
+
+ /**
+ * Constructor for UncategorizedSQLException.
+ * @param task name of current task
+ * @param sql the offending SQL statement
+ * @param ex the root cause
+ */
+ public UncategorizedSQLException(String task, String sql, SQLException ex) {
+ super(task + "; uncategorized SQLException for SQL [" + sql + "]; SQL state [" +
+ ex.getSQLState() + "]; error code [" + ex.getErrorCode() + "]; " + ex.getMessage(), ex);
+ this.sql = sql;
+ }
+
+
+ /**
+ * Return the underlying SQLException.
+ */
+ public SQLException getSQLException() {
+ return (SQLException) getCause();
+ }
+
+ /**
+ * Return the SQL that led to the problem.
+ */
+ public String getSql() {
+ return this.sql;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgPreparedStatementSetter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgPreparedStatementSetter.java
new file mode 100644
index 0000000000..e0d8c0ca87
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgPreparedStatementSetter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Simple adapter for PreparedStatementSetter that applies
+ * a given array of arguments.
+ *
+ * @author Juergen Hoeller
+ */
+class ArgPreparedStatementSetter implements PreparedStatementSetter, ParameterDisposer {
+
+ private final Object[] args;
+
+
+ /**
+ * Create a new ArgPreparedStatementSetter for the given arguments.
+ * @param args the arguments to set
+ */
+ public ArgPreparedStatementSetter(Object[] args) {
+ this.args = args;
+ }
+
+
+ public void setValues(PreparedStatement ps) throws SQLException {
+ if (this.args != null) {
+ for (int i = 0; i < this.args.length; i++) {
+ Object arg = this.args[i];
+ if (arg instanceof SqlParameterValue) {
+ SqlParameterValue paramValue = (SqlParameterValue) arg;
+ StatementCreatorUtils.setParameterValue(ps, i + 1, paramValue, paramValue.getValue());
+ }
+ else {
+ StatementCreatorUtils.setParameterValue(ps, i + 1, SqlTypeValue.TYPE_UNKNOWN, arg);
+ }
+ }
+ }
+ }
+
+ public void cleanupParameters() {
+ StatementCreatorUtils.cleanupParameters(this.args);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgTypePreparedStatementSetter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgTypePreparedStatementSetter.java
new file mode 100644
index 0000000000..f8cc54a3f3
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ArgTypePreparedStatementSetter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+
+/**
+ * Simple adapter for PreparedStatementSetter that applies
+ * given arrays of arguments and JDBC argument types.
+ *
+ * @author Juergen Hoeller
+ */
+class ArgTypePreparedStatementSetter implements PreparedStatementSetter, ParameterDisposer {
+
+ private final Object[] args;
+
+ private final int[] argTypes;
+
+
+ /**
+ * Create a new ArgTypePreparedStatementSetter for the given arguments.
+ * @param args the arguments to set
+ * @param argTypes the corresponding SQL types of the arguments
+ */
+ public ArgTypePreparedStatementSetter(Object[] args, int[] argTypes) {
+ if ((args != null && argTypes == null) || (args == null && argTypes != null) ||
+ (args != null && args.length != argTypes.length)) {
+ throw new InvalidDataAccessApiUsageException("args and argTypes parameters must match");
+ }
+ this.args = args;
+ this.argTypes = argTypes;
+ }
+
+
+ public void setValues(PreparedStatement ps) throws SQLException {
+ int argIndx = 1;
+ if (this.args != null) {
+ for (int i = 0; i < this.args.length; i++) {
+ Object arg = this.args[i];
+ if (arg instanceof Collection && this.argTypes[i] != Types.ARRAY) {
+ Collection entries = (Collection) arg;
+ for (Iterator it = entries.iterator(); it.hasNext();) {
+ Object entry = it.next();
+ if (entry instanceof Object[]) {
+ Object[] valueArray = ((Object[])entry);
+ for (int k = 0; k < valueArray.length; k++) {
+ Object argValue = valueArray[k];
+ StatementCreatorUtils.setParameterValue(ps, argIndx++, this.argTypes[i], argValue);
+ }
+ }
+ else {
+ StatementCreatorUtils.setParameterValue(ps, argIndx++, this.argTypes[i], entry);
+ }
+ }
+ }
+ else {
+ StatementCreatorUtils.setParameterValue(ps, argIndx++, this.argTypes[i], arg);
+ }
+ }
+ }
+ }
+
+ public void cleanupParameters() {
+ StatementCreatorUtils.cleanupParameters(this.args);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BatchPreparedStatementSetter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BatchPreparedStatementSetter.java
new file mode 100644
index 0000000000..8a8e523a5b
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BatchPreparedStatementSetter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Batch update callback interface used by the {@link JdbcTemplate} class.
+ *
+ *
This interface sets values on a {@link java.sql.PreparedStatement} provided
+ * by the JdbcTemplate class, for each of a number of updates in a batch using the
+ * same SQL. Implementations are responsible for setting any necessary parameters.
+ * SQL with placeholders will already have been supplied.
+ *
+ *
Implementations do not need to concern themselves with SQLExceptions
+ * that may be thrown from operations they attempt. The JdbcTemplate class will
+ * catch and handle SQLExceptions appropriately.
+ *
+ * @author Rod Johnson
+ * @since March 2, 2003
+ * @see JdbcTemplate#batchUpdate(String, BatchPreparedStatementSetter)
+ * @see InterruptibleBatchPreparedStatementSetter
+ */
+public interface BatchPreparedStatementSetter {
+
+ /**
+ * Set parameter values on the given PreparedStatement.
+ * @param ps the PreparedStatement to invoke setter methods on
+ * @param i index of the statement we're issuing in the batch, starting from 0
+ * @throws SQLException if a SQLException is encountered
+ * (i.e. there is no need to catch SQLException)
+ */
+ void setValues(PreparedStatement ps, int i) throws SQLException;
+
+ /**
+ * Return the size of the batch.
+ * @return the number of statements in the batch
+ */
+ int getBatchSize();
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
new file mode 100644
index 0000000000..88b17e7a3e
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.beans.PropertyDescriptor;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.NotWritablePropertyException;
+import org.springframework.beans.PropertyAccessorFactory;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.util.Assert;
+
+/**
+ * {@link RowMapper} implementation that converts a row into a new instance
+ * of the specified mapped target class. The mapped target class must be a
+ * top-level class and it must have a default or no-arg constructor.
+ *
+ *
Column values are mapped based on matching the column name as obtained from result set
+ * metadata to public setters for the corresponding properties. The names are matched either
+ * directly or by transforming a name separating the parts with underscores to the same name
+ * using "camel" case.
+ *
+ *
Mapping is provided for fields in the target class for many common types, e.g.:
+ * String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
+ * float, Float, double, Double, BigDecimal, java.util.Date, etc.
+ *
+ *
To facilitate mapping between columns and fields that don't have matching names,
+ * try using column aliases in the SQL statement like "select fname as first_name from customer".
+ *
+ *
Please note that this class is designed to provide convenience rather than high performance.
+ * For best performance consider using a custom RowMapper.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class BeanPropertyRowMapper implements RowMapper {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** The class we are mapping to */
+ private Class mappedClass;
+
+ /** Whether we're strictly validating */
+ private boolean checkFullyPopulated = false;
+
+ /** Map of the fields we provide mapping for */
+ private Map mappedFields;
+
+ /** Set of bean properties we provide mapping for */
+ private Set mappedProperties;
+
+
+ /**
+ * Create a new BeanPropertyRowMapper for bean-style configuration.
+ * @see #setMappedClass
+ * @see #setCheckFullyPopulated
+ */
+ public BeanPropertyRowMapper() {
+ }
+
+ /**
+ * Create a new BeanPropertyRowMapper, accepting unpopulated properties
+ * in the target bean.
+ * @param mappedClass the class that each row should be mapped to
+ */
+ public BeanPropertyRowMapper(Class mappedClass) {
+ initialize(mappedClass);
+ }
+
+ /**
+ * Create a new BeanPropertyRowMapper.
+ * @param mappedClass the class that each row should be mapped to
+ * @param checkFullyPopulated whether we're strictly validating that
+ * all bean properties have been mapped from corresponding database fields
+ */
+ public BeanPropertyRowMapper(Class mappedClass, boolean checkFullyPopulated) {
+ initialize(mappedClass);
+ this.checkFullyPopulated = checkFullyPopulated;
+ }
+
+
+ /**
+ * Set the class that each row should be mapped to.
+ */
+ public void setMappedClass(Class mappedClass) {
+ if (this.mappedClass == null) {
+ initialize(mappedClass);
+ }
+ else {
+ if (!this.mappedClass.equals(mappedClass)) {
+ throw new InvalidDataAccessApiUsageException("The mapped class can not be reassigned to map to " +
+ mappedClass + " since it is already providing mapping for " + this.mappedClass);
+ }
+ }
+ }
+
+ /**
+ * Initialize the mapping metadata for the given class.
+ * @param mappedClass the mapped class.
+ */
+ protected void initialize(Class mappedClass) {
+ this.mappedClass = mappedClass;
+ this.mappedFields = new HashMap();
+ this.mappedProperties = new HashSet();
+ PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
+ for (int i = 0; i < pds.length; i++) {
+ PropertyDescriptor pd = pds[i];
+ if (pd.getWriteMethod() != null) {
+ this.mappedFields.put(pd.getName().toLowerCase(), pd);
+ String underscoredName = underscoreName(pd.getName());
+ if (!pd.getName().toLowerCase().equals(underscoredName)) {
+ this.mappedFields.put(underscoredName, pd);
+ }
+ this.mappedProperties.add(pd.getName());
+ }
+ }
+ }
+
+ /**
+ * Convert a name in camelCase to an underscored name in lower case.
+ * Any upper case letters are converted to lower case with a preceding underscore.
+ * @param name the string containing original name
+ * @return the converted name
+ */
+ private String underscoreName(String name) {
+ StringBuffer result = new StringBuffer();
+ if (name != null && name.length() > 0) {
+ result.append(name.substring(0, 1).toLowerCase());
+ for (int i = 1; i < name.length(); i++) {
+ String s = name.substring(i, i + 1);
+ if (s.equals(s.toUpperCase())) {
+ result.append("_");
+ result.append(s.toLowerCase());
+ }
+ else {
+ result.append(s);
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Get the class that we are mapping to.
+ */
+ public final Class getMappedClass() {
+ return this.mappedClass;
+ }
+
+ /**
+ * Set whether we're strictly validating that all bean properties have been
+ * mapped from corresponding database fields.
+ *
Default is false, accepting unpopulated properties in the
+ * target bean.
+ */
+ public void setCheckFullyPopulated(boolean checkFullyPopulated) {
+ this.checkFullyPopulated = checkFullyPopulated;
+ }
+
+ /**
+ * Return whether we're strictly validating that all bean properties have been
+ * mapped from corresponding database fields.
+ */
+ public boolean isCheckFullyPopulated() {
+ return this.checkFullyPopulated;
+ }
+
+
+ /**
+ * Extract the values for all columns in the current row.
+ *
Utilizes public setters and result set metadata.
+ * @see java.sql.ResultSetMetaData
+ */
+ public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
+ Assert.state(this.mappedClass != null, "Mapped class was not specified");
+ Object mappedObject = BeanUtils.instantiateClass(this.mappedClass);
+ BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
+ initBeanWrapper(bw);
+
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int columnCount = rsmd.getColumnCount();
+ Set populatedProperties = (isCheckFullyPopulated() ? new HashSet() : null);
+
+ for (int index = 1; index <= columnCount; index++) {
+ String column = JdbcUtils.lookupColumnName(rsmd, index).toLowerCase();
+ PropertyDescriptor pd = (PropertyDescriptor) this.mappedFields.get(column);
+ if (pd != null) {
+ try {
+ Object value = getColumnValue(rs, index, pd);
+ if (logger.isDebugEnabled() && rowNumber == 0) {
+ logger.debug("Mapping column '" + column + "' to property '" +
+ pd.getName() + "' of type " + pd.getPropertyType());
+ }
+ bw.setPropertyValue(pd.getName(), value);
+ if (populatedProperties != null) {
+ populatedProperties.add(pd.getName());
+ }
+ }
+ catch (NotWritablePropertyException ex) {
+ throw new DataRetrievalFailureException(
+ "Unable to map column " + column + " to property " + pd.getName(), ex);
+ }
+ }
+ }
+
+ if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
+ throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
+ "necessary to populate object of class [" + this.mappedClass + "]: " + this.mappedProperties);
+ }
+
+ return mappedObject;
+ }
+
+ /**
+ * Initialize the given BeanWrapper to be used for row mapping.
+ * To be called for each row.
+ *
The default implementation is empty. Can be overridden in subclasses.
+ * @param bw the BeanWrapper to initialize
+ */
+ protected void initBeanWrapper(BeanWrapper bw) {
+ }
+
+ /**
+ * Retrieve a JDBC object value for the specified column.
+ *
The default implementation calls
+ * {@link JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)}.
+ * Subclasses may override this to check specific value types upfront,
+ * or to post-process values return from getResultSetValue.
+ * @param rs is the ResultSet holding the data
+ * @param index is the column index
+ * @param pd the bean property that each result object is expected to match
+ * (or null if none specified)
+ * @return the Object value
+ * @throws SQLException in case of extraction failure
+ * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)
+ */
+ protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
+ return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType());
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java
new file mode 100644
index 0000000000..13dedcf35f
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.CallableStatement;
+import java.sql.SQLException;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Generic callback interface for code that operates on a CallableStatement.
+ * Allows to execute any number of operations on a single CallableStatement,
+ * for example a single execute call or repeated execute calls with varying
+ * parameters.
+ *
+ *
Used internally by JdbcTemplate, but also useful for application code.
+ * Note that the passed-in CallableStatement can have been created by the
+ * framework or by a custom CallableStatementCreator. However, the latter is
+ * hardly ever necessary, as most custom callback actions will perform updates
+ * in which case a standard CallableStatement is fine. Custom actions will
+ * always set parameter values themselves, so that CallableStatementCreator
+ * capability is not needed either.
+ *
+ * @author Juergen Hoeller
+ * @since 16.03.2004
+ * @see JdbcTemplate#execute(String, CallableStatementCallback)
+ * @see JdbcTemplate#execute(CallableStatementCreator, CallableStatementCallback)
+ */
+public interface CallableStatementCallback {
+
+ /**
+ * Gets called by JdbcTemplate.execute with an active JDBC
+ * CallableStatement. Does not need to care about closing the Statement
+ * or the Connection, or about handling transactions: this will all be
+ * handled by Spring's JdbcTemplate.
+ *
+ *
NOTE: Any ResultSets opened should be closed in finally blocks
+ * within the callback implementation. Spring will close the Statement
+ * object after the callback returned, but this does not necessarily imply
+ * that the ResultSet resources will be closed: the Statement objects might
+ * get pooled by the connection pool, with close calls only
+ * returning the object to the pool but not physically closing the resources.
+ *
+ *
If called without a thread-bound JDBC transaction (initiated by
+ * DataSourceTransactionManager), the code will simply get executed on the
+ * JDBC connection with its transactional semantics. If JdbcTemplate is
+ * configured to use a JTA-aware DataSource, the JDBC connection and thus
+ * the callback code will be transactional if a JTA transaction is active.
+ *
+ *
Allows for returning a result object created within the callback, i.e.
+ * a domain object or a collection of domain objects. A thrown RuntimeException
+ * is treated as application exception: it gets propagated to the caller of
+ * the template.
+ *
+ * @param cs active JDBC CallableStatement
+ * @return a result object, or null if none
+ * @throws SQLException if thrown by a JDBC method, to be auto-converted
+ * into a DataAccessException by a SQLExceptionTranslator
+ * @throws DataAccessException in case of custom exceptions
+ */
+ Object doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreator.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreator.java
new file mode 100644
index 0000000000..8a42702394
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * One of the three central callback interfaces used by the JdbcTemplate class.
+ * This interface creates a CallableStatement given a connection, provided
+ * by the JdbcTemplate class. Implementations are responsible for providing
+ * SQL and any necessary parameters.
+ *
+ *
Implementations do not need to concern themselves with
+ * SQLExceptions that may be thrown from operations they attempt.
+ * The JdbcTemplate class will catch and handle SQLExceptions appropriately.
+ *
+ *
A PreparedStatementCreator should also implement the SqlProvider interface
+ * if it is able to provide the SQL it uses for PreparedStatement creation.
+ * This allows for better contextual information in case of exceptions.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @see JdbcTemplate#execute(CallableStatementCreator, CallableStatementCallback)
+ * @see JdbcTemplate#call
+ * @see SqlProvider
+ */
+public interface CallableStatementCreator {
+
+ /**
+ * Create a callable statement in this connection. Allows implementations to use
+ * CallableStatements.
+ * @param con Connection to use to create statement
+ * @return a callable statement
+ * @throws SQLException there is no need to catch SQLExceptions
+ * that may be thrown in the implementation of this method.
+ * The JdbcTemplate class will handle them.
+ */
+ CallableStatement createCallableStatement(Connection con) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java
new file mode 100644
index 0000000000..941bb81c64
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
+
+/**
+ * Helper class that efficiently creates multiple {@link CallableStatementCreator}
+ * objects with different parameters based on a SQL statement and a single
+ * set of parameter declarations.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ */
+public class CallableStatementCreatorFactory {
+
+ /** The SQL call string, which won't change when the parameters change. */
+ private final String callString;
+
+ /** List of SqlParameter objects. May not be null. */
+ private final List declaredParameters;
+
+ private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
+
+ private boolean updatableResults = false;
+
+ private NativeJdbcExtractor nativeJdbcExtractor;
+
+
+ /**
+ * Create a new factory. Will need to add parameters via the
+ * {@link #addParameter} method or have no parameters.
+ */
+ public CallableStatementCreatorFactory(String callString) {
+ this.callString = callString;
+ this.declaredParameters = new LinkedList();
+ }
+
+ /**
+ * Create a new factory with the given SQL and the given parameters.
+ * @param callString the SQL call string
+ * @param declaredParameters list of {@link SqlParameter} objects
+ */
+ public CallableStatementCreatorFactory(String callString, List declaredParameters) {
+ this.callString = callString;
+ this.declaredParameters = declaredParameters;
+ }
+
+
+ /**
+ * Add a new declared parameter.
+ *
Order of parameter addition is significant.
+ * @param param the parameter to add to the list of declared parameters
+ */
+ public void addParameter(SqlParameter param) {
+ this.declaredParameters.add(param);
+ }
+
+ /**
+ * Set whether to use prepared statements that return a specific type of ResultSet.
+ * specific type of ResultSet.
+ * @param resultSetType the ResultSet type
+ * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
+ * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
+ * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
+ */
+ public void setResultSetType(int resultSetType) {
+ this.resultSetType = resultSetType;
+ }
+
+ /**
+ * Set whether to use prepared statements capable of returning updatable ResultSets.
+ */
+ public void setUpdatableResults(boolean updatableResults) {
+ this.updatableResults = updatableResults;
+ }
+
+ /**
+ * Specify the NativeJdbcExtractor to use for unwrapping CallableStatements, if any.
+ */
+ public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
+ this.nativeJdbcExtractor = nativeJdbcExtractor;
+ }
+
+
+ /**
+ * Return a new CallableStatementCreator instance given this parameters.
+ * @param params list of parameters (may be null)
+ */
+ public CallableStatementCreator newCallableStatementCreator(Map params) {
+ return new CallableStatementCreatorImpl(params != null ? params : new HashMap());
+ }
+
+ /**
+ * Return a new CallableStatementCreator instance given this parameter mapper.
+ * @param inParamMapper ParameterMapper implementation that will return a Map of parameters
+ */
+ public CallableStatementCreator newCallableStatementCreator(ParameterMapper inParamMapper) {
+ return new CallableStatementCreatorImpl(inParamMapper);
+ }
+
+
+ /**
+ * CallableStatementCreator implementation returned by this class.
+ */
+ private class CallableStatementCreatorImpl implements CallableStatementCreator, SqlProvider, ParameterDisposer {
+
+ private ParameterMapper inParameterMapper;
+
+ private Map inParameters;
+
+ /**
+ * Create a new CallableStatementCreatorImpl.
+ * @param inParamMapper ParameterMapper implementation for mapping input parameters
+ */
+ public CallableStatementCreatorImpl(ParameterMapper inParamMapper) {
+ this.inParameterMapper = inParamMapper;
+ }
+
+ /**
+ * Create a new CallableStatementCreatorImpl.
+ * @param inParams list of SqlParameter objects
+ */
+ public CallableStatementCreatorImpl(Map inParams) {
+ this.inParameters = inParams;
+ }
+
+ public CallableStatement createCallableStatement(Connection con) throws SQLException {
+ // If we were given a ParameterMapper, we must let the mapper do its thing to create the Map.
+ if (this.inParameterMapper != null) {
+ this.inParameters = this.inParameterMapper.createMap(con);
+ }
+ else {
+ if (this.inParameters == null) {
+ throw new InvalidDataAccessApiUsageException(
+ "A ParameterMapper or a Map of parameters must be provided");
+ }
+ }
+
+ CallableStatement cs = null;
+ if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) {
+ cs = con.prepareCall(callString);
+ }
+ else {
+ cs = con.prepareCall(callString, resultSetType,
+ updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
+ }
+
+ // Determine CallabeStatement to pass to custom types.
+ CallableStatement csToUse = cs;
+ if (nativeJdbcExtractor != null) {
+ csToUse = nativeJdbcExtractor.getNativeCallableStatement(cs);
+ }
+
+ int sqlColIndx = 1;
+ for (int i = 0; i < declaredParameters.size(); i++) {
+ SqlParameter declaredParam = (SqlParameter) declaredParameters.get(i);
+ if (!declaredParam.isResultsParameter()) {
+ // So, it's a call parameter - part of the call string.
+ // Get the value - it may still be null.
+ Object inValue = this.inParameters.get(declaredParam.getName());
+ if (declaredParam instanceof ResultSetSupportingSqlParameter) {
+ // It's an output parameter: SqlReturnResultSet parameters already excluded.
+ // It need not (but may be) supplied by the caller.
+ if (declaredParam instanceof SqlOutParameter) {
+ if (declaredParam.getTypeName() != null) {
+ cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getTypeName());
+ }
+ else {
+ if (declaredParam.getScale() != null) {
+ cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getScale().intValue());
+ }
+ else {
+ cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType());
+ }
+ }
+ if (declaredParam.isInputValueProvided()) {
+ StatementCreatorUtils.setParameterValue(csToUse, sqlColIndx, declaredParam, inValue);
+ }
+ }
+ }
+ else {
+ // It's an input parameter; must be supplied by the caller.
+ if (!this.inParameters.containsKey(declaredParam.getName())) {
+ throw new InvalidDataAccessApiUsageException(
+ "Required input parameter '" + declaredParam.getName() + "' is missing");
+ }
+ StatementCreatorUtils.setParameterValue(csToUse, sqlColIndx, declaredParam, inValue);
+ }
+ sqlColIndx++;
+ }
+ }
+
+ return cs;
+ }
+
+ public String getSql() {
+ return callString;
+ }
+
+ public void cleanupParameters() {
+ if (this.inParameters != null) {
+ StatementCreatorUtils.cleanupParameters(this.inParameters.values());
+ }
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("CallableStatementCreatorFactory.CallableStatementCreatorImpl: sql=[");
+ buf.append(callString).append("]; parameters=").append(this.inParameters);
+ return buf.toString();
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java
new file mode 100644
index 0000000000..7f4b7ff4ec
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * {@link RowMapper} implementation that creates a java.util.Map
+ * for each row, representing all columns as key-value pairs: one
+ * entry for each column, with the column name as key.
+ *
+ *
The Map implementation to use and the key to use for each column
+ * in the column Map can be customized through overriding
+ * {@link #createColumnMap} and {@link #getColumnKey}, respectively.
+ *
+ *
Note: By default, ColumnMapRowMapper will try to build a linked Map
+ * with case-insensitive keys, to preserve column order as well as allow any
+ * casing to be used for column names. This requires Commons Collections on the
+ * classpath (which will be autodetected). Else, the fallback is a standard linked
+ * HashMap, which will still preserve column order but requires the application
+ * to specify the column names in the same casing as exposed by the driver.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see JdbcTemplate#queryForList(String)
+ * @see JdbcTemplate#queryForMap(String)
+ */
+public class ColumnMapRowMapper implements RowMapper {
+
+ public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int columnCount = rsmd.getColumnCount();
+ Map mapOfColValues = createColumnMap(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+ String key = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i));
+ Object obj = getColumnValue(rs, i);
+ mapOfColValues.put(key, obj);
+ }
+ return mapOfColValues;
+ }
+
+ /**
+ * Create a Map instance to be used as column map.
+ *
By default, a linked case-insensitive Map will be created if possible,
+ * else a plain HashMap (see Spring's CollectionFactory).
+ * @param columnCount the column count, to be used as initial
+ * capacity for the Map
+ * @return the new Map instance
+ * @see org.springframework.core.CollectionFactory#createLinkedCaseInsensitiveMapIfPossible
+ */
+ protected Map createColumnMap(int columnCount) {
+ return CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(columnCount);
+ }
+
+ /**
+ * Determine the key to use for the given column in the column Map.
+ * @param columnName the column name as returned by the ResultSet
+ * @return the column key to use
+ * @see java.sql.ResultSetMetaData#getColumnName
+ */
+ protected String getColumnKey(String columnName) {
+ return columnName;
+ }
+
+ /**
+ * Retrieve a JDBC object value for the specified column.
+ *
The default implementation uses the getObject method.
+ * Additionally, this implementation includes a "hack" to get around Oracle
+ * returning a non standard object for their TIMESTAMP datatype.
+ * @param rs is the ResultSet holding the data
+ * @param index is the column index
+ * @return the Object returned
+ * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue
+ */
+ protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
+ return JdbcUtils.getResultSetValue(rs, index);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java
new file mode 100644
index 0000000000..4986887b4a
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Generic callback interface for code that operates on a JDBC Connection.
+ * Allows to execute any number of operations on a single Connection,
+ * using any type and number of Statements.
+ *
+ *
This is particularly useful for delegating to existing data access code
+ * that expects a Connection to work on and throws SQLException. For newly
+ * written code, it is strongly recommended to use JdbcTemplate's more specific
+ * operations, for example a query or update variant.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.3
+ * @see JdbcTemplate#execute(ConnectionCallback)
+ * @see JdbcTemplate#query
+ * @see JdbcTemplate#update
+ */
+public interface ConnectionCallback {
+
+ /**
+ * Gets called by JdbcTemplate.execute with an active JDBC
+ * Connection. Does not need to care about activating or closing the
+ * Connection, or handling transactions.
+ *
+ *
If called without a thread-bound JDBC transaction (initiated by
+ * DataSourceTransactionManager), the code will simply get executed on the
+ * JDBC connection with its transactional semantics. If JdbcTemplate is
+ * configured to use a JTA-aware DataSource, the JDBC Connection and thus
+ * the callback code will be transactional if a JTA transaction is active.
+ *
+ *
Allows for returning a result object created within the callback, i.e.
+ * a domain object or a collection of domain objects. Note that there's special
+ * support for single step actions: see JdbcTemplate.queryForObject
+ * etc. A thrown RuntimeException is treated as application exception:
+ * it gets propagated to the caller of the template.
+ *
+ * @param con active JDBC Connection
+ * @return a result object, or null if none
+ * @throws SQLException if thrown by a JDBC method, to be auto-converted
+ * to a DataAccessException by a SQLExceptionTranslator
+ * @throws DataAccessException in case of custom exceptions
+ * @see JdbcTemplate#queryForObject(String, Class)
+ * @see JdbcTemplate#queryForRowSet(String)
+ */
+ Object doInConnection(Connection con) throws SQLException, DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/DisposableSqlTypeValue.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/DisposableSqlTypeValue.java
new file mode 100644
index 0000000000..b813a4eb8e
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/DisposableSqlTypeValue.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Subinterface of {@link SqlTypeValue} that adds a cleanup callback,
+ * to be invoked after the value has been set and the corresponding
+ * statement has been executed.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see org.springframework.jdbc.core.support.SqlLobValue
+ */
+public interface DisposableSqlTypeValue extends SqlTypeValue {
+
+ /**
+ * Clean up resources held by this type value,
+ * for example the LobCreator in case of a SqlLobValue.
+ * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup()
+ * @see org.springframework.jdbc.support.SqlValue#cleanup()
+ */
+ void cleanup();
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/InterruptibleBatchPreparedStatementSetter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/InterruptibleBatchPreparedStatementSetter.java
new file mode 100644
index 0000000000..08999f6ea4
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/InterruptibleBatchPreparedStatementSetter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Extension of the {@link BatchPreparedStatementSetter} interface,
+ * adding a batch exhaustion check.
+ *
+ *
This interface allows you to signal the end of a batch rather than
+ * having to determine the exact batch size upfront. Batch size is still
+ * being honored but it is now the maximum size of the batch.
+ *
+ *
The {@link #isBatchExhausted} method is called after each call to
+ * {@link #setValues} to determine whether there were some values added,
+ * or if the batch was determined to be complete and no additional values
+ * were provided during the last call to setValues.
+ *
+ *
Consider extending the
+ * {@link org.springframework.jdbc.core.support.AbstractInterruptibleBatchPreparedStatementSetter}
+ * base class instead of implementing this interface directly, using a single
+ * setValuesIfAvailable callback method that checks for available
+ * values and sets them, returning whether values have actually been provided.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see JdbcTemplate#batchUpdate(String, BatchPreparedStatementSetter)
+ * @see org.springframework.jdbc.core.support.AbstractInterruptibleBatchPreparedStatementSetter
+ */
+public interface InterruptibleBatchPreparedStatementSetter extends BatchPreparedStatementSetter {
+
+ /**
+ * Return whether the batch is complete, that is, whether there were no
+ * additional values added during the last setValues call.
+ *
NOTE: If this method returns true, any parameters
+ * that might have been set during the last setValues call will
+ * be ignored! Make sure that you set a corresponding internal flag if you
+ * detect exhaustion at the beginning of your setValues
+ * implementation, letting this method return true based on the flag.
+ * @param i index of the statement we're issuing in the batch, starting from 0
+ * @return whether the batch is already exhausted
+ * @see #setValues
+ * @see org.springframework.jdbc.core.support.AbstractInterruptibleBatchPreparedStatementSetter#setValuesIfAvailable
+ */
+ boolean isBatchExhausted(int i);
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
new file mode 100644
index 0000000000..6e64330cdd
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
@@ -0,0 +1,936 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+
+/**
+ * Interface specifying a basic set of JDBC operations.
+ * Implemented by {@link JdbcTemplate}. Not often used directly, but a useful
+ * option to enhance testability, as it can easily be mocked or stubbed.
+ *
+ *
Alternatively, the standard JDBC infrastructure can be mocked.
+ * However, mocking this interface constitutes significantly less work.
+ * As an alternative to a mock objects approach to testing data access code,
+ * consider the powerful integration testing support provided in the
+ * org.springframework.test package, shipped in
+ * spring-mock.jar.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see JdbcTemplate
+ */
+public interface JdbcOperations {
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with a plain java.sql.Connection
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC Connection. This allows for implementing arbitrary
+ * data access operations, within Spring's managed JDBC environment:
+ * that is, participating in Spring-managed transactions and converting
+ * JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param action the callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(ConnectionCallback action) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with static SQL (java.sql.Statement)
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC Statement. This allows for implementing arbitrary data
+ * access operations on a single Statement, within Spring's managed JDBC
+ * environment: that is, participating in Spring-managed transactions and
+ * converting JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param action callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(StatementCallback action) throws DataAccessException;
+
+ /**
+ * Issue a single SQL execute, typically a DDL statement.
+ * @param sql static SQL to execute
+ * @throws DataAccessException if there is any problem
+ */
+ void execute(String sql) throws DataAccessException;
+
+ /**
+ * Execute a query given static SQL, reading the ResultSet with a
+ * ResultSetExtractor.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * query method with null as argument array.
+ * @param sql SQL query to execute
+ * @param rse object that will extract all rows of results
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #query(String, Object[], ResultSetExtractor)
+ */
+ Object query(String sql, ResultSetExtractor rse) throws DataAccessException;
+
+ /**
+ * Execute a query given static SQL, reading the ResultSet on a per-row
+ * basis with a RowCallbackHandler.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * query method with null as argument array.
+ * @param sql SQL query to execute
+ * @param rch object that will extract results, one row at a time
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #query(String, Object[], RowCallbackHandler)
+ */
+ void query(String sql, RowCallbackHandler rch) throws DataAccessException;
+
+ /**
+ * Execute a query given static SQL, mapping each row to a Java object
+ * via a RowMapper.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * query method with null as argument array.
+ * @param sql SQL query to execute
+ * @param rowMapper object that will map one object per row
+ * @return the result List, containing mapped objects
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #query(String, Object[], RowMapper)
+ */
+ List query(String sql, RowMapper rowMapper) throws DataAccessException;
+
+ /**
+ * Execute a query given static SQL, mapping a single result row to a Java
+ * object via a RowMapper.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForObject method with null as argument array.
+ * @param sql SQL query to execute
+ * @param rowMapper object that will map one object per row
+ * @return the single mapped object
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForObject(String, Object[], RowMapper)
+ */
+ Object queryForObject(String sql, RowMapper rowMapper) throws DataAccessException;
+
+ /**
+ * Execute a query for a result object, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForObject method with null as argument array.
+ *
This method is useful for running static SQL with a known outcome.
+ * The query is expected to be a single row/single column query; the returned
+ * result will be directly mapped to the corresponding object type.
+ * @param sql SQL query to execute
+ * @param requiredType the type that the result object is expected to match
+ * @return the result object of the required type, or null in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForObject(String, Object[], Class)
+ */
+ Object queryForObject(String sql, Class requiredType) throws DataAccessException;
+
+ /**
+ * Execute a query for a result Map, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForMap method with null as argument array.
+ *
The query is expected to be a single row query; the result row will be
+ * mapped to a Map (one entry for each column, using the column name as the key).
+ * @param sql SQL query to execute
+ * @return the result Map (one entry for each column, using the
+ * column name as the key)
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForMap(String, Object[])
+ * @see ColumnMapRowMapper
+ */
+ Map queryForMap(String sql) throws DataAccessException;
+
+ /**
+ * Execute a query that results in a long value, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForLong method with null as argument array.
+ *
This method is useful for running static SQL with a known outcome.
+ * The query is expected to be a single row/single column query that results
+ * in a long value.
+ * @param sql SQL query to execute
+ * @return the long value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForLong(String, Object[])
+ */
+ long queryForLong(String sql) throws DataAccessException;
+
+ /**
+ * Execute a query that results in an int value, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForInt method with null as argument array.
+ *
This method is useful for running static SQL with a known outcome.
+ * The query is expected to be a single row/single column query that results
+ * in an int value.
+ * @param sql SQL query to execute
+ * @return the int value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForInt(String, Object[])
+ */
+ int queryForInt(String sql) throws DataAccessException;
+
+ /**
+ * Execute a query for a result list, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForList method with null as argument array.
+ *
The results will be mapped to a List (one entry for each row) of
+ * result objects, each of them matching the specified element type.
+ * @param sql SQL query to execute
+ * @param elementType the required type of element in the result list
+ * (for example, Integer.class)
+ * @return a List of objects that match the specified element type
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForList(String, Object[], Class)
+ * @see SingleColumnRowMapper
+ */
+ List queryForList(String sql, Class elementType) throws DataAccessException;
+
+ /**
+ * Execute a query for a result list, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForList method with null as argument array.
+ *
The results will be mapped to a List (one entry for each row) of
+ * Maps (one entry for each column using the column name as the key).
+ * Each element in the list will be of the form returned by this interface's
+ * queryForMap() methods.
+ * @param sql SQL query to execute
+ * @return an List that contains a Map per row
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForList(String, Object[])
+ */
+ List queryForList(String sql) throws DataAccessException;
+
+ /**
+ * Execute a query for a SqlRowSet, given static SQL.
+ *
Uses a JDBC Statement, not a PreparedStatement. If you want to
+ * execute a static query with a PreparedStatement, use the overloaded
+ * queryForRowSet method with null as argument array.
+ *
The results will be mapped to an SqlRowSet which holds the data in a
+ * disconnected fashion. This wrapper will translate any SQLExceptions thrown.
+ *
Note that that, for the default implementation, JDBC RowSet support needs to
+ * be available at runtime: by default, Sun's com.sun.rowset.CachedRowSetImpl
+ * class is used, which is part of JDK 1.5+ and also available separately as part of
+ * Sun's JDBC RowSet Implementations download (rowset.jar).
+ * @param sql SQL query to execute
+ * @return a SqlRowSet representation (possibly a wrapper around a
+ * javax.sql.rowset.CachedRowSet)
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForRowSet(String, Object[])
+ * @see SqlRowSetResultSetExtractor
+ * @see javax.sql.rowset.CachedRowSet
+ */
+ SqlRowSet queryForRowSet(String sql) throws DataAccessException;
+
+ /**
+ * Issue a single SQL update operation (such as an insert, update or delete statement).
+ * @param sql static SQL to execute
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem.
+ */
+ int update(String sql) throws DataAccessException;
+
+ /**
+ * Issue multiple SQL updates on a single JDBC Statement using batching.
+ *
Will fall back to separate updates on a single Statement if the JDBC
+ * driver does not support batch updates.
+ * @param sql defining an array of SQL statements that will be executed.
+ * @return an array of the number of rows affected by each statement
+ * @throws DataAccessException if there is any problem executing the batch
+ */
+ int[] batchUpdate(String[] sql) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with prepared statements
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC PreparedStatement. This allows for implementing arbitrary
+ * data access operations on a single Statement, within Spring's managed
+ * JDBC environment: that is, participating in Spring-managed transactions
+ * and converting JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param psc object that can create a PreparedStatement given a Connection
+ * @param action callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)
+ throws DataAccessException;
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC PreparedStatement. This allows for implementing arbitrary
+ * data access operations on a single Statement, within Spring's managed
+ * JDBC environment: that is, participating in Spring-managed transactions
+ * and converting JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param sql SQL to execute
+ * @param action callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(String sql, PreparedStatementCallback action) throws DataAccessException;
+
+ /**
+ * Query using a prepared statement, reading the ResultSet with a
+ * ResultSetExtractor.
+ *
A PreparedStatementCreator can either be implemented directly or
+ * configured through a PreparedStatementCreatorFactory.
+ * @param psc object that can create a PreparedStatement given a Connection
+ * @param rse object that will extract results
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if there is any problem
+ * @see PreparedStatementCreatorFactory
+ */
+ Object query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException;
+
+ /**
+ * Query using a prepared statement, reading the ResultSet with a
+ * ResultSetExtractor.
+ * @param sql SQL query to execute
+ * @param pss object that knows how to set values on the prepared statement.
+ * If this is null, the SQL will be assumed to contain no bind parameters.
+ * Even if there are no bind parameters, this object may be used to
+ * set fetch size and other performance options.
+ * @param rse object that will extract results
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if there is any problem
+ */
+ Object query(String sql, PreparedStatementSetter pss, ResultSetExtractor rse)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, reading the ResultSet with a
+ * ResultSetExtractor.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param rse object that will extract results
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if the query fails
+ * @see java.sql.Types
+ */
+ Object query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, reading the ResultSet with a
+ * ResultSetExtractor.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param rse object that will extract results
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if the query fails
+ */
+ Object query(String sql, Object[] args, ResultSetExtractor rse) throws DataAccessException;
+
+ /**
+ * Query using a prepared statement, reading the ResultSet on a per-row
+ * basis with a RowCallbackHandler.
+ *
A PreparedStatementCreator can either be implemented directly or
+ * configured through a PreparedStatementCreatorFactory.
+ * @param psc object that can create a PreparedStatement given a Connection
+ * @param rch object that will extract results, one row at a time
+ * @throws DataAccessException if there is any problem
+ * @see PreparedStatementCreatorFactory
+ */
+ void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * PreparedStatementSetter implementation that knows how to bind values
+ * to the query, reading the ResultSet on a per-row basis with a
+ * RowCallbackHandler.
+ * @param sql SQL query to execute
+ * @param pss object that knows how to set values on the prepared statement.
+ * If this is null, the SQL will be assumed to contain no bind parameters.
+ * Even if there are no bind parameters, this object may be used to
+ * set fetch size and other performance options.
+ * @param rch object that will extract results, one row at a time
+ * @throws DataAccessException if the query fails
+ */
+ void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list of
+ * arguments to bind to the query, reading the ResultSet on a per-row basis
+ * with a RowCallbackHandler.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param rch object that will extract results, one row at a time
+ * @throws DataAccessException if the query fails
+ * @see java.sql.Types
+ */
+ void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list of
+ * arguments to bind to the query, reading the ResultSet on a per-row basis
+ * with a RowCallbackHandler.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param rch object that will extract results, one row at a time
+ * @throws DataAccessException if the query fails
+ */
+ void query(String sql, Object[] args, RowCallbackHandler rch) throws DataAccessException;
+
+ /**
+ * Query using a prepared statement, mapping each row to a Java object
+ * via a RowMapper.
+ *
A PreparedStatementCreator can either be implemented directly or
+ * configured through a PreparedStatementCreatorFactory.
+ * @param psc object that can create a PreparedStatement given a Connection
+ * @param rowMapper object that will map one object per row
+ * @return the result List, containing mapped objects
+ * @throws DataAccessException if there is any problem
+ * @see PreparedStatementCreatorFactory
+ */
+ List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * PreparedStatementSetter implementation that knows how to bind values
+ * to the query, mapping each row to a Java object via a RowMapper.
+ * @param sql SQL query to execute
+ * @param pss object that knows how to set values on the prepared statement.
+ * If this is null, the SQL will be assumed to contain no bind parameters.
+ * Even if there are no bind parameters, this object may be used to
+ * set fetch size and other performance options.
+ * @param rowMapper object that will map one object per row
+ * @return the result List, containing mapped objects
+ * @throws DataAccessException if the query fails
+ */
+ List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, mapping each row to a Java object
+ * via a RowMapper.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param rowMapper object that will map one object per row
+ * @return the result List, containing mapped objects
+ * @throws DataAccessException if the query fails
+ * @see java.sql.Types
+ */
+ List query(String sql, Object[] args, int[] argTypes, RowMapper rowMapper)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, mapping each row to a Java object
+ * via a RowMapper.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param rowMapper object that will map one object per row
+ * @return the result List, containing mapped objects
+ * @throws DataAccessException if the query fails
+ */
+ List query(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, mapping a single result row to a
+ * Java object via a RowMapper.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type)
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param rowMapper object that will map one object per row
+ * @return the single mapped object
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if the query fails
+ */
+ Object queryForObject(String sql, Object[] args, int[] argTypes, RowMapper rowMapper)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a list
+ * of arguments to bind to the query, mapping a single result row to a
+ * Java object via a RowMapper.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param rowMapper object that will map one object per row
+ * @return the single mapped object
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if the query fails
+ */
+ Object queryForObject(String sql, Object[] args, RowMapper rowMapper)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result object.
+ *
The query is expected to be a single row/single column query; the returned
+ * result will be directly mapped to the corresponding object type.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param requiredType the type that the result object is expected to match
+ * @return the result object of the required type, or null in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForObject(String, Class)
+ * @see java.sql.Types
+ */
+ Object queryForObject(String sql, Object[] args, int[] argTypes, Class requiredType)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result object.
+ *
The query is expected to be a single row/single column query; the returned
+ * result will be directly mapped to the corresponding object type.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param requiredType the type that the result object is expected to match
+ * @return the result object of the required type, or null in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForObject(String, Class)
+ */
+ Object queryForObject(String sql, Object[] args, Class requiredType) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result Map.
+ *
The query is expected to be a single row query; the result row will be
+ * mapped to a Map (one entry for each column, using the column name as the key).
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return the result Map (one entry for each column, using the
+ * column name as the key)
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if the query fails
+ * @see #queryForMap(String)
+ * @see ColumnMapRowMapper
+ * @see java.sql.Types
+ */
+ Map queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result Map.
+ * The queryForMap() methods defined by this interface are appropriate
+ * when you don't have a domain model. Otherwise, consider using
+ * one of the queryForObject() methods.
+ *
The query is expected to be a single row query; the result row will be
+ * mapped to a Map (one entry for each column, using the column name as the key).
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return the result Map (one entry for each column, using the
+ * column name as the key)
+ * @throws IncorrectResultSizeDataAccessException if the query does not
+ * return exactly one row
+ * @throws DataAccessException if the query fails
+ * @see #queryForMap(String)
+ * @see ColumnMapRowMapper
+ */
+ Map queryForMap(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, resulting in a long value.
+ *
The query is expected to be a single row/single column query that
+ * results in a long value.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return the long value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForLong(String)
+ * @see java.sql.Types
+ */
+ long queryForLong(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, resulting in a long value.
+ *
The query is expected to be a single row/single column query that
+ * results in a long value.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return the long value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForLong(String)
+ */
+ long queryForLong(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, resulting in an int value.
+ *
The query is expected to be a single row/single column query that
+ * results in an int value.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return the int value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForInt(String)
+ * @see java.sql.Types
+ */
+ int queryForInt(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, resulting in an int value.
+ *
The query is expected to be a single row/single column query that
+ * results in an int value.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return the int value, or 0 in case of SQL NULL
+ * @throws IncorrectResultSizeDataAccessException if the query does not return
+ * exactly one row, or does not return exactly one column in that row
+ * @throws DataAccessException if the query fails
+ * @see #queryForInt(String)
+ */
+ int queryForInt(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result list.
+ *
The results will be mapped to a List (one entry for each row) of
+ * result objects, each of them matching the specified element type.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @param elementType the required type of element in the result list
+ * (for example, Integer.class)
+ * @return a List of objects that match the specified element type
+ * @throws DataAccessException if the query fails
+ * @see #queryForList(String, Class)
+ * @see SingleColumnRowMapper
+ */
+ List queryForList(String sql, Object[] args, int[] argTypes, Class elementType)
+ throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result list.
+ *
The results will be mapped to a List (one entry for each row) of
+ * result objects, each of them matching the specified element type.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @param elementType the required type of element in the result list
+ * (for example, Integer.class)
+ * @return a List of objects that match the specified element type
+ * @throws DataAccessException if the query fails
+ * @see #queryForList(String, Class)
+ * @see SingleColumnRowMapper
+ */
+ List queryForList(String sql, Object[] args, Class elementType) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result list.
+ *
The results will be mapped to a List (one entry for each row) of
+ * Maps (one entry for each column, using the column name as the key).
+ * Thus Each element in the list will be of the form returned by this interface's
+ * queryForMap() methods.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return a List that contains a Map per row
+ * @throws DataAccessException if the query fails
+ * @see #queryForList(String)
+ * @see java.sql.Types
+ */
+ List queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a result list.
+ *
The results will be mapped to a List (one entry for each row) of
+ * Maps (one entry for each column, using the column name as the key).
+ * Each element in the list will be of the form returned by this interface's
+ * queryForMap() methods.
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return a List that contains a Map per row
+ * @throws DataAccessException if the query fails
+ * @see #queryForList(String)
+ */
+ List queryForList(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a SqlRowSet.
+ *
The results will be mapped to an SqlRowSet which holds the data in a
+ * disconnected fashion. This wrapper will translate any SQLExceptions thrown.
+ *
Note that that, for the default implementation, JDBC RowSet support needs to
+ * be available at runtime: by default, Sun's com.sun.rowset.CachedRowSetImpl
+ * class is used, which is part of JDK 1.5+ and also available separately as part of
+ * Sun's JDBC RowSet Implementations download (rowset.jar).
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return a SqlRowSet representation (possibly a wrapper around a
+ * javax.sql.rowset.CachedRowSet)
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForRowSet(String)
+ * @see SqlRowSetResultSetExtractor
+ * @see javax.sql.rowset.CachedRowSet
+ * @see java.sql.Types
+ */
+ SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Query given SQL to create a prepared statement from SQL and a
+ * list of arguments to bind to the query, expecting a SqlRowSet.
+ *
The results will be mapped to an SqlRowSet which holds the data in a
+ * disconnected fashion. This wrapper will translate any SQLExceptions thrown.
+ *
Note that that, for the default implementation, JDBC RowSet support needs to
+ * be available at runtime: by default, Sun's com.sun.rowset.CachedRowSetImpl
+ * class is used, which is part of JDK 1.5+ and also available separately as part of
+ * Sun's JDBC RowSet Implementations download (rowset.jar).
+ * @param sql SQL query to execute
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return a SqlRowSet representation (possibly a wrapper around a
+ * javax.sql.rowset.CachedRowSet)
+ * @throws DataAccessException if there is any problem executing the query
+ * @see #queryForRowSet(String)
+ * @see SqlRowSetResultSetExtractor
+ * @see javax.sql.rowset.CachedRowSet
+ */
+ SqlRowSet queryForRowSet(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Issue a single SQL update operation (such as an insert, update or delete statement)
+ * using a PreparedStatementCreator to provide SQL and any required parameters.
+ *
A PreparedStatementCreator can either be implemented directly or
+ * configured through a PreparedStatementCreatorFactory.
+ * @param psc object that provides SQL and any necessary parameters
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem issuing the update
+ * @see PreparedStatementCreatorFactory
+ */
+ int update(PreparedStatementCreator psc) throws DataAccessException;
+
+ /**
+ * Issue an update statement using a PreparedStatementCreator to provide SQL and
+ * any required parameters. Generated keys will be put into the given KeyHolder.
+ *
Note that the given PreparedStatementCreator has to create a statement
+ * with activated extraction of generated keys (a JDBC 3.0 feature). This can
+ * either be done directly or through using a PreparedStatementCreatorFactory.
+ * @param psc object that provides SQL and any necessary parameters
+ * @param generatedKeyHolder KeyHolder that will hold the generated keys
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem issuing the update
+ * @see PreparedStatementCreatorFactory
+ * @see org.springframework.jdbc.support.GeneratedKeyHolder
+ */
+ int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) throws DataAccessException;
+
+ /**
+ * Issue an update statement using a PreparedStatementSetter to set bind parameters,
+ * with given SQL. Simpler than using a PreparedStatementCreator as this method
+ * will create the PreparedStatement: The PreparedStatementSetter just needs to
+ * set parameters.
+ * @param sql SQL containing bind parameters
+ * @param pss helper that sets bind parameters. If this is null
+ * we run an update with static SQL.
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem issuing the update
+ */
+ int update(String sql, PreparedStatementSetter pss) throws DataAccessException;
+
+ /**
+ * Issue a single SQL update operation (such as an insert, update or delete statement)
+ * via a prepared statement, binding the given arguments.
+ * @param sql SQL containing bind parameters
+ * @param args arguments to bind to the query
+ * @param argTypes SQL types of the arguments
+ * (constants from java.sql.Types)
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem issuing the update
+ * @see java.sql.Types
+ */
+ int update(String sql, Object[] args, int[] argTypes) throws DataAccessException;
+
+ /**
+ * Issue a single SQL update operation (such as an insert, update or delete statement)
+ * via a prepared statement, binding the given arguments.
+ * @param sql SQL containing bind parameters
+ * @param args arguments to bind to the query
+ * (leaving it to the PreparedStatement to guess the corresponding SQL type);
+ * may also contain {@link SqlParameterValue} objects which indicate not
+ * only the argument value but also the SQL type and optionally the scale
+ * @return the number of rows affected
+ * @throws DataAccessException if there is any problem issuing the update
+ */
+ int update(String sql, Object[] args) throws DataAccessException;
+
+ /**
+ * Issue multiple update statements on a single PreparedStatement,
+ * using batch updates and a BatchPreparedStatementSetter to set values.
+ *
Will fall back to separate updates on a single PreparedStatement
+ * if the JDBC driver does not support batch updates.
+ * @param sql defining PreparedStatement that will be reused.
+ * All statements in the batch will use the same SQL.
+ * @param pss object to set parameters on the PreparedStatement
+ * created by this method
+ * @return an array of the number of rows affected by each statement
+ * @throws DataAccessException if there is any problem issuing the update
+ */
+ int[] batchUpdate(String sql, BatchPreparedStatementSetter pss) throws DataAccessException;
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with callable statements
+ //-------------------------------------------------------------------------
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC CallableStatement. This allows for implementing arbitrary
+ * data access operations on a single Statement, within Spring's managed
+ * JDBC environment: that is, participating in Spring-managed transactions
+ * and converting JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param csc object that can create a CallableStatement given a Connection
+ * @param action callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(CallableStatementCreator csc, CallableStatementCallback action)
+ throws DataAccessException;
+
+ /**
+ * Execute a JDBC data access operation, implemented as callback action
+ * working on a JDBC CallableStatement. This allows for implementing arbitrary
+ * data access operations on a single Statement, within Spring's managed
+ * JDBC environment: that is, participating in Spring-managed transactions
+ * and converting JDBC SQLExceptions into Spring's DataAccessException hierarchy.
+ *
The callback action can return a result object, for example a
+ * domain object or a collection of domain objects.
+ * @param callString the SQL call string to execute
+ * @param action callback object that specifies the action
+ * @return a result object returned by the action, or null
+ * @throws DataAccessException if there is any problem
+ */
+ Object execute(String callString, CallableStatementCallback action) throws DataAccessException;
+
+ /**
+ * Execute a SQL call using a CallableStatementCreator to provide SQL and any
+ * required parameters.
+ * @param csc object that provides SQL and any necessary parameters
+ * @param declaredParameters list of declared SqlParameter objects
+ * @return Map of extracted out parameters
+ * @throws DataAccessException if there is any problem issuing the update
+ */
+ Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
new file mode 100644
index 0000000000..00f9324b6f
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
@@ -0,0 +1,1374 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.dao.support.DataAccessUtils;
+import org.springframework.jdbc.SQLWarningException;
+import org.springframework.jdbc.datasource.ConnectionProxy;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.support.JdbcAccessor;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+import org.springframework.util.Assert;
+
+/**
+ * This is the central class in the JDBC core package.
+ * It simplifies the use of JDBC and helps to avoid common errors.
+ * It executes core JDBC workflow, leaving application code to provide SQL
+ * and extract results. This class executes SQL queries or updates, initiating
+ * iteration over ResultSets and catching JDBC exceptions and translating
+ * them to the generic, more informative exception hierarchy defined in the
+ * org.springframework.dao package.
+ *
+ *
Code using this class need only implement callback interfaces, giving
+ * them a clearly defined contract. The {@link PreparedStatementCreator} callback
+ * interface creates a prepared statement given a Connection, providing SQL and
+ * any necessary parameters. The {@link ResultSetExtractor} interface extracts
+ * values from a ResultSet. See also {@link PreparedStatementSetter} and
+ * {@link RowMapper} for two popular alternative callback interfaces.
+ *
+ *
Can be used within a service implementation via direct instantiation
+ * with a DataSource reference, or get prepared in an application context
+ * and given to services as bean reference. Note: The DataSource should
+ * always be configured as a bean in the application context, in the first case
+ * given to the service directly, in the second case to the prepared template.
+ *
+ *
Because this class is parameterizable by the callback interfaces and
+ * the {@link org.springframework.jdbc.support.SQLExceptionTranslator}
+ * interface, there should be no need to subclass it.
+ *
+ *
All SQL operations performed by this class are logged at debug level,
+ * using "org.springframework.jdbc.core.JdbcTemplate" as log category.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Thomas Risberg
+ * @since May 3, 2001
+ * @see PreparedStatementCreator
+ * @see PreparedStatementSetter
+ * @see CallableStatementCreator
+ * @see PreparedStatementCallback
+ * @see CallableStatementCallback
+ * @see ResultSetExtractor
+ * @see RowCallbackHandler
+ * @see RowMapper
+ * @see org.springframework.jdbc.support.SQLExceptionTranslator
+ */
+public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
+
+ private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
+
+ private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
+
+
+ /** Custom NativeJdbcExtractor */
+ private NativeJdbcExtractor nativeJdbcExtractor;
+
+ /** If this variable is false, we will throw exceptions on SQL warnings */
+ private boolean ignoreWarnings = true;
+
+ /**
+ * If this variable is set to a non-zero value, it will be used for setting the
+ * fetchSize property on statements used for query processing.
+ */
+ private int fetchSize = 0;
+
+ /**
+ * If this variable is set to a non-zero value, it will be used for setting the
+ * maxRows property on statements used for query processing.
+ */
+ private int maxRows = 0;
+
+ /**
+ * If this variable is set to a non-zero value, it will be used for setting the
+ * queryTimeout property on statements used for query processing.
+ */
+ private int queryTimeout = 0;
+
+ /**
+ * If this variable is set to true then all results checking will be bypassed for any
+ * callable statement processing. This can be used to avoid a bug in some older Oracle
+ * JDBC drivers like 10.1.0.2.
+ */
+ private boolean skipResultsProcessing = false;
+
+ /**
+ * If this variable is set to true then all results from a stored procedure call
+ * that don't have a corresponding SqlOutParameter declaration will be bypassed.
+ * All other results processng will be take place unless the variable
+ * skipResultsProcessing is set to true
+ */
+ private boolean skipUndeclaredResults = false;
+
+ /**
+ * If this variable is set to true then execution of a CallableStatement will return
+ * the results in a Map that uses case insensitive names for the parameters if
+ * Commons Collections is available on the classpath.
+ */
+ private boolean resultsMapCaseInsensitive = false;
+
+
+ /**
+ * Construct a new JdbcTemplate for bean usage.
+ *
Note: The DataSource has to be set before using the instance.
+ * @see #setDataSource
+ */
+ public JdbcTemplate() {
+ }
+
+ /**
+ * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
+ *
Note: This will not trigger initialization of the exception translator.
+ * @param dataSource the JDBC DataSource to obtain connections from
+ */
+ public JdbcTemplate(DataSource dataSource) {
+ setDataSource(dataSource);
+ afterPropertiesSet();
+ }
+
+ /**
+ * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
+ *
Note: Depending on the "lazyInit" flag, initialization of the exception translator
+ * will be triggered.
+ * @param dataSource the JDBC DataSource to obtain connections from
+ * @param lazyInit whether to lazily initialize the SQLExceptionTranslator
+ */
+ public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
+ setDataSource(dataSource);
+ setLazyInit(lazyInit);
+ afterPropertiesSet();
+ }
+
+
+ /**
+ * Set a NativeJdbcExtractor to extract native JDBC objects from wrapped handles.
+ * Useful if native Statement and/or ResultSet handles are expected for casting
+ * to database-specific implementation classes, but a connection pool that wraps
+ * JDBC objects is used (note: any pool will return wrapped Connections).
+ */
+ public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
+ this.nativeJdbcExtractor = extractor;
+ }
+
+ /**
+ * Return the current NativeJdbcExtractor implementation.
+ */
+ public NativeJdbcExtractor getNativeJdbcExtractor() {
+ return this.nativeJdbcExtractor;
+ }
+
+ /**
+ * Set whether or not we want to ignore SQLWarnings.
+ *
Default is "true", swallowing and logging all warnings. Switch this flag
+ * to "false" to make the JdbcTemplate throw a SQLWarningException instead.
+ * @see java.sql.SQLWarning
+ * @see org.springframework.jdbc.SQLWarningException
+ * @see #handleWarnings
+ */
+ public void setIgnoreWarnings(boolean ignoreWarnings) {
+ this.ignoreWarnings = ignoreWarnings;
+ }
+
+ /**
+ * Return whether or not we ignore SQLWarnings.
+ */
+ public boolean isIgnoreWarnings() {
+ return this.ignoreWarnings;
+ }
+
+ /**
+ * Set the fetch size for this JdbcTemplate. This is important for processing
+ * large result sets: Setting this higher than the default value will increase
+ * processing speed at the cost of memory consumption; setting this lower can
+ * avoid transferring row data that will never be read by the application.
+ *
Default is 0, indicating to use the JDBC driver's default.
+ * @see java.sql.Statement#setFetchSize
+ */
+ public void setFetchSize(int fetchSize) {
+ this.fetchSize = fetchSize;
+ }
+
+ /**
+ * Return the fetch size specified for this JdbcTemplate.
+ */
+ public int getFetchSize() {
+ return this.fetchSize;
+ }
+
+ /**
+ * Set the maximum number of rows for this JdbcTemplate. This is important
+ * for processing subsets of large result sets, avoiding to read and hold
+ * the entire result set in the database or in the JDBC driver if we're
+ * never interested in the entire result in the first place (for example,
+ * when performing searches that might return a large number of matches).
+ *
Default is 0, indicating to use the JDBC driver's default.
+ * @see java.sql.Statement#setMaxRows
+ */
+ public void setMaxRows(int maxRows) {
+ this.maxRows = maxRows;
+ }
+
+ /**
+ * Return the maximum number of rows specified for this JdbcTemplate.
+ */
+ public int getMaxRows() {
+ return this.maxRows;
+ }
+
+ /**
+ * Set the query timeout for statements that this JdbcTemplate executes.
+ *
Default is 0, indicating to use the JDBC driver's default.
+ *
Note: Any timeout specified here will be overridden by the remaining
+ * transaction timeout when executing within a transaction that has a
+ * timeout specified at the transaction level.
+ * @see java.sql.Statement#setQueryTimeout
+ */
+ public void setQueryTimeout(int queryTimeout) {
+ this.queryTimeout = queryTimeout;
+ }
+
+ /**
+ * Return the query timeout for statements that this JdbcTemplate executes.
+ */
+ public int getQueryTimeout() {
+ return this.queryTimeout;
+ }
+
+ /**
+ * Set whether results processing should be skipped. Can be used to optimize callable
+ * statement processing when we know that no results are being passed back - the processing
+ * of out parameter will still take place. This can be used to avoid a bug in some older
+ * Oracle JDBC drivers like 10.1.0.2.
+ */
+ public void setSkipResultsProcessing(boolean skipResultsProcessing) {
+ this.skipResultsProcessing = skipResultsProcessing;
+ }
+
+ /**
+ * Return whether results processing should be skipped.
+ */
+ public boolean isSkipResultsProcessing() {
+ return this.skipResultsProcessing;
+ }
+
+ /**
+ * Set whether undelared results should be skipped.
+ */
+ public void setSkipUndeclaredResults(boolean skipUndeclaredResults) {
+ this.skipUndeclaredResults = skipUndeclaredResults;
+ }
+
+ /**
+ * Return whether undeclared results should be skipped.
+ */
+ public boolean isSkipUndeclaredResults() {
+ return this.skipUndeclaredResults;
+ }
+
+ /**
+ * Set whether execution of a CallableStatement will return the results in a Map
+ * that uses case insensitive names for the parameters.
+ */
+ public void setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) {
+ this.resultsMapCaseInsensitive = resultsMapCaseInsensitive;
+ }
+
+ /**
+ * Return whether execution of a CallableStatement will return the results in a Map
+ * that uses case insensitive names for the parameters.
+ */
+ public boolean isResultsMapCaseInsensitive() {
+ return this.resultsMapCaseInsensitive;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with a plain java.sql.Connection
+ //-------------------------------------------------------------------------
+
+ public Object execute(ConnectionCallback action) throws DataAccessException {
+ Assert.notNull(action, "Callback object must not be null");
+
+ Connection con = DataSourceUtils.getConnection(getDataSource());
+ try {
+ Connection conToUse = con;
+ if (this.nativeJdbcExtractor != null) {
+ // Extract native JDBC Connection, castable to OracleConnection or the like.
+ conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
+ }
+ else {
+ // Create close-suppressing Connection proxy, also preparing returned Statements.
+ conToUse = createConnectionProxy(con);
+ }
+ return action.doInConnection(conToUse);
+ }
+ catch (SQLException ex) {
+ // Release Connection early, to avoid potential connection pool deadlock
+ // in the case when the exception translator hasn't been initialized yet.
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ con = null;
+ throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
+ }
+ finally {
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ }
+ }
+
+ /**
+ * Create a close-suppressing proxy for the given JDBC Connection.
+ * Called by the execute method.
+ *
The proxy also prepares returned JDBC Statements, applying
+ * statement settings such as fetch size, max rows, and query timeout.
+ * @param con the JDBC Connection to create a proxy for
+ * @return the Connection proxy
+ * @see java.sql.Connection#close()
+ * @see #execute(ConnectionCallback)
+ * @see #applyStatementSettings
+ */
+ protected Connection createConnectionProxy(Connection con) {
+ return (Connection) Proxy.newProxyInstance(
+ ConnectionProxy.class.getClassLoader(),
+ new Class[] {ConnectionProxy.class},
+ new CloseSuppressingInvocationHandler(con));
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with static SQL (java.sql.Statement)
+ //-------------------------------------------------------------------------
+
+ public Object execute(StatementCallback action) throws DataAccessException {
+ Assert.notNull(action, "Callback object must not be null");
+
+ Connection con = DataSourceUtils.getConnection(getDataSource());
+ Statement stmt = null;
+ try {
+ Connection conToUse = con;
+ if (this.nativeJdbcExtractor != null &&
+ this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
+ conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
+ }
+ stmt = conToUse.createStatement();
+ applyStatementSettings(stmt);
+ Statement stmtToUse = stmt;
+ if (this.nativeJdbcExtractor != null) {
+ stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
+ }
+ Object result = action.doInStatement(stmtToUse);
+ handleWarnings(stmt);
+ return result;
+ }
+ catch (SQLException ex) {
+ // Release Connection early, to avoid potential connection pool deadlock
+ // in the case when the exception translator hasn't been initialized yet.
+ JdbcUtils.closeStatement(stmt);
+ stmt = null;
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ con = null;
+ throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
+ }
+ finally {
+ JdbcUtils.closeStatement(stmt);
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ }
+ }
+
+ public void execute(final String sql) throws DataAccessException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing SQL statement [" + sql + "]");
+ }
+
+ class ExecuteStatementCallback implements StatementCallback, SqlProvider {
+ public Object doInStatement(Statement stmt) throws SQLException {
+ stmt.execute(sql);
+ return null;
+ }
+ public String getSql() {
+ return sql;
+ }
+ }
+ execute(new ExecuteStatementCallback());
+ }
+
+ public Object query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
+ Assert.notNull(sql, "SQL must not be null");
+ Assert.notNull(rse, "ResultSetExtractor must not be null");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing SQL query [" + sql + "]");
+ }
+
+ class QueryStatementCallback implements StatementCallback, SqlProvider {
+ public Object doInStatement(Statement stmt) throws SQLException {
+ ResultSet rs = null;
+ try {
+ rs = stmt.executeQuery(sql);
+ ResultSet rsToUse = rs;
+ if (nativeJdbcExtractor != null) {
+ rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
+ }
+ return rse.extractData(rsToUse);
+ }
+ finally {
+ JdbcUtils.closeResultSet(rs);
+ }
+ }
+ public String getSql() {
+ return sql;
+ }
+ }
+ return execute(new QueryStatementCallback());
+ }
+
+ public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
+ query(sql, new RowCallbackHandlerResultSetExtractor(rch));
+ }
+
+ public List query(String sql, RowMapper rowMapper) throws DataAccessException {
+ return (List) query(sql, new RowMapperResultSetExtractor(rowMapper));
+ }
+
+ public Map queryForMap(String sql) throws DataAccessException {
+ return (Map) queryForObject(sql, getColumnMapRowMapper());
+ }
+
+ public Object queryForObject(String sql, RowMapper rowMapper) throws DataAccessException {
+ List results = query(sql, rowMapper);
+ return DataAccessUtils.requiredSingleResult(results);
+ }
+
+ public Object queryForObject(String sql, Class requiredType) throws DataAccessException {
+ return queryForObject(sql, getSingleColumnRowMapper(requiredType));
+ }
+
+ public long queryForLong(String sql) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, Long.class);
+ return (number != null ? number.longValue() : 0);
+ }
+
+ public int queryForInt(String sql) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, Integer.class);
+ return (number != null ? number.intValue() : 0);
+ }
+
+ public List queryForList(String sql, Class elementType) throws DataAccessException {
+ return query(sql, getSingleColumnRowMapper(elementType));
+ }
+
+ public List queryForList(String sql) throws DataAccessException {
+ return query(sql, getColumnMapRowMapper());
+ }
+
+ public SqlRowSet queryForRowSet(String sql) throws DataAccessException {
+ return (SqlRowSet) query(sql, new SqlRowSetResultSetExtractor());
+ }
+
+ public int update(final String sql) throws DataAccessException {
+ Assert.notNull(sql, "SQL must not be null");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing SQL update [" + sql + "]");
+ }
+
+ class UpdateStatementCallback implements StatementCallback, SqlProvider {
+ public Object doInStatement(Statement stmt) throws SQLException {
+ int rows = stmt.executeUpdate(sql);
+ if (logger.isDebugEnabled()) {
+ logger.debug("SQL update affected " + rows + " rows");
+ }
+ return new Integer(rows);
+ }
+ public String getSql() {
+ return sql;
+ }
+ }
+ return ((Integer) execute(new UpdateStatementCallback())).intValue();
+ }
+
+ public int[] batchUpdate(final String[] sql) throws DataAccessException {
+ Assert.notEmpty(sql, "SQL array must not be empty");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing SQL batch update of " + sql.length + " statements");
+ }
+
+ class BatchUpdateStatementCallback implements StatementCallback, SqlProvider {
+ private String currSql;
+ public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
+ int[] rowsAffected = new int[sql.length];
+ if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) {
+ for (int i = 0; i < sql.length; i++) {
+ this.currSql = sql[i];
+ stmt.addBatch(sql[i]);
+ }
+ rowsAffected = stmt.executeBatch();
+ }
+ else {
+ for (int i = 0; i < sql.length; i++) {
+ this.currSql = sql[i];
+ if (!stmt.execute(sql[i])) {
+ rowsAffected[i] = stmt.getUpdateCount();
+ }
+ else {
+ throw new InvalidDataAccessApiUsageException("Invalid batch SQL statement: " + sql[i]);
+ }
+ }
+ }
+ return rowsAffected;
+ }
+ public String getSql() {
+ return currSql;
+ }
+ }
+ return (int[]) execute(new BatchUpdateStatementCallback());
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with prepared statements
+ //-------------------------------------------------------------------------
+
+ public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)
+ throws DataAccessException {
+
+ Assert.notNull(psc, "PreparedStatementCreator must not be null");
+ Assert.notNull(action, "Callback object must not be null");
+ if (logger.isDebugEnabled()) {
+ String sql = getSql(psc);
+ logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
+ }
+
+ Connection con = DataSourceUtils.getConnection(getDataSource());
+ PreparedStatement ps = null;
+ try {
+ Connection conToUse = con;
+ if (this.nativeJdbcExtractor != null &&
+ this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
+ conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
+ }
+ ps = psc.createPreparedStatement(conToUse);
+ applyStatementSettings(ps);
+ PreparedStatement psToUse = ps;
+ if (this.nativeJdbcExtractor != null) {
+ psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
+ }
+ Object result = action.doInPreparedStatement(psToUse);
+ handleWarnings(ps);
+ return result;
+ }
+ catch (SQLException ex) {
+ // Release Connection early, to avoid potential connection pool deadlock
+ // in the case when the exception translator hasn't been initialized yet.
+ if (psc instanceof ParameterDisposer) {
+ ((ParameterDisposer) psc).cleanupParameters();
+ }
+ String sql = getSql(psc);
+ psc = null;
+ JdbcUtils.closeStatement(ps);
+ ps = null;
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ con = null;
+ throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);
+ }
+ finally {
+ if (psc instanceof ParameterDisposer) {
+ ((ParameterDisposer) psc).cleanupParameters();
+ }
+ JdbcUtils.closeStatement(ps);
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ }
+ }
+
+ public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
+ return execute(new SimplePreparedStatementCreator(sql), action);
+ }
+
+ /**
+ * Query using a prepared statement, allowing for a PreparedStatementCreator
+ * and a PreparedStatementSetter. Most other query methods use this method,
+ * but application code will always work with either a creator or a setter.
+ * @param psc Callback handler that can create a PreparedStatement given a
+ * Connection
+ * @param pss object that knows how to set values on the prepared statement.
+ * If this is null, the SQL will be assumed to contain no bind parameters.
+ * @param rse object that will extract results.
+ * @return an arbitrary result object, as returned by the ResultSetExtractor
+ * @throws DataAccessException if there is any problem
+ */
+ public Object query(
+ PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
+ throws DataAccessException {
+
+ Assert.notNull(rse, "ResultSetExtractor must not be null");
+ logger.debug("Executing prepared SQL query");
+
+ return execute(psc, new PreparedStatementCallback() {
+ public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
+ ResultSet rs = null;
+ try {
+ if (pss != null) {
+ pss.setValues(ps);
+ }
+ rs = ps.executeQuery();
+ ResultSet rsToUse = rs;
+ if (nativeJdbcExtractor != null) {
+ rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
+ }
+ return rse.extractData(rsToUse);
+ }
+ finally {
+ JdbcUtils.closeResultSet(rs);
+ if (pss instanceof ParameterDisposer) {
+ ((ParameterDisposer) pss).cleanupParameters();
+ }
+ }
+ }
+ });
+ }
+
+ public Object query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException {
+ return query(psc, null, rse);
+ }
+
+ public Object query(String sql, PreparedStatementSetter pss, ResultSetExtractor rse) throws DataAccessException {
+ return query(new SimplePreparedStatementCreator(sql), pss, rse);
+ }
+
+ public Object query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException {
+ return query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rse);
+ }
+
+ public Object query(String sql, Object[] args, ResultSetExtractor rse) throws DataAccessException {
+ return query(sql, new ArgPreparedStatementSetter(args), rse);
+ }
+
+ public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {
+ query(psc, new RowCallbackHandlerResultSetExtractor(rch));
+ }
+
+ public void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
+ query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
+ }
+
+ public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {
+ query(sql, new ArgTypePreparedStatementSetter(args, argTypes), rch);
+ }
+
+ public void query(String sql, Object[] args, RowCallbackHandler rch) throws DataAccessException {
+ query(sql, new ArgPreparedStatementSetter(args), rch);
+ }
+
+ public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException {
+ return (List) query(psc, new RowMapperResultSetExtractor(rowMapper));
+ }
+
+ public List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException {
+ return (List) query(sql, pss, new RowMapperResultSetExtractor(rowMapper));
+ }
+
+ public List query(String sql, Object[] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException {
+ return (List) query(sql, args, argTypes, new RowMapperResultSetExtractor(rowMapper));
+ }
+
+ public List query(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException {
+ return (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper));
+ }
+
+ public Object queryForObject(String sql, Object[] args, int[] argTypes, RowMapper rowMapper)
+ throws DataAccessException {
+
+ List results = (List) query(sql, args, argTypes, new RowMapperResultSetExtractor(rowMapper, 1));
+ return DataAccessUtils.requiredSingleResult(results);
+ }
+
+ public Object queryForObject(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException {
+ List results = (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper, 1));
+ return DataAccessUtils.requiredSingleResult(results);
+ }
+
+ public Object queryForObject(String sql, Object[] args, int[] argTypes, Class requiredType)
+ throws DataAccessException {
+
+ return queryForObject(sql, args, argTypes, getSingleColumnRowMapper(requiredType));
+ }
+
+ public Object queryForObject(String sql, Object[] args, Class requiredType) throws DataAccessException {
+ return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));
+ }
+
+ public Map queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ return (Map) queryForObject(sql, args, argTypes, getColumnMapRowMapper());
+ }
+
+ public Map queryForMap(String sql, Object[] args) throws DataAccessException {
+ return (Map) queryForObject(sql, args, getColumnMapRowMapper());
+ }
+
+ public long queryForLong(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, args, argTypes, Long.class);
+ return (number != null ? number.longValue() : 0);
+ }
+
+ public long queryForLong(String sql, Object[] args) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, args, Long.class);
+ return (number != null ? number.longValue() : 0);
+ }
+
+ public int queryForInt(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, args, argTypes, Integer.class);
+ return (number != null ? number.intValue() : 0);
+ }
+
+ public int queryForInt(String sql, Object[] args) throws DataAccessException {
+ Number number = (Number) queryForObject(sql, args, Integer.class);
+ return (number != null ? number.intValue() : 0);
+ }
+
+ public List queryForList(String sql, Object[] args, int[] argTypes, Class elementType) throws DataAccessException {
+ return query(sql, args, argTypes, getSingleColumnRowMapper(elementType));
+ }
+
+ public List queryForList(String sql, Object[] args, Class elementType) throws DataAccessException {
+ return query(sql, args, getSingleColumnRowMapper(elementType));
+ }
+
+ public List queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ return query(sql, args, argTypes, getColumnMapRowMapper());
+ }
+
+ public List queryForList(String sql, Object[] args) throws DataAccessException {
+ return query(sql, args, getColumnMapRowMapper());
+ }
+
+ public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ return (SqlRowSet) query(sql, args, argTypes, new SqlRowSetResultSetExtractor());
+ }
+
+ public SqlRowSet queryForRowSet(String sql, Object[] args) throws DataAccessException {
+ return (SqlRowSet) query(sql, args, new SqlRowSetResultSetExtractor());
+ }
+
+ protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
+ throws DataAccessException {
+
+ logger.debug("Executing prepared SQL update");
+
+ Integer result = (Integer) execute(psc, new PreparedStatementCallback() {
+ public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
+ try {
+ if (pss != null) {
+ pss.setValues(ps);
+ }
+ int rows = ps.executeUpdate();
+ if (logger.isDebugEnabled()) {
+ logger.debug("SQL update affected " + rows + " rows");
+ }
+ return new Integer(rows);
+ }
+ finally {
+ if (pss instanceof ParameterDisposer) {
+ ((ParameterDisposer) pss).cleanupParameters();
+ }
+ }
+ }
+ });
+ return result.intValue();
+ }
+
+ public int update(PreparedStatementCreator psc) throws DataAccessException {
+ return update(psc, (PreparedStatementSetter) null);
+ }
+
+ public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
+ throws DataAccessException {
+
+ Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
+ logger.debug("Executing SQL update and returning generated keys");
+
+ Integer result = (Integer) execute(psc, new PreparedStatementCallback() {
+ public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
+ int rows = ps.executeUpdate();
+ List generatedKeys = generatedKeyHolder.getKeyList();
+ generatedKeys.clear();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys != null) {
+ try {
+ RowMapper rowMapper = getColumnMapRowMapper();
+ RowMapperResultSetExtractor rse = new RowMapperResultSetExtractor(rowMapper, 1);
+ generatedKeys.addAll((List) rse.extractData(keys));
+ }
+ finally {
+ JdbcUtils.closeResultSet(keys);
+ }
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
+ }
+ return new Integer(rows);
+ }
+ });
+ return result.intValue();
+ }
+
+ public int update(String sql, PreparedStatementSetter pss) throws DataAccessException {
+ return update(new SimplePreparedStatementCreator(sql), pss);
+ }
+
+ public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
+ return update(sql, new ArgTypePreparedStatementSetter(args, argTypes));
+ }
+
+ public int update(String sql, Object[] args) throws DataAccessException {
+ return update(sql, new ArgPreparedStatementSetter(args));
+ }
+
+ public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing SQL batch update [" + sql + "]");
+ }
+
+ return (int[]) execute(sql, new PreparedStatementCallback() {
+ public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
+ try {
+ int batchSize = pss.getBatchSize();
+ InterruptibleBatchPreparedStatementSetter ipss =
+ (pss instanceof InterruptibleBatchPreparedStatementSetter ?
+ (InterruptibleBatchPreparedStatementSetter) pss : null);
+ if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
+ for (int i = 0; i < batchSize; i++) {
+ pss.setValues(ps, i);
+ if (ipss != null && ipss.isBatchExhausted(i)) {
+ break;
+ }
+ ps.addBatch();
+ }
+ return ps.executeBatch();
+ }
+ else {
+ List rowsAffected = new ArrayList();
+ for (int i = 0; i < batchSize; i++) {
+ pss.setValues(ps, i);
+ if (ipss != null && ipss.isBatchExhausted(i)) {
+ break;
+ }
+ rowsAffected.add(new Integer(ps.executeUpdate()));
+ }
+ int[] rowsAffectedArray = new int[rowsAffected.size()];
+ for (int i = 0; i < rowsAffectedArray.length; i++) {
+ rowsAffectedArray[i] = ((Integer) rowsAffected.get(i)).intValue();
+ }
+ return rowsAffectedArray;
+ }
+ }
+ finally {
+ if (pss instanceof ParameterDisposer) {
+ ((ParameterDisposer) pss).cleanupParameters();
+ }
+ }
+ }
+ });
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Methods dealing with callable statements
+ //-------------------------------------------------------------------------
+
+ public Object execute(CallableStatementCreator csc, CallableStatementCallback action)
+ throws DataAccessException {
+
+ Assert.notNull(csc, "CallableStatementCreator must not be null");
+ Assert.notNull(action, "Callback object must not be null");
+ if (logger.isDebugEnabled()) {
+ String sql = getSql(csc);
+ logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : ""));
+ }
+
+ Connection con = DataSourceUtils.getConnection(getDataSource());
+ CallableStatement cs = null;
+ try {
+ Connection conToUse = con;
+ if (this.nativeJdbcExtractor != null) {
+ conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
+ }
+ cs = csc.createCallableStatement(conToUse);
+ applyStatementSettings(cs);
+ CallableStatement csToUse = cs;
+ if (this.nativeJdbcExtractor != null) {
+ csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs);
+ }
+ Object result = action.doInCallableStatement(csToUse);
+ handleWarnings(cs);
+ return result;
+ }
+ catch (SQLException ex) {
+ // Release Connection early, to avoid potential connection pool deadlock
+ // in the case when the exception translator hasn't been initialized yet.
+ if (csc instanceof ParameterDisposer) {
+ ((ParameterDisposer) csc).cleanupParameters();
+ }
+ String sql = getSql(csc);
+ csc = null;
+ JdbcUtils.closeStatement(cs);
+ cs = null;
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ con = null;
+ throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex);
+ }
+ finally {
+ if (csc instanceof ParameterDisposer) {
+ ((ParameterDisposer) csc).cleanupParameters();
+ }
+ JdbcUtils.closeStatement(cs);
+ DataSourceUtils.releaseConnection(con, getDataSource());
+ }
+ }
+
+ public Object execute(String callString, CallableStatementCallback action) throws DataAccessException {
+ return execute(new SimpleCallableStatementCreator(callString), action);
+ }
+
+ public Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException {
+ final List updateCountParameters = new ArrayList();
+ final List resultSetParameters = new ArrayList();
+ final List callParameters = new ArrayList();
+ for (int i = 0; i < declaredParameters.size(); i++) {
+ SqlParameter parameter = (SqlParameter) declaredParameters.get(i);
+ if (parameter.isResultsParameter()) {
+ if (parameter instanceof SqlReturnResultSet) {
+ resultSetParameters.add(parameter);
+ }
+ else {
+ updateCountParameters.add(parameter);
+ }
+ }
+ else {
+ callParameters.add(parameter);
+ }
+ }
+ return (Map) execute(csc, new CallableStatementCallback() {
+ public Object doInCallableStatement(CallableStatement cs) throws SQLException {
+ boolean retVal = cs.execute();
+ int updateCount = cs.getUpdateCount();
+ if (logger.isDebugEnabled()) {
+ logger.debug("CallableStatement.execute() returned '" + retVal + "'");
+ logger.debug("CallableStatement.getUpdateCount() returned " + updateCount);
+ }
+ Map returnedResults = createResultsMap();
+ if (retVal || updateCount != -1) {
+ returnedResults.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount));
+ }
+ returnedResults.putAll(extractOutputParameters(cs, callParameters));
+ return returnedResults;
+ }
+ });
+ }
+
+ /**
+ * Extract returned ResultSets from the completed stored procedure.
+ * @param cs JDBC wrapper for the stored procedure
+ * @param updateCountParameters Parameter list of declared update count parameters for the stored procedure
+ * @param resultSetParameters Parameter list of declared resturn resultSet parameters for the stored procedure
+ * @return Map that contains returned results
+ */
+ protected Map extractReturnedResults(
+ CallableStatement cs, List updateCountParameters, List resultSetParameters, int updateCount)
+ throws SQLException {
+
+ Map returnedResults = new HashMap();
+ int rsIndex = 0;
+ int updateIndex = 0;
+ boolean moreResults;
+ if (!skipResultsProcessing) {
+ do {
+ if (updateCount == -1) {
+ if (resultSetParameters != null && resultSetParameters.size() > rsIndex) {
+ SqlReturnResultSet declaredRsParam = (SqlReturnResultSet)resultSetParameters.get(rsIndex);
+ returnedResults.putAll(processResultSet(cs.getResultSet(), declaredRsParam));
+ rsIndex++;
+ }
+ else {
+ if (!skipUndeclaredResults) {
+ String rsName = RETURN_RESULT_SET_PREFIX + (rsIndex + 1);
+ SqlReturnResultSet undeclaredRsParam = new SqlReturnResultSet(rsName, new ColumnMapRowMapper());
+ logger.info("Added default SqlReturnResultSet parameter named " + rsName);
+ returnedResults.putAll(processResultSet(cs.getResultSet(), undeclaredRsParam));
+ rsIndex++;
+ }
+ }
+ }
+ else {
+ if (updateCountParameters != null && updateCountParameters.size() > updateIndex) {
+ SqlReturnUpdateCount ucParam = (SqlReturnUpdateCount)updateCountParameters.get(updateIndex);
+ String declaredUcName = ucParam.getName();
+ returnedResults.put(declaredUcName, new Integer(updateCount));
+ updateIndex++;
+ }
+ else {
+ if (!skipUndeclaredResults) {
+ String undeclaredUcName = RETURN_UPDATE_COUNT_PREFIX + (updateIndex + 1);
+ logger.info("Added default SqlReturnUpdateCount parameter named " + undeclaredUcName);
+ returnedResults.put(undeclaredUcName, new Integer(updateCount));
+ updateIndex++;
+ }
+ }
+ }
+ moreResults = cs.getMoreResults();
+ updateCount = cs.getUpdateCount();
+ if (logger.isDebugEnabled()) {
+ logger.debug("CallableStatement.getUpdateCount() returned " + updateCount);
+ }
+ }
+ while (moreResults || updateCount != -1);
+ }
+ return returnedResults;
+ }
+
+ /**
+ * Extract output parameters from the completed stored procedure.
+ * @param cs JDBC wrapper for the stored procedure
+ * @param parameters parameter list for the stored procedure
+ * @return Map that contains returned results
+ */
+ protected Map extractOutputParameters(CallableStatement cs, List parameters) throws SQLException {
+ Map returnedResults = new HashMap();
+ int sqlColIndex = 1;
+ for (int i = 0; i < parameters.size(); i++) {
+ SqlParameter param = (SqlParameter) parameters.get(i);
+ if (param instanceof SqlOutParameter) {
+ SqlOutParameter outParam = (SqlOutParameter) param;
+ if (outParam.isReturnTypeSupported()) {
+ Object out = outParam.getSqlReturnType().getTypeValue(
+ cs, sqlColIndex, outParam.getSqlType(), outParam.getTypeName());
+ returnedResults.put(outParam.getName(), out);
+ }
+ else {
+ Object out = cs.getObject(sqlColIndex);
+ if (out instanceof ResultSet) {
+ if (outParam.isResultSetSupported()) {
+ returnedResults.putAll(processResultSet((ResultSet) out, outParam));
+ }
+ else {
+ String rsName = outParam.getName();
+ SqlReturnResultSet rsParam = new SqlReturnResultSet(rsName, new ColumnMapRowMapper());
+ returnedResults.putAll(processResultSet(cs.getResultSet(), rsParam));
+ logger.info("Added default SqlReturnResultSet parameter named " + rsName);
+ }
+ }
+ else {
+ returnedResults.put(outParam.getName(), out);
+ }
+ }
+ }
+ if (!(param.isResultsParameter())) {
+ sqlColIndex++;
+ }
+ }
+ return returnedResults;
+ }
+
+ /**
+ * Process the given ResultSet from a stored procedure.
+ * @param rs the ResultSet to process
+ * @param param the corresponding stored procedure parameter
+ * @return Map that contains returned results
+ */
+ protected Map processResultSet(ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException {
+ if (rs == null) {
+ return Collections.EMPTY_MAP;
+ }
+ Map returnedResults = new HashMap();
+ try {
+ ResultSet rsToUse = rs;
+ if (this.nativeJdbcExtractor != null) {
+ rsToUse = this.nativeJdbcExtractor.getNativeResultSet(rs);
+ }
+ if (param.getRowMapper() != null) {
+ RowMapper rowMapper = param.getRowMapper();
+ Object result = (new RowMapperResultSetExtractor(rowMapper)).extractData(rsToUse);
+ returnedResults.put(param.getName(), result);
+ }
+ else if (param.getRowCallbackHandler() != null) {
+ RowCallbackHandler rch = param.getRowCallbackHandler();
+ (new RowCallbackHandlerResultSetExtractor(rch)).extractData(rsToUse);
+ returnedResults.put(param.getName(), "ResultSet returned from stored procedure was processed");
+ }
+ else if (param.getResultSetExtractor() != null) {
+ Object result = param.getResultSetExtractor().extractData(rsToUse);
+ returnedResults.put(param.getName(), result);
+ }
+ }
+ finally {
+ JdbcUtils.closeResultSet(rs);
+ }
+ return returnedResults;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Implementation hooks and helper methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Create a new RowMapper for reading columns as key-value pairs.
+ * @return the RowMapper to use
+ * @see ColumnMapRowMapper
+ */
+ protected RowMapper getColumnMapRowMapper() {
+ return new ColumnMapRowMapper();
+ }
+
+ /**
+ * Create a new RowMapper for reading result objects from a single column.
+ * @param requiredType the type that each result object is expected to match
+ * @return the RowMapper to use
+ * @see SingleColumnRowMapper
+ */
+ protected RowMapper getSingleColumnRowMapper(Class requiredType) {
+ return new SingleColumnRowMapper(requiredType);
+ }
+
+ /**
+ * Create a Map instance to be used as results map.
+ *
If "isResultsMapCaseInsensitive" has been set to true, a linked case-insensitive Map
+ * will be created if possible, else a plain HashMap (see Spring's CollectionFactory).
+ * @return the results Map instance
+ * @see #setResultsMapCaseInsensitive
+ * @see org.springframework.core.CollectionFactory#createLinkedCaseInsensitiveMapIfPossible
+ */
+ protected Map createResultsMap() {
+ if (isResultsMapCaseInsensitive()) {
+ return CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(10);
+ }
+ else {
+ return new LinkedHashMap();
+ }
+ }
+
+ /**
+ * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement),
+ * applying statement settings such as fetch size, max rows, and query timeout.
+ * @param stmt the JDBC Statement to prepare
+ * @throws SQLException if thrown by JDBC API
+ * @see #setFetchSize
+ * @see #setMaxRows
+ * @see #setQueryTimeout
+ * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
+ */
+ protected void applyStatementSettings(Statement stmt) throws SQLException {
+ int fetchSize = getFetchSize();
+ if (fetchSize > 0) {
+ stmt.setFetchSize(fetchSize);
+ }
+ int maxRows = getMaxRows();
+ if (maxRows > 0) {
+ stmt.setMaxRows(maxRows);
+ }
+ DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
+ }
+
+ /**
+ * Throw an SQLWarningException if we're not ignoring warnings,
+ * else log the warnings (at debug level).
+ * @param stmt the current JDBC statement
+ * @throws SQLWarningException if not ignoring warnings
+ * @see org.springframework.jdbc.SQLWarningException
+ */
+ protected void handleWarnings(Statement stmt) throws SQLException {
+ if (isIgnoreWarnings()) {
+ if (logger.isDebugEnabled()) {
+ SQLWarning warningToLog = stmt.getWarnings();
+ while (warningToLog != null) {
+ logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
+ warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
+ warningToLog = warningToLog.getNextWarning();
+ }
+ }
+ }
+ else {
+ handleWarnings(stmt.getWarnings());
+ }
+ }
+
+ /**
+ * Throw an SQLWarningException if encountering an actual warning.
+ * @param warning the warnings object from the current statement.
+ * May be null, in which case this method does nothing.
+ * @throws SQLWarningException in case of an actual warning to be raised
+ */
+ protected void handleWarnings(SQLWarning warning) throws SQLWarningException {
+ if (warning != null) {
+ throw new SQLWarningException("Warning not ignored", warning);
+ }
+ }
+
+ /**
+ * Determine SQL from potential provider object.
+ * @param sqlProvider object that's potentially a SqlProvider
+ * @return the SQL string, or null
+ * @see SqlProvider
+ */
+ private static String getSql(Object sqlProvider) {
+ if (sqlProvider instanceof SqlProvider) {
+ return ((SqlProvider) sqlProvider).getSql();
+ }
+ else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Invocation handler that suppresses close calls on JDBC COnnections.
+ * Also prepares returned Statement (Prepared/CallbackStatement) objects.
+ * @see java.sql.Connection#close()
+ */
+ private class CloseSuppressingInvocationHandler implements InvocationHandler {
+
+ private final Connection target;
+
+ public CloseSuppressingInvocationHandler(Connection target) {
+ this.target = target;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Invocation on ConnectionProxy interface coming in...
+
+ if (method.getName().equals("getTargetConnection")) {
+ // Handle getTargetConnection method: return underlying Connection.
+ return this.target;
+ }
+ else if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of PersistenceManager proxy.
+ return new Integer(System.identityHashCode(proxy));
+ }
+ else if (method.getName().equals("close")) {
+ // Handle close method: suppress, not valid.
+ return null;
+ }
+
+ // Invoke method on target Connection.
+ try {
+ Object retVal = method.invoke(this.target, args);
+
+ // If return value is a JDBC Statement, apply statement settings
+ // (fetch size, max rows, transaction timeout).
+ if (retVal instanceof Statement) {
+ applyStatementSettings(((Statement) retVal));
+ }
+
+ return retVal;
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+
+ /**
+ * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement.
+ */
+ private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {
+
+ private final String sql;
+
+ public SimplePreparedStatementCreator(String sql) {
+ Assert.notNull(sql, "SQL must not be null");
+ this.sql = sql;
+ }
+
+ public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
+ return con.prepareStatement(this.sql);
+ }
+
+ public String getSql() {
+ return this.sql;
+ }
+ }
+
+
+ /**
+ * Simple adapter for CallableStatementCreator, allowing to use a plain SQL statement.
+ */
+ private static class SimpleCallableStatementCreator implements CallableStatementCreator, SqlProvider {
+
+ private final String callString;
+
+ public SimpleCallableStatementCreator(String callString) {
+ Assert.notNull(callString, "Call string must not be null");
+ this.callString = callString;
+ }
+
+ public CallableStatement createCallableStatement(Connection con) throws SQLException {
+ return con.prepareCall(this.callString);
+ }
+
+ public String getSql() {
+ return this.callString;
+ }
+ }
+
+
+ /**
+ * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor.
+ *
Uses a regular ResultSet, so we have to be careful when using it:
+ * We don't use it for navigating since this could lead to unpredictable consequences.
+ */
+ private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor {
+
+ private final RowCallbackHandler rch;
+
+ public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
+ this.rch = rch;
+ }
+
+ public Object extractData(ResultSet rs) throws SQLException {
+ while (rs.next()) {
+ this.rch.processRow(rs);
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterDisposer.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterDisposer.java
new file mode 100644
index 0000000000..6687ec1688
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterDisposer.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Interface to be implemented by objects that can close resources
+ * allocated by parameters like SqlLobValues.
+ *
+ *
Typically implemented by PreparedStatementCreators and
+ * PreparedStatementSetters that support DisposableSqlTypeValue
+ * objects (e.g. SqlLobValue) as parameters.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see PreparedStatementCreator
+ * @see PreparedStatementSetter
+ * @see DisposableSqlTypeValue
+ * @see org.springframework.jdbc.core.support.SqlLobValue
+ */
+public interface ParameterDisposer {
+
+ /**
+ * Close the resources allocated by parameters that the implementing
+ * object holds, for example in case of a DisposableSqlTypeValue
+ * (like a SqlLobValue).
+ * @see DisposableSqlTypeValue#cleanup
+ * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup
+ */
+ public void cleanupParameters();
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterMapper.java
new file mode 100644
index 0000000000..a7a9928dea
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ParameterMapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+/**
+ * Implement this interface when parameters need to be customized based
+ * on the connection. We might need to do this to make use of proprietary
+ * features, available only with a specific Connection type.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @see CallableStatementCreatorFactory#newCallableStatementCreator(ParameterMapper)
+ * @see org.springframework.jdbc.object.StoredProcedure#execute(ParameterMapper)
+ */
+public interface ParameterMapper {
+
+ /**
+ * Create a Map of input parameters, keyed by name.
+ * @param con JDBC connection. This is useful (and the purpose of this interface)
+ * if we need to do something RDBMS-specific with a proprietary Connection
+ * implementation class. This class conceals such proprietary details. However,
+ * it is best to avoid using such proprietary RDBMS features if possible.
+ * @throws SQLException if a SQLException is encountered setting
+ * parameter values (that is, there's no need to catch SQLException)
+ * @return Map of input parameters, keyed by name (never null)
+ */
+ Map createMap(Connection con) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java
new file mode 100644
index 0000000000..0e528d85ce
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Generic callback interface for code that operates on a PreparedStatement.
+ * Allows to execute any number of operations on a single PreparedStatement,
+ * for example a single executeUpdate call or repeated
+ * executeUpdate calls with varying parameters.
+ *
+ *
Used internally by JdbcTemplate, but also useful for application code.
+ * Note that the passed-in PreparedStatement can have been created by the
+ * framework or by a custom PreparedStatementCreator. However, the latter is
+ * hardly ever necessary, as most custom callback actions will perform updates
+ * in which case a standard PreparedStatement is fine. Custom actions will
+ * always set parameter values themselves, so that PreparedStatementCreator
+ * capability is not needed either.
+ *
+ * @author Juergen Hoeller
+ * @since 16.03.2004
+ * @see JdbcTemplate#execute(String, PreparedStatementCallback)
+ * @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
+ */
+public interface PreparedStatementCallback {
+
+ /**
+ * Gets called by JdbcTemplate.execute with an active JDBC
+ * PreparedStatement. Does not need to care about closing the Statement
+ * or the Connection, or about handling transactions: this will all be
+ * handled by Spring's JdbcTemplate.
+ *
+ *
NOTE: Any ResultSets opened should be closed in finally blocks
+ * within the callback implementation. Spring will close the Statement
+ * object after the callback returned, but this does not necessarily imply
+ * that the ResultSet resources will be closed: the Statement objects might
+ * get pooled by the connection pool, with close calls only
+ * returning the object to the pool but not physically closing the resources.
+ *
+ *
If called without a thread-bound JDBC transaction (initiated by
+ * DataSourceTransactionManager), the code will simply get executed on the
+ * JDBC connection with its transactional semantics. If JdbcTemplate is
+ * configured to use a JTA-aware DataSource, the JDBC connection and thus
+ * the callback code will be transactional if a JTA transaction is active.
+ *
+ *
Allows for returning a result object created within the callback, i.e.
+ * a domain object or a collection of domain objects. Note that there's
+ * special support for single step actions: see JdbcTemplate.queryForObject etc.
+ * A thrown RuntimeException is treated as application exception, it gets
+ * propagated to the caller of the template.
+ *
+ * @param ps active JDBC PreparedStatement
+ * @return a result object, or null if none
+ * @throws SQLException if thrown by a JDBC method, to be auto-converted
+ * to a DataAccessException by a SQLExceptionTranslator
+ * @throws DataAccessException in case of custom exceptions
+ * @see JdbcTemplate#queryForObject(String, Object[], Class)
+ * @see JdbcTemplate#queryForList(String, Object[])
+ */
+ Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreator.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreator.java
new file mode 100644
index 0000000000..dc92215618
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * One of the two central callback interfaces used by the JdbcTemplate class.
+ * This interface creates a PreparedStatement given a connection, provided
+ * by the JdbcTemplate class. Implementations are responsible for providing
+ * SQL and any necessary parameters.
+ *
+ *
Implementations do not need to concern themselves with
+ * SQLExceptions that may be thrown from operations they attempt.
+ * The JdbcTemplate class will catch and handle SQLExceptions appropriately.
+ *
+ *
A PreparedStatementCreator should also implement the SqlProvider interface
+ * if it is able to provide the SQL it uses for PreparedStatement creation.
+ * This allows for better contextual information in case of exceptions.
+ *
+ * @author Rod Johnson
+ * @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
+ * @see JdbcTemplate#query(PreparedStatementCreator, RowCallbackHandler)
+ * @see JdbcTemplate#update(PreparedStatementCreator)
+ * @see SqlProvider
+ */
+public interface PreparedStatementCreator {
+
+ /**
+ * Create a statement in this connection. Allows implementations to use
+ * PreparedStatements. The JdbcTemplate will close the created statement.
+ * @param con Connection to use to create statement
+ * @return a prepared statement
+ * @throws SQLException there is no need to catch SQLExceptions
+ * that may be thrown in the implementation of this method.
+ * The JdbcTemplate class will handle them.
+ */
+ PreparedStatement createPreparedStatement(Connection con) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java
new file mode 100644
index 0000000000..401dcb0b69
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.dao.InvalidDataAccessResourceUsageException;
+import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class that efficiently creates multiple {@link PreparedStatementCreator}
+ * objects with different parameters based on a SQL statement and a single
+ * set of parameter declarations.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ */
+public class PreparedStatementCreatorFactory {
+
+ /** The SQL, which won't change when the parameters change */
+ private final String sql;
+
+ /** List of SqlParameter objects. May not be null. */
+ private final List declaredParameters;
+
+ private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
+
+ private boolean updatableResults = false;
+
+ private boolean returnGeneratedKeys = false;
+
+ private String[] generatedKeysColumnNames = null;
+
+ private NativeJdbcExtractor nativeJdbcExtractor;
+
+
+ /**
+ * Create a new factory. Will need to add parameters via the
+ * {@link #addParameter} method or have no parameters.
+ */
+ public PreparedStatementCreatorFactory(String sql) {
+ this.sql = sql;
+ this.declaredParameters = new LinkedList();
+ }
+
+ /**
+ * Create a new factory with the given SQL and JDBC types.
+ * @param sql SQL to execute
+ * @param types int array of JDBC types
+ */
+ public PreparedStatementCreatorFactory(String sql, int[] types) {
+ this.sql = sql;
+ this.declaredParameters = SqlParameter.sqlTypesToAnonymousParameterList(types);
+ }
+
+ /**
+ * Create a new factory with the given SQL and parameters.
+ * @param sql SQL
+ * @param declaredParameters list of {@link SqlParameter} objects
+ * @see SqlParameter
+ */
+ public PreparedStatementCreatorFactory(String sql, List declaredParameters) {
+ this.sql = sql;
+ this.declaredParameters = declaredParameters;
+ }
+
+
+ /**
+ * Add a new declared parameter.
+ *
Order of parameter addition is significant.
+ * @param param the parameter to add to the list of declared parameters
+ */
+ public void addParameter(SqlParameter param) {
+ this.declaredParameters.add(param);
+ }
+
+ /**
+ * Set whether to use prepared statements that return a specific type of ResultSet.
+ * @param resultSetType the ResultSet type
+ * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
+ * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
+ * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
+ */
+ public void setResultSetType(int resultSetType) {
+ this.resultSetType = resultSetType;
+ }
+
+ /**
+ * Set whether to use prepared statements capable of returning updatable ResultSets.
+ */
+ public void setUpdatableResults(boolean updatableResults) {
+ this.updatableResults = updatableResults;
+ }
+
+ /**
+ * Set whether prepared statements should be capable of returning auto-generated keys.
+ */
+ public void setReturnGeneratedKeys(boolean returnGeneratedKeys) {
+ this.returnGeneratedKeys = returnGeneratedKeys;
+ }
+
+ /**
+ * Set the column names of the auto-generated keys.
+ */
+ public void setGeneratedKeysColumnNames(String[] names) {
+ this.generatedKeysColumnNames = names;
+ }
+
+ /**
+ * Specify the NativeJdbcExtractor to use for unwrapping PreparedStatements, if any.
+ */
+ public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
+ this.nativeJdbcExtractor = nativeJdbcExtractor;
+ }
+
+
+ /**
+ * Return a new PreparedStatementSetter for the given parameters.
+ * @param params list of parameters (may be null)
+ */
+ public PreparedStatementSetter newPreparedStatementSetter(List params) {
+ return new PreparedStatementCreatorImpl(params != null ? params : Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Return a new PreparedStatementSetter for the given parameters.
+ * @param params the parameter array (may be null)
+ */
+ public PreparedStatementSetter newPreparedStatementSetter(Object[] params) {
+ return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Return a new PreparedStatementCreator for the given parameters.
+ * @param params list of parameters (may be null)
+ */
+ public PreparedStatementCreator newPreparedStatementCreator(List params) {
+ return new PreparedStatementCreatorImpl(params != null ? params : Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Return a new PreparedStatementCreator for the given parameters.
+ * @param params the parameter array (may be null)
+ */
+ public PreparedStatementCreator newPreparedStatementCreator(Object[] params) {
+ return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Return a new PreparedStatementCreator for the given parameters.
+ * @param sqlToUse the actual SQL statement to use (if different from
+ * the factory's, for example because of named parameter expanding)
+ * @param params the parameter array (may be null)
+ */
+ public PreparedStatementCreator newPreparedStatementCreator(String sqlToUse, Object[] params) {
+ return new PreparedStatementCreatorImpl(
+ sqlToUse, params != null ? Arrays.asList(params) : Collections.EMPTY_LIST);
+ }
+
+
+ /**
+ * PreparedStatementCreator implementation returned by this class.
+ */
+ private class PreparedStatementCreatorImpl
+ implements PreparedStatementCreator, PreparedStatementSetter, SqlProvider, ParameterDisposer {
+
+ private final String actualSql;
+
+ private final List parameters;
+
+ public PreparedStatementCreatorImpl(List parameters) {
+ this(sql, parameters);
+ }
+
+ public PreparedStatementCreatorImpl(String actualSql, List parameters) {
+ this.actualSql = actualSql;
+ Assert.notNull(parameters, "Parameters List must not be null");
+ this.parameters = parameters;
+ if (this.parameters.size() != declaredParameters.size()) {
+ // account for named parameters being used multiple times
+ Set names = new HashSet();
+ for (int i = 0; i < parameters.size(); i++) {
+ Object o = parameters.get(i);
+ if (o instanceof SqlParameterValue) {
+ names.add(((SqlParameterValue)o).getName());
+ }
+ else {
+ names.add("Parameter #" + i);
+ }
+ }
+ if (names.size() != declaredParameters.size()) {
+ throw new InvalidDataAccessApiUsageException(
+ "SQL [" + sql + "]: given " + names.size() +
+ " parameters but expected " + declaredParameters.size());
+ }
+ }
+ }
+
+ public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
+ PreparedStatement ps = null;
+ if (generatedKeysColumnNames != null || returnGeneratedKeys) {
+ try {
+ if (generatedKeysColumnNames != null) {
+ ps = con.prepareStatement(this.actualSql, generatedKeysColumnNames);
+ }
+ else {
+ ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS);
+ }
+ }
+ catch (AbstractMethodError ex) {
+ throw new InvalidDataAccessResourceUsageException(
+ "The JDBC driver is not compliant to JDBC 3.0 and thus " +
+ "does not support retrieval of auto-generated keys", ex);
+ }
+ }
+ else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) {
+ ps = con.prepareStatement(this.actualSql);
+ }
+ else {
+ ps = con.prepareStatement(this.actualSql, resultSetType,
+ updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
+ }
+ setValues(ps);
+ return ps;
+ }
+
+ public void setValues(PreparedStatement ps) throws SQLException {
+ // Determine PreparedStatement to pass to custom types.
+ PreparedStatement psToUse = ps;
+ if (nativeJdbcExtractor != null) {
+ psToUse = nativeJdbcExtractor.getNativePreparedStatement(ps);
+ }
+
+ // Set arguments: Does nothing if there are no parameters.
+ int sqlColIndx = 1;
+ for (int i = 0; i < this.parameters.size(); i++) {
+ Object in = this.parameters.get(i);
+ SqlParameter declaredParameter = null;
+ // SqlParameterValue overrides declared parameter metadata, in particular for
+ // independence from the declared parameter position in case of named parameters.
+ if (in instanceof SqlParameterValue) {
+ SqlParameterValue paramValue = (SqlParameterValue) in;
+ in = paramValue.getValue();
+ declaredParameter = paramValue;
+ }
+ else {
+ if (declaredParameters.size() <= i) {
+ throw new InvalidDataAccessApiUsageException(
+ "SQL [" + sql + "]: unable to access parameter number " + (i + 1) +
+ " given only " + declaredParameters.size() + " parameters");
+
+ }
+ declaredParameter = (SqlParameter) declaredParameters.get(i);
+ }
+ if (in instanceof Collection && declaredParameter.getSqlType() != Types.ARRAY) {
+ Collection entries = (Collection) in;
+ for (Iterator it = entries.iterator(); it.hasNext();) {
+ Object entry = it.next();
+ if (entry instanceof Object[]) {
+ Object[] valueArray = ((Object[])entry);
+ for (int k = 0; k < valueArray.length; k++) {
+ Object argValue = valueArray[k];
+ StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, argValue);
+ }
+ }
+ else {
+ StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, entry);
+ }
+ }
+ }
+ else {
+ StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, in);
+ }
+ }
+ }
+
+ public String getSql() {
+ return sql;
+ }
+
+ public void cleanupParameters() {
+ StatementCreatorUtils.cleanupParameters(this.parameters);
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("PreparedStatementCreatorFactory.PreparedStatementCreatorImpl: sql=[");
+ buf.append(sql).append("]; parameters=").append(this.parameters);
+ return buf.toString();
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementSetter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementSetter.java
new file mode 100644
index 0000000000..fb12a1b48d
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementSetter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * General callback interface used by the {@link JdbcTemplate} class.
+ *
+ *
This interface sets values on a {@link java.sql.PreparedStatement} provided
+ * by the JdbcTemplate class, for each of a number of updates in a batch using the
+ * same SQL. Implementations are responsible for setting any necessary parameters.
+ * SQL with placeholders will already have been supplied.
+ *
+ *
It's easier to use this interface than {@link PreparedStatementCreator}:
+ * The JdbcTemplate will create the PreparedStatement, with the callback
+ * only being responsible for setting parameter values.
+ *
+ *
Implementations do not need to concern themselves with
+ * SQLExceptions that may be thrown from operations they attempt.
+ * The JdbcTemplate class will catch and handle SQLExceptions appropriately.
+ *
+ * @author Rod Johnson
+ * @since March 2, 2003
+ * @see JdbcTemplate#update(String, PreparedStatementSetter)
+ * @see JdbcTemplate#query(String, PreparedStatementSetter, ResultSetExtractor)
+ */
+public interface PreparedStatementSetter {
+
+ /**
+ * Set parameter values on the given PreparedStatement.
+ * @param ps the PreparedStatement to invoke setter methods on
+ * @throws SQLException if a SQLException is encountered
+ * (i.e. there is no need to catch SQLException)
+ */
+ void setValues(PreparedStatement ps) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java
new file mode 100644
index 0000000000..118bc9d0b2
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Callback interface used by {@link JdbcTemplate}'s query methods.
+ * Implementations of this interface perform the actual work of extracting
+ * results from a {@link java.sql.ResultSet}, but don't need to worry
+ * about exception handling. {@link java.sql.SQLException SQLExceptions}
+ * will be caught and handled by the calling JdbcTemplate.
+ *
+ *
This interface is mainly used within the JDBC framework itself.
+ * A {@link RowMapper} is usually a simpler choice for ResultSet processing,
+ * mapping one result object per row instead of one result object for
+ * the entire ResultSet.
+ *
+ *
Note: In contrast to a {@link RowCallbackHandler}, a ResultSetExtractor
+ * object is typically stateless and thus reusable, as long as it doesn't
+ * access stateful resources (such as output streams when streaming LOB
+ * contents) or keep result state within the object.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since April 24, 2003
+ * @see JdbcTemplate
+ * @see RowCallbackHandler
+ * @see RowMapper
+ * @see org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor
+ */
+public interface ResultSetExtractor {
+
+ /**
+ * Implementations must implement this method to process the entire ResultSet.
+ * @param rs ResultSet to extract data from. Implementations should
+ * not close this: it will be closed by the calling JdbcTemplate.
+ * @return an arbitrary result object, or null if none
+ * (the extractor will typically be stateful in the latter case).
+ * @throws SQLException if a SQLException is encountered getting column
+ * values or navigating (that is, there's no need to catch SQLException)
+ * @throws DataAccessException in case of custom exceptions
+ */
+ Object extractData(ResultSet rs) throws SQLException, DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetSupportingSqlParameter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetSupportingSqlParameter.java
new file mode 100644
index 0000000000..003a7d6fa1
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/ResultSetSupportingSqlParameter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Common base class for ResultSet-supporting SqlParameters like
+ * {@link SqlOutParameter} and {@link SqlReturnResultSet}.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ */
+public class ResultSetSupportingSqlParameter extends SqlParameter {
+
+ private ResultSetExtractor resultSetExtractor;
+
+ private RowCallbackHandler rowCallbackHandler;
+
+ private RowMapper rowMapper;
+
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType) {
+ super(name, sqlType);
+ }
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType, int scale) {
+ super(name, sqlType, scale);
+ }
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType, String typeName) {
+ super(name, sqlType, typeName);
+ }
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rse ResultSetExtractor to use for parsing the ResultSet
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType, ResultSetExtractor rse) {
+ super(name, sqlType);
+ this.resultSetExtractor = rse;
+ }
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rch RowCallbackHandler to use for parsing the ResultSet
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType, RowCallbackHandler rch) {
+ super(name, sqlType);
+ this.rowCallbackHandler = rch;
+ }
+
+ /**
+ * Create a new ResultSetSupportingSqlParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rm RowMapper to use for parsing the ResultSet
+ */
+ public ResultSetSupportingSqlParameter(String name, int sqlType, RowMapper rm) {
+ super(name, sqlType);
+ this.rowMapper = rm;
+ }
+
+
+ /**
+ * Does this parameter support a ResultSet, i.e. does it hold a
+ * ResultSetExtractor, RowCallbackHandler or RowMapper?
+ */
+ public boolean isResultSetSupported() {
+ return (this.resultSetExtractor != null || this.rowCallbackHandler != null || this.rowMapper != null);
+ }
+
+ /**
+ * Return the ResultSetExtractor held by this parameter, if any.
+ */
+ public ResultSetExtractor getResultSetExtractor() {
+ return resultSetExtractor;
+ }
+
+ /**
+ * Return the RowCallbackHandler held by this parameter, if any.
+ */
+ public RowCallbackHandler getRowCallbackHandler() {
+ return this.rowCallbackHandler;
+ }
+
+ /**
+ * Return the RowMapper held by this parameter, if any.
+ */
+ public RowMapper getRowMapper() {
+ return this.rowMapper;
+ }
+
+
+ /**
+ *
This implementation always returns false.
+ */
+ public boolean isInputValueProvided() {
+ return false;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCallbackHandler.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCallbackHandler.java
new file mode 100644
index 0000000000..4d34ccffe8
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCallbackHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * An interface used by {@link JdbcTemplate} for processing rows of a
+ * {@link java.sql.ResultSet} on a per-row basis. Implementations of
+ * this interface perform the actual work of processing each row
+ * but don't need to worry about exception handling.
+ * {@link java.sql.SQLException SQLExceptions} will be caught and handled
+ * by the calling JdbcTemplate.
+ *
+ *
In contrast to a {@link ResultSetExtractor}, a RowCallbackHandler
+ * object is typically stateful: It keeps the result state within the
+ * object, to be available for later inspection. See
+ * {@link RowCountCallbackHandler} for a usage example.
+ *
+ *
Consider using a {@link RowMapper} instead if you need to map
+ * exactly one result object per row, assembling them into a List.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see JdbcTemplate
+ * @see RowMapper
+ * @see ResultSetExtractor
+ * @see RowCountCallbackHandler
+ */
+public interface RowCallbackHandler {
+
+ /**
+ * Implementations must implement this method to process each row of data
+ * in the ResultSet. This method should not call next() on
+ * the ResultSet; it is only supposed to extract values of the current row.
+ *
Exactly what the implementation chooses to do is up to it:
+ * A trivial implementation might simply count rows, while another
+ * implementation might build an XML document.
+ * @param rs the ResultSet to process (pre-initialized for the current row)
+ * @throws SQLException if a SQLException is encountered getting
+ * column values (that is, there's no need to catch SQLException)
+ */
+ void processRow(ResultSet rs) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCountCallbackHandler.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCountCallbackHandler.java
new file mode 100644
index 0000000000..88efe072ec
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowCountCallbackHandler.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import org.springframework.jdbc.support.JdbcUtils;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+/**
+ * Implementation of RowCallbackHandler. Convenient superclass for callback handlers.
+ * An instance can only be used once.
+ *
+ *
We can either use this on its own (for example, in a test case, to ensure
+ * that our result sets have valid dimensions), or use it as a superclass
+ * for callback handlers that actually do something, and will benefit
+ * from the dimension information it provides.
+ *
+ *
A usage example with JdbcTemplate:
+ *
+ *
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); // reusable object
+ *
+ * RowCountCallbackHandler countCallback = new RowCountCallbackHandler(); // not reusable
+ * jdbcTemplate.query("select * from user", countCallback);
+ * int rowCount = countCallback.getRowCount();
+ *
+ * @author Rod Johnson
+ * @since May 3, 2001
+ */
+public class RowCountCallbackHandler implements RowCallbackHandler {
+
+ /** Rows we've seen so far */
+ private int rowCount;
+
+ /** Columns we've seen so far */
+ private int columnCount;
+
+ /**
+ * Indexed from 0. Type (as in java.sql.Types) for the columns
+ * as returned by ResultSetMetaData object.
+ */
+ private int[] columnTypes;
+
+ /**
+ * Indexed from 0. Column name as returned by ResultSetMetaData object.
+ */
+ private String[] columnNames;
+
+
+ /**
+ * Implementation of ResultSetCallbackHandler.
+ * Work out column size if this is the first row, otherwise just count rows.
+ *
Subclasses can perform custom extraction or processing
+ * by overriding the processRow(ResultSet, int) method.
+ * @see #processRow(java.sql.ResultSet, int)
+ */
+ public final void processRow(ResultSet rs) throws SQLException {
+ if (this.rowCount == 0) {
+ ResultSetMetaData rsmd = rs.getMetaData();
+ this.columnCount = rsmd.getColumnCount();
+ this.columnTypes = new int[this.columnCount];
+ this.columnNames = new String[this.columnCount];
+ for (int i = 0; i < this.columnCount; i++) {
+ this.columnTypes[i] = rsmd.getColumnType(i + 1);
+ this.columnNames[i] = JdbcUtils.lookupColumnName(rsmd, i + 1);
+ }
+ // could also get column names
+ }
+ processRow(rs, this.rowCount++);
+ }
+
+ /**
+ * Subclasses may override this to perform custom extraction
+ * or processing. This class's implementation does nothing.
+ * @param rs ResultSet to extract data from. This method is
+ * invoked for each row
+ * @param rowNum number of the current row (starting from 0)
+ */
+ protected void processRow(ResultSet rs, int rowNum) throws SQLException {
+ }
+
+
+ /**
+ * Return the types of the columns as java.sql.Types constants
+ * Valid after processRow is invoked the first time.
+ * @return the types of the columns as java.sql.Types constants.
+ * Indexed from 0 to n-1.
+ */
+ public final int[] getColumnTypes() {
+ return columnTypes;
+ }
+
+ /**
+ * Return the names of the columns.
+ * Valid after processRow is invoked the first time.
+ * @return the names of the columns.
+ * Indexed from 0 to n-1.
+ */
+ public final String[] getColumnNames() {
+ return columnNames;
+ }
+
+ /**
+ * Return the row count of this ResultSet
+ * Only valid after processing is complete
+ * @return the number of rows in this ResultSet
+ */
+ public final int getRowCount() {
+ return rowCount;
+ }
+
+ /**
+ * Return the number of columns in this result set.
+ * Valid once we've seen the first row,
+ * so subclasses can use it during processing
+ * @return the number of columns in this result set
+ */
+ public final int getColumnCount() {
+ return columnCount;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java
new file mode 100644
index 0000000000..1b439c9048
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * An interface used by {@link JdbcTemplate} for mapping rows of a
+ * {@link java.sql.ResultSet} on a per-row basis. Implementations of this
+ * interface perform the actual work of mapping each row to a result object,
+ * but don't need to worry about exception handling.
+ * {@link java.sql.SQLException SQLExceptions} will be caught and handled
+ * by the calling JdbcTemplate.
+ *
+ *
Typically used either for {@link JdbcTemplate}'s query methods
+ * or for out parameters of stored procedures. RowMapper objects are
+ * typically stateless and thus reusable; they are an ideal choice for
+ * implementing row-mapping logic in a single place.
+ *
+ *
Alternatively, consider subclassing
+ * {@link org.springframework.jdbc.object.MappingSqlQuery} from the
+ * jdbc.object package: Instead of working with separate
+ * JdbcTemplate and RowMapper objects, you can build executable query
+ * objects (containing row-mapping logic) in that style.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @see JdbcTemplate
+ * @see RowCallbackHandler
+ * @see ResultSetExtractor
+ * @see org.springframework.jdbc.object.MappingSqlQuery
+ */
+public interface RowMapper {
+
+ /**
+ * Implementations must implement this method to map each row of data
+ * in the ResultSet. This method should not call next() on
+ * the ResultSet; it is only supposed to map values of the current row.
+ * @param rs the ResultSet to map (pre-initialized for the current row)
+ * @param rowNum the number of the current row
+ * @return the result object for the current row
+ * @throws SQLException if a SQLException is encountered getting
+ * column values (that is, there's no need to catch SQLException)
+ */
+ Object mapRow(ResultSet rs, int rowNum) throws SQLException;
+
+}
+
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java
new file mode 100644
index 0000000000..989fc5b57f
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * Adapter implementation of the ResultSetExtractor interface that delegates
+ * to a RowMapper which is supposed to create an object for each row.
+ * Each object is added to the results List of this ResultSetExtractor.
+ *
+ *
Useful for the typical case of one object per row in the database table.
+ * The number of entries in the results list will match the number of rows.
+ *
+ *
Note that a RowMapper object is typically stateless and thus reusable;
+ * just the RowMapperResultSetExtractor adapter is stateful.
+ *
+ *
A usage example with JdbcTemplate:
+ *
+ *
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); // reusable object
+ * RowMapper rowMapper = new UserRowMapper(); // reusable object
+ *
+ * List allUsers = (List) jdbcTemplate.query(
+ * "select * from user",
+ * new RowMapperResultSetExtractor(rowMapper, 10));
+ *
+ * User user = (User) jdbcTemplate.queryForObject(
+ * "select * from user where id=?", new Object[] {id},
+ * new RowMapperResultSetExtractor(rowMapper, 1));
+ *
+ *
Alternatively, consider subclassing MappingSqlQuery from the jdbc.object
+ * package: Instead of working with separate JdbcTemplate and RowMapper objects,
+ * you can have executable query objects (containing row-mapping logic) there.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see RowMapper
+ * @see JdbcTemplate
+ * @see org.springframework.jdbc.object.MappingSqlQuery
+ */
+public class RowMapperResultSetExtractor implements ResultSetExtractor {
+
+ private final RowMapper rowMapper;
+
+ private final int rowsExpected;
+
+
+ /**
+ * Create a new RowMapperResultSetExtractor.
+ * @param rowMapper the RowMapper which creates an object for each row
+ */
+ public RowMapperResultSetExtractor(RowMapper rowMapper) {
+ this(rowMapper, 0);
+ }
+
+ /**
+ * Create a new RowMapperResultSetExtractor.
+ * @param rowMapper the RowMapper which creates an object for each row
+ * @param rowsExpected the number of expected rows
+ * (just used for optimized collection handling)
+ */
+ public RowMapperResultSetExtractor(RowMapper rowMapper, int rowsExpected) {
+ Assert.notNull(rowMapper, "RowMapper is required");
+ this.rowMapper = rowMapper;
+ this.rowsExpected = rowsExpected;
+ }
+
+
+ public Object extractData(ResultSet rs) throws SQLException {
+ List results = (this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList());
+ int rowNum = 0;
+ while (rs.next()) {
+ results.add(this.rowMapper.mapRow(rs, rowNum++));
+ }
+ return results;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java
new file mode 100644
index 0000000000..87eab93d2a
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import org.springframework.dao.TypeMismatchDataAccessException;
+import org.springframework.jdbc.IncorrectResultSetColumnCountException;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.util.NumberUtils;
+
+/**
+ * {@link RowMapper} implementation that converts a single column into a single
+ * result value per row. Expects to operate on a java.sql.ResultSet
+ * that just contains a single column.
+ *
+ *
The type of the result value for each row can be specified. The value
+ * for the single column will be extracted from the ResultSet
+ * and converted into the specified target type.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see JdbcTemplate#queryForList(String, Class)
+ * @see JdbcTemplate#queryForObject(String, Class)
+ */
+public class SingleColumnRowMapper implements RowMapper {
+
+ private Class requiredType;
+
+
+ /**
+ * Create a new SingleColumnRowMapper.
+ * @see #setRequiredType
+ */
+ public SingleColumnRowMapper() {
+ }
+
+ /**
+ * Create a new SingleColumnRowMapper.
+ * @param requiredType the type that each result object is expected to match
+ */
+ public SingleColumnRowMapper(Class requiredType) {
+ this.requiredType = requiredType;
+ }
+
+ /**
+ * Set the type that each result object is expected to match.
+ *
If not specified, the column value will be exposed as
+ * returned by the JDBC driver.
+ */
+ public void setRequiredType(Class requiredType) {
+ this.requiredType = requiredType;
+ }
+
+
+ /**
+ * Extract a value for the single column in the current row.
+ *
Validates that there is only one column selected,
+ * then delegates to getColumnValue() and also
+ * convertValueToRequiredType, if necessary.
+ * @see java.sql.ResultSetMetaData#getColumnCount()
+ * @see #getColumnValue(java.sql.ResultSet, int, Class)
+ * @see #convertValueToRequiredType(Object, Class)
+ */
+ public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+ // Validate column count.
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int nrOfColumns = rsmd.getColumnCount();
+ if (nrOfColumns != 1) {
+ throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
+ }
+
+ // Extract column value from JDBC ResultSet.
+ Object result = getColumnValue(rs, 1, this.requiredType);
+ if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
+ // Extracted value does not match already: try to convert it.
+ try {
+ return convertValueToRequiredType(result, this.requiredType);
+ }
+ catch (IllegalArgumentException ex) {
+ throw new TypeMismatchDataAccessException(
+ "Type mismatch affecting row number " + rowNum + " and column type '" +
+ rsmd.getColumnTypeName(1) + "': " + ex.getMessage());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve a JDBC object value for the specified column.
+ *
The default implementation calls
+ * {@link JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)}.
+ * If no required type has been specified, this method delegates to
+ * getColumnValue(rs, index), which basically calls
+ * ResultSet.getObject(index) but applies some additional
+ * default conversion to appropriate value types.
+ * @param rs is the ResultSet holding the data
+ * @param index is the column index
+ * @param requiredType the type that each result object is expected to match
+ * (or null if none specified)
+ * @return the Object value
+ * @throws SQLException in case of extraction failure
+ * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)
+ * @see #getColumnValue(java.sql.ResultSet, int)
+ */
+ protected Object getColumnValue(ResultSet rs, int index, Class requiredType) throws SQLException {
+ if (requiredType != null) {
+ return JdbcUtils.getResultSetValue(rs, index, requiredType);
+ }
+ else {
+ // No required type specified -> perform default extraction.
+ return getColumnValue(rs, index);
+ }
+ }
+
+ /**
+ * Retrieve a JDBC object value for the specified column, using the most
+ * appropriate value type. Called if no required type has been specified.
+ *
The default implementation delegates to JdbcUtils.getResultSetValue(),
+ * which uses the ResultSet.getObject(index) method. Additionally,
+ * it includes a "hack" to get around Oracle returning a non-standard object for
+ * their TIMESTAMP datatype. See the JdbcUtils#getResultSetValue()
+ * javadoc for details.
+ * @param rs is the ResultSet holding the data
+ * @param index is the column index
+ * @return the Object value
+ * @throws SQLException in case of extraction failure
+ * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue(java.sql.ResultSet, int)
+ */
+ protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
+ return JdbcUtils.getResultSetValue(rs, index);
+ }
+
+ /**
+ * Convert the given column value to the specified required type.
+ * Only called if the extracted column value does not match already.
+ *
If the required type is String, the value will simply get stringified
+ * via toString(). In case of a Number, the value will be
+ * converted into a Number, either through number conversion or through
+ * String parsing (depending on the value type).
+ * @param value the column value as extracted from getColumnValue()
+ * (never null)
+ * @param requiredType the type that each result object is expected to match
+ * (never null)
+ * @return the converted value
+ * @see #getColumnValue(java.sql.ResultSet, int, Class)
+ */
+ protected Object convertValueToRequiredType(Object value, Class requiredType) {
+ if (String.class.equals(requiredType)) {
+ return value.toString();
+ }
+ else if (Number.class.isAssignableFrom(requiredType)) {
+ if (value instanceof Number) {
+ // Convert original Number to target Number class.
+ return NumberUtils.convertNumberToTargetClass(((Number) value), requiredType);
+ }
+ else {
+ // Convert stringified value to target Number class.
+ return NumberUtils.parseNumber(value.toString(), requiredType);
+ }
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Value [" + value + "] is of type [" + value.getClass().getName() +
+ "] and cannot be converted to required type [" + requiredType.getName() + "]");
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlInOutParameter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlInOutParameter.java
new file mode 100644
index 0000000000..c236e9f024
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlInOutParameter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Subclass of {@link SqlOutParameter} to represent an INOUT parameter.
+ * Will return true for SqlParameter's {@link #isInputValueProvided}
+ * test, in contrast to a standard SqlOutParameter.
+ *
+ *
Output parameters - like all stored procedure parameters -
+ * must have names.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class SqlInOutParameter extends SqlOutParameter {
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ */
+ public SqlInOutParameter(String name, int sqlType) {
+ super(name, sqlType);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ */
+ public SqlInOutParameter(String name, int sqlType, int scale) {
+ super(name, sqlType, scale);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ */
+ public SqlInOutParameter(String name, int sqlType, String typeName) {
+ super(name, sqlType, typeName);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ * @param sqlReturnType custom value handler for complex type (optional)
+ */
+ public SqlInOutParameter(String name, int sqlType, String typeName, SqlReturnType sqlReturnType) {
+ super(name, sqlType, typeName, sqlReturnType);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rse ResultSetExtractor to use for parsing the ResultSet
+ */
+ public SqlInOutParameter(String name, int sqlType, ResultSetExtractor rse) {
+ super(name, sqlType, rse);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rch RowCallbackHandler to use for parsing the ResultSet
+ */
+ public SqlInOutParameter(String name, int sqlType, RowCallbackHandler rch) {
+ super(name, sqlType, rch);
+ }
+
+ /**
+ * Create a new SqlInOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rm RowMapper to use for parsing the ResultSet
+ */
+ public SqlInOutParameter(String name, int sqlType, RowMapper rm) {
+ super(name, sqlType, rm);
+ }
+
+
+ /**
+ * This implementation always returns true.
+ */
+ public boolean isInputValueProvided() {
+ return true;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlOutParameter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlOutParameter.java
new file mode 100644
index 0000000000..9454e2bdaf
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlOutParameter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Subclass of SqlParameter to represent an output parameter.
+ * No additional properties: instanceof will be used to check
+ * for such types.
+ *
+ *
Output parameters - like all stored procedure parameters -
+ * must have names.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @see SqlReturnResultSet
+ * @see SqlInOutParameter
+ */
+public class SqlOutParameter extends ResultSetSupportingSqlParameter {
+
+ private SqlReturnType sqlReturnType;
+
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ */
+ public SqlOutParameter(String name, int sqlType) {
+ super(name, sqlType);
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ */
+ public SqlOutParameter(String name, int sqlType, int scale) {
+ super(name, sqlType, scale);
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ */
+ public SqlOutParameter(String name, int sqlType, String typeName) {
+ super(name, sqlType, typeName);
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ * @param sqlReturnType custom value handler for complex type (optional)
+ */
+ public SqlOutParameter(String name, int sqlType, String typeName, SqlReturnType sqlReturnType) {
+ super(name, sqlType, typeName);
+ this.sqlReturnType = sqlReturnType;
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rse ResultSetExtractor to use for parsing the ResultSet
+ */
+ public SqlOutParameter(String name, int sqlType, ResultSetExtractor rse) {
+ super(name, sqlType, rse);
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rch RowCallbackHandler to use for parsing the ResultSet
+ */
+ public SqlOutParameter(String name, int sqlType, RowCallbackHandler rch) {
+ super(name, sqlType, rch);
+ }
+
+ /**
+ * Create a new SqlOutParameter.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param rm RowMapper to use for parsing the ResultSet
+ */
+ public SqlOutParameter(String name, int sqlType, RowMapper rm) {
+ super(name, sqlType, rm);
+ }
+
+
+ /**
+ * Return the custom return type, if any.
+ */
+ public SqlReturnType getSqlReturnType() {
+ return this.sqlReturnType;
+ }
+
+ /**
+ * Return whether this parameter holds a custom return type.
+ */
+ public boolean isReturnTypeSupported() {
+ return (this.sqlReturnType != null);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameter.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameter.java
new file mode 100644
index 0000000000..b2f86e37bd
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameter.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * Object to represent a SQL parameter definition.
+ *
+ *
Parameters may be anonymous, in which case "name" is null.
+ * However, all parameters must define a SQL type according to {@link java.sql.Types}.
+ *
+ * @author Rod Johnson
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @see java.sql.Types
+ */
+public class SqlParameter {
+
+ /** The name of the parameter, if any */
+ private String name;
+
+ /** SQL type constant from java.sql.Types */
+ private final int sqlType;
+
+ /** Used for types that are user-named like: STRUCT, DISTINCT, JAVA_OBJECT, named array types */
+ private String typeName;
+
+
+ /** The scale to apply in case of a NUMERIC or DECIMAL type, if any */
+ private Integer scale;
+
+
+ /**
+ * Create a new anonymous SqlParameter, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ */
+ public SqlParameter(int sqlType) {
+ this.sqlType = sqlType;
+ }
+
+ /**
+ * Create a new anonymous SqlParameter, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ */
+ public SqlParameter(int sqlType, String typeName) {
+ this.sqlType = sqlType;
+ this.typeName = typeName;
+ }
+
+ /**
+ * Create a new anonymous SqlParameter, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ */
+ public SqlParameter(int sqlType, int scale) {
+ this.sqlType = sqlType;
+ this.scale = new Integer(scale);
+ }
+
+ /**
+ * Create a new SqlParameter, supplying name and SQL type.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ */
+ public SqlParameter(String name, int sqlType) {
+ this.name = name;
+ this.sqlType = sqlType;
+ }
+
+ /**
+ * Create a new SqlParameter, supplying name and SQL type.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ */
+ public SqlParameter(String name, int sqlType, String typeName) {
+ this.name = name;
+ this.sqlType = sqlType;
+ this.typeName = typeName;
+ }
+
+ /**
+ * Create a new SqlParameter, supplying name and SQL type.
+ * @param name name of the parameter, as used in input and output maps
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ */
+ public SqlParameter(String name, int sqlType, int scale) {
+ this.name = name;
+ this.sqlType = sqlType;
+ this.scale = new Integer(scale);
+ }
+
+ /**
+ * Copy constructor.
+ * @param otherParam the SqlParameter object to copy from
+ */
+ public SqlParameter(SqlParameter otherParam) {
+ Assert.notNull(otherParam, "SqlParameter object must not be null");
+ this.name = otherParam.name;
+ this.sqlType = otherParam.sqlType;
+ this.typeName = otherParam.typeName;
+ this.scale = otherParam.scale;
+ }
+
+
+ /**
+ * Return the name of the parameter.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Return the SQL type of the parameter.
+ */
+ public int getSqlType() {
+ return this.sqlType;
+ }
+
+ /**
+ * Return the type name of the parameter, if any.
+ */
+ public String getTypeName() {
+ return this.typeName;
+ }
+
+ /**
+ * Return the scale of the parameter, if any.
+ */
+ public Integer getScale() {
+ return this.scale;
+ }
+
+
+ /**
+ * Return whether this parameter holds input values that should be set
+ * before execution even if they are null.
+ *
This implementation always returns true.
+ */
+ public boolean isInputValueProvided() {
+ return true;
+ }
+
+ /**
+ * Return whether this parameter is an implicit return parameter used during the
+ * results preocessing of the CallableStatement.getMoreResults/getUpdateCount.
+ *
This implementation always returns false.
+ */
+ public boolean isResultsParameter() {
+ return false;
+ }
+
+
+ /**
+ * Convert a list of JDBC types, as defined in java.sql.Types,
+ * to a List of SqlParameter objects as used in this package.
+ */
+ public static List sqlTypesToAnonymousParameterList(int[] types) {
+ List result = new LinkedList();
+ if (types != null) {
+ for (int i = 0; i < types.length; i++) {
+ result.add(new SqlParameter(types[i]));
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameterValue.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameterValue.java
new file mode 100644
index 0000000000..088b756e76
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlParameterValue.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Object to represent a SQL parameter value, including parameter metadata
+ * such as the SQL type and the scale for numeric values.
+ *
+ *
Designed for use with {@link JdbcTemplate}'s operations that take an array of
+ * argument values: Each such argument value may be a SqlParameterValue,
+ * indicating the SQL type (and optionally the scale) instead of letting the
+ * template guess a default type. Note that this only applies to the operations with
+ * a 'plain' argument array, not to the overloaded variants with an explicit type array.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.5
+ * @see java.sql.Types
+ * @see JdbcTemplate#query(String, Object[], ResultSetExtractor)
+ * @see JdbcTemplate#query(String, Object[], RowCallbackHandler)
+ * @see JdbcTemplate#query(String, Object[], RowMapper)
+ * @see JdbcTemplate#update(String, Object[])
+ */
+public class SqlParameterValue extends SqlParameter {
+
+ private final Object value;
+
+
+ /**
+ * Create a new SqlParameterValue, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param value the value object
+ */
+ public SqlParameterValue(int sqlType, Object value) {
+ super(sqlType);
+ this.value = value;
+ }
+
+ /**
+ * Create a new SqlParameterValue, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param typeName the type name of the parameter (optional)
+ * @param value the value object
+ */
+ public SqlParameterValue(int sqlType, String typeName, Object value) {
+ super(sqlType, typeName);
+ this.value = value;
+ }
+
+ /**
+ * Create a new SqlParameterValue, supplying the SQL type.
+ * @param sqlType SQL type of the parameter according to java.sql.Types
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ * @param value the value object
+ */
+ public SqlParameterValue(int sqlType, int scale, Object value) {
+ super(sqlType, scale);
+ this.value = value;
+ }
+
+ /**
+ * Create a new SqlParameterValue based on the given SqlParameter declaration.
+ * @param declaredParam the declared SqlParameter to define a value for
+ * @param value the value object
+ */
+ public SqlParameterValue(SqlParameter declaredParam, Object value) {
+ super(declaredParam);
+ this.value = value;
+ }
+
+
+ /**
+ * Return the value object that this parameter value holds.
+ */
+ public Object getValue() {
+ return this.value;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlProvider.java
new file mode 100644
index 0000000000..5dc642be86
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Interface to be implemented by objects that can provide SQL strings.
+ *
+ *
Typically implemented by PreparedStatementCreators, CallableStatementCreators
+ * and StatementCallbacks that want to expose the SQL they use to create their
+ * statements, to allow for better contextual information in case of exceptions.
+ *
+ * @author Juergen Hoeller
+ * @since 16.03.2004
+ * @see PreparedStatementCreator
+ * @see CallableStatementCreator
+ * @see StatementCallback
+ */
+public interface SqlProvider {
+
+ /**
+ * Return the SQL string for this object, i.e.
+ * typically the SQL used for creating statements.
+ * @return the SQL string, or null
+ */
+ String getSql();
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnResultSet.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnResultSet.java
new file mode 100644
index 0000000000..5cf7be27f8
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnResultSet.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+/**
+ * Represents a returned {@link java.sql.ResultSet} from a stored procedure call.
+ *
+ *
A {@link ResultSetExtractor}, {@link RowCallbackHandler} or {@link RowMapper}
+ * must be provided to handle any returned rows.
+ *
+ *
Returned {@link java.sql.ResultSet ResultSets} - like all stored procedure
+ * parameters - must have names.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ */
+public class SqlReturnResultSet extends ResultSetSupportingSqlParameter {
+
+ /**
+ * Create a new instance of the {@link SqlReturnResultSet} class.
+ * @param name name of the parameter, as used in input and output maps
+ * @param extractor ResultSetExtractor to use for parsing the {@link java.sql.ResultSet}
+ */
+ public SqlReturnResultSet(String name, ResultSetExtractor extractor) {
+ super(name, 0, extractor);
+ }
+
+ /**
+ * Create a new instance of the {@link SqlReturnResultSet} class.
+ * @param name name of the parameter, as used in input and output maps
+ * @param handler RowCallbackHandler to use for parsing the {@link java.sql.ResultSet}
+ */
+ public SqlReturnResultSet(String name, RowCallbackHandler handler) {
+ super(name, 0, handler);
+ }
+
+ /**
+ * Create a new instance of the {@link SqlReturnResultSet} class.
+ * @param name name of the parameter, as used in input and output maps
+ * @param mapper RowMapper to use for parsing the {@link java.sql.ResultSet}
+ */
+ public SqlReturnResultSet(String name, RowMapper mapper) {
+ super(name, 0, mapper);
+ }
+
+ /**
+ * Return whether this parameter is an implicit return parameter used during the
+ * results preocessing of the CallableStatement.getMoreResults/getUpdateCount.
+ *
This implementation always returns true.
+ */
+ public boolean isResultsParameter() {
+ return true;
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnType.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnType.java
new file mode 100644
index 0000000000..f4d056a5ee
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnType.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.CallableStatement;
+import java.sql.SQLException;
+
+/**
+ * Interface to be implemented for retrieving values for more complex database-specific
+ * types not supported by the standard CallableStatement.getObject method.
+ *
+ *
Implementations perform the actual work of getting the actual values. They must
+ * implement the callback method getTypeValue which can throw SQLExceptions
+ * that will be caught and translated by the calling code. This callback method has
+ * access to the underlying Connection via the given CallableStatement object, if that
+ * should be needed to create any database-specific objects.
+ *
+ * @author Thomas Risberg
+ * @since 1.1
+ * @see java.sql.Types
+ * @see java.sql.CallableStatement#getObject
+ * @see org.springframework.jdbc.object.StoredProcedure#execute(java.util.Map)
+ */
+public interface SqlReturnType {
+
+ /**
+ * Constant that indicates an unknown (or unspecified) SQL type.
+ * Passed into setTypeValue if the original operation method does
+ * not specify a SQL type.
+ * @see java.sql.Types
+ * @see JdbcOperations#update(String, Object[])
+ */
+ int TYPE_UNKNOWN = Integer.MIN_VALUE;
+
+
+ /**
+ * Get the type value from the specific object.
+ * @param cs the CallableStatement to operate on
+ * @param paramIndex the index of the parameter for which we need to set the value
+ * @param sqlType SQL type of the parameter we are setting
+ * @param typeName the type name of the parameter
+ * @return the target value
+ * @throws SQLException if a SQLException is encountered setting parameter values
+ * (that is, there's no need to catch SQLException)
+ * @see java.sql.Types
+ * @see java.sql.CallableStatement#getObject
+ */
+ Object getTypeValue(CallableStatement cs, int paramIndex, int sqlType, String typeName)
+ throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnUpdateCount.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnUpdateCount.java
new file mode 100644
index 0000000000..3a60e62e45
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlReturnUpdateCount.java
@@ -0,0 +1,41 @@
+package org.springframework.jdbc.core;
+
+import java.sql.Types;
+
+/**
+ * Represents a returned update count from a stored procedure call.
+ *
+ *
Returned update counts - like all stored procedure
+ * parameters - must have names.
+ *
+ * @author Thomas Risberg
+ */
+public class SqlReturnUpdateCount extends SqlParameter {
+
+ /**
+ * Create a new instance of the {@link SqlReturnUpdateCount} class.
+ * @param name name of the parameter, as used in input and output maps
+ */
+ public SqlReturnUpdateCount(String name) {
+ super(name, Types.INTEGER);
+ }
+
+
+ /**
+ * Return whether this parameter holds input values that should be set
+ * before execution even if they are null.
+ *
This implementation always returns false.
+ */
+ public boolean isInputValueProvided() {
+ return false;
+ }
+
+ /**
+ * Return whether this parameter is an implicit return parameter used during the
+ * results preocessing of the CallableStatement.getMoreResults/getUpdateCount.
+ *
This implementation always returns true.
+ */
+ public boolean isResultsParameter() {
+ return true;
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlRowSetResultSetExtractor.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlRowSetResultSetExtractor.java
new file mode 100644
index 0000000000..c17038027f
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlRowSetResultSetExtractor.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.sql.rowset.CachedRowSet;
+
+import com.sun.rowset.CachedRowSetImpl;
+
+import org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+
+/**
+ * ResultSetExtractor implementation that returns a Spring SqlRowSet
+ * representation for each given ResultSet.
+ *
+ *
The default implementation uses a standard JDBC CachedRowSet underneath.
+ * This means that JDBC RowSet support needs to be available at runtime:
+ * by default, Sun's com.sun.rowset.CachedRowSetImpl class is
+ * used, which is part of JDK 1.5+ and also available separately as part of
+ * Sun's JDBC RowSet Implementations download (rowset.jar).
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #newCachedRowSet
+ * @see org.springframework.jdbc.support.rowset.SqlRowSet
+ * @see JdbcTemplate#queryForRowSet(String)
+ * @see javax.sql.rowset.CachedRowSet
+ */
+public class SqlRowSetResultSetExtractor implements ResultSetExtractor {
+
+ public Object extractData(ResultSet rs) throws SQLException {
+ return createSqlRowSet(rs);
+ }
+
+ /**
+ * Create a SqlRowSet that wraps the given ResultSet,
+ * representing its data in a disconnected fashion.
+ *
This implementation creates a Spring ResultSetWrappingSqlRowSet
+ * instance that wraps a standard JDBC CachedRowSet instance.
+ * Can be overridden to use a different implementation.
+ * @param rs the original ResultSet (connected)
+ * @return the disconnected SqlRowSet
+ * @throws SQLException if thrown by JDBC methods
+ * @see #newCachedRowSet
+ * @see org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet
+ */
+ protected SqlRowSet createSqlRowSet(ResultSet rs) throws SQLException {
+ CachedRowSet rowSet = newCachedRowSet();
+ rowSet.populate(rs);
+ return new ResultSetWrappingSqlRowSet(rowSet);
+ }
+
+ /**
+ * Create a new CachedRowSet instance, to be populated by
+ * the createSqlRowSet implementation.
+ *
The default implementation creates a new instance of
+ * Sun's com.sun.rowset.CachedRowSetImpl class,
+ * which is part of JDK 1.5+ and also available separately
+ * as part of Sun's JDBC RowSet Implementations download.
+ * @return a new CachedRowSet instance
+ * @throws SQLException if thrown by JDBC methods
+ * @see #createSqlRowSet
+ * @see com.sun.rowset.CachedRowSetImpl
+ */
+ protected CachedRowSet newCachedRowSet() throws SQLException {
+ return new CachedRowSetImpl();
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlTypeValue.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlTypeValue.java
new file mode 100644
index 0000000000..6a55f386fb
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/SqlTypeValue.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * Interface to be implemented for setting values for more complex database-specific
+ * types not supported by the standard setObject method. This is
+ * effectively an extended variant of {@link org.springframework.jdbc.support.SqlValue}.
+ *
+ *
Implementations perform the actual work of setting the actual values. They must
+ * implement the callback method setTypeValue which can throw SQLExceptions
+ * that will be caught and translated by the calling code. This callback method has
+ * access to the underlying Connection via the given PreparedStatement object, if that
+ * should be needed to create any database-specific objects.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see java.sql.Types
+ * @see java.sql.PreparedStatement#setObject
+ * @see JdbcOperations#update(String, Object[], int[])
+ * @see org.springframework.jdbc.support.SqlValue
+ */
+public interface SqlTypeValue {
+
+ /**
+ * Constant that indicates an unknown (or unspecified) SQL type.
+ * Passed into setTypeValue if the original operation method
+ * does not specify a SQL type.
+ * @see java.sql.Types
+ * @see JdbcOperations#update(String, Object[])
+ */
+ int TYPE_UNKNOWN = JdbcUtils.TYPE_UNKNOWN;
+
+
+ /**
+ * Set the type value on the given PreparedStatement.
+ * @param ps the PreparedStatement to work on
+ * @param paramIndex the index of the parameter for which we need to set the value
+ * @param sqlType SQL type of the parameter we are setting
+ * @param typeName the type name of the parameter (optional)
+ * @throws SQLException if a SQLException is encountered while setting parameter values
+ * @see java.sql.Types
+ * @see java.sql.PreparedStatement#setObject
+ */
+ void setTypeValue(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java
new file mode 100644
index 0000000000..bcda002ec2
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.springframework.dao.DataAccessException;
+
+/**
+ * Generic callback interface for code that operates on a JDBC Statement.
+ * Allows to execute any number of operations on a single Statement,
+ * for example a single executeUpdate call or repeated
+ * executeUpdate calls with varying SQL.
+ *
+ *
Used internally by JdbcTemplate, but also useful for application code.
+ *
+ * @author Juergen Hoeller
+ * @since 16.03.2004
+ * @see JdbcTemplate#execute(StatementCallback)
+ */
+public interface StatementCallback {
+
+ /**
+ * Gets called by JdbcTemplate.execute with an active JDBC
+ * Statement. Does not need to care about closing the Statement or the
+ * Connection, or about handling transactions: this will all be handled
+ * by Spring's JdbcTemplate.
+ *
+ *
NOTE: Any ResultSets opened should be closed in finally blocks
+ * within the callback implementation. Spring will close the Statement
+ * object after the callback returned, but this does not necessarily imply
+ * that the ResultSet resources will be closed: the Statement objects might
+ * get pooled by the connection pool, with close calls only
+ * returning the object to the pool but not physically closing the resources.
+ *
+ *
If called without a thread-bound JDBC transaction (initiated by
+ * DataSourceTransactionManager), the code will simply get executed on the
+ * JDBC connection with its transactional semantics. If JdbcTemplate is
+ * configured to use a JTA-aware DataSource, the JDBC connection and thus
+ * the callback code will be transactional if a JTA transaction is active.
+ *
+ *
Allows for returning a result object created within the callback, i.e.
+ * a domain object or a collection of domain objects. Note that there's
+ * special support for single step actions: see JdbcTemplate.queryForObject etc.
+ * A thrown RuntimeException is treated as application exception, it gets
+ * propagated to the caller of the template.
+ *
+ * @param stmt active JDBC Statement
+ * @return a result object, or null if none
+ * @throws SQLException if thrown by a JDBC method, to be auto-converted
+ * to a DataAccessException by a SQLExceptionTranslator
+ * @throws DataAccessException in case of custom exceptions
+ * @see JdbcTemplate#queryForObject(String, Class)
+ * @see JdbcTemplate#queryForRowSet(String)
+ */
+ Object doInStatement(Statement stmt) throws SQLException, DataAccessException;
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java
new file mode 100644
index 0000000000..8c2ee40322
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core;
+
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.jdbc.support.SqlValue;
+
+/**
+ * Utility methods for PreparedStatementSetter/Creator and CallableStatementCreator
+ * implementations, providing sophisticated parameter management (including support
+ * for LOB values).
+ *
+ *
Used by PreparedStatementCreatorFactory and CallableStatementCreatorFactory,
+ * but also available for direct use in custom setter/creator implementations.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see PreparedStatementSetter
+ * @see PreparedStatementCreator
+ * @see CallableStatementCreator
+ * @see PreparedStatementCreatorFactory
+ * @see CallableStatementCreatorFactory
+ * @see SqlParameter
+ * @see SqlTypeValue
+ * @see org.springframework.jdbc.core.support.SqlLobValue
+ */
+public abstract class StatementCreatorUtils {
+
+ private static final Log logger = LogFactory.getLog(StatementCreatorUtils.class);
+
+ private static Map javaTypeToSqlTypeMap = new HashMap(32);
+
+ static {
+ /* JDBC 3.0 only - not compatible with e.g. MySQL at present
+ javaTypeToSqlTypeMap.put(boolean.class, new Integer(Types.BOOLEAN));
+ javaTypeToSqlTypeMap.put(Boolean.class, new Integer(Types.BOOLEAN));
+ */
+ javaTypeToSqlTypeMap.put(byte.class, new Integer(Types.TINYINT));
+ javaTypeToSqlTypeMap.put(Byte.class, new Integer(Types.TINYINT));
+ javaTypeToSqlTypeMap.put(short.class, new Integer(Types.SMALLINT));
+ javaTypeToSqlTypeMap.put(Short.class, new Integer(Types.SMALLINT));
+ javaTypeToSqlTypeMap.put(int.class, new Integer(Types.INTEGER));
+ javaTypeToSqlTypeMap.put(Integer.class, new Integer(Types.INTEGER));
+ javaTypeToSqlTypeMap.put(long.class, new Integer(Types.BIGINT));
+ javaTypeToSqlTypeMap.put(Long.class, new Integer(Types.BIGINT));
+ javaTypeToSqlTypeMap.put(BigInteger.class, new Integer(Types.BIGINT));
+ javaTypeToSqlTypeMap.put(float.class, new Integer(Types.FLOAT));
+ javaTypeToSqlTypeMap.put(Float.class, new Integer(Types.FLOAT));
+ javaTypeToSqlTypeMap.put(double.class, new Integer(Types.DOUBLE));
+ javaTypeToSqlTypeMap.put(Double.class, new Integer(Types.DOUBLE));
+ javaTypeToSqlTypeMap.put(BigDecimal.class, new Integer(Types.DECIMAL));
+ javaTypeToSqlTypeMap.put(java.sql.Date.class, new Integer(Types.DATE));
+ javaTypeToSqlTypeMap.put(java.sql.Time.class, new Integer(Types.TIME));
+ javaTypeToSqlTypeMap.put(java.sql.Timestamp.class, new Integer(Types.TIMESTAMP));
+ javaTypeToSqlTypeMap.put(Blob.class, new Integer(Types.BLOB));
+ javaTypeToSqlTypeMap.put(Clob.class, new Integer(Types.CLOB));
+ }
+
+
+ /**
+ * Derive a default SQL type from the given Java type.
+ * @param javaType the Java type to translate
+ * @return the corresponding SQL type, or null if none found
+ */
+ public static int javaTypeToSqlParameterType(Class javaType) {
+ Integer sqlType = (Integer) javaTypeToSqlTypeMap.get(javaType);
+ if (sqlType != null) {
+ return sqlType.intValue();
+ }
+ if (Number.class.isAssignableFrom(javaType)) {
+ return Types.NUMERIC;
+ }
+ if (isStringValue(javaType)) {
+ return Types.VARCHAR;
+ }
+ if (isDateValue(javaType) || Calendar.class.isAssignableFrom(javaType)) {
+ return Types.TIMESTAMP;
+ }
+ return SqlTypeValue.TYPE_UNKNOWN;
+ }
+
+ /**
+ * Set the value for a parameter. The method used is based on the SQL type
+ * of the parameter and we can handle complex types like arrays and LOBs.
+ * @param ps the prepared statement or callable statement
+ * @param paramIndex index of the parameter we are setting
+ * @param param the parameter as it is declared including type
+ * @param inValue the value to set
+ * @throws SQLException if thrown by PreparedStatement methods
+ */
+ public static void setParameterValue(
+ PreparedStatement ps, int paramIndex, SqlParameter param, Object inValue)
+ throws SQLException {
+
+ setParameterValueInternal(ps, paramIndex, param.getSqlType(), param.getTypeName(), param.getScale(), inValue);
+ }
+
+ /**
+ * Set the value for a parameter. The method used is based on the SQL type
+ * of the parameter and we can handle complex types like arrays and LOBs.
+ * @param ps the prepared statement or callable statement
+ * @param paramIndex index of the parameter we are setting
+ * @param sqlType the SQL type of the parameter
+ * @param inValue the value to set (plain value or a SqlTypeValue)
+ * @throws SQLException if thrown by PreparedStatement methods
+ * @see SqlTypeValue
+ */
+ public static void setParameterValue(
+ PreparedStatement ps, int paramIndex, int sqlType, Object inValue)
+ throws SQLException {
+
+ setParameterValueInternal(ps, paramIndex, sqlType, null, null, inValue);
+ }
+
+ /**
+ * Set the value for a parameter. The method used is based on the SQL type
+ * of the parameter and we can handle complex types like arrays and LOBs.
+ * @param ps the prepared statement or callable statement
+ * @param paramIndex index of the parameter we are setting
+ * @param sqlType the SQL type of the parameter
+ * @param typeName the type name of the parameter
+ * (optional, only used for SQL NULL and SqlTypeValue)
+ * @param inValue the value to set (plain value or a SqlTypeValue)
+ * @throws SQLException if thrown by PreparedStatement methods
+ * @see SqlTypeValue
+ */
+ public static void setParameterValue(
+ PreparedStatement ps, int paramIndex, int sqlType, String typeName, Object inValue)
+ throws SQLException {
+
+ setParameterValueInternal(ps, paramIndex, sqlType, typeName, null, inValue);
+ }
+
+ /**
+ * Set the value for a parameter. The method used is based on the SQL type
+ * of the parameter and we can handle complex types like arrays and LOBs.
+ * @param ps the prepared statement or callable statement
+ * @param paramIndex index of the parameter we are setting
+ * @param sqlType the SQL type of the parameter
+ * @param typeName the type name of the parameter
+ * (optional, only used for SQL NULL and SqlTypeValue)
+ * @param scale the number of digits after the decimal point
+ * (for DECIMAL and NUMERIC types)
+ * @param inValue the value to set (plain value or a SqlTypeValue)
+ * @throws SQLException if thrown by PreparedStatement methods
+ * @see SqlTypeValue
+ */
+ private static void setParameterValueInternal(
+ PreparedStatement ps, int paramIndex, int sqlType, String typeName, Integer scale, Object inValue)
+ throws SQLException {
+
+ String typeNameToUse = typeName;
+ int sqlTypeToUse = sqlType;
+ Object inValueToUse = inValue;
+
+ // override type info?
+ if (inValue instanceof SqlParameterValue) {
+ SqlParameterValue parameterValue = (SqlParameterValue) inValue;
+ if (logger.isDebugEnabled()) {
+ logger.debug("Overriding typeinfo with runtime info from SqlParameterValue: column index " + paramIndex +
+ ", SQL type " + parameterValue.getSqlType() +
+ ", Type name " + parameterValue.getTypeName());
+ }
+ if (parameterValue.getSqlType() != SqlTypeValue.TYPE_UNKNOWN) {
+ sqlTypeToUse = parameterValue.getSqlType();
+ }
+ if (parameterValue.getTypeName() != null) {
+ typeNameToUse = parameterValue.getTypeName();
+ }
+ inValueToUse = parameterValue.getValue();
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Setting SQL statement parameter value: column index " + paramIndex +
+ ", parameter value [" + inValueToUse +
+ "], value class [" + (inValueToUse != null ? inValueToUse.getClass().getName() : "null") +
+ "], SQL type " + (sqlTypeToUse == SqlTypeValue.TYPE_UNKNOWN ? "unknown" : Integer.toString(sqlTypeToUse)));
+ }
+
+ if (inValueToUse == null) {
+ setNull(ps, paramIndex, sqlTypeToUse, typeNameToUse);
+ }
+ else {
+ setValue(ps, paramIndex, sqlTypeToUse, typeNameToUse, scale, inValueToUse);
+ }
+ }
+
+ private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException {
+ if (sqlType == SqlTypeValue.TYPE_UNKNOWN) {
+ boolean useSetObject = false;
+ sqlType = Types.NULL;
+ try {
+ DatabaseMetaData dbmd = ps.getConnection().getMetaData();
+ String databaseProductName = dbmd.getDatabaseProductName();
+ String jdbcDriverName = dbmd.getDriverName();
+ if (databaseProductName.startsWith("Informix") ||
+ jdbcDriverName.startsWith("Microsoft SQL Server")) {
+ useSetObject = true;
+ }
+ else if (databaseProductName.startsWith("DB2") ||
+ jdbcDriverName.startsWith("jConnect") ||
+ jdbcDriverName.startsWith("SQLServer")||
+ jdbcDriverName.startsWith("Apache Derby Embedded")) {
+ sqlType = Types.VARCHAR;
+ }
+ }
+ catch (Throwable ex) {
+ logger.debug("Could not check database or driver name", ex);
+ }
+ if (useSetObject) {
+ ps.setObject(paramIndex, null);
+ }
+ else {
+ ps.setNull(paramIndex, sqlType);
+ }
+ }
+ else if (typeName != null) {
+ ps.setNull(paramIndex, sqlType, typeName);
+ }
+ else {
+ ps.setNull(paramIndex, sqlType);
+ }
+ }
+
+ private static void setValue(PreparedStatement ps, int paramIndex, int sqlType, String typeName,
+ Integer scale, Object inValue) throws SQLException {
+
+ if (inValue instanceof SqlTypeValue) {
+ ((SqlTypeValue) inValue).setTypeValue(ps, paramIndex, sqlType, typeName);
+ }
+ else if (inValue instanceof SqlValue) {
+ ((SqlValue) inValue).setValue(ps, paramIndex);
+ }
+ else if (sqlType == Types.VARCHAR || sqlType == Types.LONGVARCHAR ||
+ (sqlType == Types.CLOB && isStringValue(inValue.getClass()))) {
+ ps.setString(paramIndex, inValue.toString());
+ }
+ else if (sqlType == Types.DECIMAL || sqlType == Types.NUMERIC) {
+ if (inValue instanceof BigDecimal) {
+ ps.setBigDecimal(paramIndex, (BigDecimal) inValue);
+ }
+ else if (scale != null) {
+ ps.setObject(paramIndex, inValue, sqlType, scale.intValue());
+ }
+ else {
+ ps.setObject(paramIndex, inValue, sqlType);
+ }
+ }
+ else if (sqlType == Types.DATE) {
+ if (inValue instanceof java.util.Date) {
+ if (inValue instanceof java.sql.Date) {
+ ps.setDate(paramIndex, (java.sql.Date) inValue);
+ }
+ else {
+ ps.setDate(paramIndex, new java.sql.Date(((java.util.Date) inValue).getTime()));
+ }
+ }
+ else if (inValue instanceof Calendar) {
+ Calendar cal = (Calendar) inValue;
+ ps.setDate(paramIndex, new java.sql.Date(cal.getTime().getTime()), cal);
+ }
+ else {
+ ps.setObject(paramIndex, inValue, Types.DATE);
+ }
+ }
+ else if (sqlType == Types.TIME) {
+ if (inValue instanceof java.util.Date) {
+ if (inValue instanceof java.sql.Time) {
+ ps.setTime(paramIndex, (java.sql.Time) inValue);
+ }
+ else {
+ ps.setTime(paramIndex, new java.sql.Time(((java.util.Date) inValue).getTime()));
+ }
+ }
+ else if (inValue instanceof Calendar) {
+ Calendar cal = (Calendar) inValue;
+ ps.setTime(paramIndex, new java.sql.Time(cal.getTime().getTime()), cal);
+ }
+ else {
+ ps.setObject(paramIndex, inValue, Types.TIME);
+ }
+ }
+ else if (sqlType == Types.TIMESTAMP) {
+ if (inValue instanceof java.util.Date) {
+ if (inValue instanceof java.sql.Timestamp) {
+ ps.setTimestamp(paramIndex, (java.sql.Timestamp) inValue);
+ }
+ else {
+ ps.setTimestamp(paramIndex, new java.sql.Timestamp(((java.util.Date) inValue).getTime()));
+ }
+ }
+ else if (inValue instanceof Calendar) {
+ Calendar cal = (Calendar) inValue;
+ ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal);
+ }
+ else {
+ ps.setObject(paramIndex, inValue, Types.TIMESTAMP);
+ }
+ }
+ else if (sqlType == SqlTypeValue.TYPE_UNKNOWN) {
+ if (isStringValue(inValue.getClass())) {
+ ps.setString(paramIndex, inValue.toString());
+ }
+ else if (isDateValue(inValue.getClass())) {
+ ps.setTimestamp(paramIndex, new java.sql.Timestamp(((java.util.Date) inValue).getTime()));
+ }
+ else if (inValue instanceof Calendar) {
+ Calendar cal = (Calendar) inValue;
+ ps.setTimestamp(paramIndex, new java.sql.Timestamp(cal.getTime().getTime()), cal);
+ }
+ else {
+ // Fall back to generic setObject call without SQL type specified.
+ ps.setObject(paramIndex, inValue);
+ }
+ }
+ else {
+ // Fall back to generic setObject call with SQL type specified.
+ ps.setObject(paramIndex, inValue, sqlType);
+ }
+ }
+
+ /**
+ * Check whether the given value can be treated as a String value.
+ */
+ private static boolean isStringValue(Class inValueType) {
+ // Consider any CharSequence (including JDK 1.5's StringBuilder) as String.
+ return (CharSequence.class.isAssignableFrom(inValueType) ||
+ StringWriter.class.isAssignableFrom(inValueType));
+ }
+
+ /**
+ * Check whether the given value is a java.util.Date
+ * (but not one of the JDBC-specific subclasses).
+ */
+ private static boolean isDateValue(Class inValueType) {
+ return (java.util.Date.class.isAssignableFrom(inValueType) &&
+ !(java.sql.Date.class.isAssignableFrom(inValueType) ||
+ java.sql.Time.class.isAssignableFrom(inValueType) ||
+ java.sql.Timestamp.class.isAssignableFrom(inValueType)));
+ }
+
+ /**
+ * Clean up all resources held by parameter values which were passed to an
+ * execute method. This is for example important for closing LOB values.
+ * @param paramValues parameter values supplied. May be null.
+ * @see DisposableSqlTypeValue#cleanup()
+ * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup()
+ */
+ public static void cleanupParameters(Object[] paramValues) {
+ if (paramValues != null) {
+ cleanupParameters(Arrays.asList(paramValues));
+ }
+ }
+
+ /**
+ * Clean up all resources held by parameter values which were passed to an
+ * execute method. This is for example important for closing LOB values.
+ * @param paramValues parameter values supplied. May be null.
+ * @see DisposableSqlTypeValue#cleanup()
+ * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup()
+ */
+ public static void cleanupParameters(Collection paramValues) {
+ if (paramValues != null) {
+ for (Iterator it = paramValues.iterator(); it.hasNext();) {
+ Object inValue = it.next();
+ if (inValue instanceof DisposableSqlTypeValue) {
+ ((DisposableSqlTypeValue) inValue).cleanup();
+ }
+ else if (inValue instanceof SqlValue) {
+ ((SqlValue) inValue).cleanup();
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java
new file mode 100644
index 0000000000..f0ac6d9eb6
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.SqlOutParameter;
+import org.springframework.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.SqlReturnResultSet;
+import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * Class to manage context metadata used for the configuration and execution of the call.
+ *
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class CallMetaDataContext {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** name of procedure to call **/
+ private String procedureName;
+
+ /** name of catalog for call **/
+ private String catalogName;
+
+ /** name of schema for call **/
+ private String schemaName;
+
+ /** List of SqlParameter objects to be used in call execution */
+ private List callParameters = new ArrayList();
+
+ /** name to use for the return value in the output map */
+ private String functionReturnName = "return";
+
+ /** Set of in parameter names to exclude use for any not listed */
+ private Set limitedInParameterNames = new HashSet();
+
+ /** List of SqlParameter names for out parameters */
+ private List outParameterNames = new ArrayList();
+
+ /** should we access call parameter meta data info or not */
+ private boolean accessCallParameterMetaData = true;
+
+ /** indicates whether this is a procedure or a function **/
+ private boolean function;
+
+ /** indicates whether this procedure's return value should be included **/
+ private boolean returnValueRequired;
+
+ /** the provider of call meta data */
+ private CallMetaDataProvider metaDataProvider;
+
+
+ /**
+ * Specify the name used for the return value of the function.
+ */
+ public void setFunctionReturnName(String functionReturnName) {
+ this.functionReturnName = functionReturnName;
+ }
+
+ /**
+ * Get the name used for the return value of the function.
+ */
+ public String getFunctionReturnName() {
+ return this.functionReturnName;
+ }
+
+ /**
+ * Specify a limited set of in parameters to be used.
+ */
+ public void setLimitedInParameterNames(Set limitedInParameterNames) {
+ this.limitedInParameterNames = limitedInParameterNames;
+ }
+
+ /**
+ * Get a limited set of in parameters to be used.
+ */
+ public Set getLimitedInParameterNames() {
+ return this.limitedInParameterNames;
+ }
+
+ /**
+ * Specify the names of the out parameters.
+ */
+ public void setOutParameterNames(List outParameterNames) {
+ this.outParameterNames = outParameterNames;
+ }
+
+ /**
+ * Get a list of the out parameter names.
+ */
+ public List getOutParameterNames() {
+ return this.outParameterNames;
+ }
+
+ /**
+ * Specify the name of the procedure.
+ */
+ public void setProcedureName(String procedureName) {
+ this.procedureName = procedureName;
+ }
+
+ /**
+ * Get the name of the procedure.
+ */
+ public String getProcedureName() {
+ return this.procedureName;
+ }
+
+ /**
+ * Specify the name of the catalog.
+ */
+ public void setCatalogName(String catalogName) {
+ this.catalogName = catalogName;
+ }
+
+ /**
+ * Get the name of the catalog.
+ */
+ public String getCatalogName() {
+ return this.catalogName;
+ }
+
+ /**
+ * Secify the name of the schema.
+ */
+ public void setSchemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ /**
+ * Get the name of the schema.
+ */
+ public String getSchemaName() {
+ return this.schemaName;
+ }
+
+ /**
+ * Specify whether this call is a function call.
+ */
+ public void setFunction(boolean function) {
+ this.function = function;
+ }
+
+ /**
+ * Check whether this call is a function call.
+ */
+ public boolean isFunction() {
+ return this.function;
+ }
+
+ /**
+ * Specify whether a return value is required.
+ */
+ public void setReturnValueRequired(boolean returnValueRequired) {
+ this.returnValueRequired = returnValueRequired;
+ }
+
+ /**
+ * Check whether a return value is required.
+ */
+ public boolean isReturnValueRequired() {
+ return this.returnValueRequired;
+ }
+
+ /**
+ * Specify whether call parameter metadata should be accessed.
+ */
+ public void setAccessCallParameterMetaData(boolean accessCallParameterMetaData) {
+ this.accessCallParameterMetaData = accessCallParameterMetaData;
+ }
+
+ /**
+ * Check whether call parameter metadata should be accessed.
+ */
+ public boolean isAccessCallParameterMetaData() {
+ return this.accessCallParameterMetaData;
+ }
+
+
+ /**
+ * Create a ReturnResultSetParameter/SqlOutParameter depending on the support provided
+ * by the JDBC driver used for the database in use.
+ * @param parameterName the name of the parameter (also used as the name of the List returned in the output)
+ * @param rowMapper a RowMapper implementation used to map the data retuned in the result set
+ * @return the appropriate SqlParameter
+ */
+ public SqlParameter createReturnResultSetParameter(String parameterName, RowMapper rowMapper) {
+ if (this.metaDataProvider.isReturnResultSetSupported()) {
+ return new SqlReturnResultSet(parameterName, rowMapper);
+ }
+ else {
+ if (this.metaDataProvider.isRefCursorSupported()) {
+ return new SqlOutParameter(parameterName, this.metaDataProvider.getRefCursorSqlType(), rowMapper);
+ }
+ else {
+ throw new InvalidDataAccessApiUsageException("Return of a ResultSet from a stored procedure is not supported.");
+ }
+ }
+ }
+
+ /**
+ * Get the name of the single out parameter for this call. If there are multiple parameters then the name of
+ * the first one is returned.
+ */
+ public String getScalarOutParameterName() {
+ if (isFunction()) {
+ return this.functionReturnName;
+ }
+ else {
+ if (this.outParameterNames.size() > 1) {
+ logger.warn("Accessing single output value when procedure has more than one output parameter");
+ }
+ return (this.outParameterNames.size() > 0 ? this.outParameterNames.get(0) : null);
+ }
+ }
+
+ /**
+ * Get the List of SqlParameter objects to be used in call execution
+ */
+ public List getCallParameters() {
+ return this.callParameters;
+ }
+
+ /**
+ * Initialize this class with metadata from the database
+ * @param dataSource the DataSource used to retrieve metadata
+ */
+ public void initializeMetaData(DataSource dataSource) {
+ this.metaDataProvider = CallMetaDataProviderFactory.createMetaDataProvider(dataSource, this);
+ }
+
+ /**
+ * Process the list of parameters provided and if procedure column metedata is used the
+ * parameters will be matched against the metadata information and any missing ones will
+ * be automatically included
+ * @param parameters the list of parameters to use as a base
+ */
+ public void processParameters(List parameters) {
+ this.callParameters = reconcileParameters(parameters);
+ }
+
+ /**
+ * Reconcile the provided parameters with available metadata and add new ones where appropriate
+ */
+ private List reconcileParameters(List parameters) {
+ final List declaredReturnParameters = new ArrayList();
+ final Map declaredParameters = new LinkedHashMap();
+ boolean returnDeclared = false;
+ List outParameterNames = new ArrayList();
+ List metaDataParameterNames = new ArrayList();
+
+ // get the names of the meta data parameters
+ for (CallParameterMetaData meta : metaDataProvider.getCallParameterMetaData()) {
+ if (meta.getParameterType() != DatabaseMetaData.procedureColumnReturn) {
+ metaDataParameterNames.add(meta.getParameterName().toLowerCase());
+ }
+ }
+
+ // Separate implicit return parameters from explicit parameters...
+ for (SqlParameter parameter : parameters) {
+ if (parameter.isResultsParameter()) {
+ declaredReturnParameters.add(parameter);
+ }
+ else {
+ String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameter.getName()).toLowerCase();
+ declaredParameters.put(parameterNameToMatch, parameter);
+ if (parameter instanceof SqlOutParameter) {
+ outParameterNames.add(parameter.getName());
+ if (this.isFunction() && !metaDataParameterNames.contains(parameterNameToMatch)) {
+ if (!returnDeclared) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using declared out parameter '" + parameter.getName() + "' for function return value");
+ }
+ this.setFunctionReturnName(parameter.getName());
+ returnDeclared = true;
+ }
+ }
+ }
+ }
+ }
+ this.setOutParameterNames(outParameterNames);
+
+ final List workParameters = new ArrayList();
+ workParameters.addAll(declaredReturnParameters);
+
+ if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
+ workParameters.addAll(declaredParameters.values());
+ return workParameters;
+ }
+
+ Map limitedInParamNamesMap = new HashMap(this.limitedInParameterNames.size());
+ for (String limitedParameterName : this.limitedInParameterNames) {
+ limitedInParamNamesMap.put(
+ this.metaDataProvider.parameterNameToUse(limitedParameterName).toLowerCase(), limitedParameterName);
+ }
+
+ for (CallParameterMetaData meta : metaDataProvider.getCallParameterMetaData()) {
+ String parNameToCheck = null;
+ if (meta.getParameterName() != null) {
+ parNameToCheck = this.metaDataProvider.parameterNameToUse(meta.getParameterName()).toLowerCase();
+ }
+ String parNameToUse = this.metaDataProvider.parameterNameToUse(meta.getParameterName());
+ if (declaredParameters.containsKey(parNameToCheck) ||
+ (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn && returnDeclared)) {
+ SqlParameter parameter;
+ if (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn) {
+ parameter = declaredParameters.get(this.getFunctionReturnName());
+ if (parameter == null && this.getOutParameterNames().size() > 0) {
+ parameter = declaredParameters.get(this.getOutParameterNames().get(0).toLowerCase());
+ }
+ if (parameter == null) {
+ throw new InvalidDataAccessApiUsageException(
+ "Unable to locate declared parameter for function return value - " +
+ " add an SqlOutParameter with name \"" + getFunctionReturnName() +"\"");
+ }
+ else {
+ this.setFunctionReturnName(parameter.getName());
+ }
+ }
+ else {
+ parameter = declaredParameters.get(parNameToCheck);
+ }
+ if (parameter != null) {
+ workParameters.add(parameter);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using declared parameter for: " +
+ (parNameToUse == null ? getFunctionReturnName() : parNameToUse));
+ }
+ }
+ }
+ else {
+ if (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn) {
+ if (!isFunction() && !isReturnValueRequired() &&
+ this.metaDataProvider.byPassReturnParameter(meta.getParameterName())) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Bypassing metadata return parameter for: " + meta.getParameterName());
+ }
+ }
+ else {
+ String returnNameToUse =
+ ( meta.getParameterName() == null || meta.getParameterName().length() < 1 ) ?
+ this.getFunctionReturnName() : parNameToUse;
+ workParameters.add(new SqlOutParameter(returnNameToUse, meta.getSqlType()));
+ if (this.isFunction())
+ outParameterNames.add(returnNameToUse);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added metadata return parameter for: " + returnNameToUse);
+ }
+ }
+ }
+ else {
+ if (meta.getParameterType() == DatabaseMetaData.procedureColumnOut) {
+ workParameters.add(this.metaDataProvider.createDefaultOutParameter(parNameToUse, meta));
+ outParameterNames.add(parNameToUse);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added metadata out parameter for: " + parNameToUse);
+ }
+ }
+ else if (meta.getParameterType() == DatabaseMetaData.procedureColumnInOut) {
+ workParameters.add(this.metaDataProvider.createDefaultInOutParameter(parNameToUse, meta));
+ outParameterNames.add(parNameToUse);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added metadata in out parameter for: " + parNameToUse);
+ }
+ }
+ else {
+ if (this.limitedInParameterNames.size() == 0 ||
+ limitedInParamNamesMap.containsKey(parNameToUse.toLowerCase())) {
+ workParameters.add(this.metaDataProvider.createDefaultInParameter(parNameToUse, meta));
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added metadata in parameter for: " + parNameToUse);
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Limited set of parameters " + limitedInParamNamesMap.keySet() +
+ " skipped parameter for: " + parNameToUse);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return workParameters;
+
+ }
+
+ /**
+ * Match input parameter values with the parameters declared to be used in the call.
+ * @param parameterSource the input values
+ * @return a Map containing the matched parameter names with the value taken from the input
+ */
+ public Map matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
+ // For parameter source lookups we need to provide case-insensitive lookup support
+ // since the database metadata is not necessarily providing case sensitive parameter names.
+ Map caseInsensitiveParameterNames =
+ SqlParameterSourceUtils.extractCaseInsensitiveParameterNames(parameterSource);
+
+ Map callParameterNames = new HashMap(this.callParameters.size());
+ Map matchedParameters = new HashMap(this.callParameters.size());
+ for (SqlParameter parameter : this.callParameters) {
+ if (parameter.isInputValueProvided()) {
+ String parameterName = parameter.getName();
+ String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
+ if (parameterNameToMatch != null) {
+ callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
+ }
+ if (parameterName != null) {
+ if (parameterSource.hasValue(parameterName)) {
+ matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, parameterName));
+ }
+ else {
+ String lowerCaseName = parameterName.toLowerCase();
+ if (parameterSource.hasValue(lowerCaseName)) {
+ matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, lowerCaseName));
+ }
+ else {
+ String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(parameterName);
+ if (parameterSource.hasValue(propertyName)) {
+ matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, propertyName));
+ }
+ else {
+ if (caseInsensitiveParameterNames.containsKey(lowerCaseName)) {
+ String sourceName = (String) caseInsensitiveParameterNames.get(lowerCaseName);
+ matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, sourceName));
+ }
+ else {
+ logger.warn("Unable to locate the corresponding parameter value for '" + parameterName +
+ "' within the parameter values provided: " + caseInsensitiveParameterNames.values());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Matching " + caseInsensitiveParameterNames.values() + " with " + callParameterNames.values());
+ logger.debug("Found match for " + matchedParameters.keySet());
+ }
+ return matchedParameters;
+ }
+
+ /**
+ * Match input parameter values with the parameters declared to be used in the call.
+ * @param inParameters the input values
+ * @return a Map containing the matched parameter names with the value taken from the input
+ */
+ public Map matchInParameterValuesWithCallParameters(Map inParameters) {
+ if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
+ return inParameters;
+ }
+ Map callParameterNames = new HashMap(this.callParameters.size());
+ for (SqlParameter parameter : this.callParameters) {
+ if (parameter.isInputValueProvided()) {
+ String parameterName = parameter.getName();
+ String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
+ if (parameterNameToMatch != null) {
+ callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
+ }
+ }
+ }
+ Map matchedParameters = new HashMap(inParameters.size());
+ for (String parameterName : inParameters.keySet()) {
+ String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
+ String callParameterName = callParameterNames.get(parameterNameToMatch.toLowerCase());
+ if (callParameterName == null) {
+ if (logger.isDebugEnabled()) {
+ Object value = inParameters.get(parameterName);
+ if (value instanceof SqlParameterValue) {
+ value = ((SqlParameterValue)value).getValue();
+ }
+ if (value != null) {
+ logger.debug("Unable to locate the corresponding IN or IN-OUT parameter for \"" + parameterName +
+ "\" in the parameters used: " + callParameterNames.keySet());
+ }
+ }
+ }
+ else {
+ matchedParameters.put(callParameterName, inParameters.get(parameterName));
+ }
+ }
+ if (matchedParameters.size() < callParameterNames.size()) {
+ for (String parameterName : callParameterNames.keySet()) {
+ String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
+ String callParameterName = callParameterNames.get(parameterNameToMatch.toLowerCase());
+ if (!matchedParameters.containsKey(callParameterName)) {
+ logger.warn("Unable to locate the corresponding parameter value for '" + parameterName +
+ "' within the parameter values provided: " + inParameters.keySet());
+ }
+ }
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Matching " + inParameters.keySet() + " with " + callParameterNames.values());
+ logger.debug("Found match for " + matchedParameters.keySet());
+ }
+ return matchedParameters;
+ }
+
+ /**
+ * Build the call string based on configuration and metadata information.
+ * @return the call string to be used
+ */
+ public String createCallString() {
+ String callString;
+ int parameterCount = 0;
+ String catalogNameToUse = null;
+ String schemaNameToUse = null;
+
+ // For Oracle where catalogs are not supported we need to reverse the schema name
+ // and the catalog name since the cataog is used for the package name
+ if (this.metaDataProvider.isSupportsSchemasInProcedureCalls() &&
+ !this.metaDataProvider.isSupportsCatalogsInProcedureCalls()) {
+ schemaNameToUse = this.metaDataProvider.catalogNameToUse(this.getCatalogName());
+ catalogNameToUse = this.metaDataProvider.schemaNameToUse(this.getSchemaName());
+ }
+ else {
+ catalogNameToUse = this.metaDataProvider.catalogNameToUse(this.getCatalogName());
+ schemaNameToUse = this.metaDataProvider.schemaNameToUse(this.getSchemaName());
+ }
+ String procedureNameToUse = this.metaDataProvider.procedureNameToUse(this.getProcedureName());
+ if (this.isFunction() || this.isReturnValueRequired()) {
+ callString = "{? = call " +
+ (catalogNameToUse != null && catalogNameToUse.length() > 0 ? catalogNameToUse + "." : "") +
+ (schemaNameToUse != null && schemaNameToUse.length() > 0 ? schemaNameToUse + "." : "") +
+ procedureNameToUse + "(";
+ parameterCount = -1;
+ }
+ else {
+ callString = "{call " +
+ (catalogNameToUse != null && catalogNameToUse.length() > 0 ? catalogNameToUse + "." : "") +
+ (schemaNameToUse != null && schemaNameToUse.length() > 0 ? schemaNameToUse + "." : "") +
+ procedureNameToUse + "(";
+ }
+ for (SqlParameter parameter : this.callParameters) {
+ if (!(parameter.isResultsParameter())) {
+ if (parameterCount > 0) {
+ callString += ", ";
+ }
+ if (parameterCount >= 0) {
+ callString += "?";
+ }
+ parameterCount++;
+ }
+ }
+ callString += ")}";
+
+ return callString;
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java
new file mode 100644
index 0000000000..9bc9258e9a
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProvider.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.springframework.jdbc.core.SqlParameter;
+
+/**
+ * Interface specifying the API to be implemented by a class providing call metadata.
+ * This is intended for internal use by Spring's
+ * {@link org.springframework.jdbc.core.simple.SimpleJdbcTemplate}.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public interface CallMetaDataProvider {
+
+ /**
+ * Initialize using the provided DatabaseMetData.
+ * @param databaseMetaData used to retrieve database specific information
+ * @throws SQLException in case of initialization failure
+ */
+ void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException;
+
+ /**
+ * Initialize the database specific management of procedure column meta data.
+ * This is only called for databases that are supported. This initalization
+ * can be turned off by specifying that column meta data should not be used.
+ * @param databaseMetaData used to retreive database specific information
+ * @param catalogName name of catalog to use or null
+ * @param schemaName name of schema name to use or null
+ * @param procedureName name of the stored procedure
+ * @throws SQLException in case of initialization failure
+ * @see org.springframework.jdbc.core.simple.SimpleJdbcCall#withoutProcedureColumnMetaDataAccess()
+ */
+ void initializeWithProcedureColumnMetaData(
+ DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String procedureName)
+ throws SQLException;
+
+ /**
+ * Provide any modification of the procedure name passed in to match the meta data currently used.
+ * This could include alterig the case.
+ */
+ String procedureNameToUse(String procedureName);
+
+ /**
+ * Provide any modification of the catalog name passed in to match the meta data currently used.
+ * This could include alterig the case.
+ */
+ String catalogNameToUse(String catalogName);
+
+ /**
+ * Provide any modification of the schema name passed in to match the meta data currently used.
+ * This could include alterig the case.
+ */
+ String schemaNameToUse(String schemaName);
+
+ /**
+ * Provide any modification of the catalog name passed in to match the meta data currently used.
+ * The reyurned value will be used for meta data lookups. This could include alterig the case used or
+ * providing a base catalog if mone provided.
+ */
+ String metaDataCatalogNameToUse(String catalogName) ;
+
+ /**
+ * Provide any modification of the schema name passed in to match the meta data currently used.
+ * The reyurned value will be used for meta data lookups. This could include alterig the case used or
+ * providing a base schema if mone provided.
+ */
+ String metaDataSchemaNameToUse(String schemaName) ;
+
+ /**
+ * Provide any modification of the column name passed in to match the meta data currently used.
+ * This could include alterig the case.
+ * @param parameterName name of the parameter of column
+ */
+ String parameterNameToUse(String parameterName);
+
+ /**
+ * Create a default out parameter based on the provided meta data. This is used when no expicit
+ * parameter declaration has been made.
+ * @param parameterName the name of the parameter
+ * @param meta meta data used for this call
+ * @return the configured SqlOutParameter
+ */
+ SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta);
+
+ /**
+ * Create a default inout parameter based on the provided meta data. This is used when no expicit
+ * parameter declaration has been made.
+ * @param parameterName the name of the parameter
+ * @param meta meta data used for this call
+ * @return the configured SqlInOutParameter
+ */
+ SqlParameter createDefaultInOutParameter(String parameterName, CallParameterMetaData meta);
+
+ /**
+ * Create a default in parameter based on the provided meta data. This is used when no expicit
+ * parameter declaration has been made.
+ * @param parameterName the name of the parameter
+ * @param meta meta data used for this call
+ * @return the configured SqlParameter
+ */
+ SqlParameter createDefaultInParameter(String parameterName, CallParameterMetaData meta);
+
+ /**
+ * Get the name of the current user. Useful for meta data lookups etc.
+ * @return current user name from database connection
+ */
+ String getUserName();
+
+ /**
+ * Does this database support returning resultsets that should be retreived with the JDBC call
+ * {@link java.sql.Statement#getResultSet()}
+ */
+ boolean isReturnResultSetSupported();
+
+ /**
+ * Does this database support returning resultsets as ref cursors to be retreived with
+ * {@link java.sql.CallableStatement#getObject(int)} for the specified column.
+ */
+ boolean isRefCursorSupported();
+
+ /**
+ * Get the {@link java.sql.Types} type for columns that return resultsets as ref cursors if this feature
+ * is supported.
+ */
+ int getRefCursorSqlType();
+
+ /**
+ * Are we using the meta data for the procedure columns?
+ */
+ boolean isProcedureColumnMetaDataUsed();
+
+ /**
+ * Should we bypass the return parameter with the specified name.
+ * This allows the database specific implementation to skip the processing
+ * for specific results returned by the database call.
+ */
+ boolean byPassReturnParameter(String parameterName);
+
+ /**
+ * Get the call parameter metadata that is currently used.
+ * @return List of {@link CallParameterMetaData}
+ */
+ List getCallParameterMetaData();
+
+ /**
+ * Does the database support the use of catalog name in procedure calls
+ */
+ boolean isSupportsCatalogsInProcedureCalls();
+
+ /**
+ * Does the database support the use of schema name in procedure calls
+ */
+ boolean isSupportsSchemasInProcedureCalls();
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java
new file mode 100644
index 0000000000..38d47301c3
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.support.DatabaseMetaDataCallback;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.jdbc.support.MetaDataAccessException;
+
+/**
+ * Factory used to create a {@link CallMetaDataProvider} implementation based on the type of databse being used.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class CallMetaDataProviderFactory {
+
+ /** Logger */
+ private static final Log logger = LogFactory.getLog(CallMetaDataProviderFactory.class);
+
+ /** List of supported database products for procedure calls */
+ public static final List supportedDatabaseProductsForProcedures = Arrays.asList(
+ "Apache Derby",
+ "DB2",
+ "MySQL",
+ "Microsoft SQL Server",
+ "Oracle",
+ "PostgreSQL",
+ "Sybase"
+ );
+ /** List of supported database products for function calls */
+ public static final List supportedDatabaseProductsForFunctions = Arrays.asList(
+ "MySQL",
+ "Microsoft SQL Server",
+ "Oracle",
+ "PostgreSQL"
+ );
+
+ /**
+ * Create a CallMetaDataProvider based on the database metedata
+ * @param dataSource used to retrieve metedata
+ * @param context the class that holds configuration and metedata
+ * @return instance of the CallMetaDataProvider implementation to be used
+ */
+ static public CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
+ try {
+ return (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, new DatabaseMetaDataCallback() {
+ public Object processMetaData(DatabaseMetaData databaseMetaData) throws SQLException, MetaDataAccessException {
+ String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
+ boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();
+ if (context.isFunction()) {
+ if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(databaseProductName + " is not one of the databases fully supported for function calls " +
+ "-- supported are: " + supportedDatabaseProductsForFunctions);
+ }
+ if (accessProcedureColumnMetaData) {
+ logger.warn("Metadata processing disabled - you must specify all parameters explicitly");
+ accessProcedureColumnMetaData = false;
+ }
+ }
+ }
+ else {
+ if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(databaseProductName + " is not one of the databases fully supported for procedure calls " +
+ "-- supported are: " + supportedDatabaseProductsForProcedures);
+ }
+ if (accessProcedureColumnMetaData) {
+ logger.warn("Metadata processing disabled - you must specify all parameters explicitly");
+ accessProcedureColumnMetaData = false;
+ }
+ }
+ }
+
+ CallMetaDataProvider provider;
+ if ("Oracle".equals(databaseProductName)) {
+ provider = new OracleCallMetaDataProvider(databaseMetaData);
+ }
+ else if ("DB2".equals(databaseProductName)) {
+ provider = new Db2CallMetaDataProvider((databaseMetaData));
+ }
+ else if ("Apache Derby".equals(databaseProductName)) {
+ provider = new DerbyCallMetaDataProvider((databaseMetaData));
+ }
+ else if ("PostgreSQL".equals(databaseProductName)) {
+ provider = new PostgresCallMetaDataProvider((databaseMetaData));
+ }
+ else if ("Sybase".equals(databaseProductName)) {
+ provider = new SybaseCallMetaDataProvider((databaseMetaData));
+ }
+ else if ("Microsoft SQL Server".equals(databaseProductName)) {
+ provider = new SqlServerCallMetaDataProvider((databaseMetaData));
+ }
+ else {
+ provider = new GenericCallMetaDataProvider(databaseMetaData);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using " + provider.getClass().getName());
+ }
+ provider.initializeWithMetaData(databaseMetaData);
+ if (accessProcedureColumnMetaData) {
+ provider.initializeWithProcedureColumnMetaData(
+ databaseMetaData, context.getCatalogName(), context.getSchemaName(), context.getProcedureName());
+ }
+ return provider;
+ }
+ });
+ }
+ catch (MetaDataAccessException ex) {
+ throw new DataAccessResourceFailureException("Error retreiving database metadata", ex);
+ }
+
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallParameterMetaData.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallParameterMetaData.java
new file mode 100644
index 0000000000..4c1393640d
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallParameterMetaData.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+/**
+ * Holder of metadata for a specific parameter that is used for call processing.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class CallParameterMetaData {
+ private String parameterName;
+ private int parameterType;
+ private int sqlType;
+ private String typeName;
+ private boolean nullable;
+
+ /**
+ * Constructor taking all the properties
+ */
+ public CallParameterMetaData(String columnName, int columnType, int sqlType, String typeName, boolean nullable) {
+ this.parameterName = columnName;
+ this.parameterType = columnType;
+ this.sqlType = sqlType;
+ this.typeName = typeName;
+ this.nullable = nullable;
+ }
+
+
+ /**
+ * Get the parameter name.
+ */
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ /**
+ * Get the parameter type.
+ */
+ public int getParameterType() {
+ return parameterType;
+ }
+
+ /**
+ * Get the parameter SQL type.
+ */
+ public int getSqlType() {
+ return sqlType;
+ }
+
+ /**
+ * Get the parameter type name.
+ */
+ public String getTypeName() {
+ return typeName;
+ }
+
+ /**
+ * Get whether the parameter is nullable.
+ */
+ public boolean isNullable() {
+ return nullable;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/Db2CallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/Db2CallMetaDataProvider.java
new file mode 100644
index 0000000000..24fcfcc00f
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/Db2CallMetaDataProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * DB2 specific implementation for the {@link CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class Db2CallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ public Db2CallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException {
+ try {
+ setSupportsCatalogsInProcedureCalls(databaseMetaData.supportsCatalogsInProcedureCalls());
+ }
+ catch (SQLException se) {
+ logger.debug("Error retrieving 'DatabaseMetaData.supportsCatalogsInProcedureCalls' - " + se.getMessage());
+ }
+ try {
+ setSupportsSchemasInProcedureCalls(databaseMetaData.supportsSchemasInProcedureCalls());
+ }
+ catch (SQLException se) {
+ logger.debug("Error retrieving 'DatabaseMetaData.supportsSchemasInProcedureCalls' - " + se.getMessage());
+ }
+ try {
+ setStoresUpperCaseIdentifiers(databaseMetaData.storesUpperCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.debug("Error retrieving 'DatabaseMetaData.storesUpperCaseIdentifiers' - " + se.getMessage());
+ }
+ try {
+ setStoresLowerCaseIdentifiers(databaseMetaData.storesLowerCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.debug("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers' - " + se.getMessage());
+ }
+ }
+
+ @Override
+ public String metaDataSchemaNameToUse(String schemaName) {
+ // Use current user schema if no schema specified
+ return schemaName == null ? getUserName().toUpperCase() : super.metaDataSchemaNameToUse(schemaName);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/DerbyCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/DerbyCallMetaDataProvider.java
new file mode 100644
index 0000000000..6d22dee275
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/DerbyCallMetaDataProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * Derby specific implementation for the {@link CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class DerbyCallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ public DerbyCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public String metaDataSchemaNameToUse(String schemaName) {
+ // Use current user schema if no schema specified
+ return schemaName == null ? getUserName().toUpperCase() : super.metaDataSchemaNameToUse(schemaName);
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java
new file mode 100644
index 0000000000..424c573c18
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.jdbc.core.SqlOutParameter;
+import org.springframework.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.SqlInOutParameter;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.util.StringUtils;
+
+/**
+ * Generic implementation for the {@link CallMetaDataProvider} interface.
+ * This class can be extended to provide database specific behavior.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class GenericCallMetaDataProvider implements CallMetaDataProvider {
+
+ /** Logger available to subclasses */
+ protected static final Log logger = LogFactory.getLog(CallMetaDataProvider.class);
+
+ private boolean procedureColumnMetaDataUsed = false;
+
+ private String userName;
+
+ private boolean supportsCatalogsInProcedureCalls = true;
+
+ private boolean supportsSchemasInProcedureCalls = true;
+
+ private boolean storesUpperCaseIdentifiers = true;
+
+ private boolean storesLowerCaseIdentifiers = false;
+
+ private List callParameterMetaData = new ArrayList();
+
+
+ /**
+ * Constructor used to initialize with provided database meta data.
+ * @param databaseMetaData meta data to be used
+ */
+ protected GenericCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ this.userName = databaseMetaData.getUserName();
+ }
+
+
+ public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException {
+ try {
+ setSupportsCatalogsInProcedureCalls(databaseMetaData.supportsCatalogsInProcedureCalls());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.supportsCatalogsInProcedureCalls' - " + se.getMessage());
+ }
+ try {
+ setSupportsSchemasInProcedureCalls(databaseMetaData.supportsSchemasInProcedureCalls());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.supportsSchemasInProcedureCalls' - " + se.getMessage());
+ }
+ try {
+ setStoresUpperCaseIdentifiers(databaseMetaData.storesUpperCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.storesUpperCaseIdentifiers' - " + se.getMessage());
+ }
+ try {
+ setStoresLowerCaseIdentifiers(databaseMetaData.storesLowerCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers' - " + se.getMessage());
+ }
+ }
+
+ public void initializeWithProcedureColumnMetaData(DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String procedureName)
+ throws SQLException {
+
+ this.procedureColumnMetaDataUsed = true;
+ processProcedureColumns(databaseMetaData, catalogName, schemaName, procedureName);
+ }
+
+ public List getCallParameterMetaData() {
+ return callParameterMetaData;
+ }
+
+ public String procedureNameToUse(String procedureName) {
+ if (procedureName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return procedureName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return procedureName.toLowerCase();
+ else
+ return procedureName;
+ }
+
+ public String catalogNameToUse(String catalogName) {
+ if (catalogName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return catalogName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return catalogName.toLowerCase();
+ else
+ return catalogName;
+ }
+
+ public String schemaNameToUse(String schemaName) {
+ if (schemaName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return schemaName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return schemaName.toLowerCase();
+ else
+ return schemaName;
+ }
+
+ public String metaDataCatalogNameToUse(String catalogName) {
+ if (isSupportsCatalogsInProcedureCalls()) {
+ return catalogNameToUse(catalogName);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public String metaDataSchemaNameToUse(String schemaName) {
+ if (isSupportsSchemasInProcedureCalls()) {
+ return schemaNameToUse(schemaName);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public String parameterNameToUse(String parameterName) {
+ if (parameterName == null) {
+ return null;
+ }
+ else if (isStoresUpperCaseIdentifiers()) {
+ return parameterName.toUpperCase();
+ }
+ else if(isStoresLowerCaseIdentifiers()) {
+ return parameterName.toLowerCase();
+ }
+ else {
+ return parameterName;
+ }
+ }
+
+ public boolean byPassReturnParameter(String parameterName) {
+ return false;
+ }
+
+ public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) {
+ return new SqlOutParameter(parameterName, meta.getSqlType());
+ }
+
+ public SqlParameter createDefaultInOutParameter(String parameterName, CallParameterMetaData meta) {
+ return new SqlInOutParameter(parameterName, meta.getSqlType());
+ }
+
+ public SqlParameter createDefaultInParameter(String parameterName, CallParameterMetaData meta) {
+ return new SqlParameter(parameterName, meta.getSqlType());
+ }
+
+ public String getUserName() {
+ return this.userName;
+ }
+
+ public boolean isReturnResultSetSupported() {
+ return true;
+ }
+
+ public boolean isRefCursorSupported() {
+ return false;
+ }
+
+ public int getRefCursorSqlType() {
+ return Types.OTHER;
+ }
+
+ public boolean isProcedureColumnMetaDataUsed() {
+ return this.procedureColumnMetaDataUsed;
+ }
+
+
+ /**
+ * Specify whether the database supports the use of catalog name in procedure calls
+ */
+ protected void setSupportsCatalogsInProcedureCalls(boolean supportsCatalogsInProcedureCalls) {
+ this.supportsCatalogsInProcedureCalls = supportsCatalogsInProcedureCalls;
+ }
+
+ /**
+ * Does the database support the use of catalog name in procedure calls
+ */
+ public boolean isSupportsCatalogsInProcedureCalls() {
+ return this.supportsCatalogsInProcedureCalls;
+ }
+
+ /**
+ * Specify whether the database supports the use of schema name in procedure calls
+ */
+ protected void setSupportsSchemasInProcedureCalls(boolean supportsSchemasInProcedureCalls) {
+ this.supportsSchemasInProcedureCalls = supportsSchemasInProcedureCalls;
+ }
+
+ /**
+ * Does the database support the use of schema name in procedure calls
+ */
+ public boolean isSupportsSchemasInProcedureCalls() {
+ return this.supportsSchemasInProcedureCalls;
+ }
+
+ /**
+ * Specify whether the database uses upper case for identifiers
+ */
+ protected void setStoresUpperCaseIdentifiers(boolean storesUpperCaseIdentifiers) {
+ this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
+ }
+
+ /**
+ * Does the database use upper case for identifiers
+ */
+ protected boolean isStoresUpperCaseIdentifiers() {
+ return this.storesUpperCaseIdentifiers;
+ }
+
+ /**
+ * Specify whether the database uses lower case for identifiers
+ */
+ protected void setStoresLowerCaseIdentifiers(boolean storesLowerCaseIdentifiers) {
+ this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
+ }
+
+ /**
+ * Does the database use lower case for identifiers
+ */
+ protected boolean isStoresLowerCaseIdentifiers() {
+ return this.storesLowerCaseIdentifiers;
+ }
+
+
+ /**
+ * Process the procedure column metadata
+ */
+ private void processProcedureColumns(DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String procedureName) {
+ ResultSet procs = null;
+ String metaDataCatalogName = metaDataCatalogNameToUse(catalogName);
+ String metaDataSchemaName = metaDataSchemaNameToUse(schemaName);
+ String metaDataProcedureName = procedureNameToUse(procedureName);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieving metadata for " + metaDataCatalogName + "/" +
+ metaDataSchemaName + "/" + metaDataProcedureName);
+ }
+ try {
+ procs = databaseMetaData.getProcedures(
+ metaDataCatalogName,
+ metaDataSchemaName,
+ metaDataProcedureName);
+ List found = new ArrayList();
+ while (procs.next()) {
+ found.add(procs.getString("PROCEDURE_CAT") +
+ "."+procs.getString("PROCEDURE_SCHEM") +
+ "."+procs.getString("PROCEDURE_NAME"));
+ }
+ procs.close();
+ if (found.size() > 1) {
+ throw new InvalidDataAccessApiUsageException("Unable to determine the correct call signature - " +
+ "multiple procedures/functions/signatures for " + metaDataProcedureName + " found " + found);
+ }
+ if (found.size() < 1) {
+ if (metaDataProcedureName.contains(".") && !StringUtils.hasText(metaDataCatalogName)) {
+ String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf("."));
+ throw new InvalidDataAccessApiUsageException("Unable to determine the correct call signature for " +
+ metaDataProcedureName + " - package name should be specified separately using " +
+ "'.withCatalogName(\"" + packageName + "\")'");
+ }
+ }
+
+ procs = databaseMetaData.getProcedureColumns(
+ metaDataCatalogName,
+ metaDataSchemaName,
+ metaDataProcedureName,
+ null);
+ while (procs.next()) {
+ String columnName = procs.getString("COLUMN_NAME");
+ int columnType = procs.getInt("COLUMN_TYPE");
+ if (columnName == null && (
+ columnType == DatabaseMetaData.procedureColumnIn ||
+ columnType == DatabaseMetaData.procedureColumnInOut ||
+ columnType == DatabaseMetaData.procedureColumnOut)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Skipping metadata for: "
+ + columnName +
+ " " + columnType +
+ " " + procs.getInt("DATA_TYPE") +
+ " " + procs.getString("TYPE_NAME") +
+ " " + procs.getBoolean("NULLABLE") +
+ " (probably a member of a collection)"
+ );
+ }
+ }
+ else {
+ CallParameterMetaData meta = new CallParameterMetaData(
+ columnName,
+ columnType,
+ procs.getInt("DATA_TYPE"),
+ procs.getString("TYPE_NAME"),
+ procs.getBoolean("NULLABLE")
+ );
+ callParameterMetaData.add(meta);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved metadata: "
+ + meta.getParameterName() +
+ " " + meta.getParameterType() +
+ " " + meta.getSqlType() +
+ " " + meta.getTypeName() +
+ " " + meta.isNullable()
+ );
+ }
+ }
+ }
+ }
+ catch (SQLException se) {
+ logger.warn("Error while retreiving metadata for procedure columns: " + se.getMessage());
+ }
+ finally {
+ try {
+ if (procs != null)
+ procs.close();
+ }
+ catch (SQLException se) {
+ logger.warn("Problem closing resultset for procedure column metadata " + se.getMessage());
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java
new file mode 100644
index 0000000000..a1f48d648b
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+
+/**
+ * A generic implementation of the {@link TableMetaDataProvider} that should provide enough features for all supported
+ * databases.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class GenericTableMetaDataProvider implements TableMetaDataProvider {
+
+ /** Logger available to subclasses */
+ protected static final Log logger = LogFactory.getLog(TableMetaDataProvider.class);
+
+ /** indicator whether column metadata should be used */
+ private boolean tableColumnMetaDataUsed = false;
+
+ /** the version of the database */
+ private String databaseVersion;
+
+ /** the name of the user currently connected */
+ private String userName;
+
+ /** indicates whether the identifiers are uppercased */
+ private boolean storesUpperCaseIdentifiers = true;
+
+ /** indicates whether the identifiers are lowercased */
+ private boolean storesLowerCaseIdentifiers = false;
+
+ /** indicates whether generated keys retrieval is supported */
+ private boolean getGeneratedKeysSupported = true;
+
+ /** indicates whether the use of a String[] for generated keys is supported */
+ private boolean generatedKeysColumnNameArraySupported = true;
+
+ /** database product we know that don't support the use of a String[] for generated keys */
+ private List productsNotSupportingGeneratedKeysColumnNameArray = Arrays.asList(new String[] {"Apache Derby"});
+
+ /** Collection of TableParameterMetaData objects */
+ private List insertParameterMetaData = new ArrayList();
+
+
+ /**
+ * Constructor used to initialize with provided database meta data.
+ * @param databaseMetaData meta data to be used
+ * @throws SQLException
+ */
+ protected GenericTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ userName = databaseMetaData.getUserName();
+ }
+
+ /**
+ * Get whether identifiers use upper case
+ */
+ public boolean isStoresUpperCaseIdentifiers() {
+ return storesUpperCaseIdentifiers;
+ }
+
+ /**
+ * Specify whether identifiers use upper case
+ */
+ public void setStoresUpperCaseIdentifiers(boolean storesUpperCaseIdentifiers) {
+ this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
+ }
+
+ /**
+ * Get whether identifiers use lower case
+ */
+ public boolean isStoresLowerCaseIdentifiers() {
+ return storesLowerCaseIdentifiers;
+ }
+
+ /**
+ * Specify whether identifiers use lower case
+ */
+ public void setStoresLowerCaseIdentifiers(boolean storesLowerCaseIdentifiers) {
+ this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
+ }
+
+ public boolean isTableColumnMetaDataUsed() {
+ return tableColumnMetaDataUsed;
+ }
+
+ public List getTableParameterMetaData() {
+ return insertParameterMetaData;
+ }
+
+ public boolean isGetGeneratedKeysSupported() {
+ return getGeneratedKeysSupported;
+ }
+
+ public boolean isGetGeneratedKeysSimulated(){
+ return false;
+ }
+
+ public String getSimpleQueryForGetGeneratedKey(String tableName, String keyColumnName) {
+ return null;
+ }
+
+ /**
+ * Specify whether a column name array is supported for generated keys
+ */
+ public void setGetGeneratedKeysSupported(boolean getGeneratedKeysSupported) {
+ this.getGeneratedKeysSupported = getGeneratedKeysSupported;
+ }
+
+ public boolean isGeneratedKeysColumnNameArraySupported() {
+ return generatedKeysColumnNameArraySupported;
+ }
+
+ /**
+ * Specify whether a column name array is supported for generated keys
+ */
+ public void setGeneratedKeysColumnNameArraySupported(boolean generatedKeysColumnNameArraySupported) {
+ this.generatedKeysColumnNameArraySupported = generatedKeysColumnNameArraySupported;
+ }
+
+
+ public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException {
+
+ try {
+ if (databaseMetaData.supportsGetGeneratedKeys()) {
+ logger.debug("GetGeneratedKeys is supported");
+ setGetGeneratedKeysSupported(true);
+ }
+ else {
+ logger.debug("GetGeneratedKeys is not supported");
+ setGetGeneratedKeysSupported(false);
+ }
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.getGeneratedKeys' - " + se.getMessage());
+ }
+ try {
+ String databaseProductName = databaseMetaData.getDatabaseProductName();
+ if (productsNotSupportingGeneratedKeysColumnNameArray.contains(databaseProductName)) {
+ logger.debug("GeneratedKeysColumnNameArray is not supported for " + databaseProductName);
+ setGeneratedKeysColumnNameArraySupported(false);
+ }
+ else {
+ logger.debug("GeneratedKeysColumnNameArray is supported for " + databaseProductName);
+ setGeneratedKeysColumnNameArraySupported(true);
+ }
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.getDatabaseProductName' - " + se.getMessage());
+ }
+ try {
+ databaseVersion = databaseMetaData.getDatabaseProductVersion();
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.getDatabaseProductVersion' - " + se.getMessage());
+ }
+ try {
+ setStoresUpperCaseIdentifiers(databaseMetaData.storesUpperCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.storesUpperCaseIdentifiers' - " + se.getMessage());
+ }
+ try {
+ setStoresLowerCaseIdentifiers(databaseMetaData.storesLowerCaseIdentifiers());
+ }
+ catch (SQLException se) {
+ logger.warn("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers' - " + se.getMessage());
+ }
+
+ }
+
+ public void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String tableName)
+ throws SQLException {
+
+ tableColumnMetaDataUsed = true;
+
+ locateTableAndProcessMetaData(databaseMetaData, catalogName, schemaName, tableName);
+
+
+ }
+
+ public String tableNameToUse(String tableName) {
+ if (tableName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return tableName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return tableName.toLowerCase();
+ else
+ return tableName;
+ }
+
+ public String catalogNameToUse(String catalogName) {
+ if (catalogName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return catalogName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return catalogName.toLowerCase();
+ else
+ return catalogName;
+ }
+
+ public String schemaNameToUse(String schemaName) {
+ if (schemaName == null)
+ return null;
+ else if (isStoresUpperCaseIdentifiers())
+ return schemaName.toUpperCase();
+ else if(isStoresLowerCaseIdentifiers())
+ return schemaName.toLowerCase();
+ else
+ return schemaName;
+ }
+
+ public String metaDataCatalogNameToUse(String catalogName) {
+ return catalogNameToUse(catalogName);
+ }
+
+ public String metaDataSchemaNameToUse(String schemaName) {
+ if (schemaName == null) {
+ return schemaNameToUse(userName);
+ }
+ return schemaNameToUse(schemaName);
+ }
+
+
+ /**
+ * Provide access to version info for subclasses
+ */
+ protected String getDatabaseVersion() {
+ return databaseVersion;
+ }
+
+ /**
+ * Method supporting the metedata processing for a table
+ */
+ private void locateTableAndProcessMetaData(DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String tableName) {
+ Map tableMeta = new HashMap();
+ ResultSet tables = null;
+
+ try {
+ tables = databaseMetaData.getTables(
+ catalogNameToUse(catalogName),
+ schemaNameToUse(schemaName),
+ tableNameToUse(tableName),
+ null);
+ while (tables != null && tables.next()) {
+ TableMetaData tmd = new TableMetaData();
+ tmd.setCatalogName(tables.getString("TABLE_CAT"));
+ tmd.setSchemaName(tables.getString("TABLE_SCHEM"));
+ tmd.setTableName(tables.getString("TABLE_NAME"));
+ tmd.setType(tables.getString("TABLE_TYPE"));
+ if (tmd.getSchemaName() == null) {
+ tableMeta.put(userName.toUpperCase(), tmd);
+ }
+ else {
+ tableMeta.put(tmd.getSchemaName().toUpperCase(), tmd);
+ }
+ }
+ }
+ catch (SQLException se) {
+ logger.warn("Error while accessing table meta data results" + se.getMessage());
+ }
+ finally {
+ if (tables != null) {
+ try {
+ tables.close();
+ } catch (SQLException e) {
+ logger.warn("Error while closing table meta data reults" + e.getMessage());
+ }
+ }
+ }
+
+ if (tableMeta.size() < 1) {
+ logger.warn("Unable to locate table meta data for '" + tableName +"' -- column names must be provided");
+ }
+ else {
+ TableMetaData tmd = null;
+ if (schemaName == null) {
+ tmd = tableMeta.get(userName.toUpperCase());
+ if (tmd == null) {
+ tmd = tableMeta.get("PUBLIC");
+ if (tmd == null) {
+ tmd = tableMeta.get("DBO");
+ }
+ if (tmd == null) {
+ throw new DataAccessResourceFailureException("Unable to locate table meta data for '" + tableName + "' in the default schema");
+ }
+ }
+ }
+ else {
+ tmd = tableMeta.get(schemaName.toUpperCase());
+ if (tmd == null) {
+ throw new DataAccessResourceFailureException("Unable to locate table meta data for '" + tableName + "' in the '" + schemaName + "' schema");
+ }
+ }
+
+ processTableColumns(databaseMetaData, tmd);
+ }
+ }
+
+ /**
+ * Method supporting the metedata processing for a table's columns
+ */
+ private void processTableColumns(DatabaseMetaData databaseMetaData, TableMetaData tmd) {
+ ResultSet tableColumns = null;
+ String metaDataCatalogName = metaDataCatalogNameToUse(tmd.getCatalogName());
+ String metaDataSchemaName = metaDataSchemaNameToUse(tmd.getSchemaName());
+ String metaDataTableName = tableNameToUse(tmd.getTableName());
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieving metadata for " + metaDataCatalogName + "/" +
+ metaDataSchemaName + "/" + metaDataTableName);
+ }
+ try {
+ tableColumns = databaseMetaData.getColumns(
+ metaDataCatalogName,
+ metaDataSchemaName,
+ metaDataTableName,
+ null);
+ while (tableColumns.next()) {
+ TableParameterMetaData meta = new TableParameterMetaData(
+ tableColumns.getString("COLUMN_NAME"),
+ tableColumns.getInt("DATA_TYPE"),
+ tableColumns.getBoolean("NULLABLE")
+ );
+ insertParameterMetaData.add(meta);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved metadata: "
+ + meta.getParameterName() +
+ " " + meta.getSqlType() +
+ " " + meta.isNullable()
+ );
+ }
+ }
+ }
+ catch (SQLException se) {
+ logger.warn("Error while retreiving metadata for procedure columns: " + se.getMessage());
+ }
+ finally {
+ try {
+ if (tableColumns != null)
+ tableColumns.close();
+ }
+ catch (SQLException se) {
+ logger.warn("Problem closing resultset for procedure column metadata " + se.getMessage());
+ }
+ }
+
+ }
+
+
+ /**
+ * Class representing table meta data
+ */
+ private class TableMetaData {
+ private String catalogName;
+ private String schemaName;
+ private String tableName;
+ private String type;
+
+
+ public String getCatalogName() {
+ return catalogName;
+ }
+
+ public void setCatalogName(String catalogName) {
+ this.catalogName = catalogName;
+ }
+
+ public String getSchemaName() {
+ return schemaName;
+ }
+
+ public void setSchemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java
new file mode 100644
index 0000000000..d1dee3465a
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/HsqlTableMetaDataProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * The HSQL specific implementation of the {@link TableMetaDataProvider}. Suports a feature for
+ * retreiving generated keys without the JDBC 3.0 getGeneratedKeys support.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class HsqlTableMetaDataProvider extends GenericTableMetaDataProvider {
+
+ public HsqlTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ public boolean isGetGeneratedKeysSimulated() {
+ return true;
+ }
+
+
+ public String getSimpleQueryForGetGeneratedKey(String tableName, String keyColumnName) {
+ return "select max(identity()) from " + tableName;
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/OracleCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/OracleCallMetaDataProvider.java
new file mode 100644
index 0000000000..de063d5f8a
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/OracleCallMetaDataProvider.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.core.SqlOutParameter;
+import org.springframework.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.SqlInOutParameter;
+
+/**
+ * Oracle specific implementation for the {@link CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class OracleCallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ private static final String REF_CURSOR_NAME = "REF CURSOR";
+
+
+ public OracleCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public boolean isReturnResultSetSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isRefCursorSupported() {
+ return true;
+ }
+
+ @Override
+ public int getRefCursorSqlType() {
+ return -10;
+ }
+
+ @Override
+ public String metaDataCatalogNameToUse(String catalogName) {
+ // Oracle uses catalog name for package name or an empty string if no package
+ return catalogName == null ? "" : catalogNameToUse(catalogName);
+ }
+
+ @Override
+ public String metaDataSchemaNameToUse(String schemaName) {
+ // Use current user schema if no schema specified
+ return schemaName == null ? getUserName() : super.metaDataSchemaNameToUse(schemaName);
+ }
+
+ @Override
+ public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) {
+ if (meta.getSqlType() == Types.OTHER && REF_CURSOR_NAME.equals(meta.getTypeName())) {
+ return new SqlOutParameter(parameterName, getRefCursorSqlType(), new ColumnMapRowMapper());
+ }
+ else {
+ return super.createDefaultOutParameter(parameterName, meta);
+ }
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresCallMetaDataProvider.java
new file mode 100644
index 0000000000..d23db80d46
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresCallMetaDataProvider.java
@@ -0,0 +1,64 @@
+package org.springframework.jdbc.core.metadata;
+
+import org.springframework.jdbc.core.SqlParameter;
+import org.springframework.jdbc.core.SqlOutParameter;
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * Oracle specific implementation for the {@link org.springframework.jdbc.core.metadata.CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class PostgresCallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ private static final String RETURN_VALUE_NAME = "returnValue";
+
+
+ public PostgresCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public boolean isReturnResultSetSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isRefCursorSupported() {
+ return true;
+ }
+
+ @Override
+ public int getRefCursorSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ public String metaDataSchemaNameToUse(String schemaName) {
+ // Use public schema if no schema specified
+ return schemaName == null ? "public" : super.metaDataSchemaNameToUse(schemaName);
+ }
+
+ @Override
+ public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) {
+ if (meta.getSqlType() == Types.OTHER && "refcursor".equals(meta.getTypeName())) {
+ return new SqlOutParameter(parameterName, getRefCursorSqlType(), new ColumnMapRowMapper());
+ }
+ else {
+ return super.createDefaultOutParameter(parameterName, meta);
+ }
+ }
+
+ @Override
+ public boolean byPassReturnParameter(String parameterName) {
+ return (RETURN_VALUE_NAME.equals(parameterName) ||
+ super.byPassReturnParameter(parameterName));
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresTableMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresTableMetaDataProvider.java
new file mode 100644
index 0000000000..7a60b7a2ed
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/PostgresTableMetaDataProvider.java
@@ -0,0 +1,34 @@
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * The HSQL specific implementation of the {@link org.springframework.jdbc.core.metadata.TableMetaDataProvider}. Suports a feature for
+ * retreiving generated keys without the JDBC 3.0 getGeneratedKeys support.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class PostgresTableMetaDataProvider extends GenericTableMetaDataProvider {
+
+ public PostgresTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ public boolean isGetGeneratedKeysSimulated() {
+ if (getDatabaseVersion().compareTo("8.2.0") >= 0) {
+ return true;
+ }
+ else {
+ logger.warn("PostgreSQL does not support getGeneratedKeys or INSERT ... RETURNING in version " + getDatabaseVersion());
+ return false;
+ }
+ }
+
+
+ public String getSimpleQueryForGetGeneratedKey(String tableName, String keyColumnName) {
+ return "RETURNING " + keyColumnName;
+ }
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SqlServerCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SqlServerCallMetaDataProvider.java
new file mode 100644
index 0000000000..93725b0d73
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SqlServerCallMetaDataProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * SQL Server specific implementation for the {@link CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class SqlServerCallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ private static final String REMOVABLE_COLUMN_PREFIX = "@";
+
+ private static final String RETURN_VALUE_NAME = "@RETURN_VALUE";
+
+
+ public SqlServerCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public String parameterNameToUse(String parameterName) {
+ if (parameterName == null) {
+ return null;
+ }
+ else if (parameterName.length() > 1 && parameterName.startsWith(REMOVABLE_COLUMN_PREFIX)) {
+ return super.parameterNameToUse(parameterName.substring(1));
+ }
+ else {
+ return super.parameterNameToUse(parameterName);
+ }
+ }
+
+ @Override
+ public boolean byPassReturnParameter(String parameterName) {
+ return (RETURN_VALUE_NAME.equals(parameterName) || super.byPassReturnParameter(parameterName));
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SybaseCallMetaDataProvider.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SybaseCallMetaDataProvider.java
new file mode 100644
index 0000000000..7f0757366e
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/SybaseCallMetaDataProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * Sybase specific implementation for the {@link CallMetaDataProvider} interface.
+ * This class is intended for internal use by the Simple JDBC classes.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class SybaseCallMetaDataProvider extends GenericCallMetaDataProvider {
+
+ private static final String REMOVABLE_COLUMN_PREFIX = "@";
+
+ private static final String RETURN_VALUE_NAME = "RETURN_VALUE";
+
+
+ public SybaseCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
+ super(databaseMetaData);
+ }
+
+
+ @Override
+ public String parameterNameToUse(String parameterName) {
+ if (parameterName == null) {
+ return null;
+ }
+ else if (parameterName.length() > 1 && parameterName.startsWith(REMOVABLE_COLUMN_PREFIX)) {
+ return super.parameterNameToUse(parameterName.substring(1));
+ }
+ else {
+ return super.parameterNameToUse(parameterName);
+ }
+ }
+
+ @Override
+ public boolean byPassReturnParameter(String parameterName) {
+ return (RETURN_VALUE_NAME.equals(parameterName) ||
+ RETURN_VALUE_NAME.equals(parameterNameToUse(parameterName)) ||
+ super.byPassReturnParameter(parameterName));
+ }
+
+}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java
new file mode 100644
index 0000000000..d62e0d9ec7
--- /dev/null
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jdbc.core.metadata;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.core.SqlTypeValue;
+import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * Class to manage context metadata used for the configuration
+ * and execution of operations on a database table.
+ *
+ * @author Thomas Risberg
+ * @since 2.5
+ */
+public class TableMetaDataContext {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** name of procedure to call **/
+ private String tableName;
+
+ /** name of catalog for call **/
+ private String catalogName;
+
+ /** name of schema for call **/
+ private String schemaName;
+
+ /** List of columns objects to be used in this context */
+ private List tableColumns = new ArrayList();
+
+ /** should we access insert parameter meta data info or not */
+ private boolean accessTableParameterMetaData = true;
+
+ /** the provider of call meta data */
+ private TableMetaDataProvider metaDataProvider;
+
+ /** are we using generated key columns */
+ private boolean generatedKeyColumnsUsed = false;
+
+
+ /**
+ * Set the name of the table for this context.
+ */
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ /**
+ * Get the name of the table for this context.
+ */
+ public String getTableName() {
+ return this.tableName;
+ }
+
+ /**
+ * Set the name of the catalog for this context.
+ */
+ public void setCatalogName(String catalogName) {
+ this.catalogName = catalogName;
+ }
+
+ /**
+ * Get the name of the catalog for this context.
+ */
+ public String getCatalogName() {
+ return this.catalogName;
+ }
+
+ /**
+ * Set the name of the schema for this context.
+ */
+ public void setSchemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ /**
+ * Get the name of the schema for this context.
+ */
+ public String getSchemaName() {
+ return this.schemaName;
+ }
+
+ /**
+ * Specify whether we should access table column meta data.
+ */
+ public void setAccessTableParameterMetaData(boolean accessTableParameterMetaData) {
+ this.accessTableParameterMetaData = accessTableParameterMetaData;
+ }
+
+ /**
+ * Are we accessing table meta data?
+ */
+ public boolean isAccessTableParameterMetaData() {
+ return this.accessTableParameterMetaData;
+ }
+
+
+ /**
+ * Get a List of the table column names.
+ */
+ public List getTableColumns() {
+ return this.tableColumns;
+ }
+
+ /**
+ * Does this database support the JDBC 3.0 feature of retreiving generated keys
+ * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
+ */
+ public boolean isGetGeneratedKeysSupported() {
+ return this.metaDataProvider.isGetGeneratedKeysSupported();
+ }
+
+ /**
+ * Does this database support simple query to retrieve generated keys
+ * when the JDBC 3.0 feature is not supported
+ * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
+ */
+ public boolean isGetGeneratedKeysSimulated() {
+ return this.metaDataProvider.isGetGeneratedKeysSimulated();
+ }
+
+ /**
+ * Does this database support simple query to retrieve generated keys
+ * when the JDBC 3.0 feature is not supported
+ * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
+ */
+ public String getSimulationQueryForGetGeneratedKey(String tableName, String keyColumnName) {
+ return this.metaDataProvider.getSimpleQueryForGetGeneratedKey(tableName, keyColumnName);
+ }
+
+ /**
+ * Is a column name String array for retreiving generated keys supported
+ * {@link java.sql.Connection#createStruct(String, Object[])}?
+ */
+ public boolean isGeneratedKeysColumnNameArraySupported() {
+ return this.metaDataProvider.isGeneratedKeysColumnNameArraySupported();
+ }
+
+
+ /**
+ * Process the current meta data with the provided configuration options
+ * @param dataSource the DataSource being used
+ * @param declaredColumns any coluns that are declared
+ * @param generatedKeyNames name of generated keys
+ */
+ public void processMetaData(DataSource dataSource, List declaredColumns, String[] generatedKeyNames) {
+ this.metaDataProvider = TableMetaDataProviderFactory.createMetaDataProvider(dataSource, this);
+ this.tableColumns = reconcileColumnsToUse(declaredColumns, generatedKeyNames);
+ }
+
+ /**
+ * Compare columns created from metadata with declared columns and return a reconciled list.
+ * @param declaredColumns declared column names
+ * @param generatedKeyNames names of generated key columns
+ */
+ private List reconcileColumnsToUse(List declaredColumns, String[] generatedKeyNames) {
+ if (generatedKeyNames.length > 0) {
+ generatedKeyColumnsUsed = true;
+ }
+ if (declaredColumns.size() > 0) {
+ return new ArrayList(declaredColumns);
+ }
+ Set keys = new HashSet(generatedKeyNames.length);
+ for (String key : generatedKeyNames) {
+ keys.add(key.toUpperCase());
+ }
+ List columns = new ArrayList();
+ for (TableParameterMetaData meta : metaDataProvider.getTableParameterMetaData()) {
+ if (!keys.contains(meta.getParameterName().toUpperCase())) {
+ columns.add(meta.getParameterName());
+ }
+ }
+ return columns;
+ }
+
+ /**
+ * Match the provided column names and values with the list of columns used.
+ * @param parameterSource the parameter names and values
+ */
+ public List