功能介绍
Jimmer SQL DSL
-
和大部分ORM中DSL仅仅为了引入强类型体验不同,Jimmer DSL为解决原生SQL在复杂场景下开发效率低下的问题,给予全新的抽象。
因此,Jimmer SQL DSL和绝大部分SQL DSL存在本质差异 (这也是Jimmer项目立项的最初动机)
-
Jimmer DSL可以嵌入原生SQL片段,和特定数据库产品特有功能的联系没有被切断。
本章章节为了快速预览,所以我们仅仅专注于1。
至于2,有兴趣者可参见Native表达式
动态谓词
- Java
- Kotlin
@Repository
public class BookRepository {
private final JSqlClient sqlClient;
public BookRepository(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}
List<Book> findBooks(
@Nullable String name,
@Nullable BigDecimal minPrice,
@Nullable BigDecimal maxPrice,
@Nullable Fetcher<Book> fetcher
) {
BookTable table = Tables.BOOK_TABLE;
return sqlClient
.createQuery(table)
.where(table.name().ilikeIf(name)) ❶
.where(table.price().betweenIf(minPrice, maxPrice)) ❷
.select(table.fetch(fetcher))
.execute();
}
}
@Repository
class BookRepository(
private val sqlClient: KSqlClient
) {
fun findBooks(
name: String? = null,
minPrice: BigDecimal? = null,
maxPrice: BigDecimal? = null,
fetcher: Fetcher<Book>? = null
): List<Book> =
sqlClient
.createQuery(Book::class) {
where(table.name `ilike?` name) ❶
where(table.price.`between?`(minPrice, maxPrice)) ❷
select(table.fetch(table))
}
.execute()
}
其中,fetcher
参数的作用已经在快速浏览/查询任意形状中做过介绍,本文不再重复,请读者忽略之。
-
❶ 和静态谓词
ilike
不同,ilikeIf
/ilike?
是动态谓词,根据参数来决定是否添加SQL条件如果
name
既非null也非empty string,则添加SQL条件name ilike :name
-
❷ 和静态谓词
between
不同,betweenIf
/between?
是动态谓词,根据参数来决定是否添加SQL条件。存在如下四种情况-
如果
minPrice
和maxPrice
都非null,则添加SQL条件price between :minPrice and :maxPrice
。 -
如果仅
minPrice
非null,则添加SQL条件name >= :minPrice
-
如果仅
maxPrice
非null,则添加SQL条件name <= :maxPrice
-
如果
minPrice
和maxPrice
都为null,则不添加任何添加SQL条件
-
现在让我们来看看效果
-
当三个参数都为null时
- Java
- Kotlin
List<Book> books = bookRepository.findBooks(
null, // nae
null, // minPrice
null, // maxPrice
null
);val books = bookRepository.findBooks()
不会生成任何where条件,SQL如下
select
tb_1_.ID,
tb_1_.NAME,
tb_1_.EDITION,
tb_1_.PRICE,
tb_1_.STORE_ID
from BOOK tb_1_
// highlight-next-line
// No SQL predicates -
当三个参数都非null时
- Java
- Kotlin
List<Book> books = bookRepository.findBooks(
"GraphQL", // name
new BigDecimal(20), // minPrice
new BigDecimal(50), // maxPrice
null
);val books = bookRepository.findBooks(
name = "GraphQL",
minPrice = BigDecimal(20),
maxPrice = BigDecimal(50)
)会生成所有where条件,SQL如下
select
tb_1_.ID,
tb_1_.NAME,
tb_1_.EDITION,
tb_1_.PRICE,
tb_1_.STORE_ID
from BOOK tb_1_
where
/* highlight-next-line */
lower(tb_1_.NAME) like ? /* %graphql% */
and
/* highlight-next-line */
(tb_1_.PRICE between ? /* 20 */ and ? /* 50 */)
动态表连接
定义动态动态表连接
前面的例子中,我们的动态SQL条件都是作用于当前实体 (Book) 的。接下来我们通过引用关联 (一对一或多对一) 获取关联对象并为之添加过滤条件
多对一关联Book.store
关联到BookStore
实体,让我们为BookStore.name
和BookStore.website
添加动态SQL条件。
- Java
- Kotlin
@Repository
public class BookRepository {
private final JSqlClient sqlClient;
public BookRepository(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}
List<Book> findBooks(
@Nullable String name,
@Nullable BigDecimal minPrice,
@Nullable BigDecimal maxPrice,
@Nullable String storeName,
@Nullable String storeWebsite,
@Nullable Fetcher<Book> fetcher
) {
BookTable table = Tables.BOOK_TABLE;
return sqlClient
.createQuery(table)
.where(table.name().ilikeIf(name))
.where(table.price().betweenIf(minPrice, maxPrice))
.where(table.store().name().ilikeIf(storeName)) ❶
.where(table.store().website().ilikeIf(storeWebsite)) ❷
.select(table.fetch(fetcher))
.execute();
}
}
@Repository
class BookRepository(
private val sqlClient: KSqlClient
) {
fun findBooks(
name: String? = null,
minPrice: BigDecimal? = null,
maxPrice: BigDecimal? = null,
storeName: String? = null,
storeWebsite: String? = null,
fetcher: Fetcher<Book>? = null
): List<Book> =
sqlClient
.createQuery(Book::class) {
where(table.name `ilike?` name)
where(table.price.`between?`(minPrice, maxPrice))
where(table.store.name `ilike?` storeName) ❶
where(table.store.name `ilike?` storeWebsite) ❷
select(table.fetch(table))
}
.execute()
}
Java代码中的路径table.store()
或Kotlin代码中的路径table.store
叫做动态表连接,表示如下SQL逻辑
from BOOK b
/* highlight-next-line */
inner join BOOK_STORE s on b.STORE_ID = s.ID
你也可以使用外连接,Java代码为table.store(JoinType.LEFT)
,kotlinJava代码为table.`storeId?`
。
事实上,假如实体模型更加丰富,可以书写更长的路径,比如table.store().city().province()
。
这里,仅仅做入门示范和快速预览,没必要构建更丰富的实体模型以演示更长的路径,最短表链连接路径table.store()
足够。
-
❶ 当
storeName
既非null也非empty string时- 先通过
Book.store
关联到BookStore
实体 - 再为
BookStore.name
添加SQL条件
- 先通过
-
❷ 当
storeWebsite
既非null也非empty string时- 先通过
Book.store
关联到BookStore
实体 - 再为
BookStore.website
添加SQL条件
- 先通过
忽略无用表连接
如果参数storeName
和storeWebsite
都为null
- Java
- Kotlin
List<Book> books = bookRepository.findBooks(
null,
null,
null,
null, // storeName
null, // storeWebsite
null
);
val books = bookRepository.findBooks()
这会导致❶和❷两处的ilikeIf
/ilike?
无效,进一步导致table.store()
/table.store
被忽略。即,虽然创建了表连接,但未被使用。
如果在DSL中创建了表连接但没有被真正使用,那么表连接将会被自动忽略,最终在生成的SQL中没有对应的SQL连接。
注意:Jimmer没有
JPA
中的join fetch
概念,join的唯一目的是为了被其他SQL表达式引用,所以未被真正使用的join对象会被忽略。
生成的SQL如下
select
tb_1_.ID,
tb_1_.NAME,
tb_1_.EDITION,
tb_1_.PRICE,
tb_1_.STORE_ID
from BOOK tb_1_
// highlight-next-line
// No SQL table joins