MyBatis XML 中判断对象属性存在的封装方法

Gary Chen
MyBatis XML 中判断对象属性存在的封装方法

MyBatis XML 中判断对象属性存在的封装方法

在 MyBatis 的 XML 映射文件中,我们经常需要根据传入参数对象的属性是否存在来动态构建 SQL 查询。以下是几种封装方法来判断不同类型属性的存在性。

1. 基本判断方法

判断简单属性是否存在

<select id="selectUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="name != null">
    AND name = #{name}
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
</select>

判断对象属性是否存在

<select id="selectUsers" parameterType="UserQuery" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="queryParam != null and queryParam.name != null">
    AND name = #{queryParam.name}
  </if>
  <if test="queryParam != null and queryParam.age != null">
    AND age = #{queryParam.age}
  </if>
</select>

2. 封装通用判断方法

方法一:使用 <sql> 片段封装

<!-- 定义判断片段 -->
<sql id="checkProperty">
  <bind name="propertyExists" value="@com.example.MyBatisUtils@isPropertyExists(_parameter, propertyName)"/>
</sql>

<!-- 使用方式 -->
<select id="selectUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="name != null">
    <include refid="checkProperty">
      <property name="propertyName" value="'name'"/>
    </include>
    <if test="propertyExists">
      AND name = #{name}
    </if>
  </if>
</select>

对应的工具类:

public class MyBatisUtils {
    public static boolean isPropertyExists(Object object, String propertyName) {
        if (object == null) return false;
        try {
            PropertyDescriptor pd = new PropertyDescriptor(propertyName, object.getClass());
            Method getter = pd.getReadMethod();
            if (getter != null) {
                Object value = getter.invoke(object);
                return value != null;
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }
}

方法二:使用 OGNL 表达式封装

<select id="selectUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="@com.example.MyBatisUtils@isValid(name)">
    AND name = #{name}
  </if>
  <if test="@com.example.MyBatisUtils@isValid(age)">
    AND age = #{age}
  </if>
</select>

工具类增强:

public class MyBatisUtils {
    public static boolean isValid(Object value) {
        if (value == null) return false;
        if (value instanceof String) return !((String) value).isEmpty();
        if (value instanceof Collection) return !((Collection<?>) value).isEmpty();
        if (value instanceof Map) return !((Map<?, ?>) value).isEmpty();
        return true;
    }
    
    public static boolean isPropertyValid(Object obj, String property) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(property, obj.getClass());
            Object value = pd.getReadMethod().invoke(obj);
            return isValid(value);
        } catch (Exception e) {
            return false;
        }
    }
}

3. 处理复杂对象结构

判断嵌套对象属性

<select id="selectUsers" parameterType="UserQuery" resultType="User">
  SELECT * FROM users
  WHERE 1=1
  <if test="queryParam != null and queryParam.filter != null 
            and queryParam.filter.name != null">
    AND name LIKE CONCAT('%', #{queryParam.filter.name}, '%')
  </if>
  <if test="queryParam != null and queryParam.filter != null 
            and queryParam.filter.ageRange != null 
            and queryParam.filter.ageRange.min != null">
    AND age >= #{queryParam.filter.ageRange.min}
  </if>
</select>

使用动态 SQL 封装

<!-- 定义条件判断宏 -->
<sql id="condition">
  <choose>
    <when test="value == null">1=1</when>
    <when test="value instanceof java.lang.String">
      ${column} LIKE CONCAT('%', #{value}, '%')
    </when>
    <when test="value instanceof java.util.Date">
      ${column} = #{value,jdbcType=TIMESTAMP}
    </when>
    <otherwise>
      ${column} = #{value}
    </otherwise>
  </choose>
</sql>

<!-- 使用方式 -->
<select id="selectUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 
  <include refid="condition">
    <property name="column" value="'name'"/>
    <property name="value" value="name"/>
  </include>
  AND
  <include refid="condition">
    <property name="column" value="'age'"/>
    <property name="value" value="age"/>
  </include>
</select>

4. 最佳实践建议

  1. 统一参数检查:在 Java 代码中预先检查参数,减少 XML 中的复杂判断
  2. 使用包装对象:创建专门的查询参数对象,避免直接使用 Map
  3. 保持简洁:XML 中只做简单的存在性检查,复杂逻辑放在 Java 代码中
  4. 添加注释:为复杂的条件判断添加注释说明
  5. 性能考虑:避免在循环中使用复杂的 OGNL 表达式

5. 完整示例

Java 端

@Data
public class UserQuery {
    private String name;
    private Integer age;
    private Date createTime;
    private List<String> roles;
    private UserFilter filter;
}

@Data
public class UserFilter {
    private String keyword;
    private AgeRange ageRange;
}

@Data
public class AgeRange {
    private Integer min;
    private Integer max;
}

MyBatis XML

<select id="selectByQuery" parameterType="UserQuery" resultType="User">
  SELECT * FROM users
  <where>
    <include refid="queryConditions"/>
  </where>
</select>

<sql id="queryConditions">
  <!-- 基本属性检查 -->
  <if test="name != null and !name.isEmpty()">
    AND name LIKE CONCAT('%', #{name}, '%')
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
  
  <!-- 日期范围检查 -->
  <if test="createTime != null">
    AND create_time = #{createTime,jdbcType=TIMESTAMP}
  </if>
  
  <!-- 集合检查 -->
  <if test="roles != null and !roles.isEmpty()">
    AND role IN 
    <foreach collection="roles" item="role" open="(" separator="," close=")">
      #{role}
    </foreach>
  </if>
  
  <!-- 嵌套对象检查 -->
  <if test="filter != null">
    <if test="filter.keyword != null and !filter.keyword.isEmpty()">
      AND (name LIKE CONCAT('%', #{filter.keyword}, '%') 
          OR email LIKE CONCAT('%', #{filter.keyword}, '%'))
    </if>
    <if test="filter.ageRange != null">
      <if test="filter.ageRange.min != null">
        AND age >= #{filter.ageRange.min}
      </if>
      <if test="filter.ageRange.max != null">
        AND age &lt;= #{filter.ageRange.max}
      </if>
    </if>
  </if>
</sql>

通过以上封装方法,可以更优雅地在 MyBatis XML 中处理各种复杂的属性存在性判断,使 SQL 映射文件更加清晰和可维护。