• 热门专题

记录一次代码优化

作者:安静的下雪天  发布日期:2016-12-14 20:33:11
Tag标签:代码  
  • 前不久的项目时间紧张,为了尽快完成原型开发,写了一段效率相当低的代码。

    最近几天闲下来,主动把之前的代码优化了一下:)

      标签: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>

    优化分析

    在代码结构上,要避免在for循环中作查询处理。考虑将查询参数evenId从for循环中提取出来,做批量查询,然后再将查询结果设定到对应的实体类中。 在业务上,对于每一个UserEvent中的eventId,业务表A中必定对应有一条记录,而在业务表B和业务表C中则未必有与这个eventId关联的数据。因此,可以将业务表A作为主表,通过eventId与另外几个表关联查询。查询次数也由原来的至少四次减少为一次查询。 对于联合查询的结果,以UserEvent作为查询结果的实体类,使用Mybatis中的collection、association来处理结果映射。 另外,各业务表的查询中都有与用户表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. 复杂对象的映射解析

    采用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标签的使用

    如果查询接口只有一个参数,参数类型为list,则标签中的collection属性应该设定collection='list';参数类型为数组,则应设定为collection='array'。 如果查询接口有多个参数,则最好通过Map来传递各参数。此时,foreach标签的collection属性应设置为,Map中表示集合参数的键。 如上面的代码中,表示集合参数是eventIds,它在Map中的键为'eventIds' ,所以collection='eventIds'。  

    处理时间对比

    各表数据量在200、300条左右,List<UserEvent>集合记录为13条。 虽然优化后的代码行数有所增加,查询结果解析略微复杂,但是十几条数据的查询已有2秒的差距。  
About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规