在日常開發中,MyBatis 的參數綁定、變量命名規範、時間處理邏輯等細節往往容易被忽視,卻可能引發難以排查的 Bug。本文記錄一次完整的 Bug 解決過程,涵蓋 MyBatis parameterType 使用誤區、代碼結構優化、時間邊界條件處理、參數校驗健壯性、類型轉換技巧以及變量命名規範等核心知識點,並附贈 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) { ... }
同一方法多次調用 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);
<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 接收再轉型。
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) { ... }
| 轉換方向 | 推薦方式 | 備註 |
|---|---|---|
| String → Integer | Integer.valueOf(str) | 優於 new Integer(),支持緩存 |
| String → int | Integer.parseInt(str) | 拋 NumberFormatException 需捕獲 |
| Integer → String | integer.toString() | 需非空判斷 |
| int → String | String.valueOf(i) | 安全不拋 NPE |
| int → Integer | Integer.valueOf(i) | 自動裝箱 |
| String → BigDecimal | new BigDecimal(str) | 金額計算必用 |
| 獲取當前日期 | new Date() | 避免 System.currentTimeMillis() |
| 日期格式化 | DateFormat.getDateInstance().format(date) | 線程不安全,建議用 DateTimeFormatter |
// 數組 → List
String[] arr = {"A", "B"};
List list = new ArrayList<>(Arrays.asList(arr));
// List → 數組
String[] arr2 = list.toArray(new String[0]); // 推薦指定類型
label ≠ lable,region ≠ reginorder、group 等映射到 Java 實體時,建議加下劃線如 order_、group_spId 優於 id,assetCount 優於 countfor (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 沒有的供應商處理
Set 運算找出差集if 必配 else,或用 default 兜底<select id="getById" parameterType="java.lang.Integer">
SELECT * FROM t WHERE id = #{_parameter}
</select>
// Mapper
List findByIds(int[] ids);
// XML
<foreach item="id" collection="array" separator=",">#{id}</foreach>
// Mapper
List selectByIds(List staffIds);
// XML
<foreach item="item" collection="list" open="(" separator="," close=")">#{item}</foreach>
// Mapper
List query(Map params);
// XML
<if test="site!=null">AND site_name = #{site}</if>
<foreach item="id" collection="idList" ...> <!-- Map 中的 List key -->
<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% 的低級錯誤:
願每位開發者都能在細節中打磨出健壯、優雅的代碼。