告别ORM

关于ORM(对象-关系映射)使用的争论持续十年之久。一方面,许多人认为Hibernate和JPA很好的解决了许多问题(尤其是复杂对象的持久化方面)。而另一部分人认为,复杂的映射几乎扼杀了以数据为中心的应用。JPA通过在目标类型上使用硬编码方式的注解来建立标准的、声明式映射规则解决映射问题。我们认为很多的以数据为中心的问题不应该被限制在注解的狭小范围内,而应该使用更好的方式。Java 8通过新的Streams API,可以用很简洁的方式来解决这个问题。

我们通过一个很简单的实例开始,这个例子中的我们使用H2的INFORMATION_SCHEMA来存储表及列。我们将建立一个 Map<String, List<String>>类型的数据结构来包含这些信息。为了使SQL关联简单,我们使用 jOOQ:

public static void main(String[] args) 	throws Exception {
    Class.forName("org.h2.Driver");
    try (Connection c = getConnection(
            "jdbc:h2:~/sql-goodies-with-mapping",
            "sa", "")) {
        // 这条SQL语句在H2 schema中产生表名及列名
        String sql =
            "select table_name, column_name " +
            "from information_schema.columns " +
            "order by " +
                "table_catalog, " +
                "table_schema, " +
                "table_name, " +
                "ordinal_position";
        // 这是使用jOOQ的方式来执行以上的语句。
        // Result类实现了List接口, 这样使得剩下的
        // 步骤更加简单
        Result<Record> result =
        DSL.using(c)
           .fetch(sql)
    }
}

现在我们先建立这个查询,然后我们看如何通过jOOQ的Result生成一个Map<String, List<String>>:

DSL.using(c)
   .fetch(sql)
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),
       mapping(
           r -> r.getValue("COLUMN_NAME"),
           toList()
       )
   ))
   .forEach(
       (table, columns) ->
           System.out.println(table + ": " + columns)
   );

上面的例子产生如下的输出:

1 FUNCTION_COLUMNS: [ALIAS_CATALOG, ALIAS_SCHEMA, ...]
2 CONSTANTS: [CONSTANT_CATALOG, CONSTANT_SCHEMA, ...]
3 SEQUENCES: [SEQUENCE_CATALOG, SEQUENCE_SCHEMA, ...]

这是如何实现的?让我们一步一步的来看这段程序:

DSL.using(c)
   .fetch(sql)
   //这里,我们将一个List转换为一个Stream对象
   .stream()
   //我们将这个Stream元素放到一个新的集合类型中  
   .collect(
        //这个集合是一个分组行成的Map
        groupingBy(
       //分组操作后的组的key是JOOQ记录的TABLE_NAME的值
       r -> r.getValue("TABLE_NAME"),
       //分组的value是通过mapping表达式生成的
       mapping(
           //该表达式主要将每条分组得到jOOQ记录映射到对应COLUMN_NAME值上
           r -> r.getValue("COLUMN_NAME"),
           //然后再将所有的值收集到一个java.util.List中,
           toList()
       )
   ))
   //一旦得到了这个List<String,List<String>>,我们可以很简单的通过如下lambda表达式来使用。
   .forEach(
       (table, columns) ->
           System.out.println(table + ": " + columns)
   );

弄明白了吗?如果第一次看到这些东西的话,肯定觉得有点奇怪。新的类型、大量的匿名函数、以及lamda表达式,开始确实是有点混乱。最好的办法就是用这些东西进行简单的实践直到掌握他们。毕竟整个Streams API相对以前的Java Collections API来说,发生了革命性的变化。

不过好消息是:这是一个最终的、被保留的API。你在练习上花费的每一分钟都是对你将来的一种投资。

注意上面的程序使用如下的静态引入:

import static java.util.stream.Collectors.*;

同时也注意,输出不在是数据库的顺序。这是因为分组后的集合返回的是一个java.util.HashMap。在某些情况下,我们可能更倾向于用java.util.LinkedHashMap来存储这些数据,这样能保证插入集合的顺序:

DSL.using(c)
	   .fetch(sql)
	   .stream()
	   .collect(groupingBy(
	       r -> r.getValue("TABLE_NAME"),
		       // Add this Supplier to the groupingBy
	       // method call
	       LinkedHashMap::new,
	       mapping(
	           r -> r.getValue("COLUMN_NAME"),
	           toList()
	       )
	   ))
	   .forEach(...);

我们继续进行其他有意义的结果转换。想象一下,我们可能需要从上面的方案中生成一条简单的DDL。非常简单。首先,我么需要选择列的数据类型,我们简单的增加一条SQL查询:

String sql =
	    "select " +
	        "table_name, " +
	        "column_name, " +
	        "type_name " + // 增加type列
	    "from information_schema.columns " +
	    "order by " +
	        "table_catalog, " +
	        "table_schema, " +
	        "table_name, " +
	        "ordinal_position";

我也为这个例子引入了一个新的局部类,用以包装name和type属性:

class Column {
    final String name;
    final String type;
      Column(String name, String type) {
        this.name = name;
        this.type = type;
    }
}

现在,我们看一下是怎样改变Streams API的方法调用的:

result
    .stream()
    .collect(groupingBy(
        r -> r.getValue("TABLE_NAME"),
        LinkedHashMap::new,
        mapping(
            // 我们现在使用新的封装的类型来替代原来的COLUMN_NAME
            r -> new Column(
                r.getValue("COLUMN_NAME", String.class),
                r.getValue("TYPE_NAME", String.class)
            ),
            toList()
        )
    ))
    .forEach(
        (table, columns) -> {
            // 仅发出一条创建表格的语句
            System.out.println(
                "CREATE TABLE " + table + " (");
            // 将"Column"类型的指定列转换为字符串,
            // 通过逗号与换行符进行连接。
            System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",\n"))
            );
            System.out.println(");");
        }
    );

输出结果好得不可能更好了!

CREATE TABLE CATALOGS(
  CATALOG_NAME VARCHAR
);
CREATE TABLE COLLATIONS(
  NAME VARCHAR,
  KEY VARCHAR
);
CREATE TABLE COLUMNS(
  TABLE_CATALOG VARCHAR,
  TABLE_SCHEMA VARCHAR,
  TABLE_NAME VARCHAR,
  COLUMN_NAME VARCHAR,
  ORDINAL_POSITION INTEGER,
  COLUMN_DEFAULT VARCHAR,
  IS_NULLABLE VARCHAR,
  DATA_TYPE INTEGER,
  CHARACTER_MAXIMUM_LENGTH INTEGER,
  CHARACTER_OCTET_LENGTH INTEGER,
  NUMERIC_PRECISION INTEGER,
  NUMERIC_PRECISION_RADIX INTEGER,
  NUMERIC_SCALE INTEGER,
  CHARACTER_SET_NAME VARCHAR,
  COLLATION_NAME VARCHAR,
  TYPE_NAME VARCHAR,
  NULLABLE INTEGER,
  IS_COMPUTED BOOLEAN,
  SELECTIVITY INTEGER,
  CHECK_CONSTRAINT VARCHAR,
  SEQUENCE_NAME VARCHAR,
  REMARKS VARCHAR,
  SOURCE_DATA_TYPE SMALLINT
);

激动吗?ORM的时代将要终结了

再次强烈声明:ORM的时代要终结了。为什么?因为使用函数表达式来转换数据集合是软件工程中最强大的概念之一。函数式编程是非常灵活,又极具表现力。它是数据和数据流处理的核心。Java开发者都知道存在函数式编程语言。例如,每个人都曾使用SQL。思考一下,声明一个表资源,然后投影或转换为一个数组流,然后将其作为派生表返回给更高级别的SQL语句或者直接返回给java程序。

如果使用XML的话,我们可以通过XProc pipelining声明一个XSLT来转换XML,然后将结果返回给其他XML实体,如其他的XSL样式表。

Java 8的Streams正式如此。使用SQL和Streams API是数据处理的最有力的概念之一。如果增加JOOQ到其中,你就可以方便的实现对数据库类型安全的访问及查询。想象一下使用jOOQ API写SQL来代替直接使用SQL字符串:

整个方法链可以用一个单独的数据流转换链,像这样:

DSL.using(c)
   .select(
       COLUMNS.TABLE_NAME,
       COLUMNS.COLUMN_NAME,
       COLUMNS.TYPE_NAME
   )
   .from(COLUMNS)
   .orderBy(
       COLUMNS.TABLE_CATALOG,
       COLUMNS.TABLE_SCHEMA,
       COLUMNS.TABLE_NAME,
       COLUMNS.ORDINAL_POSITION
   )
   .fetch()  // jOOQ结束
   .stream() // Streams开始
   .collect(groupingBy(
       r -> r.getValue(COLUMNS.TABLE_NAME),
       LinkedHashMap::new,
       mapping(
           r -> new Column(
               r.getValue(COLUMNS.COLUMN_NAME),
               r.getValue(COLUMNS.TYPE_NAME)
           ),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> {
            // Just emit a CREATE TABLE statement
            System.out.println(
                "CREATE TABLE " + table + " (");
               System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",\n"))
            );
           System.out.println(");");
       }
   );

Java 8 是未来的趋势,使用jOOQ、Java 8及 Streams API,你可以写出强大的数据转换API。我希望你像我们一样兴奋,敬请期待更多的更好的关于Java 8内容的blog。

原文链接: javacodegeeks 翻译: ImportNew.com - paddx
译文链接: http://www.importnew.com/13098.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

5 条评论

  1. xxx 说道:

    首先jOOQ本身就是一个ORM,既然你都用了jOOQ,那什么叫告别ORM?

    Thumb up 0 Thumb down 0

    • fairjm 说道:

      JOOQ不能说是一个ORM 可以说成是SQL Builder之类的 文中只是用了他的一部分功能 他最让人激动的功能就是编译器可以知道很多SQL错误(用它的函数来构建SQL)

      Thumb up 0 Thumb down 0

  2. solq 说道:

    ….底层应该是用MAP做临时保存的,不然怎么分组归类数据

    Thumb up 0 Thumb down 0

  3. solq 说道:

    统计过程完全交给上层调用者处理这是很恐怖的

    Thumb up 0 Thumb down 1

  4. xinwendashibaike 说道:

    stream api确实是很强大,有一部分的工作确实可以不用了

    Thumb up 0 Thumb down 0

跳到底部
返回顶部