一、MyBatis整体架构与参数处理模块
在深入参数处理模块之前,我们先了解MyBatis的整体架构,以及参数处理模块在其中的重要地位。

从上图可以看出,MyBatis采用了分层架构设计,而参数处理模块位于核心处理层,是SQL执行过程中的关键环节。它负责将Java对象参数转换为JDBC能够识别的参数类型,并设置到PreparedStatement中。
1.1 参数处理模块的核心职责
参数处理模块主要承担以下核心职责:
sql
▸ 设置SQL参数将Java对象参数设置到PreparedStatement的占位符中
▸ 参数类型转换通过TypeHandler完成Java类型与JDBC类型的转换
▸ 处理参数映射根据ParameterMapping解析参数的名称、类型和处理方式
▸ 处理Null值对Null值进行特殊处理,包括JdbcType的指定
▸ 存储过程支持支持CallableStatement的参数注册
1.2 ParameterHandler接口
ParameterHandler是参数处理的顶层接口,定义了参数处理的基本方法:
csharp
public interface ParameterHandler {
// 获取参数对象
Object getParameterObject();
// 设置PreparedStatement参数
void setParameters(PreparedStatement ps)
throws SQLException;
}
二、ParameterHandler架构
ParameterHandler采用了简单但高效的设计模式。

