Spring 數據入口(第二部分)

4. 對象關係映射(ORM)數據訪問
本節討論在使用對象關係映射(ORM)時的數據訪問。

4.1。用Spring介紹ORM
Spring框架支持與Java Persistence API (JPA)的集成,並支持用於資源管理、數據訪問對象(DAO)實現和事務策略的本機Hibernate。例如,對於Hibernate,它提供了一流的支持,提供了幾個方便的IoC特性,解決了許多典型的Hibernate集成問題。您可以通過依賴項注入爲OR(對象關係)映射工具配置所有受支持的特性。它們可以參與Spring的資源和事務管理,並且遵從Spring的通用事務和DAO異常層次結構。推薦的集成風格是針對普通Hibernate或JPA api編寫DAOs代碼。

在創建數據訪問應用程序時,Spring爲您選擇的ORM層添加了重要的增強功能。您可以利用儘可能多的集成支持,並且應該將此集成工作與內部構建類似基礎設施的成本和風險進行比較。不管採用什麼技術,您都可以像使用庫一樣使用ORM支持,因爲所有東西都被設計爲一組可重用的javabean。Spring IoC容器中的ORM簡化了配置和部署。因此,本節中的大多數示例都展示了Spring容器中的配置。

使用Spring框架創建ORM dao的好處包括:

  • 更容易測試。Spring的IoC方法使得交換Hibernate SessionFactory實例、JDBC數據源實例、事務管理器和映射對象實現(如果需要)的實現和配置位置變得很容易。這反過來又使得在隔離狀態下測試與持久性相關的每段代碼變得更加容易。
  • 常見的數據訪問異常。Spring可以包裝來自ORM工具的異常,將它們從私有(可能已檢查的)異常轉換爲公共運行時DataAccessException層次結構。該特性允許您處理大多數不可恢復的持久性異常,這些異常只在適當的層中出現,而沒有煩人的樣板捕獲、拋出和異常聲明。您仍然可以根據需要捕獲和處理異常。請記住,JDBC異常(包括特定於db的方言)也被轉換爲相同的層次結構,這意味着您可以在一致的編程模型中使用JDBC執行某些操作。
  • 通用資源管理。Spring應用程序上下文可以處理Hibernate SessionFactory實例、JPA EntityManagerFactory實例、JDBC數據源實例和其他相關資源的位置和配置。這使得這些值易於管理和更改。Spring提供了對持久性資源的高效、簡單和安全的處理。例如,使用Hibernate的相關代碼通常需要使用相同的Hibernate會話來確保效率和正確的事務處理。通過通過Hibernate SessionFactory公開當前會話,Spring使創建和透明地將會話綁定到當前線程變得很容易。因此,對於任何本地或JTA事務環境,Spring解決了許多典型Hibernate使用的長期問題。
  • 綜合事務管理。可以通過@Transactional註釋或在XML配置文件中顯式配置事務AOP通知,用聲明式面向方面編程(AOP)風格的方法攔截器包裝ORM代碼。在這兩種情況下,都爲您處理事務語義和異常處理(回滾等)。正如在資源和事務管理中討論的,您還可以交換各種事務管理器,而不會影響與orm相關的代碼。例如,您可以在本地事務和JTA之間進行交換,在這兩個場景中都可以使用相同的完整服務(例如聲明性事務)。此外,與jdbc相關的代碼可以與用於ORM的代碼完全集成。這對於不適合ORM(例如批處理和BLOB流)但仍然需要與ORM操作共享公共事務的數據訪問非常有用。

注意:要獲得更全面的ORM支持,包括對其他數據庫技術(如MongoDB)的支持,您可能需要查看Spring Data suite of projects。如果您是一個JPA用戶,從https://spring.io開始使用JPA嚮導訪問數據,提供了一個很好的介紹。

4.2。一般的ORM集成注意事項
本節重點介紹適用於所有ORM技術的注意事項。Hibernate部分提供了更多的細節,還在具體的上下文中顯示了這些特性和配置。

Spring的ORM一體化的主要目標是明確應用程序分層(與任何數據訪問和事務的技術)和鬆散耦合的應用程序對象,沒有更多的業務服務依賴於數據訪問或事務策略,不再硬編碼資源查找,沒有更多的方面單例,沒有更多的定製服務註冊中心。其目標是使用一種簡單且一致的方法來連接應用程序對象,使它們儘可能地可重用且不依賴於容器。所有單獨的數據訪問特性本身都是可用的,但與Spring的應用程序上下文概念很好地集成在一起,提供了基於xml的配置和不需要對Spring敏感的普通JavaBean實例的交叉引用。在典型的Spring應用程序中,許多重要的對象是javabean:數據訪問模板、數據訪問對象、事務管理器、使用數據訪問對象的業務服務和事務管理器、web視圖解析器、使用業務服務的web控制器,等等。

4.2.1。準備資源和事務管理
典型的業務應用程序充斥着重複的資源管理代碼。許多項目試圖發明自己的解決方案,有時爲了方便編程而犧牲了對故障的適當處理。Spring提倡使用簡單的解決方案來進行適當的資源處理,即在JDBC中通過模板進行IoC,併爲ORM技術應用AOP攔截器。

基礎設施提供適當的資源處理,並將特定的API異常轉換爲未檢查的基礎設施異常層次結構。Spring引入了一個DAO異常層次結構,適用於任何數據訪問策略。對於直接JDBC,前一節中提到的JdbcTemplate類提供了連接處理和將SQLException轉換爲DataAccessException層次結構,包括將特定於數據庫的SQL錯誤代碼轉換爲有意義的異常類。對於ORM技術,請參閱下一節,瞭解如何獲得相同的異常轉換好處。

在事務管理方面,JdbcTemplate類掛鉤到Spring事務支持,並通過相應的Spring事務管理器支持JTA和JDBC事務。對於支持的ORM技術,Spring通過Hibernate和JPA事務管理器以及JTA支持提供Hibernate和JPA支持。有關事務支持的詳細信息,請參閱事務管理一章。

4.2.2。Exception Translation
在DAO中使用Hibernate或JPA時,必須決定如何處理持久性技術的本機異常類。DAO拋出一個HibernateException或PersistenceException的子類,具體取決於技術。這些異常都是運行時異常,不需要聲明或捕獲。您可能還需要處理IllegalArgumentException和IllegalStateException。這意味着調用者只能將異常視爲通常是致命的,除非他們希望依賴於持久性技術自身的異常結構。如果不將調用者綁定到實現策略,就不可能捕獲特定的原因(例如樂觀鎖定失敗)。對於基於強orm或不需要任何特殊異常處理(或兩者都需要)的應用程序,這種折衷可能是可以接受的。但是,Spring允許通過@Repository註釋透明地應用異常轉換。下面的示例(一個用於Java配置,另一個用於XML配置)展示瞭如何做到這一點:

@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>

後處理器自動查找所有異常翻譯器(PersistenceExceptionTranslator接口的實現),並建議使用@Repository註釋標記的所有bean,以便發現的翻譯器能夠攔截並對拋出的異常應用適當的翻譯。

總之,您可以基於普通持久性技術的API和註釋實現DAOs,同時仍然受益於Spring管理的事務、依賴項注入和透明的異常轉換(如果需要的話)到Spring的自定義異常層次結構。

4.3。Hibernate
我們首先介紹Spring環境中的Hibernate 5,使用它來演示Spring在集成或映射器方面所採用的方法。本節詳細討論了許多問題,並展示了DAO實現和事務界定的不同變體。這些模式中的大多數可以直接轉換爲所有其他受支持的ORM工具。本章後面的部分將介紹其他ORM技術,並給出簡單的示例。

注意:從Spring Framework 5.0開始,Spring需要Hibernate ORM 4.3或更高版本的JPA支持,甚至需要Hibernate ORM 5.0+來針對本機Hibernate會話API進行編程。請注意,Hibernate團隊不再維護5.1之前的任何版本,可能很快會專門關注5.3+。

4.3.1。SessionFactory設置在一個Spring容器中
爲了避免將應用程序對象綁定到硬編碼的資源查找,可以將資源(如JDBC數據源或Hibernate SessionFactory)定義爲Spring容器中的bean。需要訪問資源的應用程序對象通過bean引用接收對此類預定義實例的引用,下一節中的DAO定義將對此進行說明。

以下摘自XML應用程序上下文定義,展示瞭如何在其上設置JDBC數據源和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>

從本地Jakarta Commons DBCP BasicDataSource切換到位於jndi的數據源(通常由應用服務器管理)只是配置問題,如下例所示:

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

您還可以訪問位於jndi的SessionFactory,使用Spring的JndiObjectFactoryBean / <jee:jndi-lookup>來檢索和公開它。然而,這在EJB上下文之外通常並不常見。

注意:Spring還提供了LocalSessionFactoryBuilder變體,與@Bean風格的配置和編程設置無縫集成(不涉及FactoryBean)。LocalSessionFactoryBean和LocalSessionFactoryBuilder都支持後臺引導,在給定的引導執行器(如SimpleAsyncTaskExecutor)上,Hibernate的初始化與應用程序的引導線程並行運行。在LocalSessionFactoryBean上,這可以通過bootstrapExecutor屬性獲得。在可編程的LocalSessionFactoryBuilder上,有一個重載的buildSessionFactory方法,它接受一個引導執行器參數。

從Spring Framework 5.1開始,這樣的本機Hibernate設置還可以在本機Hibernate訪問的旁邊公開一個JPA EntityManagerFactory,用於標準的JPA交互。有關JPA的詳細信息,請參閱本機Hibernate設置。

4.3.2。實現基於普通Hibernate API的dao
Hibernate有一個稱爲上下文會話的特性,其中Hibernate自己管理每個事務的一個當前會話。這大致相當於Spring對每個事務同步一個Hibernate會話。一個對應的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();
    }
}

這種風格類似於Hibernate參考文檔和示例,只是在實例變量中保留了SessionFactory。我們強烈建議在Hibernate的CaveatEmptor樣例應用程序的老式靜態HibernateUtil類上使用這種基於實例的設置。(一般來說,除非絕對必要,否則不要將任何資源保存在靜態變量中。)

前面的DAO示例遵循依賴項注入模式。它可以很好地裝入Spring IoC容器,就像根據Spring的HibernateTemplate進行編碼一樣。您還可以在普通Java中設置這樣的DAO(例如,在單元測試中)。爲此,實例化它並使用所需的工廠引用調用setSessionFactory(..)。作爲一個Spring bean的定義,DAO類似於以下內容:

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

</beans>

這種DAO風格的主要優點是它只依賴於Hibernate API。不需要導入任何Spring類。從非侵入性的角度來看,這很有吸引力,對於Hibernate開發人員來說可能更自然。

但是,DAO拋出的是普通的HibernateException(未選中,因此不必聲明或捕獲它),這意味着調用者只能將異常視爲通常是致命的——除非他們希望依賴於Hibernate自己的異常層次結構。如果不將調用者綁定到實現策略,就不可能捕獲特定的原因(例如樂觀鎖定失敗)。對於基於hibernate的應用程序來說,這種折衷可能是可以接受的,不需要任何特殊的異常處理,或者兩者兼而有之。

幸運的是,對於任何Spring事務策略,Spring的LocalSessionFactoryBean都支持Hibernate的SessionFactory.getCurrentSession()方法,即使使用HibernateTransactionManager也會返回當前的Spring管理的事務會話。該方法的標準行爲仍然是返回與正在進行的JTA事務相關聯的當前會話(如果有的話)。無論您使用Spring的JtaTransactionManager、EJB容器管理事務(CMTs)還是JTA,此行爲都適用。

總之,您可以基於簡單的Hibernate API實現DAOs,同時仍然能夠參與spring管理的事務。

4.3.3。聲明式事務劃分
我們建議您使用Spring的聲明式事務支持,它允許您使用AOP事務攔截器來替換Java代碼中的顯式事務界定API調用。您可以使用Java註釋或XML在Spring容器中配置此事務攔截器。這種聲明性事務功能使您可以使業務服務免於重複的事務界定代碼,並專注於添加業務邏輯,這纔是應用程序的真正價值所在。

在繼續之前,如果您還沒有閱讀聲明式事務管理,我們強烈建議您閱讀它。

您可以使用@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實現(作爲bean)和<tx:annotation-driven/>條目,並在運行時選擇@Transactional處理。下面的例子演示瞭如何做到這一點:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://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>

4.3.4。程序性事務界定
您可以在應用程序的較高層(跨越任意數量操作的較低層數據訪問服務之上)劃分事務。對周圍業務服務的實現也沒有限制。它只需要一個Spring平臺transactionmanager。同樣,後者可以來自任何地方,但最好是通過setTransactionManager(..)方法作爲bean引用。另外,應該使用setProductDao(..)方法設置productDAO。下面的代碼片段展示了Spring應用程序上下文中的事務管理器和業務服務定義,以及業務方法實現的示例:

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // do the price increase...
            }
        });
    }
}

