ORM已经过时了
最近几十年来,关于ORM究竟还有没有用的争论一直不断。很多人承认Hibernate和JPA确实很好的解决了不少实际的问题(通常是复杂对象的持久化),但有些人认为,对于面向数据的应用而言,复杂的映射关系则有点大材小用了。
JPA通过在目标类型上使用硬编码的注解,来建立标准的声明式的映射规则,进而完成映射关系。但我们认为,很多以数据为中心的应用不应该受限于注解的局限性,而应该通过一种函数式的方式来解决。Java 8的Steam API终于让我们可以用一种简洁的方式来解决这个问题了!
我们先从一个简单的例子开始,这里我们使用H2的INFORMATION\_SCHEMA来查询所有的表及字段。我们专门用一个Map<String, List<String>>类型的数据结构来存储这个信息。我们用
jOOQ来简化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
);
激动吧?看来ORM的时代已经过去了
ORM的时代应该终结了。为什么?因为软件工程里有一个很重要的思想就是使用函数式表达式来进行数据集的转化。函数式编程表达性强,并且非常通用。它是数据及数据流处理的核心。Java开发人员现在也都知道函数式编程,而大家又都用过SQL。相像一下吧。你用SQL来声明表来源,把数据转化成新的元组流,然后要么将它们作为派生表提供给其它更高级的SQL语句来使用,要么将它们交给你的应用程序来处理。
如果你用的是XML的话,你可以使用XSLT来声明XML转化,并通过
XProc管道把结果提供给其它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的内容,敬请收看。
原创文章转载请注明出处:
http://it.deepinmind.com
英文原文链接
分享到:
相关推荐
然而,由于“完全过时了,很可能没用”的描述,我们需要理解这个框架可能已经不再维护,可能不适用于最新的Android版本或者与现代的开发实践有所脱节。尽管如此,我们仍然可以从中学习到ORM的基本原理和实现思路。 ...
Hibernate是一个开源的Java对象关系映射(ORM)框架,它极大地简化了数据库操作,使得开发者能够以面向对象的方式处理数据库。Hibernate通过提供数据持久化的框架,将Java类与数据库表之间的映射关系自动化,从而...
5. **数据持久化策略**:在Java中,除了PMDK,还有其他持久化策略,比如JPA(Java Persistence API)、Hibernate等ORM框架,以及RDBMS(关系数据库管理系统)和NoSQL数据库。比较这些方法的优缺点,了解何时选择PMDK...
Java标准库提供了若干内置注解,如`@Override`用于指示方法覆盖父类方法,`@Deprecated`标记已过时的方法,`@ SuppressWarnings`用于抑制编译器警告。此外,还可以自定义注解,通过`@Retention`设置保留策略(源码...
Ebean ORM是一个轻量级的对象关系映射(ORM)框架,专...不过,需要注意的是,"deprecated"前缀意味着这个插件可能已经过时,开发者应当检查最新的Ebean ORM文档,寻找替代的解决方案,以保证项目的长期稳定性和支持。
5. `@Repeatable`:从Java 8开始引入,允许同一位置重复使用相同的注解。 六、实际应用 1. Spring框架:Spring大量使用注解进行依赖注入、AOP切面、事务管理等,极大地简化了配置。 2. JUnit:测试框架JUnit使用`...
【n-ORM:一个过时的数据访问框架】 n-ORM,作为一个曾经的数据访问框架,它在过去的开发中可能为程序员提供了便利,使得与数据库的交互更为简单。然而,根据给出的标题,这个版本已被官方不推荐使用。这通常意味着...
Java 中的 ORM (Object Relational Mapping) - **定义**:ORM 是一种编程技术,用于将对象模型映射到关系型数据库模型。 - **作用**: - 简化数据库操作,提高开发效率。 - 减少了 SQL 语句的手动编写,降低了...
3. **Java SE 1.8**(也称为Java 8): - **Lambda表达式**:函数式编程的关键特性,简化多参数无副作用的方法。 - **Stream API**:处理集合的新方式,支持串行和并行数据流操作。 - **日期和时间API**:JSR 310...
虽然现在Struts2已经较过时,但了解其工作原理对理解现代框架如Spring MVC仍然有帮助。 9. **JSP (JavaServer Pages)**: JSP是一种动态网页技术,它允许在HTML页面中嵌入Java代码,以实现动态内容的生成。JSP与...
例如,Java 7和8引入了try-with-resources语句和lambda表达式,而Java 9及以后的版本引入了模块系统。因此,对比当前的Java最佳实践,你可以看到编程风格和工具的发展历程。 总的来说,Java2010.jar源代码提供了一...
Java标准库提供了许多内置注解,如`@Deprecated`(表示某个API已过时)和`@ SuppressWarnings`(抑制编译器警告)。 注解的分类主要有三种: 1. **保留注解**:这些注解在编译时被处理,并且不会在字节码中保留。...
Hibernate是一种流行的Java ORM框架,它简化了JDBC的操作,实现了对象与数据库表之间的映射。通过配置文件或注解,开发者可以定义实体类及其与数据库表的关系,从而实现对象的CRUD(Create, Read, Update, Delete)...
在Java开发中,注解已经成为很多框架的关键组成部分,比如Spring、Hibernate等。在Spring中,`@Component`、`@Service`、`@Repository`和`@Controller`等注解用于组件扫描和依赖注入。在Hibernate中,`@Entity`、`@...
这种组件通常会提供一套面向对象的接口,将数据库表映射为Java对象,通过这些对象进行数据操作,这就是所谓的对象关系映射(ORM)技术。 "supply-dao.chm" 文件可能是一个帮助文档,包含了该组件的详细使用指南、...
2. **Hibernate ORM**: Hibernate是一个流行的Java对象关系映射(ORM)工具,它允许开发者用Java对象来操作数据库。Hibernate 3.3是一个稳定版本,支持HQL(Hibernate查询语言)和 Criteria API,简化了数据库操作。 ...
由于时间和空间的限制,这份资料剔除了过时的技术点和在实际笔试中出现概率极低的题目,确保了内容的实用性和时效性。 1. **Java基础** - 类与对象:深入理解面向对象编程,包括封装、继承、多态。 - 内存管理:...
问题 8:谈谈Java内存模型(JMM)和可见性、原子性、有序性问题。答案:Java内存模型定义了线程之间共享变量的访问规则,确保多线程环境下的正确执行。可见性是指一个线程修改了共享变量,其他线程能立即看到变化。...
NyBatis数据映射器框架介绍NyBatis是用于关系数据库的持久数据映射器框架。 NyBatis的名称是Next Generation of Your Batis... 我是在使用Java 8的情况下开发它的,因此NyBatis仅支持Java 8 。 NyBatis也可以在春天,st
J2EE(Java 2 Platform, Enterprise Edition)是一个已过时的标准,后来演变为Java EE。J2EE定义了一组用于开发和部署企业级应用的服务和技术规范,包括EJB、JSP、Servlets、JMS等。 #### 十七、SmartClient Ajax...