Nullability fine-tuning around bean properties

Issue: SPR-15720
Issue: SPR-15792
This commit is contained in:
Juergen Hoeller
2017-07-19 11:43:58 +02:00
parent 06fc092be2
commit 9fc4fb10b0
31 changed files with 243 additions and 206 deletions

View File

@@ -244,6 +244,19 @@ public class CallMetaDataContext {
}
/**
* 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);
}
private CallMetaDataProvider obtainMetaDataProvider() {
Assert.state(this.metaDataProvider != null, "No CallMetaDataProvider - call initializeMetaData first");
return this.metaDataProvider;
}
/**
* Create a ReturnResultSetParameter/SqlOutParameter depending on the support provided
* by the JDBC driver used for the database in use.
@@ -252,13 +265,13 @@ public class CallMetaDataContext {
* @return the appropriate SqlParameter
*/
public SqlParameter createReturnResultSetParameter(String parameterName, RowMapper<?> rowMapper) {
Assert.state(this.metaDataProvider != null, "No CallMetaDataProvider available");
if (this.metaDataProvider.isReturnResultSetSupported()) {
CallMetaDataProvider provider = obtainMetaDataProvider();
if (provider.isReturnResultSetSupported()) {
return new SqlReturnResultSet(parameterName, rowMapper);
}
else {
if (this.metaDataProvider.isRefCursorSupported()) {
return new SqlOutParameter(parameterName, this.metaDataProvider.getRefCursorSqlType(), rowMapper);
if (provider.isRefCursorSupported()) {
return new SqlOutParameter(parameterName, provider.getRefCursorSqlType(), rowMapper);
}
else {
throw new InvalidDataAccessApiUsageException("Return of a ResultSet from a stored procedure is not supported.");
@@ -290,14 +303,6 @@ public class CallMetaDataContext {
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 metadata is used,
* the parameters will be matched against the metadata information and any missing
@@ -312,7 +317,7 @@ public class CallMetaDataContext {
* Reconcile the provided parameters with available metadata and add new ones where appropriate.
*/
protected List<SqlParameter> reconcileParameters(List<SqlParameter> parameters) {
Assert.state(this.metaDataProvider != null, "No CallMetaDataProvider available");
CallMetaDataProvider provider = obtainMetaDataProvider();
final List<SqlParameter> declaredReturnParams = new ArrayList<>();
final Map<String, SqlParameter> declaredParams = new LinkedHashMap<>();
@@ -321,7 +326,7 @@ public class CallMetaDataContext {
List<String> metaDataParamNames = new ArrayList<>();
// Get the names of the meta data parameters
for (CallParameterMetaData meta : this.metaDataProvider.getCallParameterMetaData()) {
for (CallParameterMetaData meta : provider.getCallParameterMetaData()) {
if (meta.getParameterType() != DatabaseMetaData.procedureColumnReturn) {
metaDataParamNames.add(lowerCase(meta.getParameterName()));
}
@@ -338,7 +343,7 @@ public class CallMetaDataContext {
throw new IllegalArgumentException("Anonymous parameters not supported for calls - " +
"please specify a name for the parameter of SQL type " + param.getSqlType());
}
String paramNameToMatch = lowerCase(this.metaDataProvider.parameterNameToUse(paramName));
String paramNameToMatch = lowerCase(provider.parameterNameToUse(paramName));
declaredParams.put(paramNameToMatch, param);
if (param instanceof SqlOutParameter) {
outParamNames.add(paramName);
@@ -360,24 +365,23 @@ public class CallMetaDataContext {
List<SqlParameter> workParams = new ArrayList<>();
workParams.addAll(declaredReturnParams);
if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
if (!provider.isProcedureColumnMetaDataUsed()) {
workParams.addAll(declaredParams.values());
return workParams;
}
Map<String, String> limitedInParamNamesMap = new HashMap<>(this.limitedInParameterNames.size());
for (String limitedParamName : this.limitedInParameterNames) {
limitedInParamNamesMap.put(
lowerCase(this.metaDataProvider.parameterNameToUse(limitedParamName)), limitedParamName);
limitedInParamNamesMap.put(lowerCase(provider.parameterNameToUse(limitedParamName)), limitedParamName);
}
for (CallParameterMetaData meta : this.metaDataProvider.getCallParameterMetaData()) {
for (CallParameterMetaData meta : provider.getCallParameterMetaData()) {
String paramName = meta.getParameterName();
String paramNameToCheck = null;
if (paramName != null) {
paramNameToCheck = lowerCase(this.metaDataProvider.parameterNameToUse(paramName));
paramNameToCheck = lowerCase(provider.parameterNameToUse(paramName));
}
String paramNameToUse = this.metaDataProvider.parameterNameToUse(paramName);
String paramNameToUse = provider.parameterNameToUse(paramName);
if (declaredParams.containsKey(paramNameToCheck) ||
(meta.getParameterType() == DatabaseMetaData.procedureColumnReturn && returnDeclared)) {
SqlParameter param;
@@ -409,7 +413,7 @@ public class CallMetaDataContext {
else {
if (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn) {
if (!isFunction() && !isReturnValueRequired() && paramName != null &&
this.metaDataProvider.byPassReturnParameter(paramName)) {
provider.byPassReturnParameter(paramName)) {
if (logger.isDebugEnabled()) {
logger.debug("Bypassing metadata return parameter for '" + paramName + "'");
}
@@ -417,7 +421,7 @@ public class CallMetaDataContext {
else {
String returnNameToUse =
(StringUtils.hasLength(paramNameToUse) ? paramNameToUse : getFunctionReturnName());
workParams.add(this.metaDataProvider.createDefaultOutParameter(returnNameToUse, meta));
workParams.add(provider.createDefaultOutParameter(returnNameToUse, meta));
if (isFunction()) {
setFunctionReturnName(returnNameToUse);
outParamNames.add(returnNameToUse);
@@ -432,14 +436,14 @@ public class CallMetaDataContext {
paramNameToUse = "";
}
if (meta.getParameterType() == DatabaseMetaData.procedureColumnOut) {
workParams.add(this.metaDataProvider.createDefaultOutParameter(paramNameToUse, meta));
workParams.add(provider.createDefaultOutParameter(paramNameToUse, meta));
outParamNames.add(paramNameToUse);
if (logger.isDebugEnabled()) {
logger.debug("Added metadata out parameter for '" + paramNameToUse + "'");
}
}
else if (meta.getParameterType() == DatabaseMetaData.procedureColumnInOut) {
workParams.add(this.metaDataProvider.createDefaultInOutParameter(paramNameToUse, meta));
workParams.add(provider.createDefaultInOutParameter(paramNameToUse, meta));
outParamNames.add(paramNameToUse);
if (logger.isDebugEnabled()) {
logger.debug("Added metadata in out parameter for '" + paramNameToUse + "'");
@@ -448,7 +452,7 @@ public class CallMetaDataContext {
else {
if (this.limitedInParameterNames.isEmpty() ||
limitedInParamNamesMap.containsKey(lowerCase(paramNameToUse))) {
workParams.add(this.metaDataProvider.createDefaultInParameter(paramNameToUse, meta));
workParams.add(provider.createDefaultInParameter(paramNameToUse, meta));
if (logger.isDebugEnabled()) {
logger.debug("Added metadata in parameter for '" + paramNameToUse + "'");
}
@@ -473,8 +477,6 @@ public class CallMetaDataContext {
* @return a Map containing the matched parameter names with the value taken from the input
*/
public Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
Assert.state(this.metaDataProvider != null, "No CallMetaDataProvider available");
// 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<String, String> caseInsensitiveParameterNames =
@@ -485,7 +487,7 @@ public class CallMetaDataContext {
for (SqlParameter parameter : this.callParameters) {
if (parameter.isInputValueProvided()) {
String parameterName = parameter.getName();
String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
String parameterNameToMatch = obtainMetaDataProvider().parameterNameToUse(parameterName);
if (parameterNameToMatch != null) {
callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
}
@@ -538,8 +540,8 @@ public class CallMetaDataContext {
* @return a Map containing the matched parameter names with the value taken from the input
*/
public Map<String, ?> matchInParameterValuesWithCallParameters(Map<String, ?> inParameters) {
Assert.state(this.metaDataProvider != null, "No CallMetaDataProvider available");
if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
CallMetaDataProvider provider = obtainMetaDataProvider();
if (!provider.isProcedureColumnMetaDataUsed()) {
return inParameters;
}
@@ -547,7 +549,7 @@ public class CallMetaDataContext {
for (SqlParameter parameter : this.callParameters) {
if (parameter.isInputValueProvided()) {
String parameterName = parameter.getName();
String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
String parameterNameToMatch = provider.parameterNameToUse(parameterName);
if (parameterNameToMatch != null) {
callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
}
@@ -556,7 +558,7 @@ public class CallMetaDataContext {
Map<String, Object> matchedParameters = new HashMap<>(inParameters.size());
for (String parameterName : inParameters.keySet()) {
String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
String parameterNameToMatch = provider.parameterNameToUse(parameterName);
String callParameterName = callParameterNames.get(lowerCase(parameterNameToMatch));
if (callParameterName == null) {
if (logger.isDebugEnabled()) {
@@ -577,7 +579,7 @@ public class CallMetaDataContext {
if (matchedParameters.size() < callParameterNames.size()) {
for (String parameterName : callParameterNames.keySet()) {
String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
String parameterNameToMatch = provider.parameterNameToUse(parameterName);
String callParameterName = callParameterNames.get(lowerCase(parameterNameToMatch));
if (!matchedParameters.containsKey(callParameterName)) {
logger.warn("Unable to locate the corresponding parameter value for '" + parameterName +

View File

@@ -33,6 +33,7 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Class to manage context metadata used for the configuration
@@ -47,13 +48,16 @@ public class TableMetaDataContext {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
/** name of table for this context **/
/** Name of table for this context */
@Nullable
private String tableName;
/** name of catalog for this context **/
/** Name of catalog for this context */
@Nullable
private String catalogName;
/** name of schema for this context **/
/** Name of schema for this context */
@Nullable
private String schemaName;
/** List of columns objects to be used in this context */
@@ -66,6 +70,7 @@ public class TableMetaDataContext {
private boolean overrideIncludeSynonymsDefault = false;
/** the provider of table meta data */
@Nullable
private TableMetaDataProvider metaDataProvider;
/** are we using generated key columns */
@@ -153,41 +158,6 @@ public class TableMetaDataContext {
return this.tableColumns;
}
/**
* Does this database support the JDBC 3.0 feature of retrieving 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()}?
*/
@Nullable
public String getSimulationQueryForGetGeneratedKey(String tableName, String keyColumnName) {
return this.metaDataProvider.getSimpleQueryForGetGeneratedKey(tableName, keyColumnName);
}
/**
* Is a column name String array for retrieving 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.
@@ -200,6 +170,11 @@ public class TableMetaDataContext {
this.tableColumns = reconcileColumnsToUse(declaredColumns, generatedKeyNames);
}
private TableMetaDataProvider obtainMetaDataProvider() {
Assert.state(this.metaDataProvider != null, "No TableMetaDataProvider - call processMetaData first");
return this.metaDataProvider;
}
/**
* Compare columns created from metadata with declared columns and return a reconciled list.
* @param declaredColumns declared column names
@@ -217,7 +192,7 @@ public class TableMetaDataContext {
keys.add(key.toUpperCase());
}
List<String> columns = new ArrayList<>();
for (TableParameterMetaData meta : metaDataProvider.getTableParameterMetaData()) {
for (TableParameterMetaData meta : obtainMetaDataProvider().getTableParameterMetaData()) {
if (!keys.contains(meta.getParameterName().toUpperCase())) {
columns.add(meta.getParameterName());
}
@@ -336,7 +311,7 @@ public class TableMetaDataContext {
*/
public int[] createInsertTypes() {
int[] types = new int[getTableColumns().size()];
List<TableParameterMetaData> parameters = this.metaDataProvider.getTableParameterMetaData();
List<TableParameterMetaData> parameters = obtainMetaDataProvider().getTableParameterMetaData();
Map<String, TableParameterMetaData> parameterMap =
new LinkedHashMap<>(parameters.size());
for (TableParameterMetaData tpmd : parameters) {
@@ -361,4 +336,40 @@ public class TableMetaDataContext {
return types;
}
/**
* Does this database support the JDBC 3.0 feature of retrieving generated keys
* {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
*/
public boolean isGetGeneratedKeysSupported() {
return obtainMetaDataProvider().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 obtainMetaDataProvider().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()}?
*/
@Nullable
public String getSimulationQueryForGetGeneratedKey(String tableName, String keyColumnName) {
return obtainMetaDataProvider().getSimpleQueryForGetGeneratedKey(tableName, keyColumnName);
}
/**
* Is a column name String array for retrieving generated keys supported?
* {@link java.sql.Connection#createStruct(String, Object[])}?
*/
public boolean isGeneratedKeysColumnNameArraySupported() {
return obtainMetaDataProvider().isGeneratedKeysColumnNameArraySupported();
}
}

View File

@@ -21,7 +21,6 @@ import java.sql.SQLException;
import java.util.Properties;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Abstract base class for JDBC {@link javax.sql.DataSource} implementations
@@ -58,8 +57,7 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource {
* @see java.sql.Driver#connect(String, java.util.Properties)
*/
public void setUrl(@Nullable String url) {
Assert.hasText(url, "Property 'url' must not be empty");
this.url = url.trim();
this.url = (url != null ? url.trim() : null);
}
/**

View File

@@ -33,6 +33,7 @@ public class CustomSQLErrorCodesTranslation {
private String[] errorCodes = new String[0];
@Nullable
private Class<?> exceptionClass;