解决存储过程固有限制的方法之一就是将SQL嵌入到更加通用的语言中去。与存储过程将业务逻辑移入数据库相反,内联SQL将SQL从数据库移入了应用程序代码。这就使得SQL语句可以直接与语言进行交互。从某种意义上说,SQL成为了该语言的一个特性。有很多语言具有这种“特 性”,包括COBOL、C、甚至Java。以下就是Java中SQL的一个示例:
String name; Date hiredate; #sql { SELECT emp_name, hire_date ,hiredate FROM employee WHERE emp_num = 28959 };
内联SQL的确非常优雅,因为它做到了与语言的紧密结合。本地语言变量可以直接传递给 SQL作为参数,SQL执行结果可直接赋值给类似的变量。某种意义上,SQL的确成为了该语言的 一个特性。
但不幸的是,内联SQL并没有被广泛接受,一些重大问题的存在终于还是使得它无法开花结果。首先,SQL还不标准,它存在许多扩展版本,而每个版本又都只适用于某个特定的数据库。
SQL语言的分裂使得要实现一个既完备又可在各个数据库平台间移植的内联SQL分析器非常困难。内联SQL的第二个问题就是它往往并不是真的实现为语言的一个特性,而是使用一个预编译 器先将内联SQL “翻译”为当前语言中的相应代码。这就给像IDE (integrated development environment,集成开发环境)这样的工具带来了问题,因为这些工具可能需要预先解释代码以启 用像语法突出显示和代码完成这样的高级特性。而包含内联SQL的代码如果没有预编译器可能甚 至连编译都无法通过,这种依赖性使得人们不得不担心他们的代码在将来的可维护性。
解决内联SQL这些问题的方案之一就是将SQL从语言级移除,代之以应用程序中的某种数据结构(如字符串)来表现它。这种方法就是我们通常所说的动态SQL (dynamic SQL)。
动态SQL
动态SQL通过避免使用预编译器来解决内联SQL存在的一些问题。作为替代,SQL被表现为 一种字符串类型的数据,可以像现代语言中所有其他的字符数据一样地操纵它。因为SQL被表示 为种字符串类型,所以它就再不能像内联SQL一样直接与语言进行交互了。因此,动态SQL的实现需要一组强壮的API,用来设置SQL参数,获取结果数据,等等。
动态SQL的优点就在于其具有的灵活性。SQL可以在运行时基于不同的参数或动态的应用程 序功能来构建。例如,一个“按样例查询(query-by-example)”的Web表单就可能允许用户动态选择需要搜索的字段,以及希望搜索的数据。这就要求SQL语句的WHERE子句能够动态改变,使 用动态SQL就非常容易做到这一点。
动态SQL是目前从现代编程语言访问关系数据库的方法中最流行的。大多数这样的现代编程语言都包括用于进行数据库访问的标准API。相信Java开发人员和.NET开发人员对各自语言(分别是JDBC和ADO.NET)中的标准API都很熟悉,这些标准SQL API通常都非常强壮,为开发人员 提供了巨大的灵活性。以下就是Java语言中动态SQL的一个简单示例:
String name; Date hiredate; String sql = "SELECT emp_name, hire_date" + " FROM employee WHERE emp_num = ?"; Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement (sql)'; ps.setlnt (1, 28959);
ResultSet rs = ps.executeQuery(); while (rs.next) {
name = rs.getstring("emp_name");
hiredate = rs.getDate("hire_date"); }
rs .close () ; 应该包裹在一个try-catch, conn.close() ;代码块中
毫无疑问,动态SQL没有内联SQL那么优雅,甚至还比不上存储过程(以上代码中已经省略 了异常处理但还是非常复杂)。这些API通常都非常复杂且冗长,就像我们从之前的示例中所看到 的一样。使用这样的框架通常会带来大量的代码,而这些代码往往又具有重复性。此外,SQL语 句本身也常常因为太长而无法在一行代码中写完。这时就不得不将该SQL字符串打散为多个子串 然后通过拼接(concatenat)操作组合起来。代码中的字符串拼接操作使得SQL的可读性大大降低, 给维护和使用都带来了更多的困难。
那么,既然将SQL放在数据库中作为存储过程,或者放在语言中作为内联SQL,或者放在应 用程序中作为-种数据结构都不是最合适的,那么到底应该如何“处置”它呢?我们回避这个问 题。在现代面向对象的应用程序中,与关系数据库交互的一个最引人注目的解决方案就是对象/ 关系映射工具的使用。
0/RM
0/RM (对象/关系映射)就是被设计为用来简化对象持久化工作的,它通过将SQL完全从开 发人员的职责中排除来达到这个目的。在0/RM中,SQL是生成的。--些工具在应用程序构建或 编译时静态生成SQL,其他一些则在运行时动态生成。SQL是基于应用程序中的类与关系数据库 表之间的映射关系而生成的。除了不用写SQL语句,使用0/RM工具的API通常也比典型的SQL API要简单得多。0/RM其实并不是一个新概念,它的历史几乎与面向对象编程语言的历史一样悠 久。只是近年来的许多进展才使得0/RM又成为一个引人注目的持久化方法。
现代0/RM工具所做的决不仅仅是简单地产生几条SQL语句。它们提供了一套完整的持久化 架构,可以使你的整个应用程序从中受益。任何一个优秀的0/RM工具都提供了事务管理功能, 包括可用于处理本地事务以及分布式事务的非常简单的O/RM工具通常也会为处理各种不同 类型的数据提供许多高速缓存策略以避免不必要的数据库访问。0/RM工具可以减少数据库访问 的另一种方式就是数据延迟加载技术。延迟加载使得对数据的获取操作被推迟到绝对必要时,即直到它们确实需要被使用的那一刻。
虽然具有那么多优秀的特性,但0/RM工具仍然不是一颗“银弹”,它并非适用于所有的场景。 0/RM工具是基于一些假设和规则的。其中最普遍的一个假设就是数据库被恰当地规范化了。正如我们讨论的,那些最大的最有价值的数据库往往并没有被很好地规范化。这就会给映射带来许多麻烦,甚至需要绕些弯路,或者在设计时对效率做些折衷。没有哪一个对象/关 系解决方案可以支持每一种数据库的每一个特性、每一种能力以及设计上固有的缺陷。正如我们 之前说过的,SQL不是一个可靠的标准。基于这些原因,每一个0/RM工具都只是任何一个特定数据库所具有的全部功能的一个子集。
系列文章: