diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java
index a77e7aba118..27f69d9e466 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -18,16 +18,26 @@ package org.springframework.jdbc.core.namedparam;
import java.util.HashMap;
import java.util.Map;
+import java.util.StringJoiner;
+import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link SqlParameterSource} implementations.
- * Provides registration of SQL types per parameter.
+ * Provides registration of SQL types per parameter and a friendly
+ * {@link #toString() toString} representation enumerating all parameters for
+ * a {@code SqlParameterSource} implementing {@link #getParameterNames()}.
+ * Concrete subclasses must implement {@link #hasValue} and {@link #getValue}.
*
* @author Juergen Hoeller
+ * @author Jens Schauder
* @since 2.0
+ * @see #hasValue(String)
+ * @see #getValue(String)
+ * @see #getParameterNames()
*/
public abstract class AbstractSqlParameterSource implements SqlParameterSource {
@@ -81,4 +91,45 @@ public abstract class AbstractSqlParameterSource implements SqlParameterSource {
return this.typeNames.get(paramName);
}
+
+ /**
+ * Enumerate the parameter names and values with their corresponding SQL type if available,
+ * or just return the simple {@code SqlParameterSource} implementation class name otherwise.
+ * @since 5.2
+ * @see #getParameterNames()
+ */
+ @Override
+ public String toString() {
+ String[] parameterNames = getParameterNames();
+ if (parameterNames != null) {
+ StringJoiner result = new StringJoiner(", ", getClass().getSimpleName() + " {", "}");
+ for (String parameterName : parameterNames) {
+ Object value = getValue(parameterName);
+ if (value instanceof SqlParameterValue) {
+ value = ((SqlParameterValue) value).getValue();
+ }
+ String typeName = getTypeName(parameterName);
+ if (typeName == null) {
+ int sqlType = getSqlType(parameterName);
+ if (sqlType != TYPE_UNKNOWN) {
+ typeName = JdbcUtils.resolveTypeName(sqlType);
+ if (typeName == null) {
+ typeName = String.valueOf(sqlType);
+ }
+ }
+ }
+ StringBuilder entry = new StringBuilder();
+ entry.append(parameterName).append('=').append(value);
+ if (typeName != null) {
+ entry.append(" (type:").append(typeName).append(')');
+ }
+ result.add(entry);
+ }
+ return result.toString();
+ }
+ else {
+ return getClass().getSimpleName();
+ }
+ }
+
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
index 3bb5efe750a..83b8edb24b9 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -25,6 +25,7 @@ import org.springframework.beans.NotReadablePropertyException;
import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.jdbc.core.StatementCreatorUtils;
+import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
@@ -89,7 +90,7 @@ public class BeanPropertySqlParameterSource extends AbstractSqlParameterSource {
}
@Override
- @Nullable
+ @NonNull
public String[] getParameterNames() {
return getReadablePropertyNames();
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSource.java
index 62e61d948d9..7d37148d9ac 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -165,7 +166,7 @@ public class MapSqlParameterSource extends AbstractSqlParameterSource {
}
@Override
- @Nullable
+ @NonNull
public String[] getParameterNames() {
return StringUtils.toStringArray(this.values.keySet());
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/SqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/SqlParameterSource.java
index c9737bc51f5..63747b25284 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/SqlParameterSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/SqlParameterSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -89,7 +89,7 @@ public interface SqlParameterSource {
}
/**
- * Extract all available parameter names if possible.
+ * Enumerate all available parameter names if possible.
*
This is an optional operation, primarily for use with
* {@link org.springframework.jdbc.core.simple.SimpleJdbcInsert}
* and {@link org.springframework.jdbc.core.simple.SimpleJdbcCall}.
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java
index 2fa27474998..970eb944428 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java
@@ -16,6 +16,7 @@
package org.springframework.jdbc.support;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Blob;
@@ -28,6 +29,8 @@ import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.sql.Types;
+import java.util.HashMap;
+import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
@@ -56,6 +59,19 @@ public abstract class JdbcUtils {
private static final Log logger = LogFactory.getLog(JdbcUtils.class);
+ private static final Map typeNames = new HashMap<>();
+
+ static {
+ try {
+ for (Field field : Types.class.getFields()) {
+ typeNames.put((Integer) field.get(null), field.getName());
+ }
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to resolve JDBC Types constants", ex);
+ }
+ }
+
/**
* Close the given JDBC Connection and ignore any thrown exception.
@@ -442,6 +458,18 @@ public abstract class JdbcUtils {
Types.TINYINT == sqlType);
}
+ /**
+ * Resolve the standard type name for the given SQL type, if possible.
+ * @param sqlType the SQL type to resolve
+ * @return the corresponding constant name in {@link java.sql.Types}
+ * (e.g. "VARCHAR"/"NUMERIC"), or {@code null} if not resolvable
+ * @since 5.2
+ */
+ @Nullable
+ public static String resolveTypeName(int sqlType) {
+ return typeNames.get(sqlType);
+ }
+
/**
* Determine the column name to use. The column name is determined based on a
* lookup using ResultSetMetaData.
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSourceTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSourceTests.java
index 4edec32b0c6..4c297c4a891 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSourceTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -19,6 +19,7 @@ package org.springframework.jdbc.core.namedparam;
import java.sql.Types;
import java.util.Arrays;
+import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.tests.sample.beans.TestBean;
@@ -29,16 +30,17 @@ import static org.junit.Assert.*;
* @author Rick Evans
* @author Juergen Hoeller
* @author Arjen Poutsma
+ * @author Juergen Hoeller
*/
public class BeanPropertySqlParameterSourceTests {
@Test(expected = IllegalArgumentException.class)
- public void withNullBeanPassedToCtor() throws Exception {
+ public void withNullBeanPassedToCtor() {
new BeanPropertySqlParameterSource(null);
}
@Test(expected = IllegalArgumentException.class)
- public void getValueWhereTheUnderlyingBeanHasNoSuchProperty() throws Exception {
+ public void getValueWhereTheUnderlyingBeanHasNoSuchProperty() {
BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new TestBean());
source.getValue("thisPropertyDoesNotExist");
}
@@ -65,23 +67,57 @@ public class BeanPropertySqlParameterSourceTests {
}
@Test
- public void hasValueWhereTheUnderlyingBeanHasNoSuchProperty() throws Exception {
+ public void hasValueWhereTheUnderlyingBeanHasNoSuchProperty() {
BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new TestBean());
assertFalse(source.hasValue("thisPropertyDoesNotExist"));
}
@Test(expected = IllegalArgumentException.class)
- public void getValueWhereTheUnderlyingBeanPropertyIsNotReadable() throws Exception {
+ public void getValueWhereTheUnderlyingBeanPropertyIsNotReadable() {
BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new NoReadableProperties());
source.getValue("noOp");
}
@Test
- public void hasValueWhereTheUnderlyingBeanPropertyIsNotReadable() throws Exception {
+ public void hasValueWhereTheUnderlyingBeanPropertyIsNotReadable() {
BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new NoReadableProperties());
assertFalse(source.hasValue("noOp"));
}
+ @Test
+ public void toStringShowsParameterDetails() {
+ BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new TestBean("tb", 99));
+ assertThat(source.toString(), Matchers.allOf(
+ Matchers.startsWith("BeanPropertySqlParameterSource {"),
+ Matchers.endsWith("}"),
+ Matchers.containsString("name=tb (type:VARCHAR)"),
+ Matchers.containsString("age=99 (type:INTEGER)")
+ ));
+ }
+
+ @Test
+ public void toStringShowsCustomSqlType() {
+ BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new TestBean("tb", 99));
+ source.registerSqlType("name", Integer.MAX_VALUE);
+ assertThat(source.toString(), Matchers.allOf(
+ Matchers.startsWith("BeanPropertySqlParameterSource {"),
+ Matchers.endsWith("}"),
+ Matchers.containsString("name=tb (type:" + Integer.MAX_VALUE + ")"),
+ Matchers.containsString("age=99 (type:INTEGER)")
+ ));
+ }
+
+ @Test
+ public void toStringDoesNotShowTypeUnknown() {
+ BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(new TestBean("tb", 99));
+ assertThat(source.toString(), Matchers.allOf(
+ Matchers.startsWith("BeanPropertySqlParameterSource {"),
+ Matchers.endsWith("}"),
+ Matchers.containsString("beanFactory=null"),
+ Matchers.not(Matchers.containsString("beanFactory=null (type:"))
+ ));
+ }
+
@SuppressWarnings("unused")
private static final class NoReadableProperties {
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceTests.java
index 7c92668930c..443a72d6df3 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2006 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -16,36 +16,58 @@
package org.springframework.jdbc.core.namedparam;
+import java.sql.Types;
+
import org.junit.Test;
import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.jdbc.support.JdbcUtils;
import static org.junit.Assert.*;
/**
* @author Rick Evans
* @author Arjen Poutsma
+ * @author Juergen Hoeller
*/
public class MapSqlParameterSourceTests {
@Test
- public void nullParameterValuesPassedToCtorIsOk() throws Exception {
+ public void nullParameterValuesPassedToCtorIsOk() {
new MapSqlParameterSource(null);
}
@Test(expected = IllegalArgumentException.class)
- public void getValueChokesIfParameterIsNotPresent() throws Exception {
+ public void getValueChokesIfParameterIsNotPresent() {
MapSqlParameterSource source = new MapSqlParameterSource();
source.getValue("pechorin was right!");
}
@Test
- public void sqlParameterValueRegistersSqlType() throws Exception {
- MapSqlParameterSource msps = new MapSqlParameterSource("FOO", new SqlParameterValue(2, "Foo"));
+ public void sqlParameterValueRegistersSqlType() {
+ MapSqlParameterSource msps = new MapSqlParameterSource("FOO", new SqlParameterValue(Types.NUMERIC, "Foo"));
assertEquals("Correct SQL Type not registered", 2, msps.getSqlType("FOO"));
MapSqlParameterSource msps2 = new MapSqlParameterSource();
msps2.addValues(msps.getValues());
assertEquals("Correct SQL Type not registered", 2, msps2.getSqlType("FOO"));
}
+ @Test
+ public void toStringShowsParameterDetails() {
+ MapSqlParameterSource source = new MapSqlParameterSource("FOO", new SqlParameterValue(Types.NUMERIC, "Foo"));
+ assertEquals("MapSqlParameterSource {FOO=Foo (type:NUMERIC)}", source.toString());
+ }
+
+ @Test
+ public void toStringShowsCustomSqlType() {
+ MapSqlParameterSource source = new MapSqlParameterSource("FOO", new SqlParameterValue(Integer.MAX_VALUE, "Foo"));
+ assertEquals("MapSqlParameterSource {FOO=Foo (type:" + Integer.MAX_VALUE + ")}", source.toString());
+ }
+
+ @Test
+ public void toStringDoesNotShowTypeUnknown() {
+ MapSqlParameterSource source = new MapSqlParameterSource("FOO", new SqlParameterValue(JdbcUtils.TYPE_UNKNOWN, "Foo"));
+ assertEquals("MapSqlParameterSource {FOO=Foo}", source.toString());
+ }
+
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcUtilsTests.java
index c7459c7a03d..d7c9ceb05f7 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcUtilsTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/JdbcUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -16,6 +16,8 @@
package org.springframework.jdbc.support;
+import java.sql.Types;
+
import org.junit.Test;
import static org.junit.Assert.*;
@@ -24,6 +26,7 @@ import static org.junit.Assert.*;
* Unit tests for {@link JdbcUtils}.
*
* @author Thomas Risberg
+ * @author Juergen Hoeller
*/
public class JdbcUtilsTests {
@@ -36,6 +39,14 @@ public class JdbcUtilsTests {
assertEquals("MySQL", JdbcUtils.commonDatabaseName("MySQL"));
}
+ @Test
+ public void resolveTypeName() {
+ assertEquals("VARCHAR", JdbcUtils.resolveTypeName(Types.VARCHAR));
+ assertEquals("NUMERIC", JdbcUtils.resolveTypeName(Types.NUMERIC));
+ assertEquals("INTEGER", JdbcUtils.resolveTypeName(Types.INTEGER));
+ assertNull(JdbcUtils.resolveTypeName(JdbcUtils.TYPE_UNKNOWN));
+ }
+
@Test
public void convertUnderscoreNameToPropertyName() {
assertEquals("myName", JdbcUtils.convertUnderscoreNameToPropertyName("MY_NAME"));