一、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 |
|---|---|---|
| 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:
// 示例: 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类型的无缝转换。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论