MyBatis参数处理模块详解

一、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类型的无缝转换。

相关推荐
Chloeis Syntax2 小时前
MySQL初阶学习日记(7)--- 事务
java·数据库·笔记·学习·mysql
幽络源小助理2 小时前
SpringBoot+Vue雅苑小区管理系统源码 | Java物业项目免费下载 – 幽络源
java·vue.js·spring boot
沛沛老爹2 小时前
2025年Java发展现状与趋势:稳踞企业开发核心,云原生与AI集成成为新引擎
java·云原生·企业开发·发展趋势·java生态
锐湃2 小时前
手写agp8自定义插件,用ASM实现路由跳转
java·服务器·前端
weixin_478433322 小时前
iluwatar 设计模式
java·开发语言·设计模式
花卷HJ2 小时前
Android 多媒体文件工具类封装(MediaFileUtils)
android·java
モンキー・D・小菜鸡儿2 小时前
Android 自定义粒子连线动画视图实现:打造炫酷背景效果
android·java
Java天梯之路2 小时前
# Spring Boot 钩子全集实战(四):`SpringApplicationRunListener.environmentPrepared()` 详解
java·spring·面试
i757_w2 小时前
IDEA快捷键被占用
java·ide·intellij-idea