Java 8:ORM已经过时了

[b]ORM已经过时了[/b]

最近几十年来,关于ORM究竟还有没有用的争论一直不断。很多人承认Hibernate和JPA确实很好的解决了不少实际的问题(通常是复杂对象的持久化),但有些人认为,对于面向数据的应用而言,复杂的映射关系则有点大材小用了。

JPA通过在目标类型上使用硬编码的注解,来建立标准的声明式的映射规则,进而完成映射关系。但我们认为,很多以数据为中心的应用不应该受限于注解的局限性,而应该通过一种函数式的方式来解决。Java 8的Steam API终于让我们可以用一种简洁的方式来解决这个问题了!

我们先从一个简单的例子开始,这里我们使用H2的INFORMATION\_SCHEMA来查询所有的表及字段。我们专门用一个Map<String, List<String>>类型的数据结构来存储这个信息。我们用[url=http://blog.jooq.org]jOOQ[/url]来简化SQL的交互。下面来做一下准备工作:


public static void main(String[] args)
throws Exception {
Class.forName("org.h2.Driver");
try (Connection c = getConnection(
"jdbc:h2:~/sql-goodies-with-mapping",
"sa", "")) {

// This SQL statement produces all table
// names and column names in the H2 schema
String sql =
"select table_name, column_name " +
"from information_schema.columns " +
"order by " +
"table_catalog, " +
"table_schema, " +
"table_name, " +
"ordinal_position";

// This is jOOQ's way of executing the above
// statement. Result implements List, which
// makes subsequent steps much easier
Result<Record> result =
DSL.using(c)
.fetch(sql)
}
}


我们已经执行完查询了,现在来看下如何从查询结果中生成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)
);


上述程序会输出如下的结果:


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


这是如何工作的?我们来一步一步的分析下:


DSL.using(c)
.fetch(sql)
//这里我们把一个列表转化成一个Stream
.stream()
// 把Stream中的元素汇集成一个新的类型
.collect(
// 收集器是一个分组操作,会生成一个map
groupingBy(
// 分组操作的key是jOOQ记录的TABLE_NAME字段
r -> r.getValue("TABLE_NAME"),
// 分组操作的值是用这个映射表达式来生成的
mapping(
// 实际上是映射到每条jOOQ记录的COLUMN_NAME字段上
r -> r.getValue("COLUMN_NAME"),
// 然后将所有的值收集到一个java.util.List中
toList()
)
))
// 一旦拿到了<String, List<String>>列表,
// 就可以很容易通过lambda表达式来使用它了
.forEach(
(table, columns) ->
System.out.println(table + ": " + columns)
);


看明白了吗?第一次使用这些方法的话可能的确需要费点工夫。新的类型,泛型,lambda表达式,这些东西加到一起的话,第一次看到的话的确会有些困惑。最好的办法就是不断的去实践直到你完全掌握了。毕竟来说,和Java Collections API比起来的话,Stream API可是一次革命性的进步。

不过有个好消息就是,这些方法已经确定就是这样,不会再变了。你的每一次实践都是对未来的一次投资。

注意上述的程序用到了如下的静态导入语句:


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


同样还应该注意到,输出的结果和数据库返回的顺序是不一样的。这是因为groupingBy收集器返回的是一个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(...);


我们还可以用另一种转化结果的方法。想像一下,我们将要从上面的schema中生成DDL。这很简单。首先,我们需要知道每一列的数据类型。只需要把它加到我们的SQL查询语句中就好了:


String sql =
"select " +
"table_name, " +
"column_name, " +
"type_name " + // Add the column type
"from information_schema.columns " +
"order by " +
"table_catalog, " +
"table_schema, " +
"table_name, " +
"ordinal_position";


