ORM的主要功能之一是M as in Mapping。像jOOQ这样的库有助于将平面或嵌套的数据库记录自动映射到与SQL结果集结构相同的Java类上。 假设PostgreSQL的INFORMATION_SCHEMA
(使用jOOQ-meta模块生成的代码),以下情况在jOOQ中一直是可能的:
class Column {
String tableSchema;
String tableName;
String columnName;
}
for (Column c :
ctx.select(
COLUMNS.TABLE_SCHEMA,
COLUMNS.TABLE_NAME,
COLUMNS.COLUMN_NAME)
.from(COLUMNS)
.where(COLUMNS.TABLE_NAME.eq("t_author"))
.orderBy(COLUMNS.ORDINAL_POSITION)
.fetchInto(Column.class))
System.out.println(
c.tableSchema + "." + c.tableName + "." + c.columnName
);
上述结果如下:
public.t_author.id
public.t_author.first_name
public.t_author.last_name
public.t_author.date_of_birth
public.t_author.year_of_birth
public.t_author.address
映射是直接的,在jOOQ的 DefaultRecordMapper
.
嵌套式映射
我们有一段时间提供了一个不太为人所知的功能,即使用点符号来模拟将记录嵌套到嵌套的Java类中。假设你想在你的列和其他地方使用一个可重复使用的数据类型描述:
class Type {
String name;
int precision;
int scale;
int length;
}
class Column {
String tableSchema;
String tableName;
String columnName;
Type type;
}
你现在可以写这样的查询,你将使用点符号把一些列别名为type.name
,例如(可以有几个嵌套层次):
for (Column c :
ctx.select(
COLUMNS.TABLE_SCHEMA,
COLUMNS.TABLE_NAME,
COLUMNS.COLUMN_NAME,
COLUMNS.DATA_TYPE.as("type.name"),
COLUMNS.NUMERIC_PRECISION.as("type.precision"),
COLUMNS.NUMERIC_SCALE.as("type.scale"),
COLUMNS.CHARACTER_MAXIMUM_LENGTH.as("type.length")
)
.from(COLUMNS)
.where(COLUMNS.TABLE_NAME.eq("t_author"))
.orderBy(COLUMNS.ORDINAL_POSITION)
.fetchInto(Column.class))
System.out.println(String.format(
"%1$-30s: %2$s",
c.tableSchema + "." + c.tableName + "." + c.columnName,
c.type.name + (c.type.precision != 0
? "(" + c.type.precision + ", " + c.type.scale + ")"
: c.type.length != 0
? "(" + c.type.length + ")"
: "")
));
以上将打印:
public.t_author.id : integer(32, 0)
public.t_author.first_name : character varying(50)
public.t_author.last_name : character varying(50)
public.t_author.date_of_birth : date
public.t_author.year_of_birth : integer(32, 0)
public.t_author.address : USER-DEFINED
使用XML或JSON
使用XML或JSON,从jOOQ 3.14开始,你也可以在你的结果集映射中非常容易地嵌套集合。首先,让我们再看看如何使用jOOQ的JSON查询,例如,找到每个表的所有列:
for (Record1<JSON> record :
ctx.select(
jsonObject(
key("tableSchema").value(COLUMNS.TABLE_SCHEMA),
key("tableName").value(COLUMNS.TABLE_NAME),
key("columns").value(jsonArrayAgg(
jsonObject(
key("columnName").value(COLUMNS.COLUMN_NAME),
key("type").value(jsonObject(
"name", COLUMNS.DATA_TYPE)
)
)
).orderBy(COLUMNS.ORDINAL_POSITION))
)
)
.from(COLUMNS)
.where(COLUMNS.TABLE_NAME.in("t_author", "t_book"))
.groupBy(COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME)
.orderBy(COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME)
.fetch())
System.out.println(record.value1());
返回的JSON文档如下:
{
"tableSchema": "public",
"tableName": "t_author",
"columns": [{
"columnName": "id",
"type": {"name": "integer"}
}, {
"columnName": "first_name",
"type": {"name": "character varying"}
}, {...}]
}
{
"tableSchema": "public",
"tableName": "t_book",
"columns": [{...}, ...]
}
这已经很了不起了,不是吗?我们以前在这里和这里都写过关于这个的博客。从jOOQ 3.14开始,你可以去掉所有其他的中间件和映射什么的,使用标准的SQL/XML或SQL/JSON API直接从你的数据库产生你的XML或JSON文档
但这还不是全部!
也许,你实际上不需要JSON文档,你只是想用JSON来允许嵌套数据结构,把它们映射回Java。 这些嵌套的Java类呢:
public static class Type {
public String name;
}
public static class Column {
public String columnName;
public Type type;
}
public static class Table {
public String tableSchema;
public String tableName;
public List<Column> columns;
}
假设你的classpath上有gson或Jackson或JAXB(或者你直接配置了它们),你可以写出和以前完全一样的查询,并使用jOOQ的 DefaultRecordMapper
使用fetchInto(Table.class)
调用:
for (Table t :
ctx.select(
jsonObject(
key("tableSchema").value(COLUMNS.TABLE_SCHEMA),
key("tableName").value(COLUMNS.TABLE_NAME),
key("columns").value(jsonArrayAgg(
jsonObject(
key("columnName").value(COLUMNS.COLUMN_NAME),
key("type").value(jsonObject(
"name", COLUMNS.DATA_TYPE)
)
)
).orderBy(COLUMNS.ORDINAL_POSITION))
)
)
.from(COLUMNS)
.where(COLUMNS.TABLE_NAME.in("t_author", "t_book"))
.groupBy(COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME)
.orderBy(COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME)
.fetchInto(Table.class))
System.out.println(t.tableName + ":\n" + t.columns
.stream()
.map(c -> c.columnName + " (" + c.type.name + ")")
.collect(joining("\n ")));
输出结果是:
t_author:
id (integer)
first_name (character varying)
last_name (character varying)
date_of_birth (date)
year_of_birth (integer)
address (USER-DEFINED)
t_book:
id (integer)
author_id (integer)
co_author_id (integer)
details_id (integer)
title (character varying)
published_in (integer)
language_id (integer)
content_text (text)
content_pdf (bytea)
status (USER-DEFINED)
rec_version (integer)
rec_timestamp (timestamp without time zone)
没有连接魔法。没有cartesian product。没有重复数据删除。只有SQL原生的嵌套集合,使用直观的、声明性的方法来创建文档数据结构,并结合SQL的通常的美妙之处。
在没有jOOQ DSL的情况下使用它
当然,这也可以在没有jOOQ API的情况下工作,例如使用我们的分析器。请看我们的翻译工具。插入这个本地SQL的美丽:
SELECT
json_object(
KEY 'tableSchema' VALUE columns.table_schema,
KEY 'tableName' VALUE columns.table_name,
KEY 'columns' VALUE json_arrayagg(
json_object(
KEY 'columnName' VALUE columns.column_name,
KEY 'type' VALUE json_object(
KEY 'name' VALUE columns.data_type
)
)
)
)
FROM columns
WHERE columns.table_name IN ('t_author', 't_book')
GROUP BY columns.table_schema, columns.table_name
ORDER BY columns.table_schema, columns.table_name
而且,因为SQL的匿名性和翻译的魔鬼在细节上,所以要取出供应商的特定版本,例如PostgreSQL的:
SELECT json_build_object(
'tableSchema', columns.table_schema,
'tableName', columns.table_name,
'columns', json_agg(json_build_object(
'columnName', columns.column_name,
'type', json_build_object('name', columns.data_type)
))
)
FROM columns
WHERE columns.table_name IN (
't_author', 't_book'
)
GROUP BY
columns.table_schema,
columns.table_name
ORDER BY
columns.table_schema,
columns.table_name
你可能需要运行这个,之前:
SET search_path = 'information_schema'
总结
对于这个改变游戏规则的功能,我们已经等得太久了。我真的认为这种方法将改变我们在未来对ORM的看法。数据库优先的方法,我们可以使用SQL,而且只能使用SQL,将SQL数据映射到任何层次的数据结构上,这是非常令人感动的。 在jOOQ方面,我们还远远没有完成。如果我们能从其他类型的元数据中为你自动生成一些JSON文档声明呢?如果你能自己做这些呢?例如,将GraphQL规范映射到基于jOOQ API的JSON查询?在所有支持这些功能的SQL方言中!将嵌套数据结构从SQL映射到任何客户端、XML、JSON、对象的前景是光明的。 jOOQ 3.14即将发布,并将在未来2周内发布。你已经可以从github上构建它:https://github.com/jOOQ/jOOQ,或者如果你有许可证,可以从这里下载夜间构建:https://www.jooq.org/download/versions 期待你的反馈。