Mybatis-Plus自動屬性填充與自定義Insert into語句順序詳解
前言
系統(tǒng)中使用了Mybatis-Plus 自動屬性填充為實體統(tǒng)一進(jìn)行屬性的填值,在Mapper的xml 文件中 insert into 語句 使用<if test="id != null">id,</if> 進(jìn)行判斷會發(fā)現(xiàn)該屬性是空的,明明已經(jīng)為改字段進(jìn)行了屬性的自動填充,為什么Mybatis- 在拼接sql 語句時依然認(rèn)為 改屬性是空的呢;
1 問題重現(xiàn)
1.1 在實體中使用了屬性填充屬性:
@TableField(fill = FieldFill.INSERT) private String testFiled;
1.2 在攔截器里進(jìn)行了屬性填充:
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("testFiled", "test", metaObject);
}
1.3 mapper xml :
insert into ${prefix}knowledge_authority
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="testFiled != null">test_filed,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="testFiled != null">#{testFiled },</if>
</trim>
在對實體id 設(shè)置完成之后,進(jìn)行數(shù)據(jù)的插入,發(fā)現(xiàn)插入的數(shù)據(jù)中只有id 沒有testFiled 屬性;
2 推斷問題產(chǎn)生的原因
- 原因1:屬性填充正常,但是在xml sql 語句中,在某些情況下
<if>判斷有問題; - 原因2:自定義填充屬性有問題,導(dǎo)致想要填充的屬性沒有被填充值,導(dǎo)致進(jìn)行
<if>判斷有問題; - 愿意3:屬性填充和
<if>標(biāo)簽判斷都沒有問題,但是sql 拼接的時機(jī) 在屬性填充之前進(jìn)行;
<if> 標(biāo)簽只進(jìn)行簡單的空判斷,出問題的可能性不大,從原因2 入手:
使用自定義屬性填充時,會調(diào)用MybatisParameterHandler 類中的process()方法完成屬性填充的調(diào)用;
private void process(Object parameter) {
if (parameter != null) {
TableInfo tableInfo = null;
Object entity = parameter;
if (parameter instanceof Map) {
Map<?, ?> map = (Map)parameter;
if (map.containsKey("et")) {
Object et = map.get("et");
if (et != null) {
entity = et;
tableInfo = TableInfoHelper.getTableInfo(et.getClass());
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
}
if (tableInfo != null) {
MetaObject metaObject = this.configuration.newMetaObject(entity);
if (SqlCommandType.INSERT == this.sqlCommandType) {
// 插入時 id 的填充
this.populateKeys(tableInfo, metaObject, entity);
// 這里會在insert 時 調(diào)用我們自己定義的攔截器進(jìn)行屬性的自動填充
this.insertFill(metaObject, tableInfo);
} else {
// 這里會在update 時 調(diào)用我們自己定義的攔截器進(jìn)行屬性的自動填充
this.updateFill(metaObject, tableInfo);
}
}
}
}
通過debug 我們發(fā)現(xiàn),在插入數(shù)據(jù)時確實調(diào)用了process 方法,并對實體完成了屬性的填充,屬性填充是正常的;所以會不會是原因3 ,屬性填充的時機(jī)和sql 拼接的時機(jī)不同造成的。
如果 先進(jìn)行了sql的拼接,此時進(jìn)行 <if> 判斷時 發(fā)現(xiàn)改屬性為空,必然會跳過了該屬性的拼接,即使后面自動填充為屬性填充了數(shù)據(jù),但是由于sql已經(jīng)完成了拼接,最終執(zhí)行的sql 也是沒有該屬性的;
基于此猜想,我們將xml 中 判斷標(biāo)簽去掉,只保留占位符:
insert into ${prefix}knowledge_authority
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
test_filed,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
#{testFiled },
</trim>此時在次進(jìn)行插入,發(fā)現(xiàn)插入成功,并且testFiled 屬性也是有值的;
3 從Mybatis-Plus 代碼層面查看sql 語句的拼接
3.1 先看下sql 拼接的流程:
MybatisParameterHandler 是 Mybatis 中用于處理數(shù)據(jù)庫操作參數(shù)的接口,它的實現(xiàn)類 DefaultParameterHandler 負(fù)責(zé)將 Java 對象轉(zhuǎn)換為 JDBC 預(yù)處理語句需要的參數(shù)值,以及將參數(shù)值設(shè)置到預(yù)處理語句中。其中,BoundSql 對象就是用于封裝 SQL 語句和對應(yīng)的參數(shù)值的。
BoundSql 對象的賦值過程主要由 SqlSource 和 ParameterMapping 來完成,具體流程如下:
- Mybatis 在執(zhí)行 SQL 語句之前,會根據(jù) Mapper 接口定義的方法和傳入的參數(shù)生成 MappedStatement 對象。在 MappedStatement 中包含了 SQL 語句、參數(shù)映射信息等相關(guān)的元數(shù)據(jù)。
- MappedStatement 負(fù)責(zé)生成 BoundSql 對象。在生成 BoundSql 對象時,Mybatis 會首先根據(jù) SQL 語句和參數(shù)信息生成一個 StaticSqlSource 對象,然后再通過它生成一個 DynamicSqlSource 對象。DynamicSqlSource 會根據(jù)傳入的參數(shù)信息和 Mapper 接口定義的 SQL 語句,動態(tài)生成最終的 SQL 語句和參數(shù)值。這個過程中,ParameterMapping 負(fù)責(zé)將 Java 對象中的屬性值和 SQL 語句中的占位符做映射關(guān)聯(lián),SqlSource 負(fù)責(zé)根據(jù)參數(shù)信息和 SQL 語句生成 BoundSql 對象。
- 生成 BoundSql 對象后,Mybatis 會通過 ParameterHandler 將 BoundSql 對象中的 SQL 語句和參數(shù)值設(shè)置到 JDBC 預(yù)處理語句中。默認(rèn)的 ParameterHandler 實現(xiàn)類是 DefaultParameterHandler,它會通過反射獲取 PreparedStatement 對象,并調(diào)用 setXxx() 方法將參數(shù)值設(shè)置到預(yù)處理語句中。在設(shè)置參數(shù)值的過程中,DefaultParameterHandler 會根據(jù) ParameterMapping 中的信息獲得 Java 對象中對應(yīng)屬性的值,并將其賦值給 BoundSql 對象中對應(yīng)的參數(shù)占位符。
- 綜上所述,BoundSql 對象的賦值過程主要由 SqlSource 和 ParameterMapping 來完成。它們會根據(jù)傳入的參數(shù)信息和 Mapper 接口定義的 SQL 語句,動態(tài)生成最終的 SQL 語句和參數(shù)值,并將它們設(shè)置到 BoundSql 對象中。
3.2 MybatisParameterHandler 中的 BoundSql boundSql:
public class MybatisParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
private final SqlCommandType sqlCommandType;
public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.mappedStatement = mappedStatement;
// 拼接好的sql
this.boundSql = boundSql;
this.configuration = mappedStatement.getConfiguration();
this.sqlCommandType = mappedStatement.getSqlCommandType();
// 主鍵id 和 屬性的自動填充
this.parameterObject = this.processParameter(parameter);
}
}
可以看到在創(chuàng)建MybatisParameterHandler 對象時,boundSql 已經(jīng)完成了sql 的解析和拼接,然后在this.processParameter(parameter) 方法完成了主鍵id 和 屬性的自動填充,從構(gòu)造方法可以看到,boundSql 的拼接是先于processParameter(parameter) 屬性填充的方法的,這就解釋了為什么我們明明已經(jīng)為改屬性進(jìn)行了填充,為什么 最終自定義的insert into 語句 標(biāo)簽判斷是空的,本質(zhì)就是因為兩者的順序問題;
3.3 sql 語句的拼接:
進(jìn)入DynamicSqlSource 類getBoundSql 方法:
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
// 參數(shù)解析
DynamicContext context = new DynamicContext(this.configuration, parameterObject);
// sql 拼接
this.rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 占位符拼接
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// sql 拼接
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
this.rootSqlNode.apply(context):
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
public boolean apply(DynamicContext context) {
// 這里會判斷xml 中的所有屬性標(biāo)簽,只有判斷為true ,才進(jìn)行屬性的拼接
this.contents.forEach((node) -> {
node.apply(context);
});
return true;
}
}
可以看到這里會根據(jù)標(biāo)簽不同調(diào)用不同的實現(xiàn)完成判斷:

