当前位置:网站首页>在jOOQ中获取数据的多种不同方式
在jOOQ中获取数据的多种不同方式
2022-07-30 19:33:00 【JAVAQXQ】
jOOQ的API是关于方便的,因此,像 fetch()
这样的重要操作(最重要的操作?)也必须附带方便。获取数据的默认方式是这样的:
Result<Record1<String>> result = ctx.select(BOOK.TITLE) .from(BOOK) .fetch(); for (Record1<String> record : result) { // ... } 复制代码
它将整个结果集取到内存中,并急切地关闭底层的JDBC资源。但是我们还有什么其他的选择呢?
可迭代的获取方式
在上面的例子中, fetch()
的调用并不是严格意义上的必要。 [ResultQuery<R>](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html)
类型 方便地扩展了 Iterable<R> , 这意味着对 ResultQuery.iterator()
的调用也将执行该查询。这主要可以通过两种方式实现。
外部迭代
for (Record1<String> record : ctx .select(BOOK.TITLE) .from(BOOK) ) { // ... } 复制代码
这特别好,因为它感觉就像PL/SQL或PL/pgSQL的 FOR
循环,用于隐式游标:
FOR rec IN (SELECT book.title FROM book) LOOP -- ... END LOOP; 复制代码
不过这仍然要把整个结果集取到内存中,因为在Java中没有一个 for-with-resources
语法,它把 foreach
语法和 try-with-resources
语法结合起来。
内部迭代
JDK 8增加了 Iterable::forEach
,jOOQ的 [ResultQuery](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html)
继承了,所以你也可以这样做。
ctx.select(BOOK.TITLE) .from(BOOK) .forEach(record -> { // ... }); 复制代码
两者是完全等价的。
单一记录的获取
如果你确定你只取一个单一的值,不需要具体化一个列表。只需使用以下方法之一。鉴于这个查询:
ResultQuery<Record1<String>> query = ctx .select(BOOK.TITLE) .from(BOOK) .where(BOOK.ID.eq(1)); 复制代码
你现在可以
取一个可空的记录
这就获取了一个可空的记录,也就是说,如果没有找到记录,就会产生 null
。如果有一条以上的记录,就会产生一个 [TooManyRowsException](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/exception/TooManyRowsException.html)
会被抛出。
Record1<String> r = query.fetchOne(); 复制代码
取一个可选择的记录
null
自行车棚是真实的,那么为什么在使用jOOQ的时候不让你也骑自行车呢?与上述完全等同,但使用不同的风格,是这样的。
Optional<Record1<String>> r = query.fetchOptional(); 复制代码
取出一条记录
如果你知道你的查询正好产生一条记录,在jOOQ的API中有一个术语 "single",意思是正好一条:
Record1<String> r = query.fetchSingle(); println(r.toString()); // NPE safe! 复制代码
r.toString()
NullPointerException
的调用是安全的,因为如果记录不存在,a [NoDataFoundException](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/exception/NoDataFoundException.html)
会被抛出。
资源性获取
默认情况下是急切地把所有东西都取到内存中,因为这可能比JDBC默认的一直管理资源(包括嵌套集合、lobs等)对大多数应用更有用。从上面的 Iterator
fetching例子中可以看出,考虑到用户甚至不能通过jOOQ访问资源(默认情况下),这往往是唯一可能不产生意外资源泄露的方法。
但这并不 总是 正确的选择,所以如果你的数据集很大的话,你可以在获取数据的同时保持开放底层JDBC资源。有2种主要方式。
强制性的
通过调用 [ResultQuery.fetchLazy()](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html#fetchLazy())
,你就创建了一个 [Cursor<R>](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Cursor.html)
,它包装了底层的JDBC ResultSet
,因此,应该包含在一个 try-with-resources
语句中。
try (Cursor<Record1<String>> cursor = ctx .select(BOOK.TITLE) .from(BOOK) .fetchLazy() ) { for (Record1<String> record : cursor) { // ... } } 复制代码
Cursor<R>
仍然扩展了 Iterable<R>
,但你也可以从它那里手动获取记录,例如:
Record record; while ((record = cursor.fetchNext()) != null) { // ... } 复制代码
功能性的
如果 Stream
API更像你想处理的数据,只要调用 [ResultQuery.fetchStream()](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html#fetchStream())
来代替,那么(但别忘了也要用 try-with-resources
来包装!):
try (Stream<Record1<String>> stream = ctx .select(BOOK.TITLE) .from(BOOK) .fetchStream() ) { stream.forEach(record -> { // ... }); } 复制代码
或者,使用 Stream::map
、 Stream::reduce
,或者其他什么。遗憾的是, Stream
API并不是自动关闭的。虽然这样实现API是可能的,但它的 "逃生舱",如 Stream.iterator()
,仍然会阻止自动关闭的行为(至少,除非有更多的功能被引入,如 AutoCloseableIterator
,或其他什么)。
所以,你必须用 try-with-resources
语句打破你的流畅管道。
功能性的,但不是资源性的
当然,你总是可以先调用 fetch()
,然后再调用stream,以便直接从你的内存中流转数据。如果资源性并不重要(即对性能的影响可以忽略不计,因为结果集并不大),你可以这样写:
ctx.select(BOOK.TITLE) .from(BOOK) .fetch() .stream() .forEach(record -> { // ... }); 复制代码
或者使用 Stream::map
, Stream::reduce
, 或其他什么方式
采集器获取
从jOOQ 3.11版本开始,无论是 [ResultQuery::collect](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html#collect(java.util.stream.Collector))
和 [Cursor::collect](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Cursor.html#collect(java.util.stream.Collector))
已经被添加进来了。JDK Collector
API是非常强大的。它并没有得到应有的关注(在 Stream
API之外)。在我看来,应该有一个 Iterable::collect
方法,因为在任何集合上重新使用 Collector
类型是有意义的,例如:
Set<String> s = Set.of(1, 2, 3); List<String> l = s.collect(Collectors.toList()); 复制代码
为什么不呢? Collector
有点像 Stream
API本身的对偶。这些操作不是以流水线的语法组成的,而是以嵌套的语法组成的。除此以外,至少对我来说,它感觉非常相似。
就jOOQ而言,它们非常强大。jOOQ提供了一些有用的开箱即用的收集器,在 [Records](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Records.html)
.让我展示一下 [Records.intoMap()](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Records.html#intoMap())
的例子,它有这个重载:
<K,V,R extends Record2<K,V>> Collector<R,?,Map<K,V>> intoMap() 复制代码
这里有趣的一点是,它捕获了一个 Record2
类型的类型,作为结果映射的键和值类型。一个简单的通用技巧,以确保它只在你正好投射2列的情况下工作,比如说:
Map<Integer, String> books = ctx.select(BOOK.ID, BOOK.TITLE) .from(BOOK) .collect(Records.intoMap()); 复制代码
这完全是类型安全的。你不能投射3列,或者由于所有这些泛型而投射错误的列类型。这比直接在 ResultQuery
API上提供的等价物更方便,在那里你必须重复投影列的表达式:
Map<Integer, String> books = ctx.select(BOOK.ID, BOOK.TITLE) .from(BOOK) .fetchMap(BOOK.ID, BOOK.TITLE); 复制代码
通过 ResultQuery::collect
和 Cursor::collect
API,你可以使用任何任意的收集器,包括你自己的收集器,这真的是非常强大的!这也是为什么你可以使用 。另外,它还消除了对中间的 Result
数据结构的需要,所以它不必把所有的东西都取到内存中(当然,除非你的 Collector
反正是这样做)。
收集器在收集 MULTISET
嵌套集合时特别有用。这里已经给出了一个例子, 一个嵌套的集合也被映射到这样的 Map<K, V> 。
反应式获取
从jOOQ 3.15开始,R2DBC得到了支持 。这意味着 [ResultQuery<R>](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html)
现在也是一个反应式流 Publisher<R>
(同时支持 reactive-streams
API和JDK 9 Flow
API,以提高互操作性)。
所以,只要选择你最喜欢的反应式流API,比如reactor,然后像这样反应式地流取jOOQ的结果集:
Flux<Record1<String>> flux = Flux.from(ctx .select(BOOK.TITLE) .from(BOOK) ); 复制代码
许多获取
最后但并非最不重要的是,在极少数情况下,你的查询会产生一个以上的结果集。这在SQL Server和相关的RDBMS中曾经很流行,存储过程可以产生游标。MySQL和Oracle也有这个功能。比如说:
Results results = ctx.fetch("sp_help"); for (Result<?> result : results) { for (Record record : result) { // ... } } 复制代码
标准的 foreach
循环只会迭代结果,但你也可以使用下面的方法访问交错的行数 [Results.resultsOrRows()](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Results.html#resultsOrRows())
如果你对这个也感兴趣的话。
总结
方便和开发者的用户体验是jOOQ的API设计的核心。像任何好的集合API一样,jOOQ提供了各种可组合的基元,允许更有效地将SQL整合到你的应用程序中。
SQL只是对数据结构的描述。jOOQ帮助在JVM上以一种类型安全的方式描述该数据结构。以同样类型安全的方式进行进一步处理是很自然的,就像我们习惯于从JDK自己的集合API或第三方(如 jOOλ 、 vavr 、 streamex 等)获得的那样。
边栏推荐
- MySQL sub-database sub-table
- 跨进程启动后台服务
- Talking about Contrastive Learning (Contrastive Learning) the first bullet
- Linux download and install mysql5.7 version tutorial the most complete and detailed explanation
- LeetCode 0952. Calculate Maximum Component Size by Common Factor: Mapping / Union Search
- How architects grow
- 启动前台Activity
- 【flink】报错整理 Could not instantiate the executor. Make sure a planner module is on the classpath
- HCIP --- 企业网的三层架构
- coming!Dongfang Selection brings goods to the live broadcast of Longjiang agricultural products
猜你喜欢
VBA 运行时错误‘-2147217900(80040e14):自动化(Automation)错误
Alibaba Cloud Martial Arts Headline Event Sharing
MySQl数据库————DQL数据查询语言
Download Win11 how to change the default path?Download Win11 change the default path method
数据库索引:索引并不是万能药
[PyTorchVideo Tutorial 01] Quickly implement video action recognition
DCM 中间件家族迎来新成员
Spark学习:用spark实现ETL
MindSpore:【模型训练】【mindinsight】timeline的时间和实际用时相差很远
已删除
随机推荐
MySQL分库分表
MySQL夺命10问,你能坚持到第几问?
ERROR 1045 (28000) Access denied for user ‘root‘@‘localhost‘解决方法
[flink] Error finishing Could not instantiate the executor. Make sure a planner module is on the classpath
Range.CopyFromRecordset 方法 (Excel)
coming!Dongfang Selection brings goods to the live broadcast of Longjiang agricultural products
Install Mysql5.7 under Linux, super detailed and complete tutorial, and cloud mysql connection
又一家公司面试的内容
MySQL复制表结构、表数据的方法
MindSpore:【resnet_thor模型】尝试运行resnet_thor时报Could not convert to
试写C语言三子棋
Listen to the boot broadcast
Win11如何更改默认下载路径?Win11更改默认下载路径的方法
What is a RESTful API?
Alibaba Cloud Martial Arts Headline Event Sharing
HCIP --- 企业网的三层架构
.eslintrc.js for musicApp
DCM 中间件家族迎来新成员
MySQL database - DQL data query language
阿里面试官:给我描述一下缓存击穿的现象,并说说你的解决思路?