MyBatis參數(shù)處理模塊用法及解讀
一、MyBatis整體架構(gòu)與參數(shù)處理模塊
在深入?yún)?shù)處理模塊之前,我們先了解MyBatis的整體架構(gòu),以及參數(shù)處理模塊在其中的重要地位。

從上圖可以看出,MyBatis采用了分層架構(gòu)設(shè)計(jì),而參數(shù)處理模塊位于核心處理層,是SQL執(zhí)行過(guò)程中的關(guān)鍵環(huán)節(jié)。它負(fù)責(zé)將Java對(duì)象參數(shù)轉(zhuǎn)換為JDBC能夠識(shí)別的參數(shù)類(lèi)型,并設(shè)置到PreparedStatement中。
1.1 參數(shù)處理模塊的核心職責(zé)
參數(shù)處理模塊主要承擔(dān)以下核心職責(zé):
? 設(shè)置SQL參數(shù)將Java對(duì)象參數(shù)設(shè)置到PreparedStatement的占位符中 ? 參數(shù)類(lèi)型轉(zhuǎn)換通過(guò)TypeHandler完成Java類(lèi)型與JDBC類(lèi)型的轉(zhuǎn)換 ? 處理參數(shù)映射根據(jù)ParameterMapping解析參數(shù)的名稱、類(lèi)型和處理方式 ? 處理Null值對(duì)Null值進(jìn)行特殊處理,包括JdbcType的指定 ? 存儲(chǔ)過(guò)程支持支持CallableStatement的參數(shù)注冊(cè)
1.2 ParameterHandler接口
ParameterHandler是參數(shù)處理的頂層接口,定義了參數(shù)處理的基本方法:
public interface ParameterHandler {
// 獲取參數(shù)對(duì)象
Object getParameterObject();
// 設(shè)置PreparedStatement參數(shù)
void setParameters(PreparedStatement ps)
throws SQLException;
}
二、ParameterHandler架構(gòu)
ParameterHandler采用了簡(jiǎn)單但高效的設(shè)計(jì)模式。