并且會逐個進(jìn)行屬性的判斷,只有為true 才進(jìn)行屬性拼接:

以IfSqlNode 為例,可以看出只有當(dāng)屬性不為空時,才返回true 否則返回false,只有在返回true 時后續(xù)才會對改屬性進(jìn)行拼接

4 總結(jié)
Mybatis-Plus 自定義的sql 語句其BoundSql的解析和拼接是在屬性填充之前進(jìn)行的,所以如果在自定義sql 語句中使用了<if>標(biāo)簽進(jìn)行屬性的非空判斷,就不會拼接改屬性,此時需要在自定義的sql 中去除<<if>的非空判斷直接使用#{testFiled },這樣最終在進(jìn)數(shù)據(jù)插入時,Mybatis會動態(tài)的替換掉改占位符。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Intellij IDEA 與maven 版本不符 Unable to import maven project See
這篇文章主要介紹了Intellij IDEA 與maven 版本不符 Unable to import maven project See logs for details: No implementation for org.apache.maven.model.path.PathTranslator was bound,本文通過圖文給大家分享解決方案,需要的朋友可以參考下2020-08-08
springboot整合sentinel接口熔斷的實現(xiàn)示例
為了防止慢接口導(dǎo)致的服務(wù)阻塞,可以通過添加熔斷處理來避免應(yīng)用的大量工作線程陷入阻塞,保證其他接口的正常運(yùn)行,本文介紹了如何使用Spring Boot與Sentinel進(jìn)行接口熔斷的配置與實現(xiàn),感興趣的可以了解一下2024-09-09
SpringBoot項目實現(xiàn)短信發(fā)送接口開發(fā)的實踐
本文主要介紹了SpringBoot項目實現(xiàn)短信發(fā)送接口開發(fā)的實踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10
Spring boot+mybatis+thymeleaf 實現(xiàn)登錄注冊增刪改查功能的示例代碼
這篇文章主要介紹了Spring boot+mybatis+thymeleaf 實現(xiàn)登錄注冊增刪改查功能的示例代碼,本文通過實例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
解決2022.3.1版本中?IDEA中?XML文件提示屎黃色背景的方法
這篇文章主要介紹了解決2022.3.1版本中?IDEA中?XML文件屎黃色背景?的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01

