Spring ORM數據訪問——JPA

JPA

Spring JPA在org.springframework.orm.jpa包中已經可用,Spring JPA用了Hibernate集成相似的方法來提供更易於理解的JPA支持,與此同時,瞭解了JPA底層實現,可以理解更多的Spring JPA特性。

Spring中JPA配置的三個選項

Spring JPA支持提供了三種配置JPAEntityManagerFactory的方法,之後通過EntityManagerFactory來獲取對應的實體管理器。

LocalEntityManagerFactoryBean

通常只有在簡單的部署環境中使用此選項,例如在獨立應用程序或者進行集成測試時,纔會使用這種方式。

LocalEntityManagerFactoryBean創建一個適用於應用程序且僅使用JPA進行數據訪問的簡單部署環境的EntityManagerFactory。工廠bean會使用JPAPersistenceProvider自動檢測機制,並且在大多數情況下,僅要求開發者指定持久化單元的名稱:

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

這種形式的JPA部署是最簡單的,同時限制也很多。開發者不能引用現有的JDBCDataSource bean定義,並且不支持全局事務。而且,持久化類的織入(weaving)(字節碼轉換)是特定於提供者的,通常需要在啓動時指定特定的JVM代理。該選項僅適用於符合JPA Spec的獨立應用程序或測試環境。

從JNDI中獲取EntityManagerFactory

在部署到J2EE服務器時可以使用此選項。檢查服務器的文檔來了解如何將自定義JPA提供程序部署到服務器中,從而對服務器進行比默認更多的個性化定製。

從JNDI獲取EntityManagerFactory(例如在Java EE環境中),只需要在XML配置中加入配置信息即可:

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

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

在這種情況下,整個持久化單元部署(包括持久化類的織入(weaving)(字節碼轉換))都取決於J2EE服務器。JDBC DataSource通過META-INF/persistence.xml文件中的JNDI位置進行定義; 而EntityManager事務與服務器JTA子系統集成。 Spring僅使用獲取的EntityManagerFactory,通過依賴注入將其傳遞給應用程序對象,通常通過JtaTransactionManager來管理持久性單元的事務。

如果在同一應用程序中使用多個持久性單元,則這種JNDI檢索的持久性單元的bean名稱應與應用程序用於引用它們的持久性單元名稱相匹配,例如@PersistenceUnit@PersistenceContext註釋。

LocalContainerEntityManagerFactoryBean

在基於Spring的應用程序環境中使用此選項來實現完整的JPA功能。這包括諸如Tomcat的Web容器,以及具有複雜持久性要求的獨立應用程序和集成測試。

LocalContainerEntityManagerFactoryBean可以完全控制EntityManagerFactory的配置,同時適用於需要細粒度定製的環境。 LocalContainerEntityManagerFactoryBean會基於persistence.xml文件,dataSourceLookup策略和指定的loadTimeWeaver來創建一個PersistenceUnitInfo實例。因此,可以在JNDI之外使用自定義數據源並控制織入(weaving)過程。以下示例顯示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設置選項,允許在應用程序中進行靈活的本地配置。它支持連接到現有的JDBCDataSource,支持本地和全局事務等。但是,它對運行時環境施加了需求,其中之一就是如果持久性提供程序需要字節碼轉換,就需要有織入(weaving)能力的類加載器。

此選項可能與J2EE服務器的內置JPA功能衝突。在完整的J2EE環境中,請考慮從JNDI獲取EntityManagerFactory。或者,在開發者的LocalContainerEntityManagerFactoryBean定義中指定一個自定義persistenceXmlLocation,例如META-INF/my-persistence.xml,並且只在應用程序jar文件中包含有該名稱的描述符。因爲J2EE服務器僅查找默認的META-INF/persistence.xml文件,所以它會忽略這種自定義持久性單元,從而避免了與Spring驅動的JPA設置之間發生衝突。 (例如,這適用於Resin 3.1)

何時需要加載時間織入?
並非所有JPA提供商都需要JVM代理。Hibernate就是一個不需要JVM代理的例子。如果開發者的提供商不需要代理或開發者有其他替代方案,例如通過定製編譯器或Ant任務在構建時應用增強功能,則不用使用加載時間編織器。

