【Java】記錄一次代碼優化

前不久的項目時間緊張,爲了儘快完成原型開發,寫了一段效率至關低的代碼。

最近幾天閒下來,主動把以前的代碼優化了一下:)java

 
標籤:Java、Mybatis、MySQL
概況:本地系統從另一個系統獲得實體類集合List<UserEvent>,可是實體中只有eventId信息,其餘屬性值均爲空。
須要從數據庫中查詢數據,完善List<UserEvent>的信息並返回。
相關業務表以及對應的實體類,以下圖。(爲了迴避項目信息,相關業務內容均省略,如下表名、實體名、代碼變量名等均用字母ABC代替。)

原處理

1.先來看代碼,乍一看邏輯清晰,符合正常思惟習慣。
可是仔細查看發現,for循環中的每步處理都和數據查詢有關。假設有10次循環,每次循環中有4次數據庫鏈接查詢,一共須要鏈接數據庫40次。
每次數據庫鏈接都須要必定的開銷,隨着循環量不斷增長,處理時間將成倍增加,形成資源浪費。
 1  String eventId = "";
 2  String aTime = "";
 3  for (UserEvent event : userEventList) {
 4       eventId = event.getEventId();
 5   
 6       // 獲取業務B信息
 7       List<EntityB> listB = mapperB.selectBInfoByEventId(eventId);
 8       event.setListB(listB);
 9   
10      // 獲取業務C信息
11      List<EntityC> listC = mapperC.selectCInfoByEventId(eventId);
12      event.setListC(listC);
13  
14      // 查看是否有業務B處理
15      EntityB entityB = mapperB.selectBInfoByPrimary(phone, eventId);
16      event.setIsActionB(null == entityB ? "false" : "true");
17  
18      // 獲取業務A和用戶信息
19      User userInfo = mapperA.selectEventUserInfoByEventId(eventId);
20      if(null != userInfo){
21          aTime = userInfo.getTime();
22          event.setTime(aTime == null ? "" : sdfMd.format(sdfYmd.parse(aTime)));
23                      event.setUserInfo(userInfo);
24       }
25  }

 

2.再來看查詢語句。
業務表A和業務表B沒有複雜的查詢。只有業務表C使用了一個子查詢,來獲取表內自身數據引用的信息。
各業務表數據都須要關聯到用戶表User。
 1   <select id="selectBInfoByEventId" parameterType="String" resultType="EntityA">
 2     SELECT
 3       B.phone     AS phone,
 4       B.time      AS time,
 5       U.name      AS userName
 6     FROM table_b B
 7     LEFT JOIN user U ON U.phone = B.phone
 8     WHERE B.event_id = #{eventId}
 9     ORDER BY B.time ASC
10   </select>
11   <select id="selectCInfoByEventId" parameterType="String" resultType="EntityC">
12     SELECT
13       C.cmtId,
14       C.referId,
15       C.time,
16       U.name   AS userName
17       ( SELECT TU.name FROM table_c TC
18         LEFT JOIN user TU ON TU.phone = TC.phone
19         WHERE TC.cmt_id = TC.refer_id
20        ) AS referName
21     FROM table_c C
22       LEFT JOIN user U ON C.phone = U.phone
23     WHERE C.event_id = #{eventId}
24     ORDER BY C.time ASC
25   <select id="selectEventUserInfoByEventId" parameterType="java.lang.String" resultType="User">
26       SELECT
27           U.name,
28           U.picId,
29           A.time
30        FROM table_a A
31        LEFT JOIN user U ON U.phone = A.phone
32       WHERE A.event_id = #{eventId}
33   </select>

 

優化分析

  1. 在代碼結構上,要避免在for循環中做查詢處理。考慮將查詢參數evenId從for循環中提取出來,作批量查詢,而後再將查詢結果設定到對應的實體類中。
  2. 在業務上,對於每個UserEvent中的eventId,業務表A中一定對應有一條記錄,而在業務表B和業務表C中則未必有與這個eventId關聯的數據。所以,能夠將業務表A做爲主表,經過eventId與另外幾個表關聯查詢。查詢次數也由原來的至少四次減小爲一次查詢。
  3. 對於聯合查詢的結果,以UserEvent做爲查詢結果的實體類,使用Mybatis中的collection、association來處理結果映射。
  4. 另外,各業務表的查詢中都有與用戶表User的關聯,考慮將各業務信息的查詢處理建立爲視圖。這樣不只能簡化聯合查詢中SQL語句,也能夠隔離基礎表的數據。

 

優化後的代碼 

  int eventSize = userEventList.size();
  List<String> eventIds = new ArrayList<String>(); 
  // 若是考慮去掉重複數據,可使用集合Set,可是做爲Mybatis的輸入參數,最後仍是須要將Set轉化爲List。
