1. 程式人生 > >mybatis 使用小結

mybatis 使用小結

一、多資料來源問題

主要思路是把dataSource、sqlSesstionFactory、MapperScannerConfigurer在配置中區分開,各Mapper對應的包名、類名區分開

複製程式碼
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
8 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 9 http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
11 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" 12 default-autowire="byName"> 13 14 <bean id="dataSource1" class="org.h2.jdbcx.JdbcConnectionPool" 15 destroy-method="dispose"> 16 <constructor-arg> 17 <bean class="org.h2.jdbcx.JdbcDataSource"> 18 <property name="URL" value="jdbc:h2:r:/h2db/awbprint/a"/> 19 <property name="user" value="sa"/> 20 <property name="password" value="sa"/> 21 </bean> 22 </constructor-arg> 23 </bean> 24 25 26 <bean id="dataSource2" class="org.h2.jdbcx.JdbcConnectionPool" 27 destroy-method="dispose"> 28 <constructor-arg> 29 <bean class="org.h2.jdbcx.JdbcDataSource"> 30 <property name="URL" value="jdbc:h2:r:/h2db/awbprint/b"/> 31 <property name="user" value="sa"/> 32 <property name="password" value="sa"/> 33 </bean> 34 </constructor-arg> 35 </bean> 36 37 <bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean"> 38 <property name="dataSource" ref="dataSource1"/> 39 <property name="configLocation" value="classpath:mybatis-config.xml"></property> 40 <property name="typeAliasesPackage" value="awbprint.mybatis.entity"></property> 41 <property name="mapperLocations" value="classpath:mybatis/a/**/*.xml"></property> 42 </bean> 43 44 <bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean"> 45 <property name="dataSource" ref="dataSource2"/> 46 <property name="configLocation" value="classpath:mybatis-config.xml"></property> 47 <property name="typeAliasesPackage" value="awbprint.mybatis.entity"></property> 48 <property name="mapperLocations" value="classpath:mybatis/b/**/*.xml"></property> 49 </bean> 50 51 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 52 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory1"></property> 53 <property name="basePackage" value="awbprint.mybatis.mapper.a"/> 54 </bean> 55 56 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 57 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory2"></property> 58 <property name="basePackage" value="awbprint.mybatis.mapper.b"/> 59 </bean> 60 61 62 </beans>
複製程式碼

上面的配置,一個連h2的a資料庫,一個連h2的b資料庫,至於事務管理器,大家可參考這個思路,建二個,各管各的。

專案中mapper介面及對映檔案均用包名區分開,如下圖:

二、如何使用Map做為引數及動態條件生成

複製程式碼
 1     <resultMap id="BaseResultMap" type="awbprint.mybatis.entity.PrintLayout">
 2         <id column="D_RECID" property="recid" jdbcType="DECIMAL"/>
 3         <result column="D_USER_NAME" property="userName" jdbcType="VARCHAR"/>
 4         <result column="D_NAME" property="name" jdbcType="VARCHAR"/>
 5         <result column="D_TYPE" property="type" jdbcType="VARCHAR"/>
 6         ...
 7        
 8     </resultMap>
 9 
10    <sql id="Base_Column_List">
11         D_RECID, D_USER_NAME, D_NAME, D_TYPE, ...
12     </sql>
13 
14     <select id="select" resultMap="BaseResultMap"
15             parameterType="java.util.Map">
16         select
17         <include refid="Base_Column_List"/>
18         from T_PRINT_LAYOUT
19         where D_USER_NAME = #{userName,jdbcType=VARCHAR} and D_TYPE = #{awbType,jdbcType=VARCHAR}
20         <if test="recId != null">
21             and D_RECID = #{recId,jdbcType=DECIMAL}
22         </if>
23         <if test="ids != null">
24             or D_RECID in
25             <foreach item="item" index="index" collection="ids"
26                      open="(" separator="," close=")">
27                 #{item}
28             </foreach>
29         </if>
30 
31     </select>
複製程式碼

14-31演示瞭如何使用Map做為引數,動態傳入查詢條件,及List引數生成in(...)條件

java端程式碼示例:

複製程式碼
 1         PrintLayoutMapper mapper = context.getBean(PrintLayoutMapper.class);
 2         
 3         Map<String, Object> map = new HashMap<String, Object>();
 4         map.put("userName", "ADMIN");
 5         map.put("awbType", "CARGOLABEL_MU");
 6         map.put("recId", 1);
 7         
 8         List<Integer> ids = new ArrayList<Integer>();
 9         ids.add(0, 1);
10         ids.add(0, 2);
11         ids.add(0, 3);
12         
13         map.put("ids", ids);
14 
15         List<?> list = mapper.select(map);
複製程式碼

其實PrintLayoutMapper介面的定義為:

1 public interface PrintLayoutMapper {    
2     ...
3     
4     List<PrintLayout> select(Map<String, Object> map);
5 }

