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.BatchUpdateException;
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.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.InvalidResultSetAccessException;
import org.springframework.jdbc.SQLWarningException;
import org.springframework.jdbc.UncategorizedSQLException;
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.rowset.SqlRowSet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.StringUtils;
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-";
private boolean ignoreWarnings = true;
private int fetchSize = -1;
private int maxRows = -1;
private int queryTimeout = -1;
private boolean skipResultsProcessing = false;
private boolean skipUndeclaredResults = false;
private boolean resultsMapCaseInsensitive = false;
public JdbcTemplate() {
}
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
afterPropertiesSet();
}
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
setDataSource(dataSource);
setLazyInit(lazyInit);
afterPropertiesSet();
}
public void setIgnoreWarnings(boolean ignoreWarnings) {
this.ignoreWarnings = ignoreWarnings;
}
public boolean isIgnoreWarnings() {
return this.ignoreWarnings;
}
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
public int getFetchSize() {
return this.fetchSize;
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
public int getQueryTimeout() {
return this.queryTimeout;
}
public void setSkipResultsProcessing(boolean skipResultsProcessing) {
this.skipResultsProcessing = skipResultsProcessing;
}
public boolean isSkipResultsProcessing() {
return this.skipResultsProcessing;
}
public void setSkipUndeclaredResults(boolean skipUndeclaredResults) {
this.skipUndeclaredResults = skipUndeclaredResults;
}
public boolean isSkipUndeclaredResults() {
return this.skipUndeclaredResults;
}
public void setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) {
this.resultsMapCaseInsensitive = resultsMapCaseInsensitive;
}
public boolean isResultsMapCaseInsensitive() {
return this.resultsMapCaseInsensitive;
}
@Override
@Nullable
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
try {
Connection conToUse = createConnectionProxy(con);
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
String sql = getSql(action);
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("ConnectionCallback", sql, ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
protected Connection createConnectionProxy(Connection con) {
return (Connection) Proxy.newProxyInstance(
ConnectionProxy.class.getClassLoader(),
new Class<?>[] {ConnectionProxy.class},
new CloseSuppressingInvocationHandler(con));
}
@Nullable
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
if (closeResources) {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
@Override
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
return execute(action, true);
}
@Override
public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
@Override
@Nullable
public Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
@Override
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback(), true);
}
@Override
@Nullable
public <T> T (final String sql, final ResultSetExtractor<T> 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<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback(), true);
}
@Override
public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
query(sql, new RowCallbackHandlerResultSetExtractor(rch));
}
@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
@Override
public <T> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper) throws DataAccessException {
class StreamStatementCallback implements StatementCallback<Stream<T>>, SqlProvider {
@Override
public Stream<T> doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
Connection con = stmt.getConnection();
return new ResultSetSpliterator<>(rs, rowMapper).stream().onClose(() -> {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
});
}
@Override
public String getSql() {
return sql;
}
}
return result(execute(new StreamStatementCallback(), false));
}
@Override
public Map<String, Object> queryForMap(String sql) throws DataAccessException {
return result(queryForObject(sql, getColumnMapRowMapper()));
}
@Override
@Nullable
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper);
return DataAccessUtils.nullableSingleResult(results);
}
@Override
@Nullable
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}
@Override
public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException {
return query(sql, getSingleColumnRowMapper(elementType));
}
@Override
public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {
return query(sql, getColumnMapRowMapper());
}
@Override
public SqlRowSet queryForRowSet(String sql) throws DataAccessException {
return result(query(sql, new SqlRowSetResultSetExtractor()));
}
@Override
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<Integer>, SqlProvider {
@Override
public Integer doInStatement(Statement stmt) throws SQLException {
int rows = stmt.executeUpdate(sql);
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
@Override
public String getSql() {
return sql;
}
}
return updateCount(execute(new UpdateStatementCallback(), true));
}
@Override
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<int[]>, SqlProvider {
@Nullable
private String currSql;
@Override
public int[] doInStatement(Statement stmt) throws SQLException, DataAccessException {
int[] rowsAffected = new int[sql.length];
if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) {
for (String sqlStmt : sql) {
this.currSql = appendSql(this.currSql, sqlStmt);
stmt.addBatch(sqlStmt);
}
try {
rowsAffected = stmt.executeBatch();
}
catch (BatchUpdateException ex) {
String batchExceptionSql = null;
for (int i = 0; i < ex.getUpdateCounts().length; i++) {
if (ex.getUpdateCounts()[i] == Statement.EXECUTE_FAILED) {
batchExceptionSql = appendSql(batchExceptionSql, sql[i]);
}
}
if (StringUtils.hasLength(batchExceptionSql)) {
this.currSql = batchExceptionSql;
}
throw ex;
}
}
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;
}
private String appendSql(@Nullable String sql, String statement) {
return (StringUtils.hasLength(sql) ? sql + "; " + statement : statement);
}
@Override
@Nullable
public String getSql() {
return this.currSql;
}
}
int[] result = execute(new BatchUpdateStatementCallback(), true);
Assert.state(result != null, "No update counts");
return result;
}
@Nullable
private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)
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(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
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 translateException("PreparedStatementCallback", sql, ex);
}
finally {
if (closeResources) {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
@Override
@Nullable
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
return execute(psc, action, true);
}
@Override
@Nullable
public <T> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action, true);
}
@Nullable
public <T> T (
PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
logger.debug("Executing prepared SQL query");
return execute(psc, new PreparedStatementCallback<T>() {
@Override
@Nullable
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
}, true);
}
@Override
@Nullable
public <T> T (PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException {
return query(psc, null, rse);
}
@Override
@Nullable
public <T> T (String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
@Override
@Nullable
public <T> T (String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse);
}
@Deprecated
@Override
@Nullable
public <T> T (String sql, @Nullable Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), rse);
}
@Override
@Nullable
public <T> T (String sql, ResultSetExtractor<T> rse, @Nullable Object... args) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), rse);
}
@Override
public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {
query(psc, new RowCallbackHandlerResultSetExtractor(rch));
}
@Override
public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
}
@Override
public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {
query(sql, newArgTypePreparedStatementSetter(args, argTypes), rch);
}
@Deprecated
@Override
public void query(String sql, @Nullable Object[] args, RowCallbackHandler rch) throws DataAccessException {
query(sql, newArgPreparedStatementSetter(args), rch);
}
@Override
public void query(String sql, RowCallbackHandler rch, @Nullable Object... args) throws DataAccessException {
query(sql, newArgPreparedStatementSetter(args), rch);
}
@Override
public <T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper)));
}
@Override
public <T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper)));
}
@Override
public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper)));
}
@Deprecated
@Override
public <T> List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, args, new RowMapperResultSetExtractor<>(rowMapper)));
}
@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
return result(query(sql, args, new RowMapperResultSetExtractor<>(rowMapper)));
}
public <T> Stream<T> queryForStream(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss,
RowMapper<T> rowMapper) throws DataAccessException {
return result(execute(psc, ps -> {
if (pss != null) {
pss.setValues(ps);
}
ResultSet rs = ps.executeQuery();
Connection con = ps.getConnection();
return new ResultSetSpliterator<>(rs, rowMapper).stream().onClose(() -> {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
});
}, false));
}
@Override
public <T> Stream<T> queryForStream(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {
return queryForStream(psc, null, rowMapper);
}
@Override
public <T> Stream<T> queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {
return queryForStream(new SimplePreparedStatementCreator(sql), pss, rowMapper);
}
@Override
public <T> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
return queryForStream(new SimplePreparedStatementCreator(sql), newArgPreparedStatementSetter(args), rowMapper);
}
@Override
@Nullable
public <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper)
throws DataAccessException {
List<T> results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results);
}
@Deprecated
@Override
@Nullable
public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results);
}
@Override
@Nullable
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results);
}
@Override
@Nullable
public <T> T queryForObject(String sql, Object[] args, int[] argTypes, Class<T> requiredType)
throws DataAccessException {
return queryForObject(sql, args, argTypes, getSingleColumnRowMapper(requiredType));
}
@Deprecated
@Override
public <T> T queryForObject(String sql, @Nullable Object[] args, Class<T> requiredType) throws DataAccessException {
return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));
}
@Override
public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException {
return queryForObject(sql, args, getSingleColumnRowMapper(requiredType));
}
@Override
public Map<String, Object> queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper()));
}
@Override
public Map<String, Object> queryForMap(String sql, @Nullable Object... args) throws DataAccessException {
return result(queryForObject(sql, args, getColumnMapRowMapper()));
}
@Override
public <T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType) throws DataAccessException {
return query(sql, args, argTypes, getSingleColumnRowMapper(elementType));
}
@Deprecated
@Override
public <T> List<T> queryForList(String sql, @Nullable Object[] args, Class<T> elementType) throws DataAccessException {
return query(sql, args, getSingleColumnRowMapper(elementType));
}
@Override
public <T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args) throws DataAccessException {
return query(sql, args, getSingleColumnRowMapper(elementType));
}
@Override
public List<Map<String, Object>> queryForList(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return query(sql, args, argTypes, getColumnMapRowMapper());
}
@Override
public List<Map<String, Object>> queryForList(String sql, @Nullable Object... args) throws DataAccessException {
return query(sql, args, getColumnMapRowMapper());
}
@Override
public SqlRowSet queryForRowSet(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return result(query(sql, args, argTypes, new SqlRowSetResultSetExtractor()));
}
@Override
public SqlRowSet queryForRowSet(String sql, @Nullable Object... args) throws DataAccessException {
return result(query(sql, args, new SqlRowSetResultSetExtractor()));
}
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}, true));
}
@Override
public int update(PreparedStatementCreator psc) throws DataAccessException {
return update(psc, (PreparedStatementSetter) null);
}
@Override
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");
return updateCount(execute(psc, ps -> {
int rows = ps.executeUpdate();
List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
generatedKeys.clear();
ResultSet keys = ps.getGeneratedKeys();
if (keys != null) {
try {
RowMapperResultSetExtractor<Map<String, Object>> rse =
new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1);
generatedKeys.addAll(result(rse.extractData(keys)));
}
finally {
JdbcUtils.closeResultSet(keys);
}
}
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
}
return rows;
}, true));
}
@Override
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
return update(new SimplePreparedStatementCreator(sql), pss);
}
@Override
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}
@Override
public int update(String sql, @Nullable Object... args) throws DataAccessException {
return update(sql, newArgPreparedStatementSetter(args));
}
@Override
public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL batch update [" + sql + "]");
}
int[] result = execute(sql, (PreparedStatementCallback<int[]>) ps -> {
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<Integer> rowsAffected = new ArrayList<>();
for (int i = 0; i < batchSize; i++) {
pss.setValues(ps, i);
if (ipss != null && ipss.isBatchExhausted(i)) {
break;
}
rowsAffected.add(ps.executeUpdate());
}
int[] rowsAffectedArray = new int[rowsAffected.size()];
for (int i = 0; i < rowsAffectedArray.length; i++) {
rowsAffectedArray[i] = rowsAffected.get(i);
}
return rowsAffectedArray;
}
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
});
Assert.state(result != null, "No result array");
return result;
}
@Override
public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException {
return batchUpdate(sql, batchArgs, new int[0]);
}
@Override
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {
if (batchArgs.isEmpty()) {
return new int[0];
}
return batchUpdate(
sql,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Object[] values = batchArgs.get(i);
int colIndex = 0;
for (Object value : values) {
colIndex++;
if (value instanceof SqlParameterValue) {
SqlParameterValue paramValue = (SqlParameterValue) value;
StatementCreatorUtils.setParameterValue(ps, colIndex, paramValue, paramValue.getValue());
}
else {
int colType;
if (argTypes.length < colIndex) {
colType = SqlTypeValue.TYPE_UNKNOWN;
}
else {
colType = argTypes[colIndex - 1];
}
StatementCreatorUtils.setParameterValue(ps, colIndex, colType, value);
}
}
}
@Override
public int getBatchSize() {
return batchArgs.size();
}
});
}
@Override
public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize,
final ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize);
}
int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> {
List<int[]> rowsAffected = new ArrayList<>();
try {
boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection());
int n = 0;
for (T obj : batchArgs) {
pss.setValues(ps, obj);
n++;
if (batchSupported) {
ps.addBatch();
if (n % batchSize == 0 || n == batchArgs.size()) {
if (logger.isTraceEnabled()) {
int batchIdx = (n % batchSize == 0) ? n / batchSize : (n / batchSize) + 1;
int items = n - ((n % batchSize == 0) ? n / batchSize - 1 : (n / batchSize)) * batchSize;
logger.trace("Sending SQL batch update #" + batchIdx + " with " + items + " items");
}
rowsAffected.add(ps.executeBatch());
}
}
else {
int i = ps.executeUpdate();
rowsAffected.add(new int[] {i});
}
}
int[][] result1 = new int[rowsAffected.size()][];
for (int i = 0; i < result1.length; i++) {
result1[i] = rowsAffected.get(i);
}
return result1;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
});
Assert.state(result != null, "No result array");
return result;
}
@Override
@Nullable
public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> 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(obtainDataSource());
CallableStatement cs = null;
try {
cs = csc.createCallableStatement(con);
applyStatementSettings(cs);
T result = action.doInCallableStatement(cs);
handleWarnings(cs);
return result;
}
catch (SQLException ex) {
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 translateException("CallableStatementCallback", sql, ex);
}
finally {
if (csc instanceof ParameterDisposer) {
((ParameterDisposer) csc).cleanupParameters();
}
JdbcUtils.closeStatement(cs);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
@Override
@Nullable
public <T> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException {
return execute(new SimpleCallableStatementCreator(callString), action);
}
@Override
public Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
throws DataAccessException {
final List<SqlParameter> updateCountParameters = new ArrayList<>();
final List<SqlParameter> resultSetParameters = new ArrayList<>();
final List<SqlParameter> callParameters = new ArrayList<>();
for (SqlParameter parameter : declaredParameters) {
if (parameter.isResultsParameter()) {
if (parameter instanceof SqlReturnResultSet) {
resultSetParameters.add(parameter);
}
else {
updateCountParameters.add(parameter);
}
}
else {
callParameters.add(parameter);
}
}
Map<String, Object> result = execute(csc, cs -> {
boolean retVal = cs.execute();
int updateCount = cs.getUpdateCount();
if (logger.isTraceEnabled()) {
logger.trace("CallableStatement.execute() returned '" + retVal + "'");
logger.trace("CallableStatement.getUpdateCount() returned " + updateCount);
}
Map<String, Object> resultsMap = createResultsMap();
if (retVal || updateCount != -1) {
resultsMap.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount));
}
resultsMap.putAll(extractOutputParameters(cs, callParameters));
return resultsMap;
});
Assert.state(result != null, "No result map");
return result;
}
protected Map<String, Object> (CallableStatement cs,
@Nullable List<SqlParameter> updateCountParameters, @Nullable List<SqlParameter> resultSetParameters,
int updateCount) throws SQLException {
Map<String, Object> results = new LinkedHashMap<>(4);
int rsIndex = 0;
int updateIndex = 0;
boolean moreResults;
if (!this.skipResultsProcessing) {
do {
if (updateCount == -1) {
if (resultSetParameters != null && resultSetParameters.size() > rsIndex) {
SqlReturnResultSet declaredRsParam = (SqlReturnResultSet) resultSetParameters.get(rsIndex);
results.putAll(processResultSet(cs.getResultSet(), declaredRsParam));
rsIndex++;
}
else {
if (!this.skipUndeclaredResults) {
String rsName = RETURN_RESULT_SET_PREFIX + (rsIndex + 1);
SqlReturnResultSet undeclaredRsParam = new SqlReturnResultSet(rsName, getColumnMapRowMapper());
if (logger.isTraceEnabled()) {
logger.trace("Added default SqlReturnResultSet parameter named '" + rsName + "'");
}
results.putAll(processResultSet(cs.getResultSet(), undeclaredRsParam));
rsIndex++;
}
}
}
else {
if (updateCountParameters != null && updateCountParameters.size() > updateIndex) {
SqlReturnUpdateCount ucParam = (SqlReturnUpdateCount) updateCountParameters.get(updateIndex);
String declaredUcName = ucParam.getName();
results.put(declaredUcName, updateCount);
updateIndex++;
}
else {
if (!this.skipUndeclaredResults) {
String undeclaredName = RETURN_UPDATE_COUNT_PREFIX + (updateIndex + 1);
if (logger.isTraceEnabled()) {
logger.trace("Added default SqlReturnUpdateCount parameter named '" + undeclaredName + "'");
}
results.put(undeclaredName, updateCount);
updateIndex++;
}
}
}
moreResults = cs.getMoreResults();
updateCount = cs.getUpdateCount();
if (logger.isTraceEnabled()) {
logger.trace("CallableStatement.getUpdateCount() returned " + updateCount);
}
}
while (moreResults || updateCount != -1);
}
return results;
}
protected Map<String, Object> (CallableStatement cs, List<SqlParameter> parameters)
throws SQLException {
Map<String, Object> results = CollectionUtils.newLinkedHashMap(parameters.size());
int sqlColIndex = 1;
for (SqlParameter param : parameters) {
if (param instanceof SqlOutParameter) {
SqlOutParameter outParam = (SqlOutParameter) param;
Assert.state(outParam.getName() != null, "Anonymous parameters not allowed");
SqlReturnType returnType = outParam.getSqlReturnType();
if (returnType != null) {
Object out = returnType.getTypeValue(cs, sqlColIndex, outParam.getSqlType(), outParam.getTypeName());
results.put(outParam.getName(), out);
}
else {
Object out = cs.getObject(sqlColIndex);
if (out instanceof ResultSet) {
if (outParam.isResultSetSupported()) {
results.putAll(processResultSet((ResultSet) out, outParam));
}
else {
String rsName = outParam.getName();
SqlReturnResultSet rsParam = new SqlReturnResultSet(rsName, getColumnMapRowMapper());
results.putAll(processResultSet((ResultSet) out, rsParam));
if (logger.isTraceEnabled()) {
logger.trace("Added default SqlReturnResultSet parameter named '" + rsName + "'");
}
}
}
else {
results.put(outParam.getName(), out);
}
}
}
if (!(param.isResultsParameter())) {
sqlColIndex++;
}
}
return results;
}
protected Map<String, Object> processResultSet(
@Nullable ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException {
if (rs != null) {
try {
if (param.getRowMapper() != null) {
RowMapper<?> rowMapper = param.getRowMapper();
Object data = (new RowMapperResultSetExtractor<>(rowMapper)).extractData(rs);
return Collections.singletonMap(param.getName(), data);
}
else if (param.getRowCallbackHandler() != null) {
RowCallbackHandler rch = param.getRowCallbackHandler();
(new RowCallbackHandlerResultSetExtractor(rch)).extractData(rs);
return Collections.singletonMap(param.getName(),
"ResultSet returned from stored procedure was processed");
}
else if (param.getResultSetExtractor() != null) {
Object data = param.getResultSetExtractor().extractData(rs);
return Collections.singletonMap(param.getName(), data);
}
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
return Collections.emptyMap();
}
protected RowMapper<Map<String, Object>> getColumnMapRowMapper() {
return new ColumnMapRowMapper();
}
protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) {
return new SingleColumnRowMapper<>(requiredType);
}
protected Map<String, Object> createResultsMap() {
if (isResultsMapCaseInsensitive()) {
return new LinkedCaseInsensitiveMap<>();
}
else {
return new LinkedHashMap<>();
}
}
protected void applyStatementSettings(Statement stmt) throws SQLException {
int fetchSize = getFetchSize();
if (fetchSize != -1) {
stmt.setFetchSize(fetchSize);
}
int maxRows = getMaxRows();
if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}
protected PreparedStatementSetter newArgPreparedStatementSetter(@Nullable Object[] args) {
return new ArgumentPreparedStatementSetter(args);
}
protected PreparedStatementSetter newArgTypePreparedStatementSetter(Object[] args, int[] argTypes) {
return new ArgumentTypePreparedStatementSetter(args, argTypes);
}
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());
}
}
protected void handleWarnings(@Nullable SQLWarning warning) throws SQLWarningException {
if (warning != null) {
throw new SQLWarningException("Warning not ignored", warning);
}
}
protected DataAccessException translateException(String task, @Nullable String sql, SQLException ex) {
DataAccessException dae = getExceptionTranslator().translate(task, sql, ex);
return (dae != null ? dae : new UncategorizedSQLException(task, sql, ex));
}
@Nullable
private static String getSql(Object sqlProvider) {
if (sqlProvider instanceof SqlProvider) {
return ((SqlProvider) sqlProvider).getSql();
}
else {
return null;
}
}
private static <T> T result(@Nullable T result) {
Assert.state(result != null, "No result");
return result;
}
private static int updateCount(@Nullable Integer result) {
Assert.state(result != null, "No update count");
return result;
}
private class CloseSuppressingInvocationHandler implements InvocationHandler {
private final Connection target;
public CloseSuppressingInvocationHandler(Connection target) {
this.target = target;
}
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "equals":
return (proxy == args[0]);
case "hashCode":
return System.identityHashCode(proxy);
case "close":
return null;
case "isClosed":
return false;
case "getTargetConnection":
return this.target;
case "unwrap":
return (((Class<?>) args[0]).isInstance(proxy) ? proxy : this.target.unwrap((Class<?>) args[0]));
case "isWrapperFor":
return (((Class<?>) args[0]).isInstance(proxy) || this.target.isWrapperFor((Class<?>) args[0]));
}
try {
Object retVal = method.invoke(this.target, args);
if (retVal instanceof Statement) {
applyStatementSettings(((Statement) retVal));
}
return retVal;
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
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;
}
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement(this.sql);
}
@Override
public String getSql() {
return this.sql;
}
}
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;
}
@Override
public CallableStatement createCallableStatement(Connection con) throws SQLException {
return con.prepareCall(this.callString);
}
@Override
public String getSql() {
return this.callString;
}
}
private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> {
private final RowCallbackHandler rch;
public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
this.rch = rch;
}
@Override
@Nullable
public Object extractData(ResultSet rs) throws SQLException {
while (rs.next()) {
this.rch.processRow(rs);
}
return null;
}
}
private static class ResultSetSpliterator<T> implements Spliterator<T> {
private final ResultSet rs;
private final RowMapper<T> rowMapper;
private int rowNum = 0;
public ResultSetSpliterator(ResultSet rs, RowMapper<T> rowMapper) {
this.rs = rs;
this.rowMapper = rowMapper;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
try {
if (this.rs.next()) {
action.accept(this.rowMapper.mapRow(this.rs, this.rowNum++));
return true;
}
return false;
}
catch (SQLException ex) {
throw new InvalidResultSetAccessException(ex);
}
}
@Override
@Nullable
public Spliterator<T> trySplit() {
return null;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
@Override
public int characteristics() {
return Spliterator.ORDERED;
}
public Stream<T> stream() {
return StreamSupport.stream(this, false);
}
}
}