AbstractSqlParameterSource enumerates parameter values in toString()

Closes gh-2080
This commit is contained in:
Juergen Hoeller 2019-04-02 14:56:38 +02:00
parent 4a5b9d39d4
commit a11a592734
8 changed files with 170 additions and 20 deletions

View File

@ -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();
}
}
}

View File

@ -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();
}

View File

@ -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());
}

View File

@ -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.
* <p>This is an optional operation, primarily for use with
* {@link org.springframework.jdbc.core.simple.SimpleJdbcInsert}
* and {@link org.springframework.jdbc.core.simple.SimpleJdbcCall}.

View File

@ -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<Integer, String> 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.

View File

@ -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 {

View File

@ -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());
}
}

View File

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