使用DSL构建查询

郭胜凯2025/11/21

说明

在使用数据库操作时,通常需要构建复杂的查询条件。使用DSL(领域特定语言)可以让我们以更直观和类型安全的方式来构建这些查询条件。 下面是如何使用DSL来构建查询的示例。

假设我们使用DSL来优化一下这个示例

多条件查询

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> searchStudent(String name, Integer minAge, Integer maxAge, Sex sex) {
        return studentMapper.select(Where
                .where(Student::getName, C.LIKE, name)
                .and(Student::getAge, C.GTE, minAge)
                .and(Student::getAge, C.LTE, maxAge)
                .and(Student::getSex, C.EQ, sex)
        );
    }
}

对应的SQL语句

SELECT
    ...
FROM
    STUDENT
WHERE
    NAME LIKE CONCAT('%', #{name}, '%')
    AND AGE >= #{minAge}
    AND AGE <= #{maxAge}
    AND SEX = #{sex}

注意

此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。

比如:

  • 当传入的name参数不含通配符, 比如"John"时, 生成的SQL会自动添加通配符: NAME LIKE CONCAT('%', #{name}, '%')
  • 当传入的name参数包含通配符, 比如"%John"时, 生成的SQL会严格使用入参: NAME LIKE #{name}

动态条件查询

假设我们有一些查询条件是可选的,可以根据传入的参数动态添加条件:

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> searchStudent(String name, Integer minAge, Integer maxAge, Sex sex) {
        Where where = Where.where();
        if(StringUtil.isNotBlank(name)){
            where.and(Student::getName, C.LIKE, name);
        }
        if(minAge != null){
            where.and(Student::getAge, C.GTE, minAge);
        }
        if(maxAge != null){
            where.and(Student::getAge, C.LTE, maxAge);
        }
        if(sex != null){
            where.and(Student::getSex, C.EQ, sex);
        }
        return studentMapper.select(where);
    }
}

对应的SQL语句

SELECT
    ...
FROM
    STUDENT
<where>
    <if test="name != null and name != ''">
        AND NAME LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="minAge != null">
        AND AGE >= #{minAge}
    </if>
    <if test="maxAge != null">
        AND AGE &lt;= #{maxAge}
    </if>
    <if test="sex != null">
        AND SEX = #{sex}
    </if>
</where>

注意

此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。

比如:

  • 当传入的name参数不含通配符, 比如"John"时, 生成的SQL会自动添加通配符: NAME LIKE CONCAT('%', #{name}, '%')
  • 当传入的name参数包含通配符, 比如"%John"时, 生成的SQL会严格使用入参: NAME LIKE #{name}

简化动态条件查询

正如上面的代码所示,我们可以根据传入的参数动态地添加查询条件,从而实现灵活的查询功能。 但随之而来的代码冗长和可读性下降的问题. 因此, 我们对连接符进行了封装, 可以避免这种冗余代码.

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> searchStudent(String name, Integer minAge, Integer maxAge, Sex sex) {
        Where where = Where.where()
                .ifAnd(Student::getName, C.LIKE, name)
                .ifAnd(Student::getAge, C.GTE, minAge)
                .ifAnd(Student::getAge, C.LTE, maxAge)
                .ifAnd(Student::getSex, C.EQ, sex);
        return studentMapper.select(where);
    }
}

对应的SQL语句

SELECT
    ...
FROM
    STUDENT
<where>
    <if test="name != null and name != ''">
        AND NAME LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="minAge != null">
        AND AGE >= #{minAge}
    </if>
    <if test="maxAge != null">
        AND AGE &lt;= #{maxAge}
    </if>
    <if test="sex != null">
        AND SEX = #{sex}
    </if>
</where>

注意

此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。

比如:

  • 当传入的name参数不含通配符, 比如"John"时, 生成的SQL会自动添加通配符: NAME LIKE CONCAT('%', #{name}, '%')
  • 当传入的name参数包含通配符, 比如"%John"时, 生成的SQL会严格使用入参: NAME LIKE #{name}

通过这种方式,我们可以大大简化动态条件查询的代码,提高代码的可读性和维护性。 但仍然有一些问题, 那就是它不利于现代编辑器的自动填充.

注意: 当使用ifAndifOr来添加条件时, 如果传入的值为null或空字符串, 该条件将不会被添加到查询中. 如果您需要生成IS NULLIS NOT NULL条件, 请使用标准的andor方法.

利用单参方法提升类型安全和自动补全

在现代IDE中, 我们可以利用单参方法来实现更好的类型安全和自动补全支持. 通过这种方式, 我们可以直接引用实体类的属性方法, 避免了参数拼接带来的错误风险.

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> searchStudent(String name, Integer minAge, Integer maxAge, Sex sex) {
        return studentMapper.select(Where.where()
                .ifAnd(Student::getName).like(name)
                .ifAnd(Student::getAge).gte(minAge)
                .ifAnd(Student::getAge).lte(maxAge)
                .ifAnd(Student::getSex).eq(sex)
        );
    }
}

对应的SQL语句

同上.

更利于阅读的方法名

虽然上面的方式已经很好地解决了类型安全和自动补全的问题, 并且极大地简化了动态条件查询的代码, 但仍然存在一个问题, 那就是方法名不够直观.

为了解决这个问题, 我们对操作符方法进行了冗余声明, 使其更符合自然语言的表达习惯.

@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> searchStudent(String name, Integer minAge, Integer maxAge, Sex sex) {
        return studentMapper.select(Where.where()
                .ifAnd(Student::getName).like(name)
                .ifAnd(Student::getAge).greaterThanOrEquals(minAge)
                .ifAnd(Student::getAge).lessThanOrEquals(maxAge)
                .ifAnd(Student::getSex).equalsFor(sex)
        );
    }
}

对应的SQL语句

同上.

至于使用哪种方法, 完全取决于个人喜好和团队的编码规范。无论选择哪种方式, 关键是要确保代码的可读性和可维护性。

总之, 最后这两种方式是我们推荐的使用方式, 它们不仅提高了代码的可读性, 还充分利用了现代IDE的功能, 使得开发过程更加高效和愉快。

Last Updated 11/23/2025, 9:16:20 PM