在恒定时间内访问复合记录中的列
Accessing columns from a composite record in constant time
我正在从具有多个连接子句的查询中检索记录,使其复合。该记录有大约 20 列,所以我正在寻找一种访问数据的有效方法。我只会查看数据,不会修改任何内容或提交回数据库。
我查看了 jOOQ 提供的两个简短易读的选项:
- 使用
fetchOne()
、 的 Record
实例中的 get(Field<?>)
- 通过
fromMap(Map<String, ?>)
使用来自 fetchOneMap()
的映射创建 jOOQ 生成记录的实例,并使用生成的 getter。
为了尝试找出这些选项的效率,我查看了这些方法的实现方式。如果我没看错的话,get
是线性实现的(通过 field0(Field<?>, FieldOrIndex<U>)
),这将使访问 n
字段的时间复杂度为 O(n^2).
我想也许 fromMap(Map<String, ?>)
可以有效地代替初始化模式生成的记录,因此可以使用生成的 getter。然而,fromMap
最终调用 from(Record)
循环遍历所有字段,同时(在循环内)间接调用 field0
(这是线性的),使得 fromMap
的时间复杂度为 O (n^2) 也是如此。当然,检索 n
列现在的复杂度为 O(n),但较早的初始化造成了瓶颈。
原则上,我的问题是:
有没有一种方法可以通过简单的方法(单个参数,如 get(Field<?>)
或生成的 getter)以某种恒定的方式(因此总体上是线性的)访问复合记录的列数据?还是我只剩下使用 fetchOneMap
中的 Map<String, Object>
, 牺牲了 由于手动转换而导致的一些可读性?
提前致谢!
常见 Record.get(Field<T>)
方法的 O(N)
读取时间复杂度当然很不幸。通过索引访问数据时可以实现更快的访问,例如通过:
Record.get(int)
- Con:无类型安全
- Con:按索引访问容易出错
Record20.value3()
等
- 专业版:最多可输入 22 列
- Con:按索引访问容易出错
Records.mapping(Function20)
等
- 专业版:最多可输入 22 列
- Con:按索引访问容易出错
- 注意:这在映射到DTO/POJO时特别强大,see this manual section about ad-hoc conversion.
- 使用
DefaultRecordMapper
通过反射将数据映射到 DTO / POJO。
- Pro:反射查找缓存在您的
Configuration
实例中,如果您在会话之间共享它,那么每个查询类型只会发生一次批量开销
- Con: 查询和 DTO / POJO 之间的映射没有类型安全
- 使用生成的
TableRecord
类型
- Pro:它们在内部通过索引访问列,但将索引映射到类型安全名称
- Con:您将倾向于总是获取 table 的所有列,这比 much 更糟表现。此外,这种方法不适用于 joins
- 还有更多选择。
假设的理想解决方案是:
- 一个编译器插件,它可以内省你的每一个查询并为那个单一的查询生成一个 DTO / POJO,例如作为 Java 16
record
类型,然后使用基于索引的访问在 jOOQ 记录和 Java 记录之间进行映射。这将像 F# 类型提供程序一样工作。如果这在 Java / jOOQ 中可行,这将是默认选项。
因此,如您所见,这是一种权衡。通过索引访问值时(最多 22 次),您不必放弃类型安全,但就像 JDBC 一样,当您将列添加到行的开头时,基于索引的访问很容易出错,当所有其他列移动 1.
虽然 jOOQ Result
不是处理内存中数据的最佳数据结构,但过去的所有基准测试都表明实际 SQL 查询产生的开销是数量级的比此记录访问造成的开销更重要,即使 运行 对内存中的 H2 数据库进行微不足道的查询也是如此。使用 Map<Field<?>, ?>
的替代内部表示并没有表现得更好,因为 Map
的内部内存开销与 Object[]
相比,以及增加的 CPU 开销 -至少对于小 N
。例如,它可能会更好。 200 列,但是,200 列查询在服务器端产生的开销比映射多几个数量级。
TL;DR:
如果您可以测量 显着的开销,例如使用分析器,那么最快的访问总是通过索引,例如使用 Record.get(int)
or Record.get(int, Class<T>)
。不过,您很可能不会测量任何重要的东西。
我正在从具有多个连接子句的查询中检索记录,使其复合。该记录有大约 20 列,所以我正在寻找一种访问数据的有效方法。我只会查看数据,不会修改任何内容或提交回数据库。
我查看了 jOOQ 提供的两个简短易读的选项:
- 使用
fetchOne()
、 的 - 通过
fromMap(Map<String, ?>)
使用来自fetchOneMap()
的映射创建 jOOQ 生成记录的实例,并使用生成的 getter。
Record
实例中的 get(Field<?>)
为了尝试找出这些选项的效率,我查看了这些方法的实现方式。如果我没看错的话,get
是线性实现的(通过 field0(Field<?>, FieldOrIndex<U>)
),这将使访问 n
字段的时间复杂度为 O(n^2).
我想也许 fromMap(Map<String, ?>)
可以有效地代替初始化模式生成的记录,因此可以使用生成的 getter。然而,fromMap
最终调用 from(Record)
循环遍历所有字段,同时(在循环内)间接调用 field0
(这是线性的),使得 fromMap
的时间复杂度为 O (n^2) 也是如此。当然,检索 n
列现在的复杂度为 O(n),但较早的初始化造成了瓶颈。
原则上,我的问题是:
有没有一种方法可以通过简单的方法(单个参数,如 get(Field<?>)
或生成的 getter)以某种恒定的方式(因此总体上是线性的)访问复合记录的列数据?还是我只剩下使用 fetchOneMap
中的 Map<String, Object>
, 牺牲了 由于手动转换而导致的一些可读性?
提前致谢!
常见 Record.get(Field<T>)
方法的 O(N)
读取时间复杂度当然很不幸。通过索引访问数据时可以实现更快的访问,例如通过:
Record.get(int)
- Con:无类型安全
- Con:按索引访问容易出错
Record20.value3()
等- 专业版:最多可输入 22 列
- Con:按索引访问容易出错
Records.mapping(Function20)
等- 专业版:最多可输入 22 列
- Con:按索引访问容易出错
- 注意:这在映射到DTO/POJO时特别强大,see this manual section about ad-hoc conversion.
- 使用
DefaultRecordMapper
通过反射将数据映射到 DTO / POJO。- Pro:反射查找缓存在您的
Configuration
实例中,如果您在会话之间共享它,那么每个查询类型只会发生一次批量开销 - Con: 查询和 DTO / POJO 之间的映射没有类型安全
- Pro:反射查找缓存在您的
- 使用生成的
TableRecord
类型- Pro:它们在内部通过索引访问列,但将索引映射到类型安全名称
- Con:您将倾向于总是获取 table 的所有列,这比 much 更糟表现。此外,这种方法不适用于 joins
- 还有更多选择。
假设的理想解决方案是:
- 一个编译器插件,它可以内省你的每一个查询并为那个单一的查询生成一个 DTO / POJO,例如作为 Java 16
record
类型,然后使用基于索引的访问在 jOOQ 记录和 Java 记录之间进行映射。这将像 F# 类型提供程序一样工作。如果这在 Java / jOOQ 中可行,这将是默认选项。
因此,如您所见,这是一种权衡。通过索引访问值时(最多 22 次),您不必放弃类型安全,但就像 JDBC 一样,当您将列添加到行的开头时,基于索引的访问很容易出错,当所有其他列移动 1.
虽然 jOOQ Result
不是处理内存中数据的最佳数据结构,但过去的所有基准测试都表明实际 SQL 查询产生的开销是数量级的比此记录访问造成的开销更重要,即使 运行 对内存中的 H2 数据库进行微不足道的查询也是如此。使用 Map<Field<?>, ?>
的替代内部表示并没有表现得更好,因为 Map
的内部内存开销与 Object[]
相比,以及增加的 CPU 开销 -至少对于小 N
。例如,它可能会更好。 200 列,但是,200 列查询在服务器端产生的开销比映射多几个数量级。
TL;DR:
如果您可以测量 显着的开销,例如使用分析器,那么最快的访问总是通过索引,例如使用 Record.get(int)
or Record.get(int, Class<T>)
。不过,您很可能不会测量任何重要的东西。