你的位置:首页 > Java教程

[Java教程]记录一次bug解决过程:规范变量名称和mybatis的使用以及代码优化


一、总结

  1. Mybatis中当parameterType为基本数据类型的时候,统一采用_parameter来代替基本数据类型变量。
  2. Mybatis中resultMap返回一个对象,resultType返回一个Map简单数据类型(由于需要缓存到JVM中)的映射关系。
  3. String类型转Integer类型;String类型转int类型用到的方法是不一样的。
  4. 方法入口处第一行写new Date(),防止时间在23:59:59跨界对逻辑带来影响。
  5. 考虑到上线app_resource表忘记配置供应商比例,在代码中逻辑中注意要加入空指针判断,增强代码健壮性。
  6. 核心代码处要加注释,关键代码处要打日志,业务逻辑执行失败要考虑是否需要告警邮件。
  7. 变量命名要规范;测试工单的工单标题命名要规范。
  8. 代码逻辑中有if使用的地方,尽量想想else使用的场景,保证逻辑严谨性。
  9. VPN软件的使用;热部署的使用(http://docs.alibaba-inc.com/)。

二、Bug描述:Mybatis中parameterType使用

mapper层中使用parameterType="java.lang.Integer"基本类型,代码报错:

//org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: // There is no getter for property named 'siteId' in 'class java.lang.Integer'

解决办法,当入参为基本数据类型的使用,使用_parameter代替基本数据类型,如下:

 <select id="getRulesInfoBysiteId" parameterType="java.lang.Integer" resultMap="RulesMap" >     SELECT      a.site_id,      a.site_name,      b.id AS city_id,      b.`name` AS city_name,      c.id AS region_id,      c.`name` AS region_name    FROM      idc_site a,      city b,      area c    WHERE      a.region = c.`name`    AND a.city = b.`name`    AND a.is_deleted = 'n'    AND b.is_deleted = 'n'    AND c.is_deleted = 'n'    <if test="_parameter != null">      AND a.site_id = #{_parameter,jdbcType=INTEGER}    </if> </select>

或者在mapper层的接口中,给这个方法的参数加上@param(value=“siteId”),这样就能在.仅使用于基本数据类型。

//mapper层对应的接口中必须加@Resource注解,否则在Dao层注入*Ext会失败
@Resourcepublic interface SiteMapperExt extends SiteMapper { //mapper层对应的接口中加mybatis提供的注解@Param("siteId") public RulesInfo getRulesInfoBysiteId(@Param("siteId")Integer siteId);}

更多使用详情请看最后附文。

三、Bug描述:

  /**   * 自动分配物流供应商   */  @Override  public void autoAssignSupplier(RuleInfos ruleInfos, String deviceType, WorkOrderMain workOrder, int amounts) {    // 精确匹配规则制定 物流供应商    LogisticsAssignRules bean = getExactMatchSPId(ruleInfos.getSourceRegionId(), ruleInfos.getTargetRegionId(),                           ruleInfos.getSourceCityId(), ruleInfos.getTargetCityId(),                           ruleInfos.getSourceSiteName(), ruleInfos.getTargetSiteName(),                           deviceType, amounts);    if (null == bean) {      // 按比例规则制定物流供应商      Map<String, String> supplierRatesMap = getSpRates();      Map<String, String> logicOf90DaysBefore = getAssignRates();      String supplierId = getSupplierIdBy90Days(supplierRatesMap, logicOf90DaysBefore, amounts);      logisticsWorkOrderBo.LogisticsAssigned(workOrder, WorkOrderStatsCst.LogisticsOrderState.unassigned,                          supplierId, getSpRatesDesc(), WorkOrderCst.DEFAULT_VALUE_YES);      logger.info("auto assign supplier as rates,supplierId = {}, description = {}", supplierId, getSpRatesDesc());    } else {      // 精确匹配,直接分配物流供应商      logisticsWorkOrderBo.LogisticsAssigned(workOrder, WorkOrderStatsCst.LogisticsOrderState.unassigned,                          bean.getSpId().toString(), bean.getRuleJsonVal(),                          WorkOrderCst.DEFAULT_VALUE_YES);      logger.info("auto assign supplier start as rules, supplierId = {}, description = {}",            bean.getSpId().toString(), getSpRatesDesc());    }  }

在接口调用中,当传递属性过多的时候,可以考虑用对象来传递,方便以后的扩展。如本代码中,当后续添加规则时,需要更新方法。另外对于公用的东西,尽量维护在静态枚举值中。

四、Bug描述:方法入口处统一获取当前时间new Date()

在代码中的时间要作为条件来筛选数据,如果同一个方法中,在多个地方出现new Date(),算上程序执行的纳秒级别的时间,可能在当前日期的“23:59:59 纳秒”产生跨界时间的问题,给代码造成概率极低的隐患。

SELECT  d.sp_id,  COUNT(a.sn) AS asset_countsFROM  idc_asset_list aLEFT JOIN idc_work_order_main b ON a.order_id = b.idLEFT JOIN idc_order_atomic_list c ON c.order_id = b.idLEFT JOIN idc_atomic_logistics d ON d.atomic_id = c.atomic_idWHERE  a.is_deleted = 'n'AND b.is_deleted = 'n'AND c.is_deleted = 'n'AND d.is_deleted = 'n'AND d.sp_id IS NOT NULLAND b.gmt_create < CONCAT('2016-08-04', '23:59:59')AND b.gmt_create > date_sub(  '2016-08-04 00:00:00',  INTERVAL 3 MONTH)AND (  b.state != 'cancle'  OR b.sub_state != 'cancle')GROUP BY  d.sp_idORDER BY  sp_id DESC

因为要将上述数据缓存到JVM中,数据结构在集群中的一台机器上只维护一份。一天最多查询8次。

使用到的SQL如下:

  <select id = "getLogisticsList90DaysBefore" parameterType="java.lang.String" resultType ="java.util.Map">      SELECT        d.sp_id AS spId,        COUNT(a.sn) AS assetCounts      FROM        idc_asset_list a      LEFT JOIN idc_work_order_main b ON a.order_id = b.id      LEFT JOIN idc_order_atomic_list c ON c.order_id = b.id      LEFT JOIN idc_atomic_logistics d ON d.atomic_id = c.atomic_id      WHERE        a.is_deleted = 'n'      AND b.is_deleted = 'n'      AND c.is_deleted = 'n'      AND d.is_deleted = 'n'      AND d.sp_id IS NOT NULL      AND (b.state != 'cancle' OR b.sub_state != 'cancle')      <if test = "_parameter != null and _parameter !=''">        AND a.gmt_create &lt;= CONCAT(#{yesterday},' 23:59:59')        AND a.gmt_create &gt;= DATE_SUB(CONCAT(#{yesterday},' 00:00:00'), INTERVAL 3 MONTH)      </if>      GROUP BY        d.sp_id      ORDER BY        sp_id DESC  </select>

mapper层的代码中,我们使用了mysql函数date_sub(concat(""), interval 3 month),并且返回resultType="java.util.Map",我们使用结构List<String,Map<String,Object>>结构来接收查询结果,而没有采用resultMap封装对象来接收结果。

SQL执行之后的返回结果为list,通过断点跟踪获悉sp_id为Integer类型,asset_counts为Long类型。

//获取spIdInteger spId = map.get("spId");//获取assetCountsLong assetCounts = map.get("assetCounts");

故使用如下代码获取查询结果,但是代码中封装了数据类型,所以统一采用Object来获取。

五、Bug描述:考虑到线上缺失配置文件,添加空指针判断;为程序健壮性,必须在前后端同时对参数完整性作出校验。

  /**   * 校验参数的完整性 {设备类型与数量必填,用于规则匹配校验}   */  private void checkParameters(AssignSupplierRulesDTO dto) {    // 数量合理性校验    if (StringUtils.isNotBlank(dto.getAssetNum())) {      if (dto.getAssetNum().toCharArray().length <= 1) {        throw new ServiceException(ErrorCode.Params_Lost);      } else {        if (!(StringUtils.isNumeric(dto.getAssetNum().substring(1)))) {          throw new ServiceException(ErrorCode.Params_Invalid);        }        if (!("><=≤≥≠".contains(dto.getAssetNum().substring(0, 1)))) {          throw new ServiceException(ErrorCode.Params_Invalid);        }      }    }    // 供应商必填    if (null == dto.getSpId()) {      throw new ServiceException(ErrorCode.Params_Lost);    }    // 规则名称必填    if (StringUtils.isBlank(dto.getRuleName())) {      throw new ServiceException(ErrorCode.Params_Lost);    }    // 当指定规则类型的时候,关联性校验    if (StringUtils.isNotBlank(dto.getRuleType())) {      // 同城校验      if (dto.getRuleType().equals(WorkOrderCst.RelocationType.SameCity.name())) {        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceCity()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceCity()))) {          if (dto.getSourceCity() != dto.getTargetCity()) {            throw new ServiceException(ErrorCode.Params_Invalid);          }        }      }      // 同区域内校验      if (dto.getRuleType().equals(WorkOrderCst.RelocationType.RegionalIn)) {        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceRegion()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetRegion()))) {          if (StringUtils.isNotBlank(dto.getSourceRegion()) && StringUtils.isNotBlank(dto.getTargetRegion())) {            // 区域必须相等            if (!(dto.getSourceRegion().equals(dto.getTargetRegion()))) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceCity()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetCity()))) {          if (StringUtils.isNotBlank(dto.getSourceCity()) && StringUtils.isNotBlank(dto.getTargetCity())) {            if (!addressBo.whetherCityInTheSameArea(dto.getSourceCity(), dto.getTargetCity())) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceSite()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetSite()))) {          if (StringUtils.isNotBlank(dto.getSourceSite()) && StringUtils.isNotBlank(dto.getTargetSite())) {            if (!addressBo.whetherSiteInTheSameArea(dto.getSourceSite(), dto.getTargetSite())) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }      }      // 不同区域的校验      if (dto.getRuleType().equals(WorkOrderCst.RelocationType.RegionalOut)) {        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceRegion()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetRegion()))) {          if (StringUtils.isNotBlank(dto.getSourceRegion()) && StringUtils.isNotBlank(dto.getTargetRegion())) {            if (dto.getSourceRegion().equals(dto.getTargetRegion())) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceCity()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetCity()))) {          if (StringUtils.isNotBlank(dto.getSourceCity()) && StringUtils.isNotBlank(dto.getTargetCity())) {            if (addressBo.whetherCityInTheSameArea(dto.getSourceCity(), dto.getTargetCity())) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }        if (!(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getSourceSite()))          && !(WorkOrderCst.MATCH_ALL_PARAMETERS.equals(dto.getTargetSite()))) {          if (StringUtils.isNotBlank(dto.getSourceSite()) && StringUtils.isNotBlank(dto.getTargetSite())) {            if (addressBo.whetherSiteInTheSameArea(dto.getSourceSite(), dto.getTargetSite())) {              throw new ServiceException(ErrorCode.Params_Invalid);            }          }        }      }    }  }

六、Bug描述:String转Integer;String转int的熟练使用。

public class Test {  public static void main(String[] args) {    String number = "520";    Integer a = 521;    int b = 522;        //String转Integer    Integer.valueOf(number);        //String转int    Integer.parseInt(number);    new Integer(number).intValue();        //Integer转String    a.toString();        //Integer转int    a.intValue();        //int转String    String.valueOf(b);    Integer.toString(b);    String str = "" + b;        //int转Integer    new Integer(b);        //String转BigDecimal    new BigDecimal(number);    //获取今天日期    new Date(System.currentTimeMillis()); // Fri Aug 05 20:16:07 CST 2016    DateFormat.getDateInstance().format(new Date()); // 2016-8-5   }}

七、List和数组的转换

public class Test {  public static void main(String[] args) {    String[] family = { "XuG", "XuX", "GaiZ", "LianW" };    List<String> list = new ArrayList<String>(Arrays.asList("XuG", "XuX", "GaiZ", "LianW"));        //数组转list    List<String> list_01 = new ArrayList<String>(Arrays.asList(family));        //list转数组    String[] str = (String[])list.toArray();  }}

八、Bug描述:变量命名规范。

 

变量的命名规范要有意义,在数据库建表,创建java bean的时候,一定要保证单词使用的正确性。如label和lable;region和regin。要注意到变量的命名可能跟数据库的关键字或java的关键字有冲突,可以采用下划线的原则处理关键字冲突。

九、Bug描述:逻辑严谨性。

private String getSupplierIdBy90Days(Map<String, String> supplierRatesMap, Map<String, String> logicOf90DaysBefore,                     int dispatchNum) {    int ratesCount = 0, dispatchCount = 0;    for (String spId : supplierRatesMap.keySet()) {      ratesCount = ratesCount + Integer.parseInt(supplierRatesMap.get(spId));    }    for (String spId : logicOf90DaysBefore.keySet()) {      dispatchCount = dispatchCount + Integer.parseInt(logicOf90DaysBefore.get(spId));    }    Map<String, String> idealizedMap = new HashMap<String, String>();    for (String spId : supplierRatesMap.keySet()) {      Integer dispathNum = (dispatchCount * Integer.parseInt(supplierRatesMap.get(spId))) / ratesCount;      idealizedMap.put(spId, dispathNum.toString());    }    int gap = -1;    String supplierId = StringUtils.EMPTY;    if (CollectionUtils.isNotEmpty(logicOf90DaysBefore.keySet())) {      for (String spId : logicOf90DaysBefore.keySet()) {        if (null != idealizedMap.get(spId)) {          int mix = Integer.parseInt(idealizedMap.get(spId))               - Integer.parseInt(logicOf90DaysBefore.get(spId));          if (mix < gap) {            gap = mix;            supplierId = spId;          }        } else {          supplierId = spId; // 新添加的供应商比例        }      }    } else {      supplierId = new ArrayList<String>(supplierRatesMap.keySet()).get(0);    }    return supplierId;  }

  有判断if条件的地方,要考虑到else的可能出现情况,尤其是if else 嵌套多层的时候,可能某些else的情况遗漏,会给程序带来问题。如上述代码中的else的缺失,可能在“新添加供应商比例”的情况下,出现没有分配供应商的情况。

十、VPN工具

  VPN工具下载使用:Cisco AnyConnect VPN Client 64位下载