DefaultParameterHandler.java
/*
* Copyright 2009-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.defaults;
import java.lang.reflect.Type;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.ObjectTypeHandler;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
private ParameterMetaData paramMetaData;
private MetaObject paramMetaObject;
private HashMap<Class<?>, MetaClass> metaClassCache = new HashMap<>();
private static final ParameterMetaData NULL_PARAM_METADATA = new ParameterMetaData() {
// @formatter:off
public <T> T unwrap(Class<T> iface) throws SQLException { return null; }
public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }
public boolean isSigned(int param) throws SQLException { return false; }
public int isNullable(int param) throws SQLException { return 0; }
public int getScale(int param) throws SQLException { return 0; }
public int getPrecision(int param) throws SQLException { return 0; }
public String getParameterTypeName(int param) throws SQLException { return null; }
public int getParameterType(int param) throws SQLException { return 0; }
public int getParameterMode(int param) throws SQLException { return 0; }
public int getParameterCount() throws SQLException { return 0; }
public String getParameterClassName(int param) throws SQLException { return null; }
// @formatter:on
};
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
ParamNameResolver paramNameResolver = mappedStatement.getParamNameResolver();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
JdbcType jdbcType = parameterMapping.getJdbcType();
JdbcType actualJdbcType = jdbcType == null ? getParamJdbcType(ps, i + 1) : jdbcType;
Type propertyGenericType = null;
TypeHandler typeHandler = parameterMapping.getTypeHandler();
if (parameterMapping.hasValue()) {
value = parameterMapping.getValue();
} else if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else {
Class<? extends Object> parameterClass = parameterObject.getClass();
TypeHandler paramTypeHandler = typeHandlerRegistry.getTypeHandler(parameterClass, actualJdbcType);
if (paramTypeHandler != null) {
value = parameterObject;
typeHandler = paramTypeHandler;
} else {
paramMetaObject = getParamMetaObject();
value = paramMetaObject.getValue(propertyName);
if (typeHandler == null && value != null) {
if (paramNameResolver != null && ParamMap.class.equals(parameterClass)) {
Type actualParamType = paramNameResolver.getType(propertyName);
if (actualParamType instanceof Class) {
Class<?> actualParamClass = (Class<?>) actualParamType;
MetaClass metaClass = metaClassCache.computeIfAbsent(actualParamClass,
k -> MetaClass.forClass(k, configuration.getReflectorFactory()));
PropertyTokenizer propertyTokenizer = new PropertyTokenizer(propertyName);
String multiParamsPropertyName;
if (propertyTokenizer.hasNext()) {
multiParamsPropertyName = propertyTokenizer.getChildren();
if (metaClass.hasGetter(multiParamsPropertyName)) {
Entry<Type, Class<?>> getterType = metaClass.getGenericGetterType(multiParamsPropertyName);
propertyGenericType = getterType.getKey();
}
} else {
propertyGenericType = actualParamClass;
}
} else {
propertyGenericType = actualParamType;
}
} else {
try {
propertyGenericType = paramMetaObject.getGenericGetterType(propertyName).getKey();
typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(propertyGenericType,
actualJdbcType, null);
} catch (Exception e) {
// Not always resolvable
}
}
}
}
}
if (value == null) {
if (jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
if (typeHandler == null) {
typeHandler = ObjectTypeHandler.INSTANCE;
}
} else if (typeHandler == null) {
if (propertyGenericType == null) {
propertyGenericType = value.getClass();
}
typeHandler = typeHandlerRegistry.getTypeHandler(propertyGenericType, actualJdbcType, null);
}
if (typeHandler == null) {
typeHandler = typeHandlerRegistry.getTypeHandler(actualJdbcType);
}
if (typeHandler == null) {
throw new TypeException("Could not find type handler for Java type '" + propertyGenericType.getTypeName()
+ "' nor JDBC type '" + actualJdbcType + "'");
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
private MetaObject getParamMetaObject() {
if (paramMetaObject != null) {
return paramMetaObject;
}
paramMetaObject = configuration.newMetaObject(parameterObject);
return paramMetaObject;
}
private JdbcType getParamJdbcType(PreparedStatement ps, int paramIndex) {
try {
if (paramMetaData == null) {
paramMetaData = ps.getParameterMetaData();
}
return paramMetaData == NULL_PARAM_METADATA ? null : JdbcType.forCode(paramMetaData.getParameterType(paramIndex));
} catch (SQLException e) {
if (paramMetaData == null) {
paramMetaData = NULL_PARAM_METADATA;
}
return null;
}
}
}