1. 程式人生 > >基於mysql對mybatis中的foreach進行深入研究

基於mysql對mybatis中的foreach進行深入研究

鑑於上一篇博文一次修改mysql欄位型別引發的技術探究提到的,要對foreach裡面的collection相關的內容做一些介紹,今天就圍繞foreach,做一些資料插入和查詢相關的研究。

首先介紹一下我的環境:

1. linux redhat7

2. mysql 5.6

3. java7

4. mybatis 3.2.7 (後來遇到問題,更新到3.3.1)

第一步,在資料庫中建立測試用的表 foreach_test。如下: 

複製程式碼

mysql> desc foreach_test;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(32) | YES  |     | NULL    |                |
| age   | int(8)      | YES  |     | NULL    |                |
| idx   | int(4)      | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

複製程式碼

第二步,進行mybatis相關的mapper以及dao進行配置。今天研究和介紹的關於foreach的相關內容,將從insert以及select兩個大類進行案例介紹,其中,insert是重點,因為批量資料插入,相對比較複雜點,涉及到鍵值的更新過程。對於mysql資料庫而言,對於第一步中建立的資料表foreach_test,有一個主鍵id,是數字自增型的。這裡,結合mybatis的官方文件介紹,將會有兩種更新主鍵的方法:selectKey,以及useGeneratedKeys=“true” 下面將結合這兩種方法,以及foreach的collection能夠支援的三種集合型別list,array以及map進行案例分析。

1. selectKey方案的插入,資料採用list傳入

mapper的sql語句:

複製程式碼

