最近围绕像iBATIS这样的简单JDBC框架有一些噪音。我自己也喜欢iBATIS的想法,用于不需要面向对象领域模型的应用程序,并且不处理单个事务中的深度相关实体图。如果你正在处理某种“疯狂”的遗留数据库,JDBC框架也是很有意义的;ORM解决方案通常假定关联以干净的外键形式表示,并具有适当的引用完整性约束(Hibernate3比Hibernate 2.x要少得多)。
甚至有人建议,JDBC框架是ORM的合适替代品,即使对于ORM最适用的系统:具有清晰关系模式的面向对象应用程序。他们认为,你总是/永远/比使用生成的SQL更好。好吧,我不认为这是真的,不仅因为大多数应用程序需要的绝大多数SQL代码都是枯燥的,根本不需要人工干预,而且还因为JDBC框架在ORM不同的语义级别上运行。像iBATIS这样的解决方案对其发出的SQL语义以及结果数据集的了解要少得多。这意味着,进行如有效缓存之类的性能优化的机会要少得多。(这里的“有效”主要指有效的缓存/失效策略,这对于缓存的有用性至关重要。)此外,每当看到手工编写的SQL时,我们都会看到N+1选择问题。为可能需要一起检索的每个关联组合编写新的SQL查询是非常繁琐的。HQL在这方面大有帮助,因为HQL比SQL更简洁。为了使JDBC框架能够进行ORM可以进行的优化,它必须发展到类似的复杂程度。本质上,它需要成为一个ORM,但不需要生成SQL。事实上,我们已经开始在现有的JDBC框架中看到这种演化的迹象。这开始侵蚀了声明的利益之一:声称的简单性。
这也引发了一个有趣的想法:如果通过逐渐添加内容,JDBC框架最终会变成ORM,但没有SQL生成,那么为什么不直接拿一个现有的ORM解决方案,比如,哦,嗯……Hibernate,也许……然后减去SQL生成呢?
Hibernate团队早就认识到了混合使用生成的SQL与偶尔的手写查询的必要性。在Hibernate的早期版本中,我们的解决方案只是公开Hibernate使用的JDBC连接,这样你就可以执行自己的预编译语句。这改变了一段时间以前,Max Andersen最近在这方面做了很多工作。现在,在Hibernate3中,你可以编写一个不生成SQL的整个应用程序,同时仍然利用Hibernate的所有其他功能。
我们真的期望或打算让人们以这种方式使用Hibernate吗?好吧,不是真的——我不相信有很多人真的喜欢整天编写枯燥的INSERT、UPDATE、DELETE语句。另一方面,我们确实认为很多人需要定制偶尔的查询。但为了证明这一点,我会向你展示如何做到这一点,如果你真的想的话。
让我们以一个简单的Person-Employment-Organization领域模型为例。(你可以在org.hibernate.test.sql包中找到代码,所以我不在这里复制它。)最简单的类是Person;这是映射
<class name="Person" lazy="true"> <id name="id" unsaved-value="0"> <generator class="increment"/> </id> <property name="name" not-null="true"/> <loader query-ref="person"/> <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update> <sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete> </class>
首先要注意的首先是手工编写的INSERT、UPDATE和DELETE语句。参数的?顺序与上面列出的属性顺序相匹配(我们最终必须支持命名参数,我想)。我猜那里没有特别有趣的东西。
更有趣的是<loader>标签:它定义了对一个命名查询的引用,该查询将在使用get()、load()或延迟关联检索加载人员时使用。特别是,该命名查询可能是一个本地SQL查询,在这种情况下就是
<sql-query name="person"> <return alias="p" class="Person" lock-mode="upgrade"/> SELECT NAME AS {p.name}, ID AS {p.id} FROM PERSON WHERE ID=? FOR UPDATE </sql-query>
(本地SQL查询可以返回多个实体的列
;这是一个最简单的情况,只返回一个实体。)
Employment比较复杂,特别是,不是所有属性都包含在INSERT和UPDATE语句中
<class name="Employment" lazy="true"> <id name="id" unsaved-value="0"> <generator class="increment"/> </id> <many-to-one name="employee" not-null="true" update="false"/> <many-to-one name="employer" not-null="true" update="false"/> <property name="startDate" not-null="true" update="false" insert="false"/> <property name="endDate" insert="false"/> <property name="regionCode" update="false"/> <loader query-ref="employment"/> <sql-insert> INSERT INTO EMPLOYMENT (EMPLOYEE, EMPLOYER, STARTDATE, REGIONCODE, ID) VALUES (?, ?, CURRENT_DATE, UPPER(?), ?) </sql-insert> <sql-update>UPDATE EMPLOYMENT SET ENDDATE=? WHERE ID=?</sql-update> <sql-delete>DELETE FROM EMPLOYMENT WHERE ID=?</sql-delete> </class> <sql-query name="employment"> <return alias="emp" class="Employment"/> SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, REGIONCODE as {emp.regionCode}, ID AS {emp.id} FROM EMPLOYMENT WHERE ID = ? </sql-query>
Organization的映射有一个Employments集合
<class name="Organization" lazy="true"> <id name="id" unsaved-value="0"> <generator class="increment"/> </id> <property name="name" not-null="true"/> <set name="employments" lazy="true" inverse="true"> <key column="employer"/> <!-- only needed for DDL generation --> <one-to-many class="Employment"/> <loader query-ref="organizationEmployments"/> </set> <loader query-ref="organization"/> <sql-insert> INSERT INTO ORGANIZATION (NAME, ID) VALUES ( UPPER(?), ? ) </sql-insert> <sql-update>UPDATE ORGANIZATION SET NAME=UPPER(?) WHERE ID=?</sql-update> <sql-delete>DELETE FROM ORGANIZATION WHERE ID=?</sql-delete> </class>
不仅有一个<loader>查询组织,同时也查询其就业集合
<sql-query name="organization"> <return alias="org" class="Organization"/> SELECT NAME AS {org.name}, ID AS {org.id} FROM ORGANIZATION WHERE ID=? </sql-query> <sql-query name="organizationEmployments"> <return alias="empcol" collection="Organization.employments"/> <return alias="emp" class="Employment"/> SELECT {empcol.*}, EMPLOYER AS {emp.employer}, EMPLOYEE AS {emp.employee}, STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, REGIONCODE as {emp.regionCode}, ID AS {emp.id} FROM EMPLOYMENT empcol WHERE EMPLOYER = :id AND DELETED_DATETIME IS NULL </sql-query>
在我编写这段代码的时候,我真的开始感受到Hibernate为我编写SQL语句的优势。仅仅在这个简单的例子中,我就避免了超过35行代码,这些代码我后来还需要维护。
最后,对于临时查询,我们可以使用原生SQL查询(一个命名查询,或者嵌入在Java代码中的查询)。例如
<sql-query name="allOrganizationsWithEmployees"> <return alias="org" class="Organization"/> SELECT DISTINCT NAME AS {org.name}, ID AS {org.id} FROM ORGANIZATION org INNER JOIN EMPLOYMENT e ON e.EMPLOYER = org.ID </sql-query>
我个人更喜欢用Java编程而不是XML,因此所有这些对我来说都太过于依赖XML。我想我会坚持使用SQL生成,只要可能,这几乎是在任何地方。这并不是我不喜欢SQL。实际上,我非常热爱SQL,当Hibernate的日志开启时,我特别喜欢看查询语句滚动过去。只是Hibernate编写SQL的能力比我强得多。