2.1 默认实现类
MyBatis提供了ParameterHandler的默认实现------DefaultParameterHandler:
java
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;
@Override
public void setParameters(PreparedStatement ps) {
// 获取参数映射列表
List<ParameterMapping> parameterMappings =
boundSql.getParameterMappings();
if (parameterMappings != null) {
// 遍历并设置每个参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping =
parameterMappings.get(i);
// 获取参数值并设置
Object value = getParameterValue(parameterMapping);
TypeHandler typeHandler =
parameterMapping.getTypeHandler();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
2.2 创建ParameterHandler
ParameterHandler通常由Configuration创建:
ini
public ParameterHandler newParameterHandler(
MappedStatement mappedStatement,
Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler =
mappedStatement.getLang()
.createParameterHandler(
mappedStatement,
parameterObject,
boundSql);
// 应用插件拦截
parameterHandler = (ParameterHandler)
interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
三、参数设置流程
参数设置是一个系统化的过程,涉及多个组件的协同工作。

3.1 完整设置流程
参数设置的完整流程如下:
scss
// 步骤1: Executor创建StatementHandler
StatementHandler statementHandler =
new RoutingStatementHandler(
executor,
mappedStatement,
parameterObject,
rowBounds,
resultHandler,
boundSql);
// 步骤2: StatementHandler创建PreparedStatement
Statement statement = instantiateStatement(connection);
// 步骤3: ParameterHandler设置参数
parameterHandler.setParameters(preparedStatement);
3.2 参数获取策略
参数值的获取采用多种策略:
策略1: 从BoundSql的附加参数中获取
kotlin
if (boundSql.hasAdditionalParameter(propertyName)) {
return boundSql.getAdditionalParameter(propertyName);
}
策略2: 参数对象本身
kotlin
if (parameterObject == null) {
return null;
}
策略3: 参数对象是单个基本类型
kotlin
if (typeHandlerRegistry.hasTypeHandler(
parameterObject.getClass())) {
return parameterObject;
}
策略4: 从参数对象中获取属性值
ini
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
return metaObject.getValue(propertyName);
3.3 参数示例
less
// 示例1: 基本类型参数
User selectById(Long id);
// parameterObject = 1L
// 示例2: 多参数
User selectByNameAndEmail(
@Param("name") String name,
@Param("email") String email);
// parameterObject = {name: "张三", email: "xxx@example.com"}
// 示例3: POJO参数
User insert(User user);
// 通过MetaObject反射获取user对象属性
// 示例4: 集合参数
List<User> selectByIds(List<Long> ids);
// foreach处理,生成多个参数
四、参数类型转换
参数类型转换是ParameterHandler的核心功能,通过TypeHandler实现。

4.1 TypeHandler接口
TypeHandler是类型转换的核心接口:
java
public interface TypeHandler<T> {
// 设置PreparedStatement参数
void setParameter(
PreparedStatement ps,
int i,
T parameter,
JdbcType jdbcType) throws SQLException;
// 获取ResultSet结果
T getResult(ResultSet rs, String columnName)
throws SQLException;
T getResult(ResultSet rs, int columnIndex)
throws SQLException;
// 获取CallableStatement结果
T getResult(CallableStatement cs, int columnIndex)
throws SQLException;
}
4.2 BaseTypeHandler抽象类
为了简化TypeHandler的实现,MyBatis提供了BaseTypeHandler抽象类
java
public abstract class BaseTypeHandler<T>
implements TypeHandler<T> {
@Override
public void setParameter(
PreparedStatement ps,
int i,
T parameter,
JdbcType jdbcType) throws SQLException {
if (parameter == null) {
// 处理null值
if (jdbcType == null) {
throw new TypeException(
"JDBC requires JdbcType for null parameters");
}
ps.setNull(i, jdbcType.TYPE_CODE);
} else {
// 设置非null参数
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// 子类实现具体的类型转换逻辑
protected abstract void setNonNullParameter(
PreparedStatement ps,
int i,
T parameter,
JdbcType jdbcType) throws SQLException;
}
4.3 常用TypeHandler实现
StringTypeHandler
scala
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
String parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(
ResultSet rs,
String columnName) throws SQLException {
return rs.getString(columnName);
}
}
LongTypeHandler
scala
public class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
Long parameter,
JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
@Override
public Long getNullableResult(
ResultSet rs,
String columnName) throws SQLException {
long result = rs.getLong(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
}
DateTypeHandler
scala
public class DateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
Date parameter,
JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, new Timestamp(parameter.getTime()));
}
@Override
public Date getNullableResult(
ResultSet rs,
String columnName) throws SQLException {
Timestamp timestamp = rs.getTimestamp(columnName);
return timestamp == null ? null
: new Date(timestamp.getTime());
}
}
4.4 类型注册机制
TypeHandlerRegistry负责管理所有TypeHandler:
typescript
public class TypeHandlerRegistry {
// JDBC类型到TypeHandler的映射
private final Map<JdbcType, TypeHandler<?>>
jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// Java类型到TypeHandler的映射
private final Map<Type, Map<JdbcType, TypeHandler<?>>>
typeHandlerMap = new HashMap<>();
// 注册TypeHandler
public <T> void register(
Class<T> javaType,
TypeHandler<? extends T> typeHandler) {
Map<JdbcType, TypeHandler<?>> map =
typeHandlerMap.get(javaType);
if (map == null) {
map = new HashMap<>();
typeHandlerMap.put(javaType, map);
}
map.put(null, typeHandler);
}
// 获取TypeHandler
public <T> TypeHandler<T> getTypeHandler(
Class<T> type,
JdbcType jdbcType) {
Map<JdbcType, TypeHandler<?>> map =
typeHandlerMap.get(type);
if (map == null) return null;
TypeHandler<?> handler = map.get(jdbcType);
if (handler == null) {
handler = map.get(null);
}
return (TypeHandler<T>) handler;
}
}
五、参数映射处理
ParameterMapping是参数映射的核心数据结构。

5.1 ParameterMapping结构
arduino
public class ParameterMapping {
private final String property; // 参数属性名
private final ParameterMode mode; // 参数模式(IN/OUT/INOUT)
private final Class<?> javaType; // Java类型
private final JdbcType jdbcType; // JDBC类型
private final TypeHandler<?> typeHandler; // 类型处理器
private final String resultMapId; // 结果映射ID
private final Integer numericScale; // 数值精度
}
5.2 ParameterMode枚举
arduino
public enum ParameterMode {
IN, // 输入参数
OUT, // 输出参数
INOUT // 输入输出参数
}
5.3 参数映射解析
参数映射在SQL解析阶段创建:
typescript
// SqlSourceBuilder中
public SqlSource parse(
String originalSql,
Class<?> parameterType,
Map<String, Object> additionalParameters) {
// 创建Token处理器
ParameterMappingTokenHandler handler =
new ParameterMappingTokenHandler(
configuration,
parameterType,
additionalParameters);
// 解析#{}占位符
GenericTokenParser parser =
new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
// 创建StaticSqlSource
return new StaticSqlSource(
configuration,
sql,
handler.getParameterMappings());
}
5.4 参数映射示例
xml
<!-- 示例1: 基本参数映射 -->
<select id="selectById" resultType="User">
SELECT * FROM t_user WHERE id = #{id}
</select>
<!-- ParameterMapping:
{property: id, javaType: Long, jdbcType: BIGINT} -->
<!-- 示例2: 指定jdbcType -->
<insert id="insert">
INSERT INTO t_user (name, email, create_time)
VALUES (
#{name},
#{email, jdbcType=VARCHAR},
#{createTime, jdbcType=TIMESTAMP}
)
</insert>
<!-- 示例3: 指定typeHandler -->
<insert id="insert">
INSERT INTO t_user (data)
VALUES (#{data, typeHandler=com.example.JsonTypeHandler})
</insert>
<!-- 示例4: 存储过程参数 -->
<select id="callProcedure" statementType="CALLABLE">
{call get_user_info(
#{userId, mode=IN, jdbcType=BIGINT},
#{userName, mode=OUT, jdbcType=VARCHAR},
#{userEmail, mode=OUT, jdbcType=VARCHAR}
)}
</select>
六、TypeHandler体系
MyBatis提供了丰富的TypeHandler实现,覆盖了常见的Java类型和JDBC类型。

6.1 内置TypeHandler
MyBatis内置的TypeHandler包括:
| Java类型 | JDBC类型 | TypeHandler |
|---|---|---|
| Boolean | BIT | BooleanTypeHandler |
| Byte | TINYINT | ByteTypeHandler |
| Short | SMALLINT | ShortTypeHandler |
| Integer | INTEGER | IntegerTypeHandler |
| Long | BIGINT | LongTypeHandler |
| Float | FLOAT | FloatTypeHandler |
| Double | DOUBLE | DoubleTypeHandler |
| String | VARCHAR | StringTypeHandler |
| byte[] | BLOB | BlobTypeHandler |
| Date | TIMESTAMP | DateTypeHandler |
| BigDecimal | DECIMAL | BigDecimalTypeHandler |
6.2 自定义TypeHandler
当内置TypeHandler无法满足需求时,可以自定义TypeHandler:
java
// 示例: JSON类型处理器
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler
extends BaseTypeHandler<List<String>> {
private static final Gson GSON = new Gson();
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
List<String> parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, GSON.toJson(parameter));
}
@Override
public List<String> getNullableResult(
ResultSet rs,
String columnName) throws SQLException {
String json = rs.getString(columnName);
return json == null ? null
: GSON.fromJson(json, List.class);
}
@Override
public List<String> getNullableResult(
ResultSet rs,
int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return json == null ? null
: GSON.fromJson(json, List.class);
}
@Override
public List<String> getNullableResult(
CallableStatement cs,
int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return json == null ? null
: GSON.fromJson(json, List.class);
}
}
6.3 注册自定义TypeHandler
配置自定义TypeHandler有两种方式:
方式1: 注解方式
less
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler
extends BaseTypeHandler<List<String>> {
// 实现代码...
}
方式2: 配置文件方式
xml
<typeHandlers>
<typeHandler
handler="com.example.JsonListTypeHandler"/>
</typeHandlers>
或指定Java类型:
ini
<typeHandlers>
<typeHandler
javaType="java.util.List"
jdbcType="VARCHAR"
handler="com.example.JsonListTypeHandler"/>
</typeHandlers>
七、特殊参数处理
7.1 Null值处理
对于Null值,需要指定JdbcType:
xml
<!-- 错误写法: 可能报错 -->
UPDATE t_user SET name = #{name}
<!-- 正确写法: 指定jdbcType -->
UPDATE t_user SET name = #{name, jdbcType=VARCHAR}
<!-- 全局配置: 指定Null的默认JdbcType -->
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
7.2 存储过程参数
存储过程支持IN、OUT、INOUT三种参数模式:
less
// Mapper接口
void callProcedure(
@Param("userId") Long userId,
@Param("userName") String userName,
@Param("result") Integer result);
// XML配置
<select id="callProcedure" statementType="CALLABLE">
{call calculate_discount(
#{userId, mode=IN, jdbcType=BIGINT},
#{userName, mode=IN, jdbcType=VARCHAR},
#{result, mode=OUT, jdbcType=INTEGER}
)}
</select>
7.3 数组参数处理
ini
// Mapper接口
List<User> selectByIds(Long[] ids);
// XML配置
<select id="selectByIds" resultType="User">
SELECT * FROM t_user
WHERE id IN
<foreach collection="array"
item="id"
open="("
separator=","
close=")">
#{id}
</foreach>
</select>
7.4 集合参数处理
ini
// Mapper接口
List<User> selectByIds(List<Long> ids);
// XML配置
<select id="selectByIds" resultType="User">
SELECT * FROM t_user
WHERE id IN
<foreach collection="list"
item="id"
open="("
separator=","
close=")">
#{id}
</foreach>
</select>
八、最佳实践
8.1 参数命名规范
less
// 推荐: 使用@Param注解
User selectByNameAndEmail(
@Param("userName") String name,
@Param("userEmail") String email);
// SQL中引用
<select id="selectByNameAndEmail" resultType="User">
SELECT * FROM t_user
WHERE user_name = #{userName}
AND email = #{userEmail}
</select>
8.2 类型处理建议
sql
▸ 基本类型优先使用包装类避免Null值处理问题
▸ 复杂类型自定义TypeHandler提高代码可读性
▸ 枚举类型使用EnumTypeHandler支持名称或序号存储
▸ 日期类型统一避免类型混乱
8.3 性能优化建议
▸ 复用TypeHandler实例TypeHandler是线程安全的
▸ 避免过度类型转换减少不必要的转换开销
▸ 合理使用JdbcType仅在必要时指定
8.4 常见问题解决
问题1: 参数未绑定
less
// 问题现象
Parameter 'xxx' not found.
Available parameters are [arg0, arg1, param1, param2]
// 解决方案1: 使用@Param注解
User select(
@Param("name") String name,
@Param("email") String email);
// 解决方案2: 使用默认参数名
User select(String name, String email);
// SQL中使用 #{arg0} #{arg1} 或 #{param1} #{param2}
问题2: 类型转换异常
vbnet
// 问题现象
Cause: java.lang.NumberFormatException:
For input string: "xxx"
// 解决方案: 检查参数类型映射
<select id="selectById" resultType="User">
SELECT * FROM t_user
WHERE id = #{id, javaType=Long, jdbcType=BIGINT}
</select>
问题3: Null值处理
arduino
// 问题现象
JDBC requires that the JdbcType must be specified
for all nullable parameters.
// 解决方案1: 指定jdbcType
#{name, jdbcType=VARCHAR}
// 解决方案2: 全局配置
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
九、总结
MyBatis的参数处理模块是整个框架的基础组件,通过精心设计的ParameterHandler和TypeHandler体系,实现了Java类型与JDBC类型的无缝转换。