1. 程式人生 > 程式設計 >mybatis之foreach用法詳解

mybatis之foreach用法詳解

在做mybatis的mapper.xml檔案的時候,我們時常用到這樣的情況:動態生成sql語句的查詢條件,這個時候我們就可以用mybatis的foreach了

foreach元素的屬性主要有item,index,collection,open,separator,close。

  • item:集合中元素迭代時的別名,該引數為必選。
  • index:在list和陣列中,index是元素的序號,在map中,index是元素的key,該引數可選
  • open:foreach程式碼的開始符號,一般是(和close=")"合用。常用在in(),values()時。該引數可選
  • separator:元素之間的分隔符,例如在in()的時候,separator=","會自動在元素中間用“,“隔開,避免手動輸入逗號導致sql錯誤,如in(1,2,)這樣。該引數可選。
  • close: foreach程式碼的關閉符號,一般是)和open="("合用。常用在in(),values()時。該引數可選。
  • collection: 要做foreach的物件,作為入參時,List物件預設用"list"代替作為鍵,陣列物件有"array"代替作為鍵,Map物件沒有預設的鍵。當然在作為入參時可以使用@Param("keyName")來設定鍵,設定keyName後,list,array將會失效。 除了入參這種情況外,還有一種作為引數物件的某個欄位的時候。舉個例子:如果User有屬性List ids。入參是User物件,那麼這個collection = "ids".如果User有屬性Ids ids;其中Ids是個物件,Ids有個屬性List id;入參是User物件,那麼collection = "ids.id"

在使用foreach的時候最關鍵的也是最容易出錯的就是collection屬性,該屬性是必須指定的,但是在不同情況下,該屬性的值是不一樣的,主要有一下3種情況:

  • 如果傳入的是單引數且引數型別是一個List的時候,collection屬性值為list .
  • 如果傳入的是單引數且引數型別是一個array陣列的時候,collection的屬性值為array .
  • 如果傳入的引數是多個的時候,我們就需要把它們封裝成一個Map了,當然單引數也可以封裝成map,實際上如果你在傳入引數的時候,在MyBatis裡面也是會把它封裝成一個Map的,map的key就是引數名,所以這個時候collection屬性值就是傳入的List或array物件在自己封裝的map裡面的key.

針對最後一條,我們來看一下官方說法:

注意 你可以將一個 List 例項或者陣列作為引數物件傳給 MyBatis,當你這麼做的時候,MyBatis 會自動將它包裝在一個 Map 中並以名稱為鍵。List 例項將會以“list”作為鍵,而陣列例項的鍵將是“array”。

所以,不管是多引數還是單引數的list,array型別,都可以封裝為map進行傳遞。如果傳遞的是一個List,則mybatis會封裝為一個list為key,list值為object的map,如果是array,則封裝成一個array為key,array的值為object的map,如果自己封裝呢,則colloection裡放的是自己封裝的map裡的key值。

原始碼分析

由於官方文件對這塊的使用,描述的比較簡短,細節上也被忽略掉了(可能是開源專案文件一貫的問題吧),也使用不少同學在使用中遇到了問題。特別是foreach這個函式中,collection屬性做什麼用,有什麼注意事項。由於文件不全,這塊只能通過原始碼剖析的方式來分析一下各個屬性的相關要求。

collection屬性的用途是接收輸入的陣列或是List介面實現。但對於其名稱的要求,Mybatis在實現中還是有點不好理解的,所以需要特別注意這一點。

下面開始分析原始碼(筆記使用的是Mybatis 3.0.5版本)

先找到Mybatis執行SQL配置解析的入口

MapperMethod.java類中publicObject execute(Object[] args)該方法是執行的入口.

針對in集合查詢,對應用就是selectForList或SelctForMap方法。


但不管呼叫哪個方法,都會對原來JDK傳入的引數 Object[]型別,通過 getParam方法轉換成一個Object,那這個方法是做什麼的呢?分析原始碼如下:

上圖中標紅的兩處,很驚訝的發現,一個引數與多個引數的處理方式是不同的(後續很多同學遇到的問題,就有一大部分出自這個地方)。如果引數個數大於一個,則會被封裝成Map,key值如果使用了Mybatis的 Param註解,則會使用該key值,否則預設統一使用資料序號,從1開始。這個問題先記下,繼續分析程式碼,接下來如果是selectForList操作(其它操作就對應用相應方法),會呼叫DefaultSqlSession的publicListselectList(String statement,Object parameter,RowBounds rowBounds)方法

又一個發現,見原始碼如下:

上圖示紅部分,對引數又做了一次封裝,我們看一下程式碼


現在有點清楚了,如果引數型別是List,則必須在collecion中指定為list,如果是資料組,則必須在collection屬性中指定為 array.

現在就問題就比較清楚了,如果是一個引數的話,collection的值取決於你的引數型別。

如果是多個值的話,除非使用註解Param指定,否則都是數字開頭,所以在collection中指定什麼值都是無用的。下圖是debug顯示結果。

使用方法

單引數List 型別

<select id="countByUserList" resultType="_int" parameterType="list">
select count(*) from users
 <where>
  id in
  <foreach item="item" collection="list" separator="," open="(" close=")" index="">
   #{item.id,jdbcType=NUMERIC}
  </foreach>
 </where>
</select>

測試程式碼:

@Test
 public void shouldHandleComplexNullItem() {
  SqlSession sqlSession = sqlSessionFactory.openSession();
  try {
   Mapper mapper = sqlSession.getMapper(Mapper.class);
   User user1 = new User();
   user1.setId(2);
   user1.setName("User2");
   List<User> users = new ArrayList<User>();
   users.add(user1);
   users.add(null);
   int count = mapper.countByUserList(users);
   Assert.assertEquals(1,count);
  } finally {
   sqlSession.close();
  }
 }

上述collection為array,對應的Mapper程式碼:

public List dynamicForeach2Test(int[] ids);

對應的測試程式碼:

@Test
 public void dynamicForeach2Test() {
     SqlSession session = Util.getSqlSessionFactory().openSession();
     BlogMapper blogMapper = session.getMapper(BlogMapper.class);
     int[] ids = new int[] {1,3,6,9};
     List blogs = blogMapper.dynamicForeach2Test(ids);
     for (Blog blog : blogs)
     System.out.println(blog);  
     session.close();
 }

3.自己把引數封裝成Map的型別

<select id="dynamicForeach3Test" resultType="Blog">
     select * from t_blog where title like "%"#{title}"%" and id in
     <foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
        #{item}
     </foreach>
 </select>

上述collection的值為ids,是傳入的引數Map的key,對應的Mapper程式碼:

public List dynamicForeach3Test(Map params);

對應測試程式碼:

@Test
  public void dynamicForeach3Test() {
    SqlSession session = Util.getSqlSessionFactory().openSession();
     BlogMapper blogMapper = session.getMapper(BlogMapper.class);
     final List ids = new ArrayList();
     ids.add(1);
     ids.add(2);
     ids.add(3);
     ids.add(6);
     ids.add(7);
     ids.add(9);
    Map params = new HashMap();
     params.put("ids",ids);
     params.put("title","中國");
    List blogs = blogMapper.dynamicForeach3Test(params);
     for (Blog blog : blogs)
       System.out.println(blog);
     session.close();
   }

注意注意sql語句SELECT * FROM ny_jobs WHERE id IN () 這個會報錯,所以最後判斷一下Ids是有元素的

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。