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"));