Spring的TransactionInterceptor允許使用回調代碼拋出任何已檢查的應用程序異常,而TransactionTemplate則被限制爲回調中未檢查的異常。TransactionTemplate在未選中的應用程序異常或事務僅由應用程序(通過設置TransactionStatus)標記爲rollback的情況下觸發回滾。默認情況下,TransactionInterceptor的行爲方式是相同的,但是允許對每個方法配置回滾策略。

4.3.5。事務管理策略
TransactionTemplate和TransactionInterceptor代表實際的事務處理PlatformTransactionManager實例(可以是一個HibernateTransactionManager(一個Hibernate SessionFactory)通過使用ThreadLocal會話下罩)或JtaTransactionManager(委派到容器的JTA子系統),Hibernate應用程序。您甚至可以使用定製的PlatformTransactionManager實現。從本機Hibernate事務管理切換到JTA(例如在面對應用程序的某些部署的分佈式事務需求時)只是配置問題。您可以使用Spring的JTA事務實現替換Hibernate事務管理器。事務界定和數據訪問代碼無需更改即可工作,因爲它們使用通用事務管理api。

對於跨多個Hibernate會話工廠的分佈式事務,可以將JtaTransactionManager作爲事務策略與多個LocalSessionFactoryBean定義組合使用。然後,每個DAO獲得一個特定的SessionFactory引用,該引用被傳遞到對應的bean屬性中。如果所有底層JDBC數據源都是事務性容器數據源,那麼業務服務可以在任意數量的dao和任意數量的會話工廠之間劃分事務,而不需要特別注意,只要它使用JtaTransactionManager作爲策略即可。

HibernateTransactionManager和JtaTransactionManager都允許使用Hibernate進行適當的jvm級緩存處理,而不需要特定於容器的事務管理器查詢或JCA連接器(如果不使用EJB啓動事務)。

HibernateTransactionManager可以將Hibernate JDBC連接導出爲特定數據源的純JDBC訪問代碼。這種能力允許在不使用JTA的情況下使用混合Hibernate和JDBC數據訪問進行高級事務劃分,前提是隻訪問一個數據庫。如果您已經通過LocalSessionFactoryBean類的DataSource屬性設置了帶數據源的傳入SessionFactory,那麼HibernateTransactionManager會自動將Hibernate事務公開爲JDBC事務。或者,您可以顯式地指定應該通過HibernateTransactionManager類的DataSource屬性公開事務的數據源。

4.3.6。比較容器管理的資源和本地定義的資源
您可以在容器管理的JNDI SessionFactory和本地定義的JNDI SessionFactory之間進行切換,而無需更改任何應用程序代碼。是將資源定義保存在容器中,還是在應用程序中本地保存,這主要取決於您使用的事務策略。與spring定義的本地SessionFactory相比,手動註冊的JNDI SessionFactory沒有提供任何好處。通過Hibernate的JCA連接器部署SessionFactory提供了參與Java EE服務器的管理基礎設施的附加價值,但除此之外不添加實際價值。

Spring的事務支持沒有綁定到容器。當使用除JTA之外的任何策略進行配置時,事務支持也可以在獨立或測試環境中工作。特別是在典型的單數據庫事務的情況下,Spring的單資源本地事務支持是JTA的一個輕量級和強大的替代方案。當您使用本地EJB無狀態會話bean來驅動事務時,您同時依賴於EJB容器和JTA,即使您僅訪問單個數據庫,並且僅使用無狀態會話bean來通過容器管理的事務提供聲明性事務。以編程方式直接使用JTA還需要Java EE環境。JTA並不僅僅涉及JTA本身和JNDI數據源實例的容器依賴關係。對於非spring、jta驅動的Hibernate事務,您必須使用Hibernate JCA連接器或額外的Hibernate事務代碼,併爲正確的jvm級緩存配置TransactionManagerLookup。

spring驅動的事務可以在本地定義的Hibernate SessionFactory中工作,就像在本地JDBC數據源中工作一樣,前提是它們訪問單個數據庫。因此,當您有分佈式事務需求時,您只需要使用Spring的JTA事務策略。JCA連接器需要特定於容器的部署步驟,並且(顯然)首先需要JCA支持。與部署具有本地資源定義和spring驅動的事務的簡單web應用程序相比,此配置需要更多的工作。另外,如果使用WebLogic Express(它不提供JCA),則通常需要容器的企業版。具有跨單個數據庫的本地資源和事務的Spring應用程序可以在任何Java EE web容器(不包括JTA、JCA或EJB)中工作,比如Tomcat、Resin,甚至是plain Jetty。此外,您可以很容易地在桌面應用程序或測試套件中重用這樣的中間層。

總之,如果您不使用ejb,請堅持使用本地SessionFactory設置和Spring的HibernateTransactionManager或JtaTransactionManager。您可以獲得所有的好處,包括適當的事務性jvm級緩存和分佈式事務,而不需要容器部署帶來的不便。通過JCA連接器對Hibernate SessionFactory進行JNDI註冊僅在與ejb一起使用時才有價值。

4.3.7。假的應用服務器警告與Hibernate
在一些具有非常嚴格的XADataSource實現的JTA環境中(目前只有一些WebLogic服務器和WebSphere版本),當Hibernate在不考慮該環境的JTA PlatformTransactionManager對象的情況下進行配置時,虛假的警告或異常可能出現在應用服務器日誌中。這些警告或異常表明正在訪問的連接不再有效或JDBC訪問不再有效,這可能是因爲事務不再活動。舉個例子,下面是WebLogic的一個實際異常:

java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.

您可以通過讓Hibernate知道JTA PlatformTransactionManager實例來解決這個警告,它(與Spring一起)與JTA PlatformTransactionManager實例進行同步。你有兩個選擇:

  • 直接在您的應用程序上下文,如果你已經獲得JTA PlatformTransactionManager對象(大概從JNDI通過JndiObjectFactoryBean或< jee: JNDI查找>)和餵它,例如,春天的JtaTransactionManager,最簡單的方法是指定的引用bean定義這個JTA PlatformTransactionManager實例作爲LocalSessionFactoryBean JtaTransactionManager屬性的值。然後,Spring使對象可用於Hibernate。
  • 更有可能的是,您還沒有JTA PlatformTransactionManager實例,因爲Spring的JtaTransactionManager可以自己找到它。因此,您需要配置Hibernate來直接查找JTA PlatformTransactionManager。您可以通過在Hibernate配置中配置應用程序服務器特定的TransactionManagerLookup類來實現這一點,如Hibernate手冊中所述。

本節的其餘部分描述了在Hibernate不知道JTA PlatformTransactionManager的情況下發生的事件序列。
當Hibernate沒有配置任何對JTA PlatformTransactionManager的感知時,在提交JTA事務時會發生以下事件:

  • 提交JTA事務。
  • Spring的JtaTransactionManager被同步到JTA事務,因此它是通過JTA事務管理器的afterCompletion回調被回調的。
  • 在其他活動中,這種同步可以通過Hibernate的afterTransactionCompletion回調(用於清除Hibernate緩存)觸發Spring回調到Hibernate,然後在Hibernate會話上顯式地調用close(),這會導致Hibernate試圖關閉()JDBC連接。
  • 在某些環境中,這個Connection.close()調用然後觸發警告或錯誤,因爲應用服務器不再認爲連接是可用的,因爲事務已經提交了。

當Hibernate配置了對JTA PlatformTransactionManager的感知時,在提交JTA事務時會發生以下事件:

  • JTA事務已經準備提交。
  • Spring的JtaTransactionManager與JTA事務同步,因此事務通過JTA事務管理器的beforeCompletion回調被回調。
  • Spring意識到Hibernate本身與JTA事務是同步的,其行爲與前面的場景不同。假設Hibernate會話需要關閉,Spring現在關閉它。
  • 提交JTA事務。
  • Hibernate與JTA事務保持同步,因此事務通過JTA事務管理器的afterCompletion回調被回調,可以正確地清除它的緩存。

4.4。JPA
在org.springframework.orm.jpa包下提供的Spring JPA,以類似於與Hibernate集成的方式提供了對 Java Persistence API 的全面支持,同時還支持底層實現,以便提供額外的功能。

4.4.1。Spring環境中JPA設置的三個選項
Spring JPA支持提供了三種設置JPA EntityManagerFactory的方法,應用程序使用該方法來獲取實體管理器。

使用LocalEntityManagerFactoryBean
您只能在簡單的部署環境(如獨立應用程序和集成測試)中使用此選項。

LocalEntityManagerFactoryBean創建了一個適合於簡單部署環境的EntityManagerFactory,其中應用程序僅使用JPA進行數據訪問。工廠bean使用JPA persistence provider自動檢測機制(根據JPA的Java SE引導),並且在大多數情況下,要求您只指定持久性單元名。下面的XML示例配置這樣一個bean:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    </bean>
</beans>

這種形式的JPA部署是最簡單和最受限制的。您不能引用現有的JDBC數據源bean定義,也不支持全局事務。而且,持久類的編織(字節碼轉換)是特定於提供程序的,通常需要在啓動時指定特定的JVM代理。該選項僅適用於獨立應用程序和測試環境,JPA規範就是爲這些應用程序和測試環境設計的。

從JNDI獲得EntityManagerFactory
您可以在部署到Java EE服務器時使用此選項。檢查服務器的文檔,瞭解如何將自定義JPA提供者部署到服務器中,允許使用不同於服務器默認的提供者。

從JNDI中獲得EntityManagerFactory(例如在Java EE環境中)只需更改XML配置,如下面的示例所示:

<beans>
    <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

此操作假設使用標準的Java EE引導。Java EE服務器自動檢測持久性單元(實際上是應用程序jar中的META-INF/persistence.xml文件)和Java EE部署描述符中的持久性單元-ref條目(例如web.xml),併爲這些持久性單元定義環境命名上下文位置。

在這種情況下,整個持久性單元部署,包括持久性類的編織(字節碼轉換),都取決於Java EE服務器。JDBC數據源是通過元inf /persistence.xml文件中的JNDI位置定義的。EntityManager事務與服務器的JTA子系統集成。Spring僅使用獲得的EntityManagerFactory,通過依賴項注入將其傳遞給應用程序對象,並管理持久性單元的事務(通常通過JtaTransactionManager)。

如果您在同一個應用程序中使用多個持久性單元,那麼此類jndi檢索的持久性單元的bean名稱應該與應用程序用來引用它們的持久性單元名稱相匹配(例如,在@PersistenceUnit和@PersistenceContext註釋中)。

使用LocalContainerEntityManagerFactoryBean
您可以在基於spring的應用程序環境中使用此選項來實現完整的JPA功能。這包括Tomcat等web容器、獨立應用程序和具有複雜持久性需求的集成測試。

注意:如果你想專門配置一個Hibernate設置,立即替代方法是使用Hibernate 5.2或5.3,建立本地Hibernate LocalSessionFactoryBean代替普通JPA LocalContainerEntityManagerFactoryBean,讓它與JPA訪問代碼以及本地Hibernate訪問代碼。有關JPA交互的詳細信息,請參閱本機Hibernate設置。

LocalContainerEntityManagerFactoryBean完全控制EntityManagerFactory配置,適合需要細粒度定製的環境。LocalContainerEntityManagerFactoryBean根據persistence.xml文件、提供的dataSourceLookup策略和指定的loadTimeWeaver創建一個PersistenceUnitInfo實例。因此,可以使用JNDI之外的自定義數據源並控制編織過程。下面的例子展示了LocalContainerEntityManagerFactoryBean的典型bean定義:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
    </bean>
</beans>

下面的例子展示了一個典型的persistence.xml文件:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>

注意:<exclude-unlisted-classes/>快捷方式表示不應該掃描帶註釋的實體類。顯式的‘true’值(<exclude-unlisted-classes>true</exclude-unlisted-classes/>)也表示不掃描。<exclude-unlisted-classes>false</exclude-unlisted-classes/>確實觸發掃描。但是,如果希望進行實體類掃描,我們建議省略exclude-unlisted-classes元素。

使用LocalContainerEntityManagerFactoryBean是最強大的JPA設置選項,允許在應用程序中進行靈活的本地配置。它支持到現有JDBC數據源的鏈接,支持本地和全局事務,等等。但是,它還對運行時環境提出了要求,例如,如果持久性提供程序需要字節碼轉換,那麼就需要一個支持編織的類裝入器。

此選項可能與Java EE服務器的內置JPA功能相沖突。在完整的Java EE環境中,考慮從JNDI獲得EntityManagerFactory。或者,在LocalContainerEntityManagerFactoryBean定義上指定一個定製的persistenceXmlLocation(例如,META-INF/my-persistence.xml),並在應用程序jar文件中只包含一個具有該名稱的描述符。因爲Java EE服務器只查找默認的META-INF/persistence.xml文件,所以它忽略了這樣的自定義持久性單元,從而避免了與spring驅動的JPA設置的衝突。(例如,這適用於樹脂3.1。)

何時需要加載時編織?
並不是所有的JPA提供者都需要JVM代理。Hibernate就是這樣一個例子。如果您的提供程序不需要代理,或者您有其他選擇,例如在構建時通過自定義編譯器或Ant任務應用增強,那麼您不應該使用加載時編織器。

LoadTimeWeaver接口是一個spring提供的類,它允許以特定的方式插入JPA ClassTransformer實例,具體方式取決於環境是web容器還是應用服務器。通過代理連接類轉換器通常是無效的。代理針對整個虛擬機工作,並檢查加載的每個類,這在生產服務器環境中通常是不希望的。

Spring爲各種環境提供了大量LoadTimeWeaver實現,讓ClassTransformer實例只應用於每個類裝入器,而不應用於每個VM。
有關LoadTimeWeaver實現及其設置的更多信息,請參閱AOP一章中的Spring配置,這些實現可以是通用的,也可以是針對各種平臺(如Tomcat、JBoss和WebSphere)定製的。

如Spring配置中所述,您可以通過使用上下文的@ enableloadtimeweave註釋來配置上下文範圍的LoadTimeWeaver:load-time-weaver XML元素。這樣一個全局編織器會被所有JPA LocalContainerEntityManagerFactoryBean實例自動獲取。下面的示例展示了設置加載時編織器的首選方法,它提供了平臺的自動檢測(例如Tomcat的可編織類加載器或Spring的JVM代理),並將編織器自動傳播到所有感知編織的bean:

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

但是,如果需要,您可以通過loadTimeWeaver屬性手動指定專用的編織器,如下面的示例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    </property>
</bean>

無論如何配置LTW,通過使用這種技術,依賴於工具的JPA應用程序可以在目標平臺(例如Tomcat)中運行,而不需要代理。當託管應用程序依賴於不同的JPA實現時,這一點尤其重要,因爲JPA轉換器僅應用於類加載器級別,因此彼此隔離。

處理多個持久性單元
對於依賴於多個持久性單元位置的應用程序(例如,存儲在類路徑中的各種jar中),Spring提供了PersistenceUnitManager作爲一箇中央存儲庫,並避免了持久性單元發現過程,這可能是昂貴的。默認實現允許指定多個位置。這些位置將被解析,然後通過持久性單元名進行檢索。(默認情況下,類路徑會搜索META-INF/persistence.xml文件。)下面的示例配置多個位置:

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
            <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
            <value>classpath:/my/package/**/custom-persistence.xml</value>
            <value>classpath*:META-INF/persistence.xml</value>
        </list>
    </property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        </map>
    </property>
    <!-- if no datasource is specified, use this one -->
    <property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="persistenceUnitName" value="myCustomUnit"/>
</bean>

默認的實現允許定製PersistenceUnitInfo實例(在它們被提供給JPA提供者之前),可以通過聲明的方式(通過它的屬性,它會影響所有的託管單元),也可以通過編程的方式(通過PersistenceUnitPostProcessor,它允許持久化單元的選擇)。如果沒有指定PersistenceUnitManager,則由LocalContainerEntityManagerFactoryBean創建並在內部使用它。

背景引導
LocalContainerEntityManagerFactoryBean通過bootstrapExecutor屬性支持後臺引導,如下例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="bootstrapExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
    </property>
</bean>

實際的JPA提供者引導被傳遞給指定的執行程序,然後並行地運行到應用程序引導線程。暴露的EntityManagerFactory代理可以被注入到其他應用程序組件中,甚至能夠響應EntityManagerFactoryInfo配置檢查。但是,一旦實際的JPA提供者被其他組件訪問(例如,調用createEntityManager),這些調用就會阻塞,直到後臺引導完成。特別是,當您使用Spring Data JPA時,請確保爲其存儲庫設置延遲引導。

4.4.2 實現基於JPA: EntityManagerFactory和EntityManager的DAOs

注意:儘管EntityManagerFactory實例是線程安全的,但EntityManager實例不是。根據JPA規範的定義,注入的JPA實體管理器的行爲類似於從應用服務器的JNDI環境中獲取的EntityManager。它將所有調用委託給當前事務EntityManager(如果有的話)。否則,每個操作都會返回到新創建的EntityManager,這實際上使它的使用成爲線程安全的。

通過使用注入的EntityManagerFactory或EntityManager,可以針對普通JPA編寫代碼,而不需要任何Spring依賴。如果啓用了PersistenceAnnotationBeanPostProcessor, Spring可以在字段和方法級別理解@PersistenceUnit和@PersistenceContext註釋。下面的示例顯示了一個使用@PersistenceUnit註釋的普通JPA DAO實現:

public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public Collection loadProductsByCategory(String category) {
        try (EntityManager em = this.emf.createEntityManager()) {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
    }
}

前面的DAO不依賴於Spring,仍然很好地適合於Spring應用程序上下文。此外,DAO利用註釋要求注入默認的EntityManagerFactory,如下面的bean定義示例所示:

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

作爲顯式定義PersistenceAnnotationBeanPostProcessor的替代方法,可以考慮在應用程序上下文配置中使用Spring context: annotationconfig XML元素。這樣做會自動註冊所有基於註釋的Spring標準後置處理器,包括CommonAnnotationBeanPostProcessor等等。
考慮下面的例子:

<beans>

    <!-- post-processors for all standard config annotations -->
    <context:annotation-config/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

這種DAO的主要問題是它總是通過工廠創建一個新的EntityManager。您可以通過請求注入一個事務EntityManager(也稱爲“共享EntityManager”,因爲它是實際事務EntityManager的一個共享的、線程安全的代理)而不是工廠來避免這種情況。下面的例子演示瞭如何做到這一點:

public class ProductDaoImpl implements ProductDao {

    @PersistenceContext
    private EntityManager em;

    public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return query.getResultList();
    }
}

@PersistenceContext註釋有一個名爲type的可選屬性,默認爲PersistenceContextType.TRANSACTION。您可以使用此默認設置來接收共享的EntityManager代理。另一種選擇,PersistenceContextType。擴展,是完全不同的事情。這導致了所謂的擴展EntityManager,它不是線程安全的,因此不能在併發訪問的組件(如spring管理的單例bean)中使用。擴展的EntityManager實例應該只在有狀態組件中使用,例如,駐留在會話中,EntityManager的生命週期不與當前事務綁定,而是完全取決於應用程序。

方法和field-level注入
您可以對類中的字段或方法應用表明依賴項注入的註釋(例如@PersistenceUnit和@PersistenceContext)——因此表達式有“方法級注入”和“字段級注入”。字段級註釋簡潔易用,而方法級註釋允許對注入的依賴項進行進一步處理。在這兩種情況下,成員可見性(公共的、受保護的或私有的)並不重要。

那麼類級別的註釋呢?
在Java EE平臺上,它們用於依賴項聲明,而不是資源注入。

注入的EntityManager是spring管理的(知道正在進行的事務)。儘管新的DAO實現使用EntityManager的方法級注入,而不是EntityManagerFactory,但是由於使用了註釋,所以不需要在應用程序上下文XML中進行任何更改。

這種DAO風格的主要優點是它只依賴於Java Persistence API。不需要導入任何Spring類。此外,正如JPA註釋所理解的那樣,注入將由Spring容器自動應用。從非侵入性的角度來看,這很有吸引力,而且對於JPA開發人員來說更自然。

4.4.3. Spring-driven JPA transactions

我們強烈建議您閱讀聲明式事務管理(如果您還沒有這樣做的話),以獲得對Spring聲明式事務支持的更詳細的介紹。

JPA的推薦策略是通過JPA的本地事務支持實現本地事務。Spring的JpaTransactionManager針對任何常規的JDBC連接池(沒有XA需求)提供了許多從本地JDBC事務(例如特定於事務的隔離級別和資源級只讀優化)獲得的功能。

Spring JPA還允許配置的JpaTransactionManager將JPA事務公開給訪問相同數據源的JDBC訪問代碼,前提是註冊的JpaDialect支持底層JDBC連接的檢索。Spring爲EclipseLink和Hibernate JPA實現提供了方言。有關JpaDialect機制的詳細信息,請參閱下一節。

注意:作爲一個直接的替代方案,Spring的本機HibernateTransactionManager能夠在Spring Framework 5.1和Hibernate 5.2/5.3的基礎上與JPA訪問代碼進行交互,從而適應Hibernate的一些細節並提供JDBC交互。這在結合LocalSessionFactoryBean設置時特別有意義。有關JPA交互的詳細信息,請參閱本機Hibernate設置。

4.4.4。理解JpaDialect和JpaVendorAdapter
作爲一個高級特性,JpaTransactionManager和AbstractEntityManagerFactoryBean的子類允許自定義JpaDialect傳遞到JpaDialect bean屬性。JpaDialect實現可以支持Spring支持的以下高級特性,通常以特定於供應商的方式:

  • 應用特定的事務語義(例如自定義隔離級別或事務超時)
  • 檢索事務性JDBC連接(用於公開基於JDBC的dao)
  • 持久化異常到Spring數據訪問異常的高級轉換

這對於特殊的事務語義和異常的高級翻譯尤其有價值。默認實現(DefaultJpaDialect)不提供任何特殊功能,如果需要前面列出的特性,您必須指定適當的方言。

警告:作爲一個更廣泛的提供者適應工具,主要用於Spring的全功能LocalContainerEntityManagerFactoryBean設置,JpaVendorAdapter將JpaDialect的功能與其他特定於提供者的缺省設置結合起來。爲Hibernate或EclipseLink分別指定HibernateJpaVendorAdapter或EclipseLinkJpaVendorAdapter是自動配置EntityManagerFactory設置的最方便方法。請注意,這些提供程序適配器主要設計用於spring驅動的事務管理(即用於JpaTransactionManager)。

請參閱JpaDialect和JpaVendorAdapter javadoc,以瞭解其操作的更多細節,以及如何在Spring的JPA支持中使用它們。

4.4.5。使用JTA事務管理設置JPA
作爲JpaTransactionManager的替代方案,Spring還允許通過JTA進行多資源事務協調,無論是在Java EE環境中還是使用獨立事務協調器(如Atomikos)。除了選擇Spring的JtaTransactionManager而不是JpaTransactionManager,你還需要採取以下幾步:

  • 底層JDBC連接池需要支持xa並與事務協調器集成。這在Java EE環境中通常很簡單,通過JNDI公開不同類型的數據源。有關詳細信息,請參閱應用服務器文檔。類似地,獨立的事務協調器通常附帶特殊的xa集成的數據源實現。再次檢查它的文檔。
  • 需要爲JTA配置JPA EntityManagerFactory設置。這是特定於提供程序的,通常通過在LocalContainerEntityManagerFactoryBean上指定爲jpaProperties的特殊屬性實現。對於Hibernate,這些屬性甚至是特定於版本的。有關詳細信息,請參閱Hibernate文檔。
  • Spring的HibernateJpaVendorAdapter強制執行某些面向Spring的默認設置,比如連接釋放模式on-close,它與Hibernate 5.0中的默認設置匹配,但在5.1/5.2中不再匹配。對於JTA設置,要麼不聲明HibernateJpaVendorAdapter開始,要麼關閉它的prepareConnection標誌。或者,設置Hibernate 5.2的Hibernate .connection。handling_mode屬性到delayed_tion_and_release_after_statement來恢復Hibernate自己的默認值。有關WebLogic的相關說明,請參閱Hibernate中虛假的應用服務器警告。
  • 或者,考慮從應用服務器本身獲取EntityManagerFactory(即通過JNDI查找而不是本地聲明的LocalContainerEntityManagerFactoryBean)。服務器提供的EntityManagerFactory可能需要服務器配置中的特殊定義(使部署的可移植性降低),但它是爲服務器的JTA環境設置的。

4.4.6。JPA交互的本機Hibernate設置和本機Hibernate事務
在Spring框架5.1和Hibernate 5.2/5.3中,本地LocalSessionFactoryBean設置與HibernateTransactionManager相結合,允許與@PersistenceContext和其他JPA訪問代碼進行交互。Hibernate SessionFactory現在本地實現JPA的EntityManagerFactory接口,而Hibernate會話句柄是JPA EntityManager。Spring的JPA支持功能自動檢測本機Hibernate會話。

本機Hibernate這樣的設置,因此,作爲替代標準JPA LocalContainerEntityManagerFactoryBean和JpaTransactionManager組合在很多情況下,允許交互SessionFactory.getCurrentSession()(還有HibernateTemplate)旁邊@PersistenceContext EntityManager在同一個地方事務。這樣的設置還提供了更強的Hibernate集成和更多的配置靈活性,因爲它不受JPA引導契約的約束。

在這樣的場景中,您不需要HibernateJpaVendorAdapter配置,因爲Spring的本機Hibernate設置提供了更多的特性(例如,自定義Hibernate Integrator設置、Hibernate 5.3 bean容器集成以及針對只讀事務的更強優化)。最後,您還可以通過LocalSessionFactoryBuilder來表示本機Hibernate設置,與@Bean風格的配置無縫集成(不涉及FactoryBean)。

注意:LocalSessionFactoryBean和LocalSessionFactoryBuilder支持後臺引導,就像JPA LocalContainerEntityManagerFactoryBean一樣。有關介紹,請參閱後臺引導。
在LocalSessionFactoryBean上,這可以通過bootstrapExecutor屬性獲得。在可編程的LocalSessionFactoryBuilder上,一個重載的buildSessionFactory方法接受一個引導執行器參數。

5. 使用對象-XML映射器編組XML

5.1。介紹
本章描述Spring的對象- xml映射支持。對象-XML映射(簡稱O-X映射)是將XML文檔與對象進行轉換的過程。這種轉換過程也稱爲XML編組或XML序列化。本章交替使用這些術語。

在O-X映射字段中,封送器負責將對象(圖)序列化爲XML。以類似的方式,解組程序將XML反序列化爲對象圖。此XML可以採用DOM文檔、輸入或輸出流或SAX處理程序的形式。
使用Spring滿足O/X映射需求的一些好處是:

5.1.1。易於配置
Spring的bean工廠簡化了編組器的配置,而不需要構造JAXB上下文、JiBX綁定工廠等等。您可以像配置應用程序上下文中的任何其他bean一樣配置封送器。此外,許多封送器都可以使用基於XML名稱空間的配置,這使得配置更加簡單。

5.1.2中。一致的界面
Spring的O-X映射通過兩個全局接口進行操作:編組器和反編組器。通過這些抽象,您可以相對輕鬆地切換O-X映射框架,而不需要對執行編組的類進行多少更改。這種方法還有一個額外的好處,就是可以使用混合並匹配的方法(例如,一些編組是使用JAXB執行的,另一些是使用XStream執行的)以非侵入性的方式進行XML編組,從而使您能夠利用每種技術的優勢。

5.1.3。一致的異常層次結構
Spring提供了從底層O-X映射工具的異常到它自己的異常層次結構的轉換,其中XmlMappingException作爲根異常。這些運行時異常包裝原始異常,因此不會丟失任何信息。

5.2. Marshaller and Unmarshaller
如引言中所述,編組程序將對象序列化爲XML,而反編組程序將XML流反序列化爲對象。本節描述用於此目的的兩個Spring接口。

5.2.1。理解Marshaller
Spring抽象了org.springframework.oxm背後的所有編組操作。編組接口,其主要方法如下:

public interface Marshaller {

    /**
     * Marshal the object graph with the given root into the provided Result.
     */
    void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}

Marshaller接口有一個主要方法,它將給定對象封送到給定的javax.xml.transform.Result。結果是一個基本表示XML輸出抽象的標記接口。具體實現封裝了各種XML表示,如下表所示:

Result implementation Wraps XML representation

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.Filejava.io.OutputStream, or java.io.Writer

注意:儘管marshal()方法接受一個普通對象作爲其第一個參數,但大多數Marshaller實現不能處理任意對象。相反,對象類必須映射到一個映射文件中,必須用註釋進行標記,必須向編組器註冊,或者必須有一個公共基類。請參閱本章後面的部分,以確定O-X技術如何管理這一點。

5.2.2。理解Unmarshaller
與編組器類似,我們有org.springframe .oxm。解組接口,如下清單所示:

public interface Unmarshaller {

    /**
     * Unmarshal the given provided Source into an object graph.
     */
    Object unmarshal(Source source) throws XmlMappingException, IOException;
}

這個接口還有一個方法,它從給定的javax.xml.transform中讀取數據。源(XML輸入抽象),並返回讀取的對象。因此,Source是一個具有三個具體實現的標記接口。每個封裝了不同的XML表示,如下表所示:

Source implementation Wraps XML representation

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSource, and org.xml.sax.XMLReader

StreamSource

java.io.Filejava.io.InputStream, or java.io.Reader

儘管有兩個獨立的編組接口(編組器和反編組器),Spring-WS中的所有實現都在一個類中實現。這意味着您可以連接一個marshaller類,並在您的applicationContext.xml中同時將其作爲marshaller和反編組器引用。

5.2.3。理解XmlMappingException
Spring將異常從底層的O-X映射工具轉換爲它自己的異常層次結構,使用XmlMappingException作爲根異常。這些運行時異常包裝原始異常,因此不會丟失任何信息。

另外,MarshallingFailureException和UnmarshallingFailureException提供了編組和反編組操作之間的區別,即使底層的O-X映射工具沒有這樣做。
O-X映射異常層次結構如下圖所示:

oxm exceptions

5.3。使用編組和解組器
您可以將Spring的OXM用於各種各樣的情況。在下面的示例中,我們使用它將spring管理的應用程序的設置封送爲XML文件。在下面的例子中,我們使用一個簡單的JavaBean來表示設置:

public class Settings {

    private boolean fooEnabled;

    public boolean isFooEnabled() {
        return fooEnabled;
    }

    public void setFooEnabled(boolean fooEnabled) {
        this.fooEnabled = fooEnabled;
    }
}

應用程序類使用此bean存儲其設置。除了一個主方法外,該類還有兩個方法:saveset()將設置bean保存到名爲settings的文件中。和loadSettings()再次加載這些設置。下面的main()方法構造了一個Spring應用程序上下文,並調用這兩個方法:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {

    private static final String FILE_NAME = "settings.xml";
    private Settings settings = new Settings();
    private Marshaller marshaller;
    private Unmarshaller unmarshaller;

    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    public void saveSettings() throws IOException {
        try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
            this.marshaller.marshal(settings, new StreamResult(os));
        }
    }

    public void loadSettings() throws IOException {
        try (FileInputStream is = new FileInputStream(FILE_NAME)) {
            this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
        }
    }

    public static void main(String[] args) throws IOException {
        ApplicationContext appContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Application application = (Application) appContext.getBean("application");
        application.saveSettings();
        application.loadSettings();
    }
}

應用程序需要設置編組器和反編組器屬性。我們可以使用以下applicationContext.xml來實現這一點:

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="xstreamMarshaller" />
        <property name="unmarshaller" ref="xstreamMarshaller" />
    </bean>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>

這個應用程序上下文使用XStream,但是我們可以使用本章後面描述的其他封送器實例。注意,在默認情況下,XStream不需要任何進一步的配置,因此bean定義相當簡單。還要注意,XStreamMarshaller同時實現了Marshaller和反編組器,因此我們可以在應用程序的Marshaller和反編組器屬性中引用XStreamMarshaller bean。

這個示例應用程序生成以下settings.xml文件:

?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

5.4。XML配置命名空間
通過使用來自OXM名稱空間的標記,可以更精確地配置封送器。要使這些標記可用,您必須首先在XML配置文件的前言中引用適當的模式。下面的例子演示瞭如何做到這一點:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:oxm="http://www.springframework.org/schema/oxm" //1
xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> //2
  1. 引用oxm模式。
  2. 指定oxm模式位置。

該模式提供了以下元素:

每個標記都在其各自的編組器部分中進行了解釋。但是,作爲一個示例,JAXB2編組器的配置可能類似於以下內容:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

5.5。JAXB
JAXB綁定編譯器將W3C XML模式轉換成一個或多個Java類(JAXB)。屬性文件,可能還有一些資源文件。JAXB還提供了一種從帶註釋的Java類生成模式的方法。

Spring支持JAXB 2.0 API作爲XML編組策略,遵循編組器和反編組器中描述的接口。相應的集成類駐留在org.springframework.oxm中。jaxb包。

5.5.1。使用Jaxb2Marshaller
Jaxb2Marshaller類同時實現了Spring的Marshaller和Unmarshaller接口。它需要一個上下文路徑來操作。您可以通過設置contextPath屬性來設置上下文路徑。上下文路徑是一個冒號分隔的Java包名列表,其中包含模式派生類。它還提供了一個classesToBeBound屬性,允許您設置一個由封送器支持的類數組。模式驗證是通過向bean指定一個或多個模式資源來執行的,如下面的示例所示:

<beans>
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>org.springframework.oxm.jaxb.Flight</value>
                <value>org.springframework.oxm.jaxb.Flights</value>
            </list>
        </property>
        <property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
    </bean>

    ...

</beans>

XML配置命名空間
jaxb2-marshaller元素配置org.springframe .oxm.jaxb。Jaxb2Marshaller,如下例所示:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

或者,您可以使用類綁定子元素提供要綁定到marshaller的類列表:

<oxm:jaxb2-marshaller id="marshaller">
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
    ...
</oxm:jaxb2-marshaller>

下表描述了可用的屬性:

Attribute Description Required

id

The ID of the marshaller

No

contextPath

The JAXB Context path

No

5.6。JiBX
JiBX框架提供了與Hibernate爲ORM提供的解決方案類似的解決方案:綁定定義定義瞭如何將Java對象轉換爲XML或從XML轉換爲Java對象的規則。在準備綁定和編譯類之後,JiBX綁定編譯器將增強類文件,並添加代碼來處理類實例與XML之間的轉換。

有關JiBX的更多信息,請參見JiBX網站。Spring集成類駐留在org.springframework.oxm.jibx包。

5.6.1。使用JibxMarshaller
JibxMarshaller類同時實現編組器和反編組器接口。要進行操作,需要對類的名稱進行封送,可以使用targetClass屬性設置該名稱。您還可以通過設置bindingName屬性來設置綁定名。在下面的例子中,我們綁定了Flights類:

<beans>
    <bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
        <property name="targetClass">org.springframework.oxm.jibx.Flights</property>
    </bean>
    ...
</beans>

JibxMarshaller是爲單個類配置的。如果要封送多個類,必須使用不同的targetClass屬性值配置多個JibxMarshaller實例。

XML配置命名空間
jibx-marshaller標記配置org.springframework.oxm.jibx.JibxMarshaller,如下例所示:

<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>

下表描述了可用的屬性:

Attribute Description Required

id

The ID of the marshaller

No

target-class

The target class for this marshaller

Yes

bindingName

The binding name used by this marshaller

No

5.7。XStream
XStream是一個簡單的庫,用於將對象序列化爲XML,然後再序列化回來。它不需要任何映射並生成乾淨的XML。
有關XStream的更多信息,請參見XStream網站。Spring集成類駐留在org.springframework.oxm.xstream包。

5.7.1。使用XStreamMarshaller
XStreamMarshaller不需要任何配置,可以直接在應用程序上下文中配置。爲了進一步定製XML,您可以設置一個別名映射,它由映射到類的字符串別名組成,如下面的示例所示:

<beans>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
        <property name="aliases">
            <props>
                <prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
            </props>
        </property>
    </bean>
    ...
</beans>

警告:默認情況下,XStream允許對任意類進行解組,這可能導致不安全的Java序列化效果。因此,我們不建議使用XStreamMarshaller從外部源(即Web)解組XML,因爲這會導致安全漏洞。
如果您選擇使用XStreamMarshaller來從外部源解封XML,請在XStreamMarshaller上設置supportedClasses屬性,如下面的示例所示:

<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
    <property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
    ...
</bean>

這樣做可以確保只有註冊的類纔有資格進行反編組。
此外,您可以註冊自定義轉換器,以確保只有您支持的類可以被解組。您可能希望添加一個CatchAllConverter作爲列表中的最後一個轉換器,以及顯式支持應該支持的域類的轉換器。因此,具有較低優先級和可能的安全漏洞的默認XStream轉換器不會被調用。

注意:注意,XStream是一個XML序列化庫,而不是數據綁定庫。因此,它只有有限的名稱空間支持。因此,它非常不適合在Web服務中使用。

6. 附錄
6.1。XML模式
附錄的這一部分列出了數據訪問的XML模式,包括以下內容:

6.11 The tx Schema
tx標記在Spring對事務的全面支持中配置所有這些bean。這些標記將在“事務管理”一章中討論。

注意:我們強烈建議您查看“spring-tx”。隨Spring發行版一起發佈的xsd'文件。該文件包含Spring事務配置的XML模式,並涵蓋tx名稱空間中的所有元素,包括屬性默認值和類似的信息。這個文件是內聯文檔,因此,爲了遵守DRY (Don 't Repeat Yourself)原則,這裏不重複信息。

出於完整性的考慮,要使用tx模式中的元素,您需要在Spring XML配置文件的頂部有以下序言。下面代碼段中的文本引用了正確的模式,因此tx名稱空間中的標記對您是可用的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" //1
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd  //2
        http://www.springframework.org/schema/aop  https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>
  1. 聲明tx名稱空間的用法。
  2. 指定位置(使用其他模式位置)。

注意:通常,當您使用tx名稱空間中的元素時,您也使用來自aop名稱空間的元素(因爲Spring中的聲明性事務支持是通過使用aop實現的)。前面的XML片段包含了引用aop模式所需的相關行,以便aop名稱空間中的元素對您可用。

6.1.2。jdbc模式
jdbc元素允許您快速配置嵌入式數據庫或初始化現有數據源。這些元素分別記錄在嵌入式數據庫支持和初始化數據源中。

要使用jdbc模式中的元素,您需要在Spring XML配置文件的頂部有以下序言。下面代碼段中的文本引用了正確的模式,因此jdbc名稱空間中的元素對您是可用的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" //1
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> //2

    <!-- bean definitions here -->

</beans>
  1. 聲明jdbc名稱空間的用法。
  2. 指定位置(使用其他模式位置)。

 

發佈了23 篇原創文章 · 獲贊 0 · 訪問量 484
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章