
当 MyBatis Mapper 方法无参数(如 getAllErrorNotifications())且 XML 中需访问嵌套对象(如 notification.maxRetry)时,因 notification 字段可能为 null,直接使用 会触发 OGNL 空指针异常;本文提供纯 SQL 层面的安全等价写法。
当 mybatis mapper 方法无参数(如 `getallerrornotifications()`)且 xml 中需访问嵌套对象(如 `notification.maxretry`)时,因 `notification` 字段可能为 null,直接使用 `
在 MyBatis 的 XML 映射文件中,OGNL 表达式(如 notification.maxRetry != null)要求访问路径上的每个对象都非 null。而本例中,NotificationEmail 实体虽包含 private Notification notification; 字段,但该字段在数据库查询结果中可能为 null(尤其当关联表无匹配记录时),导致 OGNL 在尝试获取 notification.maxRetry 前就因 notification 为 null 而抛出 source is null for getProperty(null, "maxRetry") 异常。
此时,不能依赖 @Param 注解传参(因方法签名无参数),也不宜强行在 Java 层预设默认 Notification 对象(违背数据真实性与设计原则)。更稳健的解决方案是:将逻辑下沉至 SQL 层,用标准 SQL 条件表达式替代 MyBatis 的 OGNL 动态标签。
以下是推荐的 SQL 改写方式(兼容 Oracle/MySQL/PostgreSQL 等主流数据库):
<!-- 替换原 <choose> 块 -->
AND (
(n.NOT_MAX_RETRY IS NOT NULL
AND (n.NOT_RETRY_COUNT IS NULL OR n.NOT_RETRY_COUNT <p>✅ <strong>逻辑说明</strong>: </p>
- 第一分支:当
NOT_MAX_RETRY非空时,启用动态上限n.NOT_MAX_RETRY; - 第二分支:当
NOT_MAX_RETRY为空时,回退至硬编码上限6; - 两个分支互斥(通过
IS NULL/IS NOT NULL判定),且均覆盖NOT_RETRY_COUNT IS NULL的容错场景,语义与原<choose></choose>完全一致。
⚠️ 注意事项:
- 此方案完全规避了 MyBatis OGNL 的空安全限制,无需修改 Java 实体或 Mapper 接口;
- 若后续需支持更多回退策略(如从配置中心读取默认值),建议将
6抽取为<bind></bind>变量或外部属性,提升可维护性; - 在复杂查询中,应始终优先考虑 SQL 原生能力处理空值逻辑,而非强依赖 MyBatis 动态标签——后者本质是“运行时拼接”,对 null 敏感且调试成本高。
综上,面对无参 Mapper 与潜在 null 嵌套对象的组合场景,拥抱 SQL 的表达力与健壮性,是比绕路注解或侵入业务代码更优雅、更可持续的实践路径。










