日期:2014-05-16  浏览次数:20571 次

Java 8 彻底改变数据库访问
摘要: Java 8终于到来了! 经过几年的等待, java程序员终于能在java中得到函数式编程的支持了. 函数式编程的支持能流程化现有的代码并且为java提供强大的能力.在这些新特性中最瞩目的是java程序员对数据库的操作方式.函数式 ...

Java 8终于到来了! 经过几年的等待, java程序员终于能在java中得到函数式编程的支持了. 函数式编程的支持能流程化现有的代码并且为java提供强大的能力.在这些新特性中最瞩目的是java程序员对数据库的操作方式.函数式编程带来了令人激动的简便高效的数据库API. Java 8 将会支持可与C#, LINQ等语言竞争的新的数据库访问方式.
处理数据的函数式方式

Java 8 不仅仅添加了函数式支持,它也通过新的函数式处理数据的方式扩展了集合(Collection)类. 而通常情况下java处理大量数据时需要大量的循环和迭代器.

例如, 假设你有一个存储客户(Customer)对象的collection:

Collection<Customer> customers;
如果你只对来自Belgium的客户感兴趣, 你将不得不迭代所有的customer对象并只保存你需要的.
Collection<Customer> belgians = new ArrayList<>();
for (Customer c : customers) {
if (c.getCountry().equals("Belgium"))
belgians.add(c);
}
这不仅花费了5行代码,而且它也不怎么抽象.假使你有1千万个对象时会怎样呢?你会通过两个线程并发过滤所有对象来提速么?那你将不得不使用大量危险的多线程代码来重写所有代码.
而通过Java 8,仅仅只需要一行代码就能实现相同的功能.通过对函数式编程的支持, Java 8 能让你只写一个函数表明你对哪些客户(对象)感兴趣然后使用那个函数对集合做过滤就可以了. Java 8 的新 Steams API 支持你这样做:
customers.stream().filter(
c -> c.getCountry().equals("Belgium")
);
上面Java 8 版本的代码不仅更短,而且更容易理解.它几乎没有什么 陈词滥调(循环或迭代器等).代码调用了filter()方法,那很明显这段代码是用来过滤客户(对象)的.你不需要再把时间浪费在解读循环中的代码来理解它在对它的数据做什么.
假使你想并发执行这段代码该怎么办呢?你只需使用另一个类型的stream
customers.parallelStream().filter(
c -> c.getCountry().equals("Belgium")
);
更另人激动的是这种函数式风格的代码也同样适用于数据库

在数据库上使用函数式方式

传统上来说, 程序员需要用特殊数据库查询语句去访问数据库的数据. 例如,下面就是用 JDBC 代码去查找来自Belgium的客户:
PreparedStatement s = con.prepareStatement(
"SELECT * "
+ "FROM Customer C "
+ "WHERE C.Country = ? ");
s.setString(1, "Belgium");
ResultSet rs = s.executeQuery();
大部分这些代码都是字符串, 这样会使编译器不能发现错误而且这草率的代码会导致安全问题. 还有这些大量的样板代码使得写数据访问代码变得十分冗余. 一些工具例如 jOOQ ,通过使用特殊的java库去提供数据库查询语言可以解决错误检查和安全问题。 或者使用对象关系映射工具可以免去大量的无趣的代码,可它们只能用在通用访问查询, 如果需要复杂的查询,还是需要用特殊的数据库查询语言。
使用Java 8,借助流式API就可以用函数式方式去查询数据库了。例如, Jinq 是一个开源的项目,它探索怎样的未来数据库API可以令函数式编程成为可能。这里就是一个使用Jinq的数据库查询:
customers.where(
c -> c.getCountry().equals("Belgium")
);
这代码几乎跟跟使用流式API的代码一样. 事实上,未来的Jinq版本可以让你用流式API直接写数据库查询。 当代码运行的时候,Jinq将自动翻译成数据库查询代码,正如之前JDBC查询一样。

这样的话,就算没有学过一些新的数据库查询语言,你也可以写出有效率的数据库查询。你可以用同样样式的代码用在java集合上。你也不需要特殊的java编译器或者虚拟机。所有的代码编译和运行在普通的java 8 JDK上。如果你的代码有错误,编译器将找出它们并且报告给你,就像普通的java代码。
Jinq 支持跟SQL92一样的复杂查询. Selection(选择), projection(投影), joins(连接), 和子查询 它都支持。翻译java代码成数据库查询的算法是十分灵活的,只要是它能接受的,都能翻译。例如,Jinq能够翻译下面的数据库查询,尽管它很复杂。
customers
.where( c -> c.getCountry().equals("Belgium") )
.where( c -> {
if (c.getSalary() < 100000)
return c.getSalary() < c.getDebt();
else
return c.getSalary() < 2 * c.getDebt();
} );
正如你看到的,java 8 的函数式编程非常适合数据库查询。而且查询紧凑,甚至复杂的查询也能够胜任。

内部运作

但这都是如何工作的呢?怎么能让普通的Java编译器将Java代码转换成数据库查询?Java 8 有什么特别之处使这个成为可能?
支持这些函数性风格的新的数据库PI的关键是一种叫做“象征性执行”的字节码分析手段。虽然你的代码是被一个普通的Java编译器编译的并运行在一个普通的Java虚拟机中,但 Jinq 能够在你被编译的Java代码运行时进行分析并从中构建数据库查询。使用 Java 8 Streams API 时,常会发现分析短小的函数时,象征性执行的工作效果最好。
要了解这个象征性执行是如何工作的,最简单的方法是用一个例子。让我们检查一下下面的查询是如何被 Jinq 转换为SQL查询语言的:
custo