跳到主要内容

Typed Tuple

当查询选择多个表达式时,默认返回Tuple2Tuple3等类型。对于局部查询这很方便, 但可复用的报表通常需要有意义的行类型,而不是位置化的_1_2属性。

@TypedTuple根据一个行类型声明生成两组相关API:

  • 可以传给select的mapper,查询结果会被直接实例化为声明的行类型。

  • 当查询作为base query使用时,暴露相同行结构的命名table facade。

声明

声明一个普通的不可变类并添加@TypedTuple

StoreStatistics.java
@TypedTuple
@lombok.Data
public class StoreStatistics {

private final UUID storeId;

private final long bookCount;

private final BigDecimal avgPrice;
}

注解处理器会生成StoreStatisticsMapper。它保留声明的属性顺序,并在编译期检查每个 属性对应的selection类型。

提示

Java record同样受支持:

@TypedTuple
public record StoreStatistics(
UUID storeId,
long bookCount,
BigDecimal avgPrice
) {}

Select投影

使用生成的mapper,而不是分别选择多个表达式:

BookTable book = BookTable.$;

List<StoreStatistics> rows = sqlClient
.createQuery(book)
.where(book.storeId().isNotNull())
.groupBy(book.storeId())
.select(
StoreStatisticsMapper
.storeId(book.storeId())
.bookCount(Expression.rowCount())
.avgPrice(book.price().avgAsDecimal())
)
.execute();

SQL投影并未改变,但每个JDBC行现在会直接实例化为StoreStatistics,无需在应用层把 Tuple3转换为业务对象。相同投影也可以用于stream以及接受投影的DML returning API。

Base Query和CTE

同一个mapper也可以被createBaseQuery选择。此时,代码生成器还会提供 StoreStatisticsTable,它的属性是DSL表达式,而不是已经实例化的值:

BookTable book = BookTable.$;

StoreStatisticsTable statistics = sqlClient
.createBaseQuery(book)
.where(book.storeId().isNotNull())
.groupBy(book.storeId())
.select(
StoreStatisticsMapper
.storeId(book.storeId())
.bookCount(Expression.rowCount())
.avgPrice(book.price().avgAsDecimal())
)
.asCteBaseTable();

List<Tuple2<UUID, BigDecimal>> rows = sqlClient
.createQuery(statistics)
.where(statistics.getBookCount().gt(2L))
.select(statistics.getStoreId(), statistics.getAvgPrice())
.execute();

使用asBaseTable()创建derived table,使用asCteBaseTable()创建CTE。base query用于 weak join、union或recursive CTE时,生成的命名facade仍会被保留。Kotlin还会生成 StoreStatisticsTable.Nullable,当outer weak join使连接表的所有列可null时会使用该类型。

命名typed table不受九个逻辑属性的限制。属性既可以代表标量表达式,也可以代表实体表; 一个实体表可以展开为多个物理SQL列,但仍然只占用一个typed-tuple属性。

向外传递实体表

当大部分列来自同一个实体、只有少量列是计算结果时,不需要在typed tuple中重复声明 实体的所有属性,只需声明一个entity-valued属性:

BookWithMetrics.java
@TypedTuple
@lombok.Data
public class BookWithMetrics {

private final Book book;

private final long authorCount;
}

把table本身传给该mapper属性,这与位置化API中的addSelect(table)selections.add(table)完全对应:

BookTable book = BookTable.$;
AuthorTableEx author = AuthorTableEx.$;

BookWithMetricsTable report = sqlClient
.createBaseQuery(book)
.select(
BookWithMetricsMapper
.book(book)
.authorCount(
sqlClient.createSubQuery(author)
.where(author.books().id().eq(book.id()))
.selectCount()
)
)
.asCteBaseTable();

sqlClient.createQuery(report)
.select(report.getBook().name(), report.getAuthorCount())
.execute();

生成的book访问器在Java中是BookTable,在Kotlin中是KNonNullTable<Book>。 投影反向传播仍然有效:内部查询只导出外部查询需要的Book列,以及实际使用的计算列。 在外部查询中选择book本身或对其应用fetcher时,也会像位置化table selection一样向内 传播所需的实体shape。

警告

Typed tuple始终可以用作普通查询的结果投影。但是,要将其用作base table,每个selection 都必须能由SQL base table表示。因此,typed base-query投影中不允许fetcher selection和 output DTO selection。

Union或recursive CTE的所有分支必须使用相同的生成typed投影。

对于小型的局部base query,仍然可以继续使用位置化的BaseTable1 ... BaseTable9 API。