使用DSL构建查询
说明
在使用数据库操作时,通常需要构建复杂的查询条件。使用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 #{name}
AND AGE >= #{minAge}
AND AGE <= #{maxAge}
AND SEX = #{sex}
注意
此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。
比如:
- 当传入的
name不含通配符(如"John")时,框架把绑定值改为%John%,SQL 仍使用NAME LIKE #{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 #{name}
</if>
<if test="minAge != null">
AND AGE >= #{minAge}
</if>
<if test="maxAge != null">
AND AGE <= #{maxAge}
</if>
<if test="sex != null">
AND SEX = #{sex}
</if>
</where>
注意
此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。
比如:
- 当传入的
name不含通配符(如"John")时,框架把绑定值改为%John%,SQL 仍使用NAME LIKE #{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 #{name}
</if>
<if test="minAge != null">
AND AGE >= #{minAge}
</if>
<if test="maxAge != null">
AND AGE <= #{maxAge}
</if>
<if test="sex != null">
AND SEX = #{sex}
</if>
</where>
注意
此处的SQL语句仅为静态示例,实际生成的SQL中, LIKE 比较符会根据入参进行动态适配。
比如:
- 当传入的
name不含通配符(如"John")时,框架把绑定值改为%John%,SQL 仍使用NAME LIKE #{name}。 - 当传入的
name参数包含通配符, 比如"%John"时, 生成的SQL会严格使用入参:NAME LIKE #{name}
通过这种方式,我们可以大大简化动态条件查询的代码,提高代码的可读性和维护性。 但仍然有一些问题, 那就是它不利于现代编辑器的自动填充.
注意: 当使用
ifAnd或ifOr来添加条件时, 如果传入的值为null或空字符串, 该条件将不会被添加到查询中. 如果您需要生成IS NULL或IS NOT NULL条件, 请使用标准的and或or方法.
利用单参方法提升类型安全和自动补全
在现代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的功能, 使得开发过程更加高效和愉快。
空值和空集合
ifWhere/ifAnd/ifOr只忽略null和空字符串""。- 空白字符串、数字
0、false和空集合不会被自动忽略,业务应先做参数规范化。 eq(null)生成IS NULL,ne(null)生成IS NOT NULL。- 其他比较符传入
null会抛出IllegalArgumentException。 in(emptyCollection)生成恒假条件1=0。notIn(emptyCollection)生成恒真条件1=1。
空集合对查询很方便,但用于删除时尤其危险:空的 notIn 不会限制数据范围。
嵌套 AND/OR
使用嵌套 Where 明确控制优先级:
Where roleBranch = Where.or(
Where.where(User::getRole).eq(Role.TEACHER)
.and(User::getStatus).eq(Status.ACTIVE),
Where.where(User::getRole).eq(Role.STUDENT)
.and(User::getStatus).eq(Status.DISABLED)
);
Where query = Where.where(User::getAge).gte(18)
.and(roleBranch);
这表示 age >= 18 AND ((role=TEACHER AND status=ACTIVE) OR (role=STUDENT AND status=DISABLED))。混合 AND/OR 时不要依赖默认优先级猜测,应主动分组。
LIKE 参数
当值不包含 % 时,框架会把绑定值调整为 %value%;只要值中已经包含 %,就按原值绑定。例如 like("张") 是包含查询,like("张%") 是前缀查询。SQL 始终使用参数绑定,不会把值拼接进 SQL。