最終生成的SQL語句為:

1 select D_RECID, D_USER_NAME, D_NAME, D_TYPE, ... from T_PRINT_LAYOUT where D_USER_NAME = ? and D_TYPE = ? and D_RECID = ? or D_RECID in ( ? , ? , ? )

三、相容不同的資料庫

複製程式碼
 1 <insert id="insert">
 2  <selectKey keyProperty="id" resultType="int" order="BEFORE">
 3  <if test="_databaseId == 'oracle'">
 4  select seq_users.nextval from dual
 5  </if>
 6  <if test="_databaseId == 'db2'">
 7  select nextval for seq_users from sysibm.sysdummy1"
 8  </if>
 9  </selectKey>
10  insert into users values (#{id}, #{name})
11 </insert>
複製程式碼

這是官方文件上的示例,演示瞭如何相容oracle與db2這二種不同的資料庫,來獲取序列的下一個值

四、加強版的分支、選擇判斷

複製程式碼
 1 <select id="findActiveBlogLike"
 2  resultType="Blog">
 3  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
 4  <choose>
 5  <when test="title != null">
 6  AND title like #{title}
 7  </when>
 8  <when test="author != null and author.name != null">
 9  AND author_name like #{author.name}
10  </when>
11  <otherwise>
12  AND featured = 1
13  </otherwise>
14  </choose>
15 </select>
複製程式碼

這也是官方文件上的示例,因為<if>...</if>並沒對應的<else>標籤,所以要達到<if>...<else>...</else> </if>的效果,得藉助<choose>、<when>、<otherwise>組合使用。

五、避免Where 空條件的尷尬

複製程式碼
1 <select id="findActiveBlogLike" resultType="Blog">
2  SELECT * FROM BLOG
3  WHERE
4  <if test="state != null">
5  state = #{state}
6  </if> 
7 </select>
複製程式碼

如果state引數為空時,最終生成SQL語句為 

1  SELECT * FROM BLOG
2  WHERE

執行會出錯,當然,你可以在where 後加一個1=1,改成

複製程式碼
1 <select id="findActiveBlogLike" resultType="Blog">
2  SELECT * FROM BLOG
3  WHERE 1=1
4  <if test="state != null">
5  and state = #{state}
6  </if> 
7 </select>
複製程式碼

但是這個做法不太“環保”(畢竟引入了一個垃圾條件),其實只要改成<where>...</where>即可

複製程式碼
1 <select id="findActiveBlogLike" resultType="Blog">
2  SELECT * FROM BLOG
3  <where>
4      <if test="state != null">
5          and state = #{state}
6      </if>
7  </where> 
8 </select>
複製程式碼

六、$與#的區別

1  select * from T_PRINT_LAYOUT where  D_RECID = ${recId}

最後生成的SQL為:

1 select * from T_PRINT_LAYOUT where  D_RECID = 1

即:直接將引數值替換到了原來${recId}的位置,相當於硬拼SQL

1  select * from T_PRINT_LAYOUT where  D_RECID = #{recid,jdbcType=DECIMAL}

最後生成的SQL為:

1 select * from T_PRINT_LAYOUT where  D_RECID = ?

即:#{...}被識別為一個SQL引數

七、大量資料的批量insert

大量資料(條數>10000)做insert時,如果按常規方式,每條insert into table(...) values(...);來提交,速度巨慢。改善效能的思路是多條insert批量提交。

oracle環境中,有一種批量insert的小技巧,原理是 insert into ... select from ...,套在mybatis上,變形為:

複製程式碼
 1 INSERT INTO T_TEST
 2   (ID, COL_A, COL_B)
 3   SELECT SEQ_TEST.NEXTVAL, A.*
 4     FROM (
 5               SELECT 'A1', 'B1' FROM DUAL
 6     UNION ALL SELECT 'A2', 'B2' FROM DUAL
 7     UNION ALL SELECT 'A3', 'B3' FROM DUAL
 8     UNION ALL SELECT 'A4', 'B4' FROM DUAL
 9     UNION ALL SELECT 'A5', 'B5' FROM DUAL
10     UNION ALL SELECT 'A6', 'B6' FROM DUAL
11     ) A
複製程式碼

中間的部分非常有規律,可以用foreach標籤生成,參考下面的片段:

複製程式碼
 1 <insert id="insertBatch2" parameterType="ctas.entity.SharkFlt">
 2     <selectKey keyProperty="recId" order="BEFORE" resultType="Long">
 3       select SEQ_CTAS_SHARK_FLT.nextval as recId from dual
 4     </selectKey>
 5     insert into CTAS_SHARK_FLT (<include refid="Base_Column_List"/>) SELECT SEQ_TEST.NEXTVAL, A.*
 6     FROM (
 7     <foreach collection="list" item="item" index="index" open="" close="" separator="union all">
 8       select #{item.awbType,jdbcType=VARCHAR}, #{item.awbPre,jdbcType=VARCHAR},... from dual
 9     </foreach>
10     ) A
11   </insert>
複製程式碼

即使這樣,也不能直接run,oracle中一次執行的sql語句長度是有限制的,如果最後拼出來的sql字串過長,會導致執行失敗,所以java端還要做一個分段處理,參考下面的處理:

複製程式碼
 1         List<SharkFlt> data = new ArrayList<SharkFlt>();
 2         for (TSharkFlt f : sharkFlts) {
 3             data.add(getSharkFlt(f));
 4         }
 5 
 6         System.out.println(data.size());
 7 
 8         long beginTime = System.currentTimeMillis();
 9         System.out.println("開始插入...");
10         SqlSessionFactory sqlSessionFactory = ctx.getBean(SqlSessionFactory.class);
11         SqlSession session = null;
12         try {
13             session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
14             int a = 2000;//每次提交2000條
15             int loop = (int) Math.ceil(data.size() / (double) a);
16 
17             List<SharkFlt> tempList = new ArrayList<SharkFlt>(a);
18             int start, stop;
19             for (int i = 0; i < loop; i++) {
20                 tempList.clear();
21                 start = i * a;
22                 stop = Math.min(i * a + a - 1, data.size() - 1);
23                 System.out.println("range:" + start + " - " + stop);
24                 for (int j = start; j <= stop; j++) {
25                     tempList.add(data.get(j));
26                 }
27                 session.insert("ctas.importer.writer.mybatis.mappper.SharkFltMapper.insertBatch2", tempList);
28                 session.commit();
29                 session.clearCache();
30                 System.out.println("已經插入" + (stop + 1) + " 條");
31             }
32         } catch (Exception e) {
33             e.printStackTrace();
34             session.rollback();
35         } finally {
36             if (session != null) {
37                 session.close();
38             }
39         }
40         long endTime = System.currentTimeMillis();
41         System.out.println("插入完成,耗時 " + (endTime - beginTime) + " 毫秒!");
複製程式碼

13,27-29這幾行是關鍵,這一段邏輯會經常使用,為了重用,可以封裝一下:

複製程式碼
 1     /**
 2      * 批量提交資料
 3      * @param sqlSessionFactory
 4      * @param mybatisSQLId SQL語句在Mapper XML檔案中的ID
 5      * @param commitCountEveryTime 每次提交的記錄數
 6      * @param list 要提交的資料列表
 7      * @param logger 日誌記錄器
 8      */
 9     private <T> void batchCommit(SqlSessionFactory sqlSessionFactory, String mybatisSQLId, int commitCountEveryTime, List<T> list, Logger logger) {
10         SqlSession session = null;
11         try {
12             session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
13             int commitCount = (int) Math.ceil(list.size() / (double) commitCountEveryTime);
14             List<T> tempList = new ArrayList<T>(commitCountEveryTime);
15             int start, stop;
16             Long startTime = System.currentTimeMillis();
17             for (int i = 0; i < commitCount; i++) {
18                 tempList.clear();
19                 start = i * commitCountEveryTime;
20                 stop = Math.min(i * commitCountEveryTime + commitCountEveryTime - 1, list.size() - 1);
21                 for (int j = start; j <= stop; j++) {
22                     tempList.add(list.get(j));
23                 }
24                 session.insert(mybatisSQLId, tempList);
25                 session.commit();
26                 session.clearCache();
27             }
28             Long endTime = System.currentTimeMillis();
29             logger.debug("batchCommit耗時:" + (endTime - startTime) + "毫秒");
30         } catch (Exception e) {
31             logger.error("batchCommit error!", e);
32             e.printStackTrace();
33             session.rollback();
34         } finally {
35             if (session != null) {
36                 session.close();
37             }
38         }
39     }
複製程式碼

對應的,如果是批量update,也是類似的思路,只不過要注意一點:oracle環境中,多條語句提交的sql語句為

begin

  update xxx set xxx =xxx ;

  update xxx set xxx =xxx;

end;

用mytais拼的時候,參考下面的寫法:

1   <update id="updateBatch" parameterType="java.util.List">
2     <foreach collection="list" item="item" index="index" open="begin" close="end;" >
3       update xxx set x=#{item.x,jdbcType=VARCHAR} where x =#{item.x,jdbcType=VARCHAR};
4     </foreach>
5   </update>

關於批量提交的效能,Oracle環境下,我大概測試了一下:

insert into ... select xxx

  union all select yyy

  union all select zzz;

最快,其次是

begin

  insert into ... values  ...;

  insert into ... values ...;

end;

當然最慢是逐條insert提交,最後談下Spring與mybatis整合後,AOP事務管理 對 批量提交的影響 ,通常情況下,我們會這樣配置AOP事務管理: