用jOOQ 3.14的SQL/XML或SQL/JSON支持嵌套集合

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) 调用:

扫描二维码关注公众号,回复: 14422447 查看本文章

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 期待你的反馈。

猜你喜欢

转载自juejin.im/post/7126376415083102216