<insert id="foreachSelectKeyInsert" parameterType="java.util.List">
      <selectKey resultType ="java.lang.Integer" keyProperty= "iid" order= "AFTER">
         SELECT LAST_INSERT_ID()
    </selectKey >
      insert into foreach_test (name, age, idx) values
      <foreach item="st" collection="list" index="idx" open="" separator="," close="">
          (#{st.name, jdbcType=VARCHAR}, #{st.age, jdbcType=INTEGER}, #{idx})
      </foreach>              
  </insert> 

複製程式碼

dao層的介面:

int foreachSelectKeyInsert(List<Du> dud);

java業務邏輯:

複製程式碼

    @GET
    @Path("/foreach/selectkey/insert")
    public String foreachSelectKeyInsert(@Context HttpServletRequest req){
                
        List<Du> dud = new ArrayList<Du>();
        for(int i=1; i < 5; i++){
            Du du1 = new Du();
            du1.setName("SelectKey" + i);
            du1.setAge(30+i);
            dud.add(du1);
        }    
        
        pes.foreachSelectKeyInsert(dud);
        
        return "SelectKey Insert OK";
    }

複製程式碼

在位址列輸入:

http://10.90.9.20:8080/ecs/demo/foreach/selectkey/insert

資料庫中得到:

複製程式碼

mysql> select * from foreach_test; 
+----+------------+------+------+
| id | name       | age  | idx  |
+----+------------+------+------+
|  1 | SelectKey1 |   31 |    0 |
|  2 | SelectKey2 |   32 |    1 |
|  3 | SelectKey3 |   33 |    2 |
|  4 | SelectKey4 |   34 |    3 |
+----+------------+------+------+
4 rows in set (0.00 sec)

複製程式碼

2. useGeneratedKeys=”true“方案的插入,資料採用list傳入

mapper的sql語句:

<insert id="foreachUseGeneratedKeysInsert1" keyProperty="id" useGeneratedKeys="true">      
      insert into foreach_test (name, age, idx) values
      <foreach item="st" collection="list" index="idx" open="" separator="," close="">
          (#{st.name, jdbcType=VARCHAR}, #{st.age, jdbcType=INTEGER}, #{idx})
      </foreach>              
  </insert>

dao介面:

int foreachUseGeneratedKeysInsert1(List<Du> dud);

java業務邏輯:

複製程式碼

   @GET
    @Path("/foreach/usegeneratedkeys/insert1")
    public String foreachUseGeneratedKeysInsert1(@Context HttpServletRequest req){
                
        List<Du> dud = new ArrayList<Du>();
        for(int i=6; i < 10; i++){
            Du du1 = new Du();
            du1.setName("UseGeneratedKeys1" + i);
            du1.setAge(30+i);
            dud.add(du1);
        }    
        pes.foreachUseGeneratedKeysInsert1(dud);
        
        return "UseGeneratedKeys1 Insert OK";
    }

複製程式碼

在位址列輸入:

http://10.90.9.20:8080/ecs/demo/foreach/usegeneratedkeys/insert1

結果爆出錯誤:

 View Code

這個錯誤,折騰了我好久,在stackoverflow網站找到了一個老外的類似問題,得到線索,說是mybatis的版本問題,3.3.1之後(我當前的研究環境是3.2.7),這個問題才得以解決。在沒有驗證是否正確之前,繼續當前環境下的研究。前端報錯了,依舊檢視資料庫,看是否有資料寫入:

複製程式碼

mysql> select * from foreach_test; 
+----+--------------------+------+------+
| id | name               | age  | idx  |
+----+--------------------+------+------+
|  1 | UseGeneratedKeys16 |   36 |    0 |
|  2 | UseGeneratedKeys17 |   37 |    1 |
|  3 | UseGeneratedKeys18 |   38 |    2 |
|  4 | UseGeneratedKeys19 |   39 |    3 |
+----+--------------------+------+------+
4 rows in set (0.00 sec)

複製程式碼

說明,資料是寫入了庫,但是前端這個錯誤很不友好。

雖然報錯,但是從插入資料庫的結果得出結論:傳入給mybatis的list(單獨引數),collection的值,預設是list,此時,item的內容為list的元素,index的值為遍歷list的序號,從0開始遞增

3. useGeneratedKeys=”true“方案的插入,資料採用map傳入

mapper的sql語句:

<insert id="foreachUseGeneratedKeysInsert2" keyProperty="id" useGeneratedKeys="true">      
      insert into foreach_test (name, age, idx) values
      <foreach item="st" collection="dudkey" index="idx" open="(" separator="),(" close=")">
          #{st.name, jdbcType=VARCHAR}, #{st.age, jdbcType=INTEGER}, #{idx}
      </foreach>              
  </insert>

dao介面:

int foreachUseGeneratedKeysInsert2(HashMap<String, List<Du>> dumap);

java業務邏輯:

複製程式碼

    @GET
    @Path("/foreach/usegeneratedkeys/insert2")
    public String foreachUseGeneratedKeysInsert2(@Context HttpServletRequest req){
                
        List<Du> dud = new ArrayList<Du>();
        for(int i=6; i < 10; i++){
            Du du1 = new Du();
            du1.setName("UseGeneratedKeys2" + i);
            du1.setAge(30+i);
            dud.add(du1);
        }    
        HashMap<String, List<Du>> dumap =  new HashMap<String, List<Du>>();
        dumap.put("dudkey", dud);
        pes.foreachUseGeneratedKeysInsert2(dumap);
        
        return "UseGeneratedKeys2 Insert OK";
    }

複製程式碼

在位址列輸入:

http://10.90.9.20:8080/ecs/demo/foreach/usegeneratedkeys/insert2

檢視資料庫:

複製程式碼

mysql> select * from foreach_test;
+----+--------------------+------+------+
| id | name               | age  | idx  |
+----+--------------------+------+------+
|  1 | UseGeneratedKeys26 |   36 |    0 |
|  2 | UseGeneratedKeys27 |   37 |    1 |
|  3 | UseGeneratedKeys28 |   38 |    2 |
|  4 | UseGeneratedKeys29 |   39 |    3 |
+----+--------------------+------+------+
4 rows in set (0.00 sec)

複製程式碼

奇怪吧,3.2.7的環境(mybatis-spring版本是1.2.4)下,傳入map,批量插入不報前面出現的id找不到的錯誤。

從這個案例,可以看出:傳入mybatis的map,若給collection的值是入參map中的key的話,那麼item的值將是map的value,此時的index值,就是map中value遍歷的序號,從0開始遞增

複製程式碼

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroRealm': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysMenuService': Invocation of init method failed; nested exception is java.lang.AbstractMethodError: org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout()Ljava/lang/Integer;
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1148)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328)
    ... 65 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysMenuService': Invocation of init method failed; nested exception is java.lang.AbstractMethodError: org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout()Ljava/lang/Integer;
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:396)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1507)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:449)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:423)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:551)
    at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:304)
    ... 73 more
Caused by: java.lang.AbstractMethodError: org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout()Ljava/lang/Integer;
    at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:358)
    at com.sun.proxy.$Proxy11.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:198)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
    at com.sun.proxy.$Proxy17.getListWithUrlNotNull(Unknown Source)
    at com.tg.ecs.system.service.impl.SysMenuService.getListWithUrlNotNull(SysMenuService.java:101)
    at com.tg.ecs.system.service.impl.SysMenuService.initFilterChain(SysMenuService.java:106)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:344)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:295)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:130)
    ... 87 more

複製程式碼