2.1 默認(rèn)實(shí)現(xiàn)類(lèi)
MyBatis提供了ParameterHandler的默認(rèn)實(shí)現(xiàn)——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) {
// 獲取參數(shù)映射列表
List<ParameterMapping> parameterMappings =
boundSql.getParameterMappings();
if (parameterMappings != null) {
// 遍歷并設(shè)置每個(gè)參數(shù)
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping =
parameterMappings.get(i);
// 獲取參數(shù)值并設(shè)置
Object value = getParameterValue(parameterMapping);
TypeHandler typeHandler =
parameterMapping.getTypeHandler();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
2.2 創(chuàng)建ParameterHandler
ParameterHandler通常由Configuration創(chuàng)建:
public ParameterHandler newParameterHandler(
MappedStatement mappedStatement,
Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler =
mappedStatement.getLang()
.createParameterHandler(
mappedStatement,
parameterObject,
boundSql);
// 應(yīng)用插件攔截
parameterHandler = (ParameterHandler)
interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
三、參數(shù)設(shè)置流程
參數(shù)設(shè)置是一個(gè)系統(tǒng)化的過(guò)程,涉及多個(gè)組件的協(xié)同工作

3.1 完整設(shè)置流程
參數(shù)設(shè)置的完整流程如下:
// 步驟1: Executor創(chuàng)建StatementHandler
StatementHandler statementHandler =
new RoutingStatementHandler(
executor,
mappedStatement,
parameterObject,
rowBounds,
resultHandler,
boundSql);
// 步驟2: StatementHandler創(chuàng)建PreparedStatement
Statement statement = instantiateStatement(connection);
// 步驟3: ParameterHandler設(shè)置參數(shù)
parameterHandler.setParameters(preparedStatement);
3.2 參數(shù)獲取策略
參數(shù)值的獲取采用多種策略:
策略1: 從BoundSql的附加參數(shù)中獲取
if (boundSql.hasAdditionalParameter(propertyName)) {
return boundSql.getAdditionalParameter(propertyName);
}
策略2: 參數(shù)對(duì)象本身
if (parameterObject == null) {
return null;
}
策略3: 參數(shù)對(duì)象是單個(gè)基本類(lèi)型
if (typeHandlerRegistry.hasTypeHandler(
parameterObject.getClass())) {
return parameterObject;
}
策略4: 從參數(shù)對(duì)象中獲取屬性值
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
return metaObject.getValue(propertyName);
3.3 參數(shù)示例
// 示例1: 基本類(lèi)型參數(shù)
User selectById(Long id);
// parameterObject = 1L
// 示例2: 多參數(shù)
User selectByNameAndEmail(
@Param("name") String name,
@Param("email") String email);
// parameterObject = {name: "張三", email: "xxx@example.com"}
// 示例3: POJO參數(shù)
User insert(User user);
// 通過(guò)MetaObject反射獲取user對(duì)象屬性
// 示例4: 集合參數(shù)
List<User> selectByIds(List<Long> ids);
// foreach處理,生成多個(gè)參數(shù)
四、參數(shù)類(lèi)型轉(zhuǎn)換
參數(shù)類(lèi)型轉(zhuǎn)換是ParameterHandler的核心功能,通過(guò)TypeHandler實(shí)現(xiàn)。

4.1 TypeHandler接口
TypeHandler是類(lèi)型轉(zhuǎn)換的核心接口:
public interface TypeHandler<T> {
// 設(shè)置PreparedStatement參數(shù)
void setParameter(
PreparedStatement ps,
int i,
T parameter,
JdbcType jdbcType) throws SQLException;
// 獲取ResultSet結(jié)果
T getResult(ResultSet rs, String columnName)
throws SQLException;
T getResult(ResultSet rs, int columnIndex)
throws SQLException;
// 獲取CallableStatement結(jié)果
T getResult(CallableStatement cs, int columnIndex)
throws SQLException;
}
4.2 BaseTypeHandler抽象類(lèi)
為了簡(jiǎn)化TypeHandler的實(shí)現(xiàn),MyBatis提供了BaseTypeHandler抽象類(lèi)
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 {
// 設(shè)置非null參數(shù)
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// 子類(lèi)實(shí)現(xiàn)具體的類(lèi)型轉(zhuǎn)換邏輯
protected abstract void setNonNullParameter(
PreparedStatement ps,
int i,
T parameter,
JdbcType jdbcType) throws SQLException;
}
4.3 常用TypeHandler實(shí)現(xiàn)
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 類(lèi)型注冊(cè)機(jī)制
TypeHandlerRegistry負(fù)責(zé)管理所有TypeHandler:
public class TypeHandlerRegistry {
// JDBC類(lèi)型到TypeHandler的映射
private final Map<JdbcType, TypeHandler<?>>
jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// Java類(lèi)型到TypeHandler的映射
private final Map<Type, Map<JdbcType, TypeHandler<?>>>
typeHandlerMap = new HashMap<>();
// 注冊(cè)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;
}
}
五、參數(shù)映射處理
ParameterMapping是參數(shù)映射的核心數(shù)據(jù)結(jié)構(gòu)。

5.1 ParameterMapping結(jié)構(gòu)
public class ParameterMapping {
private final String property; // 參數(shù)屬性名
private final ParameterMode mode; // 參數(shù)模式(IN/OUT/INOUT)
private final Class<?> javaType; // Java類(lèi)型
private final JdbcType jdbcType; // JDBC類(lèi)型
private final TypeHandler<?> typeHandler; // 類(lèi)型處理器
private final String resultMapId; // 結(jié)果映射ID
private final Integer numericScale; // 數(shù)值精度
}
5.2 ParameterMode枚舉
public enum ParameterMode {
IN, // 輸入?yún)?shù)
OUT, // 輸出參數(shù)
INOUT // 輸入輸出參數(shù)
}
5.3 參數(shù)映射解析
參數(shù)映射在SQL解析階段創(chuàng)建:
// SqlSourceBuilder中
public SqlSource parse(
String originalSql,
Class<?> parameterType,
Map<String, Object> additionalParameters) {
// 創(chuàng)建Token處理器
ParameterMappingTokenHandler handler =
new ParameterMappingTokenHandler(
configuration,
parameterType,
additionalParameters);
// 解析#{}占位符
GenericTokenParser parser =
new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
// 創(chuàng)建StaticSqlSource
return new StaticSqlSource(
configuration,
sql,
handler.getParameterMappings());
}
5.4 參數(shù)映射示例
<!-- 示例1: 基本參數(shù)映射 -->
<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: 存儲(chǔ)過(guò)程參數(shù) -->
<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實(shí)現(xiàn),覆蓋了常見(jiàn)的Java類(lèi)型和JDBC類(lèi)型

6.1 內(nèi)置TypeHandler
MyBatis內(nèi)置的TypeHandler包括:
| Java類(lèi)型 | JDBC類(lèi)型 | 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
當(dāng)內(nèi)置TypeHandler無(wú)法滿足需求時(shí),可以自定義TypeHandler:
// 示例: JSON類(lèi)型處理器
@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 注冊(cè)自定義TypeHandler
配置自定義TypeHandler有兩種方式:
方式1: 注解方式
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler
extends BaseTypeHandler<List<String>> {
// 實(shí)現(xiàn)代碼...
}
方式2: 配置文件方式
<typeHandlers>
<typeHandler
handler="com.example.JsonListTypeHandler"/>
</typeHandlers>
或指定Java類(lèi)型:
<typeHandlers>
<typeHandler
javaType="java.util.List"
jdbcType="VARCHAR"
handler="com.example.JsonListTypeHandler"/>
</typeHandlers>
七、特殊參數(shù)處理
7.1 Null值處理
對(duì)于Null值,需要指定JdbcType:
<!-- 錯(cuò)誤寫(xiě)法: 可能報(bào)錯(cuò) -->
UPDATE t_user SET name = #{name}
<!-- 正確寫(xiě)法: 指定jdbcType -->
UPDATE t_user SET name = #{name, jdbcType=VARCHAR}
<!-- 全局配置: 指定Null的默認(rèn)JdbcType -->
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
7.2 存儲(chǔ)過(guò)程參數(shù)
存儲(chǔ)過(guò)程支持IN、OUT、INOUT三種參數(shù)模式:
// 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 數(shù)組參數(shù)處理
// 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 集合參數(shù)處理
// 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>
八、最佳實(shí)踐
8.1 參數(shù)命名規(guī)范
// 推薦: 使用@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 類(lèi)型處理建議
? 基本類(lèi)型優(yōu)先使用包裝類(lèi)避免Null值處理問(wèn)題 ? 復(fù)雜類(lèi)型自定義TypeHandler提高代碼可讀性 ? 枚舉類(lèi)型使用EnumTypeHandler支持名稱或序號(hào)存儲(chǔ) ? 日期類(lèi)型統(tǒng)一避免類(lèi)型混亂
8.3 性能優(yōu)化建議
? 復(fù)用TypeHandler實(shí)例TypeHandler是線程安全的 ? 避免過(guò)度類(lèi)型轉(zhuǎn)換減少不必要的轉(zhuǎn)換開(kāi)銷(xiāo) ? 合理使用JdbcType僅在必要時(shí)指定
8.4 常見(jiàn)問(wèn)題解決
問(wèn)題1: 參數(shù)未綁定
// 問(wèn)題現(xiàn)象
Parameter 'xxx' not found.
Available parameters are [arg0, arg1, param1, param2]
// 解決方案1: 使用@Param注解
User select(
@Param("name") String name,
@Param("email") String email);
// 解決方案2: 使用默認(rèn)參數(shù)名
User select(String name, String email);
// SQL中使用 #{arg0} #{arg1} 或 #{param1} #{param2}
問(wèn)題2: 類(lèi)型轉(zhuǎn)換異常
// 問(wèn)題現(xiàn)象
Cause: java.lang.NumberFormatException:
For input string: "xxx"
// 解決方案: 檢查參數(shù)類(lèi)型映射
<select id="selectById" resultType="User">
SELECT * FROM t_user
WHERE id = #{id, javaType=Long, jdbcType=BIGINT}
</select>
問(wèn)題3: Null值處理
// 問(wèn)題現(xiàn)象
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>
九、總結(jié)
MyBatis的參數(shù)處理模塊是整個(gè)框架的基礎(chǔ)組件,通過(guò)精心設(shè)計(jì)的ParameterHandler和TypeHandler體系,實(shí)現(xiàn)了Java類(lèi)型與JDBC類(lèi)型的無(wú)縫轉(zhuǎn)換。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot實(shí)現(xiàn)多租戶系統(tǒng)架構(gòu)的5種設(shè)計(jì)方案介紹
多租戶(Multi-tenancy)是一種軟件架構(gòu)模式,允許單個(gè)應(yīng)用實(shí)例服務(wù)于多個(gè)客戶(租戶),同時(shí)保持租戶數(shù)據(jù)的隔離性和安全性,本文分享了SpringBoot環(huán)境下實(shí)現(xiàn)多租戶系統(tǒng)的5種架構(gòu)設(shè)計(jì)方案,需要的可以參考一下2025-05-05
MybatisPlus更新為null的字段及自定義sql注入
mybatis-plus在執(zhí)行更新操作,當(dāng)更新字段為空字符串或者null的則不會(huì)執(zhí)行更新,本文主要介紹了MybatisPlus更新為null的字段及自定義sql注入,感興趣的可以了解一下2024-05-05
一文探索Apache HttpClient如何設(shè)定超時(shí)時(shí)間
Apache HttpClient是一個(gè)流行的Java庫(kù),用于發(fā)送HTTP請(qǐng)求,這篇文章主要為大家介紹了Apache HttpClient如何設(shè)定超時(shí)時(shí)間,感興趣的小伙伴可以學(xué)習(xí)一下2023-10-10
關(guān)于SpringBoot使用Redis空指針的問(wèn)題(不能成功注入的問(wèn)題)
這篇文章主要介紹了關(guān)于SpringBoot使用Redis空指針的問(wèn)題(不能成功注入的問(wèn)題),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
SpringCloud?eureka(server)微服務(wù)集群搭建過(guò)程
這篇文章主要介紹了微服務(wù)SpringCloud-eureka(server)集群搭建,?項(xiàng)目搭建的主要步驟和配置就是創(chuàng)建項(xiàng)目和引入pom依賴,本文通過(guò)圖文示例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
MyBatis常用標(biāo)簽以及使用技巧總結(jié)
在我們的學(xué)習(xí)過(guò)程中,我們經(jīng)常使用到mybatis,這篇文章主要給大家介紹了關(guān)于MyBatis常用標(biāo)簽以及使用技巧的相關(guān)資料,需要的朋友可以參考下2021-05-05
關(guān)于spring項(xiàng)目中無(wú)法加載resources下文件問(wèn)題及解決方法
在學(xué)習(xí)Spring過(guò)程中,TestContext框架試圖檢測(cè)一個(gè)默認(rèn)的XML資源位置,再resources下創(chuàng)建了一個(gè)com.example的文件夾,執(zhí)行時(shí),報(bào)錯(cuò),本文給大家介紹spring項(xiàng)目中無(wú)法加載resources下文件,感興趣的朋友跟隨小編一起看看吧2023-10-10