LoadTimeWeaver是一個Spring提供的接口,它允許以特定方式插入JPAClassTransformer實例,這取決於環境是Web容器還是應用程序服務器。 通過代理掛載ClassTransformers通常性能較差。代理會對整個虛擬機進行操作,並檢查加載的每個類,這是生產服務器環境中最不需要的額外負載。

Spring爲各種環境提供了一些LoadTimeWeaver實現,允許ClassTransformer實例僅適用於每個類加載器,而不是每個VM。

有關LoadTimeWeaver的實現及其設置的通用或定製的各種平臺(如Tomcat,WebLogic,GlassFish,Resin和JBoss)的更多瞭解,請參閱AOP章節中的Spring配置一節。

如前面部分所述,開發者可以使用@EnableLoadTimeWeaving註解或者load-time-weaverXML元素來配置上下文範圍的LoadTimeWeaver。所有JPALocalContainerEntityManagerFactoryBeans都會自動拾取這樣的全局織入器。這是設置加載時間織入器的首選方式,爲平臺(WebLogic,GlassFish,Tomcat,Resin,JBoss或VM代理)提供自動檢測功能,並將織入組件自動傳播到所有可以感知織入者的Bean:

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

開發者也可以通過LocalContainerEntityManagerFactoryBeanloadTimeWeaver屬性來手動指定專用的織入器:

<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轉換器僅應用於類加載器級,彼此隔離。

處理多個持久化單元

例如,對於依賴存儲在類路徑中的各種JARS中的多個持久性單元位置的應用程序,Spring將PersistenceUnitManager作爲中央倉庫來避免可能昂貴的持久性單元發現過程。默認實現允許指定多個位置,這些位置將通過持久性單元名稱進行解析並稍後檢索。(默認情況下,搜索classpath下的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>

在默認實現傳遞給JPA provider之前,是允許通過屬性(影響全部持久化單元)或者通過PersistenceUnitPostProcessor以編程(對選擇的持久化單元進行)進行對PersistenceUnitInfo進行自定義的。如果沒有指定PersistenceUnitManager,則由LocalContainerEntityManagerFactoryBean在內部創建和使用。

基於JPA的EntityManagerFactory和EntityManager來實現DAO

雖然EntityManagerFactory實例是線程安全的,但EntityManager實例不是。注入的JPA EntityManager的行爲類似於從JPA Spec中定義的應用程序服務器的JNDI環境中提取的EntityManager。它將所有調用委託給當前事務的EntityManager(如果有);否則,它每個操作返回的都是新創建的EntityManager,通過使用不同的EntityManager來保證使用時的線程安全。

通過注入的方式使用EntityManagerFactoryEntityManager來編寫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) {
        EntityManager em = this.emf.createEntityManager();
        try {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
        finally {
            if (em != null) {
                em.close();
            }
        }
    }
}

上面的DAO對Spring的實現是沒有任何依賴的,而且很適合與Spring的應用程序上下文進行集成。而且,DAO還可以通過註解來注入默認的EntityManagerFactory

<beans>

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

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

如果不想明確定義PersistenceAnnotationBeanPostProcessor,可以考慮在應用程序上下文配置中使用Spring上下文annotation-configXML元素。這樣做會自動註冊所有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註解具有可選的屬性類型,默認值爲PersistenceContextType.TRANSACTION。此默認值是開發者所需要接收共享的EntityManager代理。替代方案PersistenceContextType.EXTENDED則完全不同:該方案會返回一個所謂擴展的EntityManager,該EntityManager不是線程安全的,因此不能在併發訪問的組件(如Spring管理的單例Bean)中使用。擴展實體管理器僅應用於狀態組件中,比如持有會話的組件,其中EntityManager的生命週期與當前事務無關,而是完全取決於應用程序。

方法和實例變量級別注入
指示依賴注入(例如@PersistenceUnit@PersistenceContext)的註解可以應用於類中的實例變量或方法,也就是表達式方法級注入和實例變量級注入。實例變量級註釋簡潔易用,而方法級別允許進一步處理注入的依賴關係。在這兩種情況下,成員的可見性(publicprotectedprivate)並不重要。
類級註解怎麼辦?
在J2EE平臺上,它們用於依賴關係聲明,而不是資源注入。

注入的EntityManager是由Spring管理的(Spring可以意識到正在進行的事務)。重要的是要注意,因爲通過註解進行注入,即使新的DAO實現使用通過方法注入的EntityManager而不是EntityManagerFactory的注入的,在應用程序上下文XML中不需要進行任何修改。

這種DAO風格的主要優點是它只依賴於Java Persistence API;不需要導入任何Spring的實現類。而且,Spring容器可以識別JPA註解來實現自動的注入和管理。從非侵入的角度來看,這種風格對JPA開發者來說可能更爲自然。

Spring驅動的JPA事務

如果開發者還沒有閱讀聲明式事務管理,強烈建議開發者先行閱讀,這樣可以更詳細地瞭解Spring的對聲明式事務支持。

JPA的推薦策略是通過JPA的本地事務支持的本地事務。 Spring的JpaTransactionManager提供了許多來自本地JDBC事務的功能,例如針對任何常規JDBC連接池(不需要XA要求)指定事務的隔離級別和資源級只讀優化等。

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

JpaDialect和JpaVendorAdapter

作爲高級功能,JpaTransactionManagerAbstractEntityManagerFactoryBean的子類支持自定義JpaDialect,將其作爲Bean傳遞給jpaDialect屬性。JpaDialect實現可以以供應商特定的方式使能Spring支持的一些高級功能:

  • 應用特定的事務語義,如自定義隔離級別或事務超時
  • 爲基於JDBC的DAO導出事務性JDBC連接
  • PersistenceExceptions到SpringDataAccessExceptions的異常轉義

這對於特殊的事務語義和異常的高級翻譯特別有價值。但是Spring使用的默認實現(DefaultJpaDialect)是不提供任何特殊功能的。如果需要上述功能,則必須指定適當的方言纔可以。

作爲一個更廣泛的供應商適應設施,主要用於Spring的全功能LocalContainerEntityManagerFactoryBean設置,JpaVendorAdapterJpaDialect的功能與其他提供者特定的默認設置相結合。指定HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter是分別爲Hibernate或EclipseLink自動配置EntityManagerFactory設置的最簡單方便的方法。但是請注意,這些提供程序適配器主要是爲了與Spring驅動的事務管理一起使用而設計的,即爲了與JpaTransactionManager配合使用的。

有關其操作的更多詳細信息以及在Spring的JPA支持中如何使用,請參閱JpaDialectJpaVendorAdapter的Javadoc。

爲JPA配置JTA事務管理

作爲JpaTransactionManager的替代方案,Spring還允許通過JTA在J2EE環境中或與獨立的事務協調器(如Atomikos)進行多資源事務協調。除了用Spring的JtaTransactionManager替換JpaTransactionManager,還有需要以下一些操作:

  • 底層JDBC連接池是需要具備XA功能,並與開發者的事務協調器集成的。這在J2EE環境中很簡單,只需通過JNDI導出不同類型的DataSource即可。有關導出DataSource等詳細信息,可以參考應用服務器文檔。類似地,獨立的事務協調器通常帶有特殊的XA集成的DataSource實現。
  • 需要爲JTA配置JPAEntityManagerFactory。這是特定於提供程序的,通常通過在LocalContainerEntityManagerFactoryBean的特殊屬性指定爲”jpaProperties”。在使用Hibernate的情況下,這些屬性甚至是需要基於特定的版本的;請查閱Hibernate文檔以獲取詳細信息。
  • Spring的HibernateJpaVendorAdapter會強制執行某些面向Spring的默認設置,例如在Hibernate 5.0中匹配Hibernate自己的默認值的連接釋放模式“on-close”,但在5.1 / 5.2中不再存在。對於JTA設置,不要聲明HibernateJpaVendorAdapter開始,或關閉其prepareConnection標誌。或者,將Hibernate 5.2的hibernate.connection.handling_mode屬性設置爲DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT以恢復Hibernate自己的默認值。有關WebLogic的相關說明,請參考Hibernate的虛假應用服務器警告一節。
  • 或者,可以考慮從應用程序服務器本身獲取EntityManagerFactory,即通過JNDI查找而不是本地聲明的LocalContainerEntityManagerFactoryBean。服務器提供的EntityManagerFactory可能需要在服務器配置中進行特殊定義,減少了部署的移植性,但是EntityManagerFactory將爲開箱即用的服務器JTA環境設置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章