超级QBE查询
Super QBE是一个非常强大的功能,利用DTO语言生成复杂查询的参数类型,并自动实现查询逻辑。
创建文件
在任何可以访问实体类型的项目中 ,创建src/main/dto
目录,在这个目录下创建文件Book.dto
。
对于Java项目而言,如果当前项目并非定义实体类型的项目,需要在为当前项目中任何一个类添加@EnableDtoGeneration
注解。
在文件头部添加如下代码
export com.yourcompany.yourproject.model.Book
-> package com.yourcompany.yourproject.dto
以上这些步骤,以及如何编译DTO文件,在DTO语言#2. 创建文件中有详细的讨论,本文不作重复性阐述。
定义Specification类型
...省略export语句...
specification BookSpecification {
}
编译后的代码
编译后,将会生成这样的Java/Kotlin代码
- Java
- Kotlin
package com.yourcompany.yourproject.dto;
import org.babyfish.jimmer.sql.ast.query.specification.JSpecification;
import org.babyfish.jimmer.sql.ast.query.specification.SpecificationArgs;
...省略其他import语句...
public class BookSpecification implements JSpecification<Book, BookTable> {
@Override
public void applyTo(SpecificationArgs<Book, BookTable> args) {}
}
package com.yourcompany.yourproject.dto;
import org.babyfish.jimmer.sql.kt.ast.query.specification.KSpecification
import org.babyfish.jimmer.sql.kt.ast.query.specification.KSpecificationArgs
...省略其他import语句...
data class BookSpecification(
// 现在data class暂时没有字段,会导致编译错误
) : KSpecification<Book> {
override fun applyTo(args: KSpecificationArgs<Book>) {}
}
applyTo
是specification编译后生成的代码中的特有方法,按照当前对象的信息动态地为Jimmer查询添加where条件。
此方法无需用户调用 (被Jimmer内部行为调用) ,用户也无需关心其内部代码实现。这里只需要知道此方法有什么用即可。
在后续的讨论中,我们会陆续为DTO文件中的BookSpecification
添加属性。
相应地,对于自动生成的BookSpecification
类而言,一方面属性会同步增加,另外一方面,applyTo
方法中的代码也会变 多。
用法
-
在查询中使用
- Java
- Kotlin
public List<Book> find(
Specification<Book> specification ❶
) {
BookTable table = Tables.BOOK_TABLE;
return sqlClient
.createQuery(table)
.where(specification) ❷
.select(table)
.execute();
}fun find(
specification: Specification<Book> ❶
): List<Book> =
sqlClient.createQuery(Book::class) {
where(specification) ❷
select(table)
}.execute()-
❶
org.babyfish.jimmer.Specification<Book>
类型的参数,用于生产各种动态SQL条件。 -
❷ 无论
specification
格式简单还是复杂,只需一个简单的where
语句就可以使用。
-
在Spring Data Repository中使用
Jimmer整合了Spring Data,所以能定义Spring Data Repository,请参 考SpringData风格以了解更多。
Jimmer的Spring Data Repository有两种查询风格,抽象方法和default方法。default方法使用specification的代码和上面雷同, 所以,我们看看抽象查询方法使用specification的例子
- Java
- Kotlin
public interface BookRepository : JRepository<Book, Long> {
public List<Book> find(
Specification<Book> specification
)
}interface BookRepository : KRepository<Book, Long> {
fun find(
specification: Specification<Book>
): List<Book>
}
属性映射
映射属性
...省略export语句...
specification BookSpecification {
name
}
这样,就将实体属性映射到了DTO中,生成的代码为
- Java
- Kotlin
public class BookSpecification implements JSpecification<Book, BookTable> {
@Nullable
private String name;
...省略getters和setters...
@Override
public void applyTo(SpecificationArgs<Book, BookTable> args) {
...略...
}
}
package com.yourcompany.yourproject.dto;
import org.babyfish.jimmer.sql.kt.ast.query.specification.KSpecification
import org.babyfish.jimmer.sql.kt.ast.query.specification.KSpecificationArgs
...省略其他import语句...
data class BookSpecification(
val name: String? = null
) : KSpecification<Book> {
override fun applyTo(args: KSpecificationArgs<Book>) {
...略...
}
}
可空性
我们发现,在生成的代码中name
字段时可以为null的,这是specification的特殊性所在。
specification用作查询参数,为了支持动态查询,默认情况下,所有属性都是可以为null的。除非显式地使用!
规定属性不能为null*(请参见DTO语言#7. 可空性)*。
用法
-
让BookSpecification的
name
字段为null- Java
- Kotlin
BookSpecification specification = new BookSpecification();
List<Book> books = bookRepository.find(specification);val specification = BookSpecification()
val books = bookRepository.find(specification)由于
specification.name
为null,生成的SQL不会带任何where
条件生成的SQL如下
select tb_1_.ID, tb_1_.NAME, tb_1_.EDITION, tb_1_.PRICE, tb_1_.STORE_ID
from BOOK tb_1_信息这种不指定specification相关属性的查询,必然导致查询没有任何条件。
这种例子本文仅仅示范一次。
-
让BookSpecification的
name
字段不为null- Java
- Kotlin
BookSpecification specification = new BookSpecification();
specification.setName("GraphQL in Action");
List<Book> books = bookRepository.find(specification);val specification = BookSpecification(
name = "GraphQL in Action"
)
val books = bookRepository.find(specification)生成的SQL如下
select tb_1_.ID, tb_1_.NAME, tb_1_.EDITION, tb_1_.PRICE, tb_1_.STORE_ID
from BOOK tb_1_
where tb_1_.NAME = ? /* GraphQL in Action */
QBE函数
初识QBE函数
上面的代码中,当指定了specification.name
后,生成的where
条件是等于。
等于不一定是我们需要的,可以为映射属性施加QBE函数,改变运算符,以like
为例
...省略export语句...
specification BookSpecification {
like(name)
}
like函数不会影响生成的BookSpecification
类的属性,但是会影响其applyTo
方法,该方法的内部实现是where
条件的添加逻辑,无需用户关心。
执行
- Java
- Kotlin
BookSpecification specification = new BookSpecification();
specification.setName("GraphQL");
List<Book> books = bookRepository.find(specification);
val specification = BookSpecification(
name = "GraphQL"
)
val books = bookRepository.find(specification)
生成的SQL如下
select tb_1_.ID, tb_1_.NAME, tb_1_.EDITION, tb_1_.PRICE, tb_1_.STORE_ID
from BOOK tb_1_
where tb_1_.NAME like ? /* %GraphQL% */
like函数的选项
like
是所有QBE函数中比较特殊的一个,支持3个选项
-
i: 大小写不敏感
-
^: 精确匹配开头 (Jimmer不会自动在参数值前面加
%
) -
$: 精确匹配结尾 (Jimmer不会自动在参数值后面加
%
)
如果需要选项,可以在like
后面加/
,再加上需要的选项。比如:like/i
、like/^
、like/$
、like/i^
、like/i$
和like/i^$
。
虽然i
、^
和$
都是可选的,但是彼此先后顺序是固定的。
让我们来试试大小写不敏感的like
,修改DTO代码如下
...省略export语句...
specification BookSpecification {
like/i(name)
}