Mybatis foreach 效能問題

2021-09-26 19:51:25 字數 4375 閱讀 5503

這裡先不考慮使用 in 好不好,如何去優化 in,如何使用 exists 或 inner join 進行代替等,這裡就只是考慮使用了 in 語句,且使用了 mybatis 的 foreach 語句進行優化,其實 foreach 的優化很簡單,就是把 in 後面的語句在**裡面拼接好,在配置檔案中直接通過 # 或 $ 當作字串直接使用即可。

在分析 foreach 原始碼之前,先構造個資料來看看它們的區別有多大。

建表語句:

create table person

( id int(11) primary key not null,

name varchar(50),

age int(11),

job varchar(50)

);

插入 1w 條資料:

pojo 類:

@getter

@setter

@tostring

@noargsconstructor

@allargsconstructor

public class person implements serializable

通過原始的方式,使用 foreach 語句:

在 dao 裡面定義方法:

listquerypersonbyids(@param("ids") listids);
配置檔案sql:

select * from person where 1=1

0">

and id in

#

執行 main 方法:

@runwith(springjunit4classrunner.class)

@contextconfiguration(locations = )

public class maintest

long start = system.currenttimemillis();

// 執行三次

personservice.querypersonbyids(ids);

personservice.querypersonbyids(ids);

personservice.querypersonbyids(ids);

long end = system.currenttimemillis();

system.out.println(string.format("耗時:%d", end - start));

}}

結果:耗時:2853

可以看到通過 foreach 的方法,大概需要 3s

在**中封裝 sql ,在配置檔案中 通過 $ 來獲取:

在 dao 新增方法:

listquerypersonbyids2(@param("ids") string ids);
配置檔案sql:

select * from person where 1=1

and id in $

執行 main 方法:

@test

public void test_3()

sb.deletecharat(sb.tostring().length() - 1);

// 最終的 sql 為 (1,2,3,4,5...)

long start2 = system.currenttimemillis();

// 執行三次

personservice.querypersonbyids2(sb.tostring());

personservice.querypersonbyids2(sb.tostring());

personservice.querypersonbyids2(sb.tostring());

long end2 = system.currenttimemillis();

system.out.println(string.format("耗時:%d", end2 - start2));

}

結果:耗時:360

通過拼接 sql,使用 $ 的方式,執行同樣的 sql ,耗時大概 360 ms

通過上面可以看到,使用不同的方式,耗時的差別還是麻大的,最快的是拼接 sql,使用 $ 當作字串處理,最慢的是 foreach。

為什麼 foreach 會慢那麼多呢,後面在分析原始碼的時候再進行分析。

下面來看下 foreach 是如何被解析的,最終解析的 sql 是什麼樣的:

在 mybatis 中,foreach屬於動態標籤的一種,也是最智慧型的其中一種,mybatis 每個動態標籤都有對應的類來進行解析,而 foreach 主要是由 foreachsqlnode 負責解析。

foreachsqlnode 主要是用來解析節點的,先來看看節點的用法:

select * from person where 1=1

0">

and id in

#

最終被 資料庫執行的 sql 為:

select  * from person where 1=1 and id in (1,2,3,4,5)
先來看看它的兩個內部類:

該類主要是用來處理字首,比如 「(」 等。

private class prefixedcontext extends dynamiccontext 

// 拼接sql

}}filtereddynamiccontext

filtereddynamiccontext 是用來處理 #{} 佔位符的,但是並未繫結引數,只是把 # 轉換為 # 之類的佔位符。

private static class filtereddynamiccontext extends dynamiccontext 

@override

generictokenparser parser = new generictokenparser("#", new tokenhandler() 轉換為 # 之類的

string newcontent = content.replacefirst("^\\s*" + item + "(?![^.,:\\s])", itemizeitem(item, index));

// 把 # 轉換為 # 之類的

if (itemindex != null && newcontent.equals(content))

// 再返回 # 或 #

}});

// 拼接sql

}private static string itemizeitem(string item, int i)

}

了解了 foreachsqlnode 它的兩個內部類之後,再來看看它的實現:

public class foreachsqlnode implements sqlnode 

boolean first = true;

// 新增開始字串

int i = 0;

for (object o : iterable) else if (separator != null) else

int uniquenumber = context.getuniquenumber();

if (o instanceof map.entry) else

if (first)

context = oldcontext;

i++;

}// 新增結束字串

return true;

} if (index != null)

} if (item != null)

}}

所以該例子:

select * from person where 1=1

0">

and id in

#

解析之後的 sql 為:

select  *  from  person where  1=1 and id in (#,  #, #, #, #)
之後在通過 preparedstatment 的 set***來進行賦值。

所以,到這裡,知道了 mybatis 在解析 foreach 的時候,最後還是解析成了 # 的方式,但是為什麼還是很慢呢,這是因為需要迴圈解析 # 之類的佔位符,foreach 的集合越大,解析越慢。既然知道了需要解析佔位符,為何不自己拼接呢,所以就可以在**中拼接好,而不再使用 foreach 啦。

mybatis foreach 用法總結

1.findbyids listids 傳入引數為單一list引數時的寫法 2.findbyids integer ids 傳入引數為單一陣列時的寫法 3.search integer ids,string title 傳入引數為多個型別的查詢條件時 mapparams new hashmap pa...

MyBatis foreach 批量插入

1.批量插入 insert into student id,name,classid values null,null,null,insert id insertmore insert into student id,name,classid values foreach collection li...

mybatis foreach標籤的使用

下面是foreach標籤的各個屬性 屬性描述 collection 表示迭代集合的名稱,可以使用 param註解指定,如下圖所示 該引數為必選 item 表示本次迭代獲取的元素,若collection為list set或者陣列,則表示其中的元素 若collection為map,則代表key valu...