当开发者创建数据访问应用程序时,spring会为开发者选择的orm层对应功能进行优化。而且,开发者可以根据需要来利用spring对集成orm的支持,开发者应该将此集成工作与维护内部类似的基础架构服务的成本和风险进行权衡。同时,开发者在使用spring集成的时候可以很大程度上不用考虑技术,将orm的支持当做一个库来使用,因为所有的组件都被设计为可重用的javabean组件了。spring ioc容器中的orm十分易于配置和部署。本节中的大多数示例都是讲解在spring容器中的来如何配置。
开发者使用spring框架来中创建自己的orm dao的好处如下:
易于测试。spring ioc的模式使得开发者可以轻易的替换hibernate的<code>sessionfactory</code>实例,jdbc的<code>datasource</code>实例,事务管理器,以及映射对象(如果有必要)的配置和实现。这一特点十分利于开发者对每个模块进行独立的测试。
泛化数据访问异常。spring可以将orm工具的异常封装起来,将所有异常(可以是受检异常)封装成运行时的<code>dataaccessexception</code>体系。这一特性可以令开发者在合适的逻辑层上处理绝大多数不可修复的持久化异常,避免了大量的<code>catch</code>,<code>throw</code>和异常的声明。开发者还可以按需来处理这些异常。其中,jdbc异常(包括一些特定db语言)都会被封装为相同的体系,意味着开发者即使使用不同的jdbc操作,基于不同的db,也可以保证一致的编程模型。
通用的资源管理。spring的应用上下文可以通过处理配置源的位置来灵活配置hibernate的<code>sessionfactory</code>实例,jpa的<code>entitymanagerfactory</code>实例,jdbc的<code>datasource</code>实例以及其他类似的资源。spring的这一特性使得这些实例的配置十分易于管理和修改。同时,spring还为处理持久化资源的配置提供了高效,易用和安全的处理方式。举个例子,有些代码使用了hibernate需要使用相同的<code>session</code>来确保高效性和正确的事务处理。spring通过hibernate的<code>sessionfactory</code>来获取当前的<code>session</code>,来透明的将<code>session</code>绑定到当前的线程。srping为任何本地或者jta事务环境解决了在使用hibernate时碰到的一些常见问题。
集成事务管理。开发者可以通过<code>@transactional</code>注解或在xml配置文件中显式配置事务aop advise拦截,将orm代码封装在声明式的aop方法拦截器中。事务的语义和异常处理(回滚等)都可以根据开发者自己的需求来定制。在后面的章节中,资源和事务管理中,开发者可以在不影响orm相关代码的情况下替换使用不同的事务管理器。例如,开发者可以在本地事务和jta之间进行交换,并在两种情况下具有相同的完整服务(如声明式事务)。而且,jdbc相关的代码在事务上完全和处理orm部分的代码集成。这对于不适用于orm的数据访问非常有用,例如批处理和blob流式传输,仍然需要与orm操作共享常见事务。
本节重点介绍适用于所有集成orm技术的注意事项。在16.3hibernate一节中提供了很多关于如何配置和使用这些特性提的信息。
spring对orm集成的主要目的是使应用层次化,可以任意选择数据访问和事务管理技术,并且为应用对象提供松耦合结构。不再将业务逻辑依赖于数据访问或者事务策略上,不再使用基于硬编码的资源查找,不再使用难以替代的单例,不再自定义服务的注册。同时,为应用提供一个简单和一致的方法来装载对象,保证他们的重用并且尽可能不依赖于容器。所有单独的数据访问功能都可以自己使用,也可以很好地与spring的<code>applicationcontext</code>集成,提供基于xml的配置和不需要spring感知的普通<code>javabean</code>实例。在典型的spring应用程序中,许多重要的对象都是<code>javabean</code>:数据访问模板,数据访问对象,事务管理器,使用数据访问对象和事务管理器的业务服务,web视图解析器,使用业务服务的web控制器等等。
通常企业应用都会包含很多重复的的资源管理代码。很多项目总是尝试去创造自己的解决方案,有时会为了开发的方便而牺牲对错误的处理。spring为资源的配置管理提供了简单易用的解决方案,在jdbc上使用模板技术,在orm上使用aop拦截技术。
spring的基础设施提供了合适的资源处理,同时spring引入了dao层的异常体系,可以适用于任何数据访问策略。对于jdbc直连来说,前面提及到的<code>jdbctemplate</code>类提供了包括连接处理,对<code>sqlexception</code>到<code>dataaccessexception</code>的异常封装,同时还包含对于一些特定数据库sql错误代码的转换。对于orm技术来说,可以参考下一节来了解异常封装的优点。
当在dao层中使用hibernate或者jpa的时候,开发者必须决定该如何处理持久化技术的一些原生异常。dao层会根据选择技术的不同而抛出<code>hibernateexception</code>或者<code>persistenceexception</code>。这些异常都属于运行时异常,所以无需显式声明和捕捉。同时,开发者同时还需要处理<code>illegalargumentexception</code>和<code>illegalstateexception</code>这类异常。一般情况下,调用方通常只能将这一类异常视为致命的异常,除非他们想要自己的应用依赖于持久性技术原生的异常体系。如果需要捕获一些特定的错误,比如乐观锁获取失败一类的错误,只能选择调用方和实现策略耦合到一起。对于那些只基于某种特定orm技术或者不需要特殊异常处理的应用来说,使用orm本身的异常体系的代价是可以接受的。但是,spring可以通过<code>@repository</code>注解透明地应用异常转换,以解耦调用方和orm技术的耦合:
<code>@repository</code>
<code>public class productdaoimpl implements productdao {</code>
<code></code>
<code>// class body here...</code>
<code>}</code>
<code><beans></code>
<code><!-- exception translation bean post processor --></code>
<code><bean class="org.springframework.dao.annotation.persistenceexceptiontranslationpostprocessor"/></code>
<code><bean id="myproductdao" class="product.productdaoimpl"/></code>
<code></beans></code>
上面的后置处理器<code>persistenceexceptiontranslationpostprocessor</code>,会自动查找所有的异常转义器(实现<code>persistenceexceptiontranslator</code>接口的bean),并且拦截所有标记为<code>@repository</code>注解的bean,通过代理来拦截异常,然后通过<code>persistenceexceptiontranslator</code>将dao层异常转义后的异常抛出。
总而言之:开发者可以既基于简单的持久化技术的api和注解来实现dao,同时还受益于spring管理的事务,依赖注入和透明异常转换(如果需要)到spring的自定义异常层次结构。
从spring 5.0开始,spring需要hibernate orm对jpa的支持要基于4.3或更高的版本,甚至hibernate orm 5.0+可以针对本机hibernate session api进行编程。请注意,hibernate团队可能不会在5.0之前维护任何版本,仅仅专注于5.2以后的版本。
开发者可以将资源如jdbc<code>datasource</code>或hibernate<code>sessionfactory</code>定义为spring容器中的bean来避免将应用程序对象绑定到硬编码的资源查找上。应用对象需要访问资源的时候,都通过对应的bean实例进行间接查找,详情可以通过下一节的dao的定义来参考。
下面引用的应用的xml元数据定义就展示了如何配置jdbc的<code>datasource</code>和<code>hibernate</code>的<code>sessionfactory</code>的:
<code><bean id="mydatasource" class="org.apache.commons.dbcp.basicdatasource" destroy-method="close"></code>
<code><property name="driverclassname" value="org.hsqldb.jdbcdriver"/></code>
<code><property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/></code>
<code><property name="username" value="sa"/></code>
<code><property name="password" value=""/></code>
<code></bean></code>
<code><bean id="mysessionfactory" class="org.springframework.orm.hibernate5.localsessionfactorybean"></code>
<code><property name="datasource" ref="mydatasource"/></code>
<code><property name="mappingresources"></code>
<code><list></code>
<code><value>product.hbm.xml</value></code>
<code></list></code>
<code></property></code>
<code><property name="hibernateproperties"></code>
<code><value></code>
<code>hibernate.dialect=org.hibernate.dialect.hsqldialect</code>
<code></value></code>
这样,从本地的jaksrta commons dbcp的<code>basicdatasource</code>转换到jndi定位的<code>datasource</code>仅仅只需要修改配置文件。
<code><jee:jndi-lookup id="mydatasource" jndi-name="java:comp/env/jdbc/myds"/></code>
开发者也可以通过spring的<code>jndiobjectfactorybean</code>或者<code><jee:jndi-lookup></code>来获取对应bean以访问jndi定位的<code>sessionfactory</code>。但是,jndi定位的<code>sessionfactory</code>在ejb上下文不常见。
hibernate有一个特性称之为上下文会话,在每个hibernate本身每个事务都管理一个当前的<code>session</code>。这大致相当于spring每个事务的一个hibernate<code>session</code>的同步。如下的dao的实现类就是基于简单的hibernate api实现的:
<code>private sessionfactory sessionfactory;</code>
<code>public void setsessionfactory(sessionfactory sessionfactory) {</code>
<code>this.sessionfactory = sessionfactory;</code>
<code>public collection loadproductsbycategory(string category) {</code>
<code>return this.sessionfactory.getcurrentsession()</code>
<code>.createquery("from test.product product where product.category=?")</code>
<code>.setparameter(0, category)</code>
<code>.list();</code>
除了需要在实例中持有<code>sessionfactory</code>引用以外,上面的代码风格跟hibernate文档中的例子十分相近。spring团队强烈建议使用这种基于实例变量的实现风格,而非守旧的<code>static hibernateutil</code>风格(总的来说,除非绝对必要,否则尽量不要使用<code>static</code>变量来持有资源)。
上面dao的实现完全符合spring依赖注入的样式:这种方式可以很好的集成spring ioc容器,就好像spring的<code>hibernatetemplate</code>代码一样。当然,dao层的实现也可以通过纯java的方式来配置(比如在ut中)。简单实例化<code>productdaoimpl</code>并且调用<code>setsessionfactory(...)</code>即可。当然,也可以使用spring bean来进行注入,参考如下xml配置:
<code><bean id="myproductdao" class="product.productdaoimpl"></code>
<code><property name="sessionfactory" ref="mysessionfactory"/></code>
上面的dao实现方式的好处在于只依赖于hibernate api,而无需引入spring的class。这从非侵入性的角度来看当然是有吸引力的,毫无疑问,这种开发方式会令hibernate开发人员将会更加自然。
然而,dao层会抛出hibernate自有异常<code>hibernateexception</code>(属于非检查异常,无需显式声明和使用try-catch),但是也意味着调用方会将异常看做致命异常——除非调用方将hibernate异常体系作为应用的异常体系来处理。而在这种情况下,除非调用方自己来实现一定的策略,否则捕获一些诸如乐观锁失败之类的特定错误是不可能的。对于强烈基于hibernate的应用程序或不需要对特殊异常处理的应用程序,这种代价可能是可以接受的。
幸运的是,spring的<code>localsessionfactorybean</code>可以通过hibernate的<code>sessionfactory.getcurrentsession()</code>方法为所有的spring事务策略提供支持,使用<code>hibernatetransactionmanager</code>返回当前的spring管理的事务的<code>session</code>。当然,该方法的标准行为仍然是返回与正在进行的jta事务相关联的当前<code>session</code>(如果有的话)。无论开发者是使用spring的<code>jtatransactionmanager</code>,ejb容器管理事务(cmt)还是jta,都会适用此行为。
总而言之:开发者可以基于纯hibernate api来实现dao,同时也可以集成spring来管理事务。
spring团队建议开发者使用spring声明式的事务支持,这样可以通过aop事务拦截器来替代事务api的显式调用。aop事务拦截器可以在spring容器中使用xml或者java的注解来进行配置。这种事务拦截器可以令开发者的代码和重复的事务代码相解耦,而开发者可以将精力更多集中在业务逻辑上,而业务逻辑才是应用的核心。
开发者可以在服务层的代码使用注解<code>@transactional</code>,这样可以让spring容器找到这些注解,以对其中注解了的方法提供事务语义。
<code>public class productserviceimpl implements productservice {</code>
<code>private productdao productdao;</code>
<code>public void setproductdao(productdao productdao) {</code>
<code>this.productdao = productdao;</code>
<code>@transactional</code>
<code>public void increasepriceofallproductsincategory(final string category) {</code>
<code>list productstochange = this.productdao.loadproductsbycategory(category);</code>
<code>// ...</code>
<code>@transactional(readonly = true)</code>
<code>public list<product> findallproducts() {</code>
<code>return this.productdao.findallproducts();</code>
开发者所需要做的就是在容器中配置<code>platformtransactionmanager</code>的实现,或者是在xml中配置<code><tx:annotation-driver/></code>标签,这样就可以在运行时支持<code>@transactional</code>的处理了。参考如下xml代码:
<code><?xml version="1.0" encoding="utf-8"?></code>
<code><beans xmlns="http://www.springframework.org/schema/beans"</code>
<code>xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"</code>
<code>xmlns:aop="http://www.springframework.org/schema/aop"</code>
<code>xmlns:tx="http://www.springframework.org/schema/tx"</code>
<code>xsi:schemalocation="</code>
<code>http://www.springframework.org/schema/beans</code>
<code>http://www.springframework.org/schema/beans/spring-beans.xsd</code>
<code>http://www.springframework.org/schema/tx</code>
<code>http://www.springframework.org/schema/tx/spring-tx.xsd</code>
<code>http://www.springframework.org/schema/aop</code>
<code>http://www.springframework.org/schema/aop/spring-aop.xsd"></code>
<code><!-- sessionfactory, datasource, etc. omitted --></code>
<code><bean id="transactionmanager"</code>
<code>class="org.springframework.orm.hibernate5.hibernatetransactionmanager"></code>
<code><property name="sessionfactory" ref="sessionfactory"/></code>
<code><tx:annotation-driven/></code>
<code><bean id="myproductservice" class="product.simpleproductservice"></code>
<code><property name="productdao" ref="myproductdao"/></code>