当前位置: 代码网 > it编程>编程语言>Java > MyBatis参数处理模块用法及解读

MyBatis参数处理模块用法及解读

2026年01月02日 Java 我要评论
一、mybatis整体架构与参数处理模块在深入参数处理模块之前,我们先了解mybatis的整体架构,以及参数处理模块在其中的重要地位。从上图可以看出,mybatis采用了分层架构设计,而参数处理模块位

一、mybatis整体架构与参数处理模块

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

从上图可以看出,mybatis采用了分层架构设计,而参数处理模块位于核心处理层,是sql执行过程中的关键环节。它负责将java对象参数转换为jdbc能够识别的参数类型,并设置到preparedstatement中。

1.1 参数处理模块的核心职责

参数处理模块主要承担以下核心职责:

▸ 设置sql参数将java对象参数设置到preparedstatement的占位符中
▸ 参数类型转换通过typehandler完成java类型与jdbc类型的转换
▸ 处理参数映射根据parametermapping解析参数的名称、类型和处理方式
▸ 处理null值对null值进行特殊处理,包括jdbctype的指定
▸ 存储过程支持支持callablestatement的参数注册

1.2 parameterhandler接口

parameterhandler是参数处理的顶层接口,定义了参数处理的基本方法:

public interface parameterhandler {
    // 获取参数对象
    object getparameterobject();

    // 设置preparedstatement参数
    void setparameters(preparedstatement ps) 
        throws sqlexception;
}

二、parameterhandler架构

parameterhandler采用了简单但高效的设计模式。

2.1 默认实现类

mybatis提供了parameterhandler的默认实现——defaultparameterhandler:

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创建:

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 完整设置流程

参数设置的完整流程如下:

// 步骤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的附加参数中获取

if (boundsql.hasadditionalparameter(propertyname)) {
    return boundsql.getadditionalparameter(propertyname);
}

策略2: 参数对象本身

if (parameterobject == null) {
    return null;
}

策略3: 参数对象是单个基本类型

if (typehandlerregistry.hastypehandler(
    parameterobject.getclass())) {
    return parameterobject;
}

策略4: 从参数对象中获取属性值

metaobject metaobject = 
    configuration.newmetaobject(parameterobject);
return metaobject.getvalue(propertyname);

3.3 参数示例

// 示例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是类型转换的核心接口:

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抽象类

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

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

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

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:

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结构

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枚举

public enum parametermode {
    in,    // 输入参数
    out,   // 输出参数
    inout  // 输入输出参数
}

5.3 参数映射解析

参数映射在sql解析阶段创建:

// 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 参数映射示例

<!-- 示例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
booleanbitbooleantypehandler
bytetinyintbytetypehandler
shortsmallintshorttypehandler
integerintegerintegertypehandler
longbigintlongtypehandler
floatfloatfloattypehandler
doubledoubledoubletypehandler
stringvarcharstringtypehandler
byte[]blobblobtypehandler
datetimestampdatetypehandler
bigdecimaldecimalbigdecimaltypehandler

6.2 自定义typehandler

当内置typehandler无法满足需求时,可以自定义typehandler:

// 示例: 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: 注解方式

@mappedtypes(list.class)
@mappedjdbctypes(jdbctype.varchar)
public class jsonlisttypehandler 
    extends basetypehandler<list<string>> {
    // 实现代码...
}

方式2: 配置文件方式

<typehandlers>
    <typehandler 
        handler="com.example.jsonlisttypehandler"/>
</typehandlers>

或指定java类型:

<typehandlers>
    <typehandler 
        javatype="java.util.list"
        jdbctype="varchar"
        handler="com.example.jsonlisttypehandler"/>
</typehandlers>

七、特殊参数处理

7.1 null值处理

对于null值,需要指定jdbctype:

<!-- 错误写法: 可能报错 -->
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三种参数模式:

// 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 数组参数处理

// 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 集合参数处理

// 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 参数命名规范

// 推荐: 使用@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 类型处理建议

▸ 基本类型优先使用包装类避免null值处理问题
▸ 复杂类型自定义typehandler提高代码可读性
▸ 枚举类型使用enumtypehandler支持名称或序号存储
▸ 日期类型统一避免类型混乱

8.3 性能优化建议

▸ 复用typehandler实例typehandler是线程安全的
▸ 避免过度类型转换减少不必要的转换开销
▸ 合理使用jdbctype仅在必要时指定

8.4 常见问题解决

问题1: 参数未绑定

// 问题现象
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: 类型转换异常

// 问题现象
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值处理

// 问题现象
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类型的无缝转换。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com