《Spring 5 官方文檔》16.ORM和數據訪問
16.1介紹一下Spring中的ORM
Spring框架在實現資源管理、數據訪問對象(DAO)層,和事務策略等方麵,支持對Java持久化API(JPA)以及原生Hibernate的集成。以Hibernate舉例來說,Spring有非常讚的IoC功能,可以解決許多典型的Hibernate配置和集成問題。開發者可以通過依賴注入來配置O-R(對象關係)映射組件支持的特性。Hibernate的這些特性可以參與Spring的資源和事務管理,並且符合Spring的通用事務和DAO層的異常體係。因此,Spring團隊推薦開發者使用Spring集成的方式來開發DAO層,而不是使用原生的Hibernate或者JPA的API。老版本的Spring DAO模板現在不推薦使用了,想了解這部分內容可以參考經典ORM使用一節。
當開發者創建數據訪問應用程序時,Spring會為開發者選擇的ORM層對應功能進行優化。而且,開發者可以根據需要來利用Spring對集成ORM的支持,開發者應該將此集成工作與維護內部類似的基礎架構服務的成本和風險進行權衡。同時,開發者在使用Spring集成的時候可以很大程度上不用考慮技術,將ORM的支持當做一個庫來使用,因為所有的組件都被設計為可重用的JavaBean組件了。Spring IoC容器中的ORM十分易於配置和部署。本節中的大多數示例都是講解在Spring容器中的來如何配置。
開發者使用Spring框架來中創建自己的ORM DAO的好處如下:
-
易於測試。Spring IoC的模式使得開發者可以輕易的替換Hibernate的
SessionFactory
實例,JDBC的DataSource
實例,事務管理器,以及映射對象(如果有必要)的配置和實現。這一特點十分利於開發者對每個模塊進行獨立的測試。 -
泛化數據訪問異常。Spring可以將ORM工具的異常封裝起來,將所有異常(可以是受檢異常)封裝成運行時的
DataAccessException
體係。這一特性可以令開發者在合適的邏輯層上處理絕大多數不可修複的持久化異常,避免了大量的catch
,throw
和異常的聲明。開發者還可以按需來處理這些異常。其中,JDBC異常(包括一些特定DB語言)都會被封裝為相同的體係,意味著開發者即使使用不同的JDBC操作,基於不同的DB,也可以保證一致的編程模型。 -
通用的資源管理。Spring的應用上下文可以通過處理配置源的位置來靈活配置Hibernate的
SessionFactory
實例,JPA的EntityManagerFactory
實例,JDBC的DataSource
實例以及其他類似的資源。Spring的這一特性使得這些實例的配置十分易於管理和修改。同時,Spring還為處理持久化資源的配置提供了高效,易用和安全的處理方式。舉個例子,有些代碼使用了Hibernate需要使用相同的Session
來確保高效性和正確的事務處理。Spring通過Hibernate的SessionFactory
來獲取當前的Session
,來透明的將Session
綁定到當前的線程。Srping為任何本地或者JTA事務環境解決了在使用Hibernate時碰到的一些常見問題。 -
集成事務管理。開發者可以通過
@Transactional
注解或在XML配置文件中顯式配置事務AOP Advise攔截,將ORM代碼封裝在聲明式的AOP方法攔截器中。事務的語義和異常處理(回滾等)都可以根據開發者自己的需求來定製。在後麵的章節中,資源和事務管理中,開發者可以在不影響ORM相關代碼的情況下替換使用不同的事務管理器。例如,開發者可以在本地事務和JTA之間進行交換,並在兩種情況下具有相同的完整服務(如聲明式事務)。而且,JDBC相關的代碼在事務上完全和處理ORM部分的代碼集成。這對於不適用於ORM的數據訪問非常有用,例如批處理和BLOB流式傳輸,仍然需要與ORM操作共享常見事務。
為了更全麵的ORM支持,包括支持其他類型的數據庫技術(如MongoDB),開發者可能需要查看Spring Data係列項目。如果開發者是JPA用戶,則可以從https://spring.io的查閱開始使用JPA訪問數據指南一文進行簡單了解。
16.2集成ORM的注意事項
本節重點介紹適用於所有集成ORM技術的注意事項。在16.3Hibernate一節中提供了很多關於如何配置和使用這些特性提的信息。
Spring對ORM集成的主要目的是使應用層次化,可以任意選擇數據訪問和事務管理技術,並且為應用對象提供鬆耦合結構。不再將業務邏輯依賴於數據訪問或者事務策略上,不再使用基於硬編碼的資源查找,不再使用難以替代的單例,不再自定義服務的注冊。同時,為應用提供一個簡單和一致的方法來裝載對象,保證他們的重用並且盡可能不依賴於容器。所有單獨的數據訪問功能都可以自己使用,也可以很好地與Spring的ApplicationContext
集成,提供基於XML的配置和不需要Spring感知的普通JavaBean
實例。在典型的Spring應用程序中,許多重要的對象都是JavaBean
:數據訪問模板,數據訪問對象,事務管理器,使用數據訪問對象和事務管理器的業務服務,Web視圖解析器,使用業務服務的Web控製器等等。
16.2.1資源和事務管理
通常企業應用都會包含很多重複的的資源管理代碼。很多項目總是嚐試去創造自己的解決方案,有時會為了開發的方便而犧牲對錯誤的處理。Spring為資源的配置管理提供了簡單易用的解決方案,在JDBC上使用模板技術,在ORM上使用AOP攔截技術。
Spring的基礎設施提供了合適的資源處理,同時Spring引入了DAO層的異常體係,可以適用於任何數據訪問策略。對於JDBC直連來說,前麵提及到的JdbcTemplate
類提供了包括連接處理,對SQLException
到DataAccessException
的異常封裝,同時還包含對於一些特定數據庫SQL錯誤代碼的轉換。對於ORM技術來說,可以參考下一節來了解異常封裝的優點。
當談到事務管理時,JdbcTemplate
類通過Spring事務管理器掛接到Spring事務支持,並支持JTA和JDBC事務。Spring通過Hibernate,JPA事務管理器和JTA的支持來提供Hibernate和JPA這類ORM技術的支持。想了解更多關於事務的描述,可以參考第13章,事務管理。
16.2.2異常轉義
當在DAO層中使用Hibernate或者JPA的時候,開發者必須決定該如何處理持久化技術的一些原生異常。DAO層會根據選擇技術的不同而拋出HibernateException
或者PersistenceException
。這些異常都屬於運行時異常,所以無需顯式聲明和捕捉。同時,開發者同時還需要處理IllegalArgumentException
和IllegalStateException
這類異常。一般情況下,調用方通常隻能將這一類異常視為致命的異常,除非他們想要自己的應用依賴於持久性技術原生的異常體係。如果需要捕獲一些特定的錯誤,比如樂觀鎖獲取失敗一類的錯誤,隻能選擇調用方和實現策略耦合到一起。對於那些隻基於某種特定ORM技術或者不需要特殊異常處理的應用來說,使用ORM本身的異常體係的代價是可以接受的。但是,Spring可以通過@Repository
注解透明地應用異常轉換,以解耦調用方和ORM技術的耦合:
@Repository
public class ProductDaoImpl implements ProductDao {
// class body here...
}
<beans>
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
上麵的後置處理器PersistenceExceptionTranslationPostProcessor
,會自動查找所有的異常轉義器(實現PersistenceExceptionTranslator
接口的Bean),並且攔截所有標記為@Repository
注解的Bean,通過代理來攔截異常,然後通過PersistenceExceptionTranslator
將DAO層異常轉義後的異常拋出。
總而言之:開發者可以既基於簡單的持久化技術的API和注解來實現DAO,同時還受益於Spring管理的事務,依賴注入和透明異常轉換(如果需要)到Spring的自定義異常層次結構。
16.3 Hibernate
我們將首先介紹Spring環境中的Hibernate 5,然後通過使用Hibernate 5來演示Spring集成O-R映射器的方法。本節將詳細介紹許多問題,並顯示DAO實現和事務劃分的不同變體。這些模式中大多數可以直接轉換為所有其他支持的ORM工具。本章中的以下部分將通過簡單的例子來介紹其他ORM技術。
從Spring 5.0開始,Spring需要Hibernate ORM對JPA的支持要基於4.3或更高的版本,甚至Hibernate ORM 5.0+可以針對本機Hibernate Session API進行編程。請注意,Hibernate團隊可能不會在5.0之前維護任何版本,僅僅專注於5.2以後的版本。
16.3.1在Spring容器中配置SessionFactory
開發者可以將資源如JDBCDataSource
或HibernateSessionFactory
定義為Spring容器中的bean來避免將應用程序對象綁定到硬編碼的資源查找上。應用對象需要訪問資源的時候,都通過對應的Bean實例進行間接查找,詳情可以通過下一節的DAO的定義來參考。
下麵引用的應用的XML元數據定義就展示了如何配置JDBC的DataSource
和Hibernate
的SessionFactory
的:
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
這樣,從本地的Jaksrta Commons DBCP的BasicDataSource
轉換到JNDI定位的DataSource
僅僅隻需要修改配置文件。
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
開發者也可以通過Spring的JndiObjectFactoryBean
或者<jee:jndi-lookup>
來獲取對應Bean以訪問JNDI定位的SessionFactory
。但是,JNDI定位的SessionFactory
在EJB上下文不常見。
16.3.2基於Hibernate API來實現DAO
Hibernate有一個特性稱之為上下文會話,在每個Hibernate本身每個事務都管理一個當前的Session
。這大致相當於Spring每個事務的一個HibernateSession
的同步。如下的DAO的實現類就是基於簡單的Hibernate API實現的:
public class ProductDaoImpl implements ProductDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
除了需要在實例中持有SessionFactory
引用以外,上麵的代碼風格跟Hibernate文檔中的例子十分相近。Spring團隊強烈建議使用這種基於實例變量的實現風格,而非守舊的static HibernateUtil
風格(總的來說,除非絕對必要,否則盡量不要使用static
變量來持有資源)。
上麵DAO的實現完全符合Spring依賴注入的樣式:這種方式可以很好的集成Spring IoC容器,就好像Spring的HibernateTemplate
代碼一樣。當然,DAO層的實現也可以通過純Java的方式來配置(比如在UT中)。簡單實例化ProductDaoImpl
並且調用setSessionFactory(...)
即可。當然,也可以使用Spring bean來進行注入,參考如下XML配置:
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
上麵的DAO實現方式的好處在於隻依賴於Hibernate API,而無需引入Spring的class。這從非侵入性的角度來看當然是有吸引力的,毫無疑問,這種開發方式會令Hibernate開發人員將會更加自然。
然而,DAO層會拋出Hibernate自有異常HibernateException
(屬於非檢查異常,無需顯式聲明和使用try-catch),但是也意味著調用方會將異常看做致命異常——除非調用方將Hibernate異常體係作為應用的異常體係來處理。而在這種情況下,除非調用方自己來實現一定的策略,否則捕獲一些諸如樂觀鎖失敗之類的特定錯誤是不可能的。對於強烈基於Hibernate的應用程序或不需要對特殊異常處理的應用程序,這種代價可能是可以接受的。
幸運的是,Spring的LocalSessionFactoryBean
可以通過Hibernate的SessionFactory.getCurrentSession()
方法為所有的Spring事務策略提供支持,使用HibernateTransactionManager
返回當前的Spring管理的事務的Session
。當然,該方法的標準行為仍然是返回與正在進行的JTA事務相關聯的當前Session
(如果有的話)。無論開發者是使用Spring的JtaTransactionManager
,EJB容器管理事務(CMT)還是JTA,都會適用此行為。
總而言之:開發者可以基於純Hibernate API來實現DAO,同時也可以集成Spring來管理事務。
16.3.3聲明式事務劃分
Spring團隊建議開發者使用Spring聲明式的事務支持,這樣可以通過AOP事務攔截器來替代事務API的顯式調用。AOP事務攔截器可以在Spring容器中使用XML或者Java的注解來進行配置。這種事務攔截器可以令開發者的代碼和重複的事務代碼相解耦,而開發者可以將精力更多集中在業務邏輯上,而業務邏輯才是應用的核心。
在繼續之前,強烈建議開發者先查閱章節13.5 聲明式事務管理的內容。
開發者可以在服務層的代碼使用注解@Transactional
,這樣可以讓Spring容器找到這些注解,以對其中注解了的方法提供事務語義。
public class ProductServiceImpl implements ProductService {
private ProductDao productDao;
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
@Transactional
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}
}
開發者所需要做的就是在容器中配置PlatformTransactionManager
的實現,或者是在XML中配置<tx:annotation-driver/>
標簽,這樣就可以在運行時支持@Transactional
的處理了。參考如下XML代碼:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="https://www.springframework.org/schema/aop"
xmlns:tx="https://www.springframework.org/schema/tx"
xsi:schemaLocation="
https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
https://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- SessionFactory, DataSource, etc. omitted -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
最後更新:2017-05-18 10:32:58