// 此處直接使用List,由於在業務上排除了重複數據的可能性。
for (int i = 0; i < eventSize; i++) { eventIds.add(userEventList.get(i).getEventId()); } Map<String, Object> paramsMap = new HashMap<String, Object>(); paramsMap.put("eventIds", eventIds); paramsMap.put("phone", phone); List<UserEvent> eventInfoList = eventMapper.selectUserEventInfo(paramsMap); // 將查詢結果轉化爲Map存儲,方便調用 Map<String, UserEvent> eventInfoMap = new HashMap<String, UserEvent>(); for(UserEvent event : eventInfoList){ eventInfoMap.put(event.getEventId(), event); } UserEvent newEvent = null; String aTime = null; for(UserEvent event : roadEventList){ // 從查詢結果Map中取出補充信息,保存到原UserEvent對象中 newEvent =eventInfoMap.get(event.getEventId()); if(null != newEvent ){ aTime = newEvent.getTime(); event.setTime(aTime == null ? "" : sdfMd.format(sdfYmd.parse(aTime ))); event.setIsActionB(newEvent.getIsActionB() == null ? "false" : newEvent.getIsActionB()); event.setUserInfo(newEvent.getUserInfo()); event.setListB(newEvent.getListB()); event.setListC(newEvent.getListC()); } }

 

    <resultMap id="UserMap" type="User">
        <result column="name" property="name" />
        <result column="picId" property="picId" />
        <result column="time" property="time" />
    </resultMap>
    
    <resultMap id="BMap" type="EntityB">
        <id column="bPhone" property="phone" />
        <result column="bUserName" property="userName" />
         <result column="bTime" property="time" />
     </resultMap>
    
     <resultMap id="CMap" type="EntityC">
         <id column="cmtId" property="cmtId" />
         <result column="referId" property="referId" />
         <result column="cUserName" property="userName" />
         <result column="referName" property="referName" />
         <result column="cTime" property="time" />
     </resultMap>
    
     <resultMap id="EventResultMap" type="com.xxxx.bean.UserEvent">
         <id column="eventId" property="eventId" />
         <result column="time" property="time" />
         <result column="isActionB" property="isActionB" />
         <association property="userInfo" resultMap="UserMap" />
         <collection property="listB" resultMap="BMap" />
         <collection property="listC" resultMap="CMap" />
     </resultMap>
 
     <select id="selectUserEventInfo" resultMap="EventResultMap">
       SELECT
           A.eventId,
           A.time,
           A.name,
           A.picId,
           CASE WHEN B.phone = #{phone} THEN "true" ELSE "false" END AS isActionB,
           B.phone          AS bPhone,
           B.userName       AS bUserName,
           B.time           AS bTime,
           C.cmtId,
           C.referId,
           C.userName       AS cUserName,
           C.referName      AS referName,
           C.time           AS cTime
        FROM v_table_a A
        LEFT JOIN v_table_b B ON B.eventId = A.eventId
        LEFT JOIN v_table_c C ON C.eventId = A.eventId
       <where>
           A.event_id in
           <foreach collection="eventIds" index="" item="eventId" 
            open
="(" separator="," close=")"> #{eventId} </foreach> </where>; </select>

 

編碼時須要注意的幾個地方

1. 複雜對象的映射解析spring

採用resultMap嵌套。其中,collection標籤表示映射一個集合,association標籤表示映射一個實體類,
標籤中的property屬性值對應的是,該集合/實體在查詢結果對象中的變量名。
 
對於各表中名稱相同的字段,須要創建別名,不然解析時沒法肯定各屬性與表字段的對應關係。
如:業務表B和業務表C中都有userName字段,在查詢語句中爲爲字段別名加了前綴來區分。
B.userName AS bUserName, < result  column = "bUserName" property = "userName" />
C.userName AS cUserName,  < result column = "cUserName" property = "userName" />
 
resultMap中type屬性表示標籤所包含內容對應映射的Java類。
該屬性能夠寫類的全路徑(如: < resultMap id = "EventResultMap" type = "com.xxxx.bean.UserEvent" > ),
也能夠配置爲簡寫的類名(如: < resultMap id = "UserMap" type = "User" > )。
簡寫的類名須要在xml配置文件中設置(以下),配好以後的簡寫類名能夠在各個sql.xml中使用。
  <!-- spring-mybatis.xml文件 -->
  <!-- 配置sqlSessionFactory -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
    <!-- 將各Java類的簡寫別名單獨放到文件mybatis.xml中,方便修改和管理 -->
     <property name="configLocation" value="classpath:xml/mybatis.xml" />
     <property name="mapperLocations" value="classpath:sql/*.xml" /> </bean>
  <!-- mybatis.xml文件 -->
  <configuration>
      <typeAliases>
          <typeAlias alias="EntityA" type="com.xxxx.model.EntityA" />
          <typeAlias alias="EntityB" type="com.xxxx.model.EntityB" />
          <typeAlias alias="EntityC" type="com.xxxx.model.EntityC" />
          <typeAlias alias="User" type="com.xxxx.model.User" />
      </typeAliases>
  </configuration>

 

2. foreach標籤的使用sql

若是查詢接口只有一個參數,參數類型爲list,則標籤中的collection屬性應該設定 collection = "list";參數類型爲數組,則應設定爲 collection = "array"
若是查詢接口有多個參數,則最好經過Map來傳遞各參數。此時,foreach標籤的collection屬性應設置爲,Map中表示集合參數的鍵。
如上面的代碼中,表示集合參數是 eventIds,它在Map中的鍵爲 "eventIds" ,因此 collection = "eventIds"
 

處理時間對比

各表數據量在200、300條左右,List<UserEvent>集合記錄爲13條。
雖然優化後的代碼行數有所增長,查詢結果解析略微複雜,可是十幾條數據的查詢已有2秒的差距。
 
相關文章
相關標籤/搜索