首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

MyBatis 參數綁定陷阱與代碼優化實戰:從 Bug 解決到最佳實踐

     編輯:Java 綜合教學

前言

在日常開發中,MyBatis 的參數綁定、變量命名規範、時間處理邏輯等細節往往容易被忽視,卻可能引發難以排查的 Bug。本文記錄一次完整的 Bug 解決過程,涵蓋 MyBatis parameterType 使用誤區、代碼結構優化、時間邊界條件處理、參數校驗健壯性、類型轉換技巧以及變量命名規範等核心知識點,並附贈 MyBatis 參數類型完整使用指南。

一、MyBatis 基礎類型參數綁定失效問題

現象描述

Mapper 接口方法入參為基本類型 Integer 時,XML 中直接使用 #{siteId} 會拋出異常:

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'

根因分析

MyBatis 將基本類型、String 等簡單類型包裝為內部參數對象,屬性名固定為 _parameter,而非變量名。

解決方案

方案一:使用 _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 註解

@Resource
public interface SiteMapperExt extends SiteMapper {
    RulesInfo getRulesInfoBysiteId(@Param("siteId") Integer siteId);
}

此時 XML 即可正常使用 #{siteId,jdbcType=INTEGER}。

二、方法參數過多:封裝對象提升擴展性

問題代碼

public void autoAssignSupplier(RuleInfos ruleInfos, String deviceType, 
    WorkOrderMain workOrder, int amounts) { ... }

優化建議

  • 將多個業務參數封裝為 DTO 對象,便於後續擴展規則字段
  • 公共常量(如狀態碼、類型枚舉)統一維護在枚舉類中,避免硬編碼
  • 單一職責原則:精確匹配、比例分配邏輯拆分為獨立方法

三、多處 new Date() 導致的時間邊界隱患

風險場景

同一方法多次調用 new Date(),若跨越「23:59:59.999」毫秒邊界,會導致查詢時間範圍不一致,產生極低概率的數據漏查或重複。

最佳實踐

// 方法入口統一獲取當前時間
Date now = new Date();
Date startTime = DateUtils.addMonths(DateUtils.truncate(now, Calendar.DAY_OF_MONTH), -3);
Date endTime = DateUtils.addSeconds(DateUtils.truncate(now, Calendar.DAY_OF_MONTH), -1);

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 <= CONCAT(#{yesterday},' 23:59:59')
    AND a.gmt_create >= DATE_SUB(CONCAT(#{yesterday},' 00:00:00'), INTERVAL 3 MONTH)
  </if>
  GROUP BY d.sp_id ORDER BY sp_id DESC
</select>

結果映射注意:sp_id 為 Integer,asset_counts 為 Long,建議統一用 Object 接收再轉型。

四、參數校驗:前後雙重防線與邏輯完整性

核心原則

  1. 前端攔截基礎格式,後端負責業務邏輯完整性校驗
  2. 配置文件缺失、空指針等異常情況需顯式判斷
  3. if-else 多層嵌套時,務必覆蓋所有分支,避免邏輯遺漏

典型校驗代碼結構

private void checkParameters(AssignSupplierRulesDTO dto) {
    // 1. 基礎非空/格式校驗
    if (StringUtils.isBlank(dto.getRuleName())) {
        throw new ServiceException(ErrorCode.Params_Lost);
    }
    
    // 2. 業務關聯性校驗(同城/同區域/跨區域)
    if (StringUtils.isNotBlank(dto.getRuleType())) {
        switch (dto.getRuleType()) {
            case "SameCity":
                validateSameCity(dto);
                break;
            case "RegionalIn":
                validateRegionalIn(dto);
                break;
            case "RegionalOut":
                validateRegionalOut(dto);
                break;
        }
    }
}

// 拆分細顆粒驗證方法,提升可讀性
private void validateSameCity(AssignSupplierRulesDTO dto) { ... }

五、Java 類型轉換速查表

轉換方向推薦方式備註
String → IntegerInteger.valueOf(str)優於 new Integer(),支持緩存
String → intInteger.parseInt(str)拋 NumberFormatException 需捕獲
Integer → Stringinteger.toString()需非空判斷
int → StringString.valueOf(i)安全不拋 NPE
int → IntegerInteger.valueOf(i)自動裝箱
String → BigDecimalnew BigDecimal(str)金額計算必用
獲取當前日期new Date()避免 System.currentTimeMillis()
日期格式化DateFormat.getDateInstance().format(date)線程不安全,建議用 DateTimeFormatter

六、List 與數組互轉

// 數組 → List
String[] arr = {"A", "B"};
List list = new ArrayList<>(Arrays.asList(arr));

// List → 數組
String[] arr2 = list.toArray(new String[0]); // 推薦指定類型

七、變量命名規範:拒絕拼寫錯誤與關鍵字衝突

  • 拼寫準確:label ≠ lable,region ≠ regin
  • 避免關鍵字:數據庫字段 order、group 等映射到 Java 實體時,建議加下劃線如 order_、group_
  • 語義清晰:spId 優於 id,assetCount 優於 count

八、邏輯嚴謹性:邊界條件與分支覆蓋

反例:供應商分配算法缺失 else 分支

for (String spId : logicOf90DaysBefore.keySet()) {
    if (idealizedMap.get(spId) != null) {
        int mix = idealized - actual;
        if (mix < gap) { gap = mix; supplierId = spId; }
    } else {
        supplierId = spId; // 新供應商邏輯
    }
}
// 缺失:idealizedMap 中有但 logicOf90DaysBefore 沒有的供應商處理

修正策略

  1. 使用 Set 運算找出差集
  2. 所有 if 必配 else,或用 default 兜底
  3. 單元測試覆蓋邊界:空集合、單元素、新增/刪除供應商

附錄:MyBatis parameterType 完整用法分類

1. 基本類型(_parameter)

<select id="getById" parameterType="java.lang.Integer">
  SELECT * FROM t WHERE id = #{_parameter}
</select>

2. 數組類型(collection="array")

// Mapper
List findByIds(int[] ids);

// XML
<foreach item="id" collection="array" separator=",">#{id}</foreach>

3. List 類型(collection="list")

// Mapper
List selectByIds(List staffIds);

// XML
<foreach item="item" collection="list" open="(" separator="," close=")">#{item}</foreach>

4. Map 類型(collection=key名)

// Mapper
List query(Map params);

// XML
<if test="site!=null">AND site_name = #{site}</if>
<foreach item="id" collection="idList" ...>  <!-- Map 中的 List key -->

5. Java Bean 對象(直接用屬性名)

<select id="query" parameterType="com.xxx.QueryDO">
  <if test="ruleId != null">AND id = #{ruleId}</if>
  <if test="ruleName != null and ruleName != ''">AND rule_name = #{ruleName}</if>
</select>

關鍵提示

  • #{}:預編譯參數,防 SQL 注入,**首選**
  • ${}:字符串直接拼接,**極易注入**,僅用於表名、列名等不可參數化場景
  • 對象非空判斷仍需 _parameter != null

總結

這次 Bug 排查串聯起 MyBatis 參數綁定機制、代碼結構設計、時間處理邊界、參數校驗體系、類型轉換細節、命名規範等工程化核心點。良好的習慣能預防 90% 的低級錯誤:

  1. 入參封裝對象,拒絕長參數列表
  2. 時間邊界統一變量,避免多次取值
  3. 校驗邏輯分層、分支全覆蓋
  4. 命名拼寫驗證,關鍵字轉義
  5. MyBatis 參數類型對應正確的 collection/佔位符

願每位開發者都能在細節中打磨出健壯、優雅的代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved