Base-Query
所谓Base-Query,即SQL中的Derived Table,CTE和Recursive CTE的统称。
Jimmer使用强类型DSL对Derived Table,CTE和Recursive CTE进行了统一支持。
和SQL语言的这些概念不同,Jimmer额外提供了一个全新颠覆性特性:投影反向传播。
这是一套全新的设计哲学,也是本文要讨论的重点。
初识Derived Table
所谓Derived Table就是利用一个查询作为其他查询的from或join的基表。例如
select a, b, c
from (
select a, b, c, d from t where ....
) derived_table
where d between 1 and 10
Jimmer实现derived table的方式如下
- Java
- Kotlin
BookTable book = BookTable.$;
AuthorTableEx author = AuthorTableEx.$;
BaseTable2<BookTable, NumericExpression<Integer>> baseTable =
sqlClient
.createBaseQuery(book)
.addSelect(book)
.addSelect(
Expression.numeric().sql(
Integer.class,
"dense_rank() over(" +
"order by %e desc" +
")",
sqlClient.createSubQuery(author)
.where(author.books().id().eq(book.id()))
.selectCount()
)
)
.asBaseTable();
List<Book> books =
sqlClient
.createQuery(baseTable)
.where(baseTable.get_2().eq(1))
.select(baseTable.get_1())
.execute();
val baseTable =
baseTableSymbol {
sqlClient.createBaseQuery(Book::class) {
selections
.add(table)
.add(
sql(Int::class, "dense_rank() over(order by %e desc)") {
expression(
subQuery(Author::class) {
where(table.books.id eq parentTable.id)
select(rowCount())
}
)
}
)
}
}
val books =
sqlClient.createQuery(baseTable) {
where(table._2 eq 1)
select(table._1)
}.execute()
对于这个例子
-
和
createQuery创建真实查询不同,createBaseQuery创建的base-query不会被直接执行。 其目的仅仅在于构建derived table,供后续createQuery创建真正的查询使用 (作为from或join的基表使用) -
内部的base-query返回了两列 (第一列是表对象,第二列是简单表达式)。外部查询使用
get_1()/_1和get_2()/_2来使用他们这种位置化形式支持返回1~9个逻辑列。对于更大或需要复用的行结构,请使用 typed tuple。
-
本例使用位置化的base-query投影API:
-
Java使用
.addSelect(...).addSelect(...)...addSelect(...)风格的代码指定投影 -
Kotlin使用
selections.add(...).add(...)...add(...)风格的代码指定投影
信息- 须采用链式编程风格,语法层面不可中断,才能保证成功的编译
- 该位置化API用于实现稍后介绍的重要特性:投影反向传播。另一种方式是把生成的
typed-tuple mapper传给
select,从而创建命名base table。
-
此查询会生成如下SQL
select
tb_1_.c1,
tb_1_.c2,
tb_1_.c3,
tb_1_.c4,
tb_1_.c5
from (
select
tb_2_.ID c1,
tb_2_.NAME c2,
tb_2_.EDITION c3,
tb_2_.PRICE c4,
tb_2_.STORE_ID c5,
dense_rank() over(order by (
select
count(1)
from AUTHOR tb_3_
inner join BOOK_AUTHOR_MAPPING tb_4_
on tb_3_.ID = tb_4_.AUTHOR_ID
where
tb_4_.BOOK_ID = tb_2_.ID
) desc) c6
from BOOK tb_2_
) tb_1_
where
tb_1_.c6 = ? /* 1 */
得到如下数据
[
{
"id": 1,
"name": "Learning GraphQL",
"edition": 1,
"price": 50,
"store": {
"id": 1
}
},
{
"id": 2,
"name": "Learning GraphQL",
"edition": 2,
"price": 55,
"store": {
"id": 1
}
},
{
"id": 3,
"name": "Learning GraphQL",
"edition": 3,
"price": 51,
"store": {
"id": 1
}
}
]
命名Base Table
对于报表查询,_1、_2这样的位置化属性很快就会变得难以阅读。
@TypedTuple可以让一个声明同时作为普通查询的结果投影,以及derived
table或CTE的命名行结构:
val statistics = sqlClient.createBaseQuery(Book::class) {
groupBy(table.storeId)
select(
StoreStatisticsMapper
.storeId(table.storeId.asNonNull())
.bookCount(rowCount())
)
}.asCteBaseTable()
sqlClient.createQuery(statistics) {
where(table.bookCount gt 2L)
select(table.storeId)
}
代码生成器为Java提供StoreStatisticsTable,为Kotlin提供non-null和nullable两个版本。
命名table也适用于weak join、union和recursive CTE,并且不受九个逻辑属性的限制。
Java和Kotlin的声明方式及完整示例请参阅Typed Tuple。