看這個錯誤,應該是mybatis-spring的版本與mybatis不匹配的原因造成的。於是,我去maven上檢視mybatis-spring支援的mybatis的版本,我選擇mybatis-spring 1.2.5的版本:再次啟動軟體,上述mybatis-spring的錯誤解除。

到目前為止,我的研究環境,變成了mybatis 3.3.1,mybatis-spring 1.2.5. 繼續後續研究!

在這個新的環境下,繼續上述案例2的測試,在位址列輸入 http://10.90.9.20:8080/ecs/demo/foreach/usegeneratedkeys/insert1, 這次不再報錯。所以,上面案例2的錯誤,還真是stackoverflow上人家描述的問題導致的。

4. useGeneratedKeys=”true“方案的插入,資料採用map傳入,注意與案例3的不同,在於傳入的資料,此處是純map,collection就是一個註解指定名稱了的map,而案例3中,foreach collection其實是map的key

mapper層的sql:

複製程式碼

<insert id="foreachUseGeneratedKeysInsert3" useGeneratedKeys="true"> 
   <!-- 
      <selectKey resultType ="java.lang.Integer" keyProperty= "iid" order= "AFTER">
         SELECT LAST_INSERT_ID()
    </selectKey >
   -->    
      insert into foreach_test (name, age, idx) values
      <foreach item="dt" collection="map" index="key" open="(" separator="),(" close=")">
          #{dt.name, jdbcType=VARCHAR}, #{dt.age, jdbcType=INTEGER}, #{key}
      </foreach>              
  </insert>

複製程式碼

dao層的介面:

int foreachUseGeneratedKeysInsert3(@Param("map") HashMap<Integer, Du> dumap);

java層的業務邏輯:

複製程式碼

    @GET
    @Path("/foreach/usegeneratedkeys/insert3")
    public String foreachUseGeneratedKeysInsert3(@Context HttpServletRequest req){
                
        List<Du> dud = new ArrayList<Du>();
        HashMap<Integer, Du> dumap =  new HashMap<Integer, Du>();
        for(int i=30; i < 33; i++){
            Du du = new Du();
            du.setName("UseGeneratedKeys3" + i);
            du.setAge(30+i);
            dud.add(du);
            dumap.put(i, du);
        }    
        int cnt = pes.foreachUseGeneratedKeysInsert3(dumap);
        System.out.println(cnt);
        
        return "UseGeneratedKeys3 Insert OK";
    }

複製程式碼

在位址列輸入:

複製程式碼

mysql> select * from foreach_test; 
+----+---------------------+------+------+
| id | name                | age  | idx  |
+----+---------------------+------+------+
|  1 | UseGeneratedKeys332 |   62 |   32 |
|  2 | UseGeneratedKeys331 |   61 |   31 |
|  3 | UseGeneratedKeys330 |   60 |   30 |
+----+---------------------+------+------+
3 rows in set (0.00 sec)

複製程式碼

這個案例:可以看出,傳入map給mybatis時,foreach的collection,採用dao層介面註解指定的變數名,然後,index指的是map的key值,而item的內容,就是map中index所對應的key值指向的value,即du物件。

5. useGeneratedKeys=”true“方案的插入,資料採用array傳入

mapper的sql:

<insert id="foreachUseGeneratedKeysInsert4" useGeneratedKeys="true"> 
      insert into foreach_test (name, age, idx) values
      <foreach item="dt" collection="array" index="seq" open="(" separator="),(" close=")">
          #{dt.name, jdbcType=VARCHAR}, #{dt.age, jdbcType=INTEGER}, #{seq}
      </foreach>              
  </insert>

dao介面:

 int foreachUseGeneratedKeysInsert4(Du[] duarray);

java應用層的邏輯:

複製程式碼

    @GET
    @Path("/foreach/usegeneratedkeys/insert4")
    public String foreachUseGeneratedKeysInsert4(@Context HttpServletRequest req){
                
        Du [] dud = new Du[4];        
        for(int i=0; i < dud.length ; i++){
            Du du = new Du();
            du.setName("UseGeneratedKeys4" + i);
            du.setAge(30+i);
            dud[i] = du;
        }    
        int cnt = pes.foreachUseGeneratedKeysInsert4(dud);
        System.out.println(cnt);
        
        return "UseGeneratedKeys4 Insert OK";
    }

複製程式碼

在位址列輸入:

http://10.90.9.20:8080/ecs/demo/foreach/usegeneratedkeys/insert4

檢視資料庫得到下面的結果:

複製程式碼

mysql> select * from foreach_test; 
+----+--------------------+------+------+
| id | name               | age  | idx  |
+----+--------------------+------+------+
|  1 | UseGeneratedKeys40 |   30 |    0 |
|  2 | UseGeneratedKeys41 |   31 |    1 |
|  3 | UseGeneratedKeys42 |   32 |    2 |
|  4 | UseGeneratedKeys43 |   33 |    3 |
+----+--------------------+------+------+
4 rows in set (0.00 sec)

複製程式碼

上述執行一切正常,從結果來看:當傳入mybatis的資料為Array時,collection的值預設就是array,除非在dao層通過註解@Param("xxx")指定其他名稱。item的值為陣列的元素,index的值為遍歷陣列的序號,從0開始,依次遞增

6.foreach用於批量的查詢

mapper的sql:

複製程式碼

  <select id="foreachSelect" resultType="com.tg.ecs.ucc.model.Du">
      select name, age from 
          foreach_test where idx in 
          <foreach item="st" collection="map" index="seq" open="(" separator="," close=")">
              #{st, jdbcType=INTEGER}
          </foreach>              
  </select> 

複製程式碼

dao介面:

List<Du> foreachSelect(@Param("map") HashMap<String, Integer> idxMap);

java的業務邏輯實現:

複製程式碼

    @GET
    @Path("/foreach/select")
    public String foreachSelect(@Context HttpServletRequest req){
        
        HashMap<String, Integer> idxMap = new HashMap<String, Integer>();
        
        for(int i=0; i<5; i++){
            idxMap.put("key" + i, i);
        }
        List<Du> res = pes.foreachSelect(idxMap);
        for(Du du: res){
            System.out.println(du.getName() + " -- " + du.getAge());
        }
        
        return "Select OK";
    }

複製程式碼

執行web應用前,檢視資料庫內容:

複製程式碼

mysql> select * from foreach_test;
+----+---------------------+------+------+
| id | name                | age  | idx  |
+----+---------------------+------+------+
|  1 | UseGeneratedKeys40  |   30 |    0 |
|  2 | UseGeneratedKeys41  |   31 |    1 |
|  3 | UseGeneratedKeys42  |   32 |    2 |
|  4 | UseGeneratedKeys43  |   33 |    3 |
|  5 | UseGeneratedKeys332 |   62 |   32 |
|  6 | UseGeneratedKeys331 |   61 |   31 |
|  7 | UseGeneratedKeys330 |   60 |   30 |
|  8 | UseGeneratedKeys16  |   36 |    0 |
|  9 | UseGeneratedKeys17  |   37 |    1 |
| 10 | UseGeneratedKeys18  |   38 |    2 |
| 11 | UseGeneratedKeys19  |   39 |    3 |
+----+---------------------+------+------+
11 rows in set (0.00 sec)

複製程式碼

執行web應用,在位址列輸入:

http://10.90.9.20:8080/ecs/demo/foreach/select

eclipse控制檯列印的結果:

複製程式碼

UseGeneratedKeys40 -- 30
UseGeneratedKeys41 -- 31
UseGeneratedKeys42 -- 32
UseGeneratedKeys43 -- 33
UseGeneratedKeys16 -- 36
UseGeneratedKeys17 -- 37
UseGeneratedKeys18 -- 38
UseGeneratedKeys19 -- 39

複製程式碼

結合上面的java程式碼和sql,輸出結果是正確的。

最後,總結一下:

1. mybatis的insert操作,這裡主要指批量插入操作,要注意鍵值的生成問題,對於mysql,有selectKey和useGeneratedKeys=“true”兩種方案,例如本案例中,主鍵是auto_increment的,所以,用useGeneratedKeys=“true”相對簡單(前提是資料庫支援自動生成鍵值)。

2. foreach的使用,主要是搞清楚item,collection,index,open,separator,close幾個屬性的使用。其中,最最重要的是collection和index的含義。本博文中,針對collection支援的三種類型:list,array以及map都做了介紹,而map相對複雜點,原則上map支援任何引數的傳入。要結合map的構造方式,以及dao層給map引數指定的引數名。對於map,collection的值若是map的key,則index是個序號,遍歷map的次序編號;若collection的值給的是map,那麼,index的值就是map的key,item的值就是key對於的value。

3. foreach中的open,separator以及close,配合使用,用於構造SQL語句。例如in指令的語句中,in後面是一個序列,所以習慣用open=“(”,separator=“,”,close=“)”。當然也不一定,這個的值,還和foreach標籤體內的SQL的寫法有關係。例如本博文案例2和案例3的這open,separator和close的值,就可以看出門道。

好了,本博文就到這裡吧,若有什麼需要探討的,可以給我留言或者加好友討論!