那么你还应该使用 Spring 的 HibernateTemplate 和/或 JpaTemplate 吗?

工程 | Alef Arendsen | 2007 年 6 月 26 日 | ...

前几天我在 TSS 上读到 Vigil Bose 的一篇文章,看到了 HibernateDaoSupport 类的用法。由于这已不再是使用 Spring 操作 Hibernate 的推荐方式,我想不妨再写一篇博客谈谈这个话题。

随着 Spring 2.0 的到来,可以直接使用 Hibernate Session API 成为可能。问题在于,在使用 Hibernate 或 Spring 的任何其他基于模板的方法时,放弃使用 HibernateTemplate 是否明智。

使用 Spring 的 XxxTemplates

在 Spring 1.0 中,我们引入了一种革命性的方式来处理抛出受检异常的数据访问 API。Spring 的模板方法及其事务同步管理器以及对运行时异常的广泛使用,使得数据访问代码中常见的任何 TCFTC(我们在 2005 年创造的术语,是 try/catch-finally-try/catch 的缩写)完全过时。下面你可以看到(简化且不完全精确的版本)Spring 的模板方法为你做了什么(以及你原本需要编写的特定代码片段)。template.png

连接获取:如果事务同步处于活动状态(在使用 Spring 的事务管理基础设施时就是如此),大多数情况下,任何 Spring 模板都在整个线程中使用同一个连接(事情实际上比这要复杂一些,但这会让我们过多地陷入细节)。

参与事务 同样,在使用事务管理功能时,Spring 会自动将任何新连接与当前事务关联起来。这同样取决于当前的传播设置等,但无论如何,你的核心代码不受其影响。

SQL 指定:这(显然)需要你自己完成。理想情况下,SQL 应使用绑定参数,以避免任何 SQL 注入的可能性。参数作为参数传递给 JDBC 模板。

语句创建/执行和结果集迭代:在你指定 SQL 后,Spring 将为你创建语句,设置你可能指定的任何参数,执行语句并为你遍历结果集。

从结果集解析结果:如果你愿意(或者你有复杂的解析需求),可以选择自己解析结果集;或者你可以让 Spring 返回一个基本类型列表,或者仅从结果集中返回一个值。

异常处理与转换:在这里,Spring 会将可能发生的任何异常转换为 Spring 自己的 DataAccessException 异常体系,从而自动将调用代码与正在使用的数据访问技术隔离开来。

连接释放:这是最后一部分,Spring 会释放使用的任何资源。当然,如果事务同步处于活动状态,资源可能不会立即释放。

模板适用于多种 API,例如

  • JDBC (JdbcTemplate)
  • Hibernate (HibernateTemplate)
  • iBatis (SqlMapClientTemplate)
  • JDO (JdoTemplate)
  • TopLink (TopLinkTemplate)
  • 消息传递 (JmsTemplate)
  • 事务管理 (TransactionTemplate)
  • JNDI (JndiTemplate)

模板真的必要吗?

模板在使用抛出受检异常的 API(与运行时异常或非受检异常相对)时增值不少,同时也为你的代码库增加了大量的一致性。学过 Spring 的 JdbcTemplate 的人可以很轻松地开始使用 Spring 的 JdoTemplate 或 Spring 的 HibernateTemplate——它们的使用方法是相似的。

Spring 模板方法最明显的影响是减少了代码量,例如在 JDBC 中。这主要是因为受检异常在模板内部被转换为运行时异常,从而消除了在你的主干代码中捕获异常的需要。其他原因包括透明的资源管理和与当前正在运行的事务的自动同步。当然,将一个框架更改为原生使用运行时异常而不是由 Spring 来做这件事是相当容易的,例如 Hibernate 从 3.0 版本开始就这么做了。Hibernate 不是唯一采用这种技术的——Java Persistence API 也使用运行时异常。

这些技术使用运行时异常的事实,基本上使得 Spring 模板在这些技术上的等价物变得无用......至少在很大程度上是这样,如果你从代码简化的角度来看。如果你纯粹为了减少执行 Hibernate 数据访问操作所需的代码量而使用 Spring HibernateTemplate,你会说你不一定需要使用模板!然而,当我们查看上面的表格时,我们可以看到 Spring 在幕后做了比你想象的更多的工作。

除了部分简化错误处理问题(我们仍然需要将特定于数据访问技术的异常转换为 Spring 的 DataAccessExceptions)之外,通过底层数据访问技术的一些改变,事务管理和资源管理问题也得到了解决。让我们更详细地看看这些改变。

资源管理 自 Hibernate 3.0.1 起(以及 Java Persistence API 发布伊始),Spring 可以管理底层资源,而无需你通过适用于这些技术的任何模板。这意味着即使你直接使用 Hibernate API(例如通过 SessionFactory.getCurrentSession()),你仍然会使用由 Spring 管理的 Hibernate Session。通过 JPA EntityManagerFactory 获取的 EntityManager 也是如此。这是你不再需要使用 Spring 的 HibernateTemplate 来获得集成体验的另一个原因。

事务管理 现在 Spring 能够在无需你通过模板的情况下为你处理底层资源,Spring 也能够在获取资源时将资源与正在进行的任何事务同步。这意味着无需通过模板也能解决事务管理问题。同样,这意味着我们不一定再需要使用 Spring 的 HibernateTemplate 了。

错误处理 在使用 Hibernate 或 JPA 提供的原生 API(即 Hibernate Session 或 JPA EntityManager)时,唯一不可直接获取的功能是将技术特定的数据访问异常转换为 Spring DataAccessException 体系。不过,正如我们稍后将看到的,这很容易解决。

不使用模板

那么,如果我们不使用 HibernateTemplate,情况会是怎样呢?展示其工作方式相当简单。我们做的第一件事是开始直接使用 Session API,而不是 HibernateTemplate。要访问 Hibernate Session,我们需要像往常一样注入 SessionFactory。

public class HibernateAccountRepository implements AccountRepository {

	private SessionFactory factory;
	
	public HibernateAccountRepository(SessionFactory factory) {
		this.factory = factory;
	}
	
	public Account loadAccount(String username) {
		return (Account)factory.getCurrentSession()
		    .createQuery("from Account acc where acc.name = :name")
		    .setParameter("name", "Alef").uniqueResult();
	}
}

以下是我们用来组装应用程序的 XML 配置。如你所见,我们当然仍然使用 Spring 的方式来配置 Hibernate(使用 LocalSessionFactoryBean)。


<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
	<!-- the works -->
</bean>

<bean id="accountRepo" class="com.mycompany.HibernateAccountRepository">
	<constructor-arg ref="sessionFactory"/>
</bean>

现在,正如我之前所说,由于 Hibernate 3.0.1 中的一个小改动,Spring 可以为你管理 Hibernate session,而无需你通过 Hibernate session。唯一缺少的是异常转换。要启用此功能,你只需使用 @Repository 注解(由 Spring 提供)标注你的仓库类,并通过后处理器开启异常转换。


@Repository // from org.springframework.stereotype
public class HibernateAccountRepository implements AccountRepository {

	// see above for full impl...
}


<!-- for the other beans in the configuration, see above -->

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

后处理器会自动识别 @Repository 注解,并指示 Spring 为此 bean 开启异常转换。这种工作方式是利用代理实现的,但这与本次讨论无关。

注意:使用 Java Persistence API (JPA) 的仓库类也同样适用。实际上,你甚至根本不需要更改后处理器或注解。

如果你在不支持注解的环境中使用 Hibernate(Java 5 之前),你仍然可以享受自动异常转换;只需使用 AOP。首先声明一个异常转换器,然后声明一段 AOP 配置,如下所示:


<bean id=“persistenceExceptionInterceptor
    class=“org.springframework.dao.support.PersistenceExceptionTranslationInterceptor"/>

<aop:config>
    <aop:advisor pointcut=“execution(* *..*Repository+.*(..))" 
                          advice-ref=“persistenceExceptionInterceptor" />
</aop:config>

这里的切入点匹配任何实现 Repository 接口的类(更准确地说,是任何以 Repository 结尾的接口)。

真正的问题是:应该选择哪种方法?

用典型的咨询师回答就是:'这取决于情况' :)。我个人更喜欢不使用 HibernateTemplate 和 JpaTemplate,因为我认为它们提供的价值已经不够了。为了保持一致性,你可以争辩说,在所有地方都选择基于模板的方法可以让你处于一个相似的局面;然而,你仍然需要学习 Hibernate 如何工作,而且对于更复杂的情况,你可能无论如何都想直接使用 Session API。请注意,即使你正在使用 HibernateTemplate(通过 Spring 的 HibernateCallback),这也仍然是可能的。

因此,简而言之(正如 HibernateTemplateJpaTemplate 的 JavaDoc 中已经提到的),如果你在一个新项目上开始使用 Hibernate 或 JPA,我建议你直接使用 Session 和/或 EntityManager API——记住:Spring 尽量做到非侵入性,这是又一个很好的例子!

[更新:小错误] [更新:添加了关于无注解异常转换的信息]

订阅 Spring 时事通讯

订阅 Spring 时事通讯,保持连接

订阅

抢先一步

VMware 提供培训和认证,助你加速进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区的所有即将到来的活动。

查看全部