使用Hibernate3作为JDBC框架

发布者:    |       Hibernate ORM

最近围绕像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的能力比我强得多。


返回顶部