这个例子中还引入了一个新的局部类,用来封装名字和类型属性:


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(

// We now collect this new wrapper type
// instead of just the COLUMN_NAME
r -> new Column(
r.getValue("COLUMN_NAME", String.class),
r.getValue("TYPE_NAME", String.class)
),
toList()
)
))
.forEach(
(table, columns) -> {

// Just emit a CREATE TABLE statement
System.out.println(
"CREATE TABLE " + table + " (");

// Map each "Column" type into a String
// containing the column specification,
// and join them using comma and
// newline. Done!
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
);


[b]激动吧?看来ORM的时代已经过去了[/b]

ORM的时代应该终结了。为什么?因为软件工程里有一个很重要的思想就是使用函数式表达式来进行数据集的转化。函数式编程表达性强,并且非常通用。它是数据及数据流处理的核心。Java开发人员现在也都知道函数式编程,而大家又都用过SQL。相像一下吧。你用SQL来声明表来源,把数据转化成新的元组流,然后要么将它们作为派生表提供给其它更高级的SQL语句来使用,要么将它们交给你的应用程序来处理。

如果你用的是XML的话,你可以使用XSLT来声明XML转化,并通过[url=http://en.wikipedia.org/wiki/XProc]XProc[/url]管道把结果提供给其它XML处理器,比如说另一个XSL表格。

使用SQL和Streams API是数据处理里面一个很重要的概念。如果你使用了jOOQ的话,你还可以进行类型安全的数据库访问及查询。想像一下如何使用jOOQ的流API来改写前面的代码,而不是直接使用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 ends here
.stream() // Streams start here
.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 + " (");

// Map each "Column" type into a String
// containing the column specification,
// and join them using comma and
// newline. Done!
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的内容,敬请收看。


原创文章转载请注明出处:[url=http://it.deepinmind.com]http://it.deepinmind.com[/url]

[url=http://blog.jooq.org/2014/04/11/java-8-friday-no-more-need-for-orms/]英文原文链接[/url]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
# 图书管理系统说明V2.0 ### 项目介绍及再版说明(2016-01-24) 其实一开始做这个小项目在2014年的9到11月,当时是作为加入Pureweber开发组的大作业完成的。虽然用了两个月的时间,但是做出的东西还是有很多的缺陷。前些日子在整理GitHub时又想起来了这个项目,记得曾经还在Django中国社区里安利初学者来读代码什么的。但是想想里面还有很多不规范的代码和一些很糟糕的写法就感觉很蛋疼。本来想直接删除了好了,但是后来想想,何不花点时间把这个小项目重构一下,就可以给以后学习Django的同学一个完整的项目参考,就可以代替了一般培训课程或者老师上课PPT里那种陈旧或者不完整的例子。 于是一共花费了大约两天时间对项目进行了重构,主要做了以下方面的工作: * 将Django的版本更新到了最新的1.9.1。 * 修改了原先项目中不规范的格式、变量名等。 * 更改了项目的目录结构,换成了Django官方推荐的目录结构模式。 * 将原来缺失的文件上传保存部分补充完整了。 * 更改了用户部分的代码,将原来手动设置session的方式去掉了,替换为Django用户模块默认的登录态保存方式. * 修复了注册用户时用户提交空密码可能造成的安全漏洞。 * 做了对python3的支持。改动不多。 ### 项目所涉及的和Django相关的功能 项目的目的是为了给Django的初学者一个完整项目的参考案例,所以尽可能多的选择了初学者常用的方法处理一些问题,比如在视图的处理上选择了视图处理函数,而不是更好用的视图处理类。在参数传递上只使用了标准的POST和GET的方式传参,而没有使用url地址中提取参数的办法。该项目中主要涉及到的Django框架相关的内容有: * Models模型字段用法,外键关系用法。 [文档](https://docs.djangoproject.com/en/1.9/topics/db/models/) * 使用ORM进行数据库查询。 [文档](https://docs.djangoproject.com/en/1.9/topics/db/queries/) * Urls配置文件的写法,Urls命名与反向查询。 [文档](https://docs.djangoproject.com/en/1.9/topics/http/urls/) * Views视图处理函数。 [文档](https://docs.djangoproject.com/en/1.9/topics/http/views/) * Templates模板。 [文档](https://docs.djangoproject.com/en/1.9/ref/templates/language/) * 在admin站点中注册模型。 [文档](https://docs.djangoproject.com/en/1.9/ref/contrib/admin/) * Django自带用户模块的注册和登录。 [文档](https://docs.djangoproject.com/en/1.9/topics/auth/default/) * 对Django自带的用户模块进行拓展。 [文档](https://docs.djangoproject.com/en/1.9/topics/auth/customizing/) * 静态文件处理。 [文档](https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/) * 还有一大堆其他的…… ### 系统说明 * 本系统使用Python的Django框架搭建。 * 前端部分使用bootstrap。 ### 运行说明 * 请参考Django官方文档[下载](https://www.djangoproject.com/download/)Django1.711.9.1版。 * 请按照Django官方文档[安装](https://docs.djangoproject.com/en/1.9/intro/install/)Django。 * 如果是水果电脑。。。请额外安装[PIL](http://www.pythonware.com/products/pil/)库。 * 通过终端进入项目文件夹。 * 在终端中执行`python manage.py runserver`命令即可运行本地开发服务器。 * 在浏览器里访问`http://127.0.0.1:8000`即可查看该网站。 ### 功能实现 * 实现了用户权限相关的基本操作(注册、登陆、修改密码、注销) * 实现了用户分级(普通用户与管理员用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值