弱连接
要解决的问题
之前我们所讨论的所有连接,都依赖于实体对象的关联属性,这意味着表连接总是和主外键相关。
然而,有时我们需要基于和主外键无关的其他业务属性连接两张表。这种相对自由的表连接虽然用得不多,但一旦需要就很有用。
这种场景下,你有两种选择。
-
利用@JoinSql声明和主外键无关的ManyToMany关联属性,然后利用关联属性进行join
信息这种方法适合于高重用性的场合。即,对应的连接操作多个业务场景都需要。
-
无需声明实体之间的关联属性,直接用本文讲解的弱连接
信息这种方法适合于低重用性的场合。即,对应的连接操作只有个别业务场景需要,不愿为此在实体中定义基于@JoinSql的关联属性。
使用方法
定义连接条件
要使用弱连接
-
Java用户需先实现
WeakJoin
接口 -
Kotlin用户需要实现
KWeakJoin
抽象类
- Java
- Kotlin
package org.babyfish.jimmer.sql.ast.table;
import org.babyfish.jimmer.sql.ast.Predicate;
public interface WeakJoin<ST extends Table<?>, TT extends Table<?>> {
Predicate on(ST source, TT target);
}
package org.babyfish.jimmer.sql.kt.ast.table
abstract class KWeakJoin<S: Any, T: Any> /* 省略超类型 */ {
abstract fun on(
source: KNonNullTable<S>,
target: KNonNullTable<T>
): KNonNullExpression<Boolean>
...省略其他代码...
}
-
对于Java而言,
WeakJoin
接口的范型参数应是预编译器为当前表实体和目标实体生成的Table
类,例如WeakJoin<BookTable, AuthorTable>
-
对于Kotlin而言,
KWeakJoin
抽象类的范型参数应为当前实体和目标实体,例如KWeakJoin<Book, Author>
开发人员需要自定义一个类,实现自定义表连接条件
- Java
- Kotlin
private static class BookAuthorJoin implements WeakJoin<BookTable, AuthorTable> {
@Override
public Predicate on(BookTable source, AuthorTable target) {
return Predicate.and(
source.businessProp1().eq(target.businessPropA()),
source.businessProp2().eq(target.businessPropB())
);
}
}
private class BookAuthorJoin : KWeakJoin<Book, Author> {
override fun on(
source: KNonNullTable<Book>,
target: KNonNullTable<Author>
): KNonNullExpression<Boolean> =
and(
source.businessProp1 eq target.businessPropA,
source.businessProp2 eq target.businessPropB
)
}
-
WeakJoin
/KWeakJoin
是一个接口,但是,实现类型必需是一个类,不能是lambda -
不要用匿名类实现。匿名类虽然可以运行,但是违背这里的设计初衷
如果该类没有任何复用价值,建议定义为私有的静态内部类
-
该类本身不带范型参数,但是,必需明确指定超接口/超类的范型参数
后续论述中,会解释为什么有前两点限制
开发人员甚至还可以使用NativeSQL表达式构建更随意的连接条件,例如 (这里假设使用MySQL,可以调用SQL函数substr
)
- Java
- Kotlin
private static class BookAuthorJoin implements WeakJoin<BookTable, AuthorTable> {
@Override
public Predicate on(BookTable source, AuthorTable target) {
return Predicate.sql(
"substr(%e, 1, 5) = substr(%e, 1, 5)",
new Expression[] {
source.code(),
target.code()
}
);
}
}
private class BookAuthorJoin : KWeakJoin<Book, Author> {
override fun on(
source: KNonNullTable<Book>,
target: KNonNullTable<Author>
): KNonNullExpression<Boolean> =
sql(Boolean::class, "substr(%e, 1, 5) = substr(%e, 1, 5)") {
expression(source.code)
expression(target.code)
}
}
这里,Java代码中的Predicate.sql
和Kotlin代码中的sql
,是在Jimmer强类型SQL DSL中混入原生SQL片段的方法。
这种方法我们从未介绍过,请参考NativeSQL表达式
使用连接条件
- Java
- Kotlin
BookTable table = Tables.BOOK_TABLE;
List<Long> bookIds = sqlClient
.createQuery(table)
.where(
table
.asTableEx() ❶
.weakJoin(BookAuthorJoin.class) ❷
.firstName().eq("Alex")
)
.select(table.id())
.distinct()
.execute();
val bookIds = sqlClient
.createQuery(Book::class) {
where(
table
.asTableEx() ❶
.weakJoin(BookAuthorJoin::class) ❷
.firstName eq "Alex"
)
select(table.id)
}
.distinct()
.execute();
-
❶处使用
asTableEx
把Table
转化为TableEx
警告后续的
weakJoin
只被TableEx
支持,Table
不支持此行为。 -
❷处代码调用
weakJoin
,以前文定义的BookAuthorJoin
类作为连接条件,连接到author表-
此处,weakJoin的返回类型是
AuthorTable
/KNonNullTable<Author>
-
这里的
weakJoin
表示内连接,可以通过如下方法支持外连接-
Java:
weakJoin(BookAuthorJoin.class, JoinType.LEFT)
-
Kotlin:
weakOuterJoin(BookAuthorJoin::class)
-
-
之前我们介绍过一个功能点,合并冲突连接。weakJoin
兼容它,这非常重要。
对同一个join起点而言,如果多次调用weakJoin
,只要参数中WeakJoin类 (这里的BookAuthorJoin类) 是相同的,它们就能合并成一次连接操作,从而保证最终SQL中没有重复连接。
这就是前面所说的BookAuthorJoin
这种类型不能用lambda或匿名类实现的原因。