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. 最佳实践建议
- 统一参数检查:在 Java 代码中预先检查参数,减少 XML 中的复杂判断
- 使用包装对象:创建专门的查询参数对象,避免直接使用 Map
- 保持简洁:XML 中只做简单的存在性检查,复杂逻辑放在 Java 代码中
- 添加注释:为复杂的条件判断添加注释说明
- 性能考虑:避免在循环中使用复杂的 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 <= #{filter.ageRange.max}
</if>
</if>
</if>
</sql>
通过以上封装方法,可以更优雅地在 MyBatis XML 中处理各种复杂的属性存在性判断,使 SQL 映射文件更加清晰和可维护。