zoukankan      html  css  js  c++  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”),这样就能在.xml中使用#{siteId,jdbcType=INTEGER}了,仅使用于基本数据类型

    //mapper层对应的接口中必须加@Resource注解,否则在Dao层注入*Ext会失败
    @Resource public 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_counts
    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.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_id
    ORDER 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类型。

    //获取spId
    Integer spId = map.get("spId");
    //获取assetCounts
    Long 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位下载

    附:Mybatis中parameterType和resultType的相关用法

    1、parameterType用法,parameterType的传入参数类型大致分为如下几类:

    基本数据类型(_parameter接收参数)

    <!-- mapper对应接口层 -->
    public RulesInfo getRulesInfoBysiteId(Integer siteId);
    
    <!-- mapper中指定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>

    数组类型(foreach中的collection必须是array,不管变量的具体名称是什么。如下面变量名为ids,collection却是array)

    <!-- mapper对应接口层:根据工单角色获取有权限的机房 -->
    public List<User> findUserListByIdList(int[] ids); 
    
    <!-- mapper中指定java.util.List类型,一般用于MySQL中IN语句中 -->
    <select id="findUserListByIdList" parameterType="java.util.HashList" resultType="User">  
        select * from user
        <where>  
            user.ID in (  
                <foreach item="guard" index="index" collection="array" separator=","> 
                    #{guard} 
                </foreach>  
            )  
        </where>  
    </select>  

    List类型(单独传入list时,foreach中的collection必须是list,不管变量的具体名称是什么。比如代码中变量名为staffIds,collection中确实list)

    <!-- mapper对应接口层 -->
    public List<IdcStaff>  selectStaffsbyIdnumbers(List<String> staffIds);
    
    <!-- mapper中指定java.util.List类型,一般用于MySQL中IN语句中 -->
    <select id="selectStaffsbyIdnumbers" parameterType="java.util.List" resultMap="BaseResultMap">
        SELECT 
        <include refid="Base_Column_List"/>
        FROM idc_staff where id_number IN
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
        AND IS_DELETED='n'
        ORDER BY staff_status
    </select>

    Map类型(MAP中有list或array时,foreach中的collection必须是具体list或array的变量名。同上)

    <!--*BoImpl.java层构造传入的map参数-->
    public List<SiteUserVo> getSiteUserPermission(String siteName, Long roleId, String workNo) {
        Map<String, Object> siteByUser = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(siteName) && !"".equals(siteName.trim())) {
            siteByUser.put("site", siteName);
        }
        if (null != roleId && 0 != roleId) {
            siteByUser.put("roleId", roleId);
        }
        if (StringUtils.isNotBlank(workNo) && !"".equals(workNo.trim())) {
            siteByUser.put("workNo", workNo);
        }
        return siteMapperExt.getSiteUserPermission(siteByUser);
    }
    
    <!-- mapper对应接口层:根据工单角色获取有权限的机房 -->
    public List<SiteUserVo> getSiteUserPermission(Map siteByUser);
    
    <!-- mapper中指定java.util.List类型,一般用于MySQL中IN语句中 -->
    <select id="getSiteUserPermission" parameterType="java.util.HashMap" resultMap="SiteUserVoMap">
        SELECT
            i.site_name,
            t.role_id,
            h.work_no
        FROM
            idc_site_user t
        LEFT JOIN idc_site i ON t.site_id = i.site_id
        LEFT JOIN app_user h ON t.user_id = h.id
        WHERE
            t.IS_DELETED = 'n'
        AND h.IS_DELETED = 'n'
        AND i.IS_DELETED = 'n' 
        <IF test = "site!=null" >
            AND i.site_name = #{site}
        </IF > 
        <IF test = "roleId!=null" >
            AND t.role_Id = #{roleId}
        </IF > 
        <IF test = "workNo!=null" >
            AND h.work_no = #{workNo}
        </IF >
    </select>

    Java对象

    <!-- mapper对应接口层:根据工单角色获取有权限的机房 -->
    List<AssignRulesVo> selectByQuery(QueryAssignRulesrDo query);
    
    <!-- mapper中指定对象类型,对象的属性名可以直接使用;如果要在SQL中判定对象是否为空,还要用_parameter -->
    <select id="selectByQuery" parameterType="com.alibaba.tboss.dal.mysql.workOrder.query.QueryAssignRulesrDo" resultMap="BaseResult">
        SELECT
            a.id,
            a.is_valid,
            a.rule_lable,
            a.rule_name,
            a.type,
            b.sp_id,
            b.sp_name,
            a.rule_content,
            c.user_name,
            a.gmt_modified,
            a.ordering,
            a.rule_json_val
        FROM
            idc_logistics_assign_rules a
            LEFT JOIN app_user c on c.work_no=a.modifier and c.is_deleted='n',
            idc_sp_info b   
        WHERE
            a.is_deleted = 'n'
        AND b.is_deleted = 'n'
        AND a.sp_id = b.sp_id
        <if test="ruleId != null">
            AND a.id = #{ruleId,jdbcType=BIGINT}
        </if>
        <if test="ruleName != null and ruleName != ''">
            AND a.rule_name IN (${ruleName})
        </if>
        ORDER BY ordering asc
        <if test="doPage == true">
            limit #{skip,jdbcType=INTEGER}, #{take,jdbcType=INTEGER}
        </if>
    </select>

    补充${}会导致SQL注入攻击,不建议使用。

    //取值的时候用的是#{}。它具体的意思是告诉MyBatis创建一个预处理语句参数。
    //使用JDBC,这样的一个参数在SQL中会由一个“?”来标识,并被传递到一个新的预处理语句中。
    
    //一般情况下,我们采用#{}取值,产生预处理语句,但是有时我们可能不希望Mybatis来帮我们预处理。
    //${columnName},这里MyBatis不会修改或转义字符串。而是直接拼接到SQL字符串后面。
  • 相关阅读:
    PowerToys插件扩展(类似Alfred)
    .net打独立运行环境遇到无法trim遇到的bug
    blazor wasm开发chrome插件
    将php代码部署到新浪云测试(简单方法,包含数据库的连接)
    python修改csv某一列的内容
    windows10调用libcurl
    Java动态脚本Groovy,高级啊!
    Linux 配置Git
    Java动态脚本Groovy读取配置文件
    Java动态脚本Groovy获取Bean(奇淫技巧操作)
  • 原文地址:https://www.cnblogs.com/RunForLove/p/5742797.html
Copyright © 2011-2022 走看看