閱讀987 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《Spring 5 官方文檔》16.ORM和數據訪問(二)

16.3.4編程式事務劃分

開發者可以在應用程序的更高級別上對事務進行標定,而不用考慮低級別的數據訪問執行了多少操作。這樣不會對業務服務的實現進行限製;隻需要定義一個Spring的PlatformTransactionManager即可。當然,PlatformTransactionManager可以從多處獲取,但最好是通過setTransactionManager(..)方法以Bean來注入,正如ProductDAO應該由setProductDao(..)方法配置一樣。下麵的代碼顯示Spring應用程序上下文中的事務管理器和業務服務的定義,以及業務方法實現的示例:

  1. <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
  2. <property name="sessionFactory" ref="mySessionFactory"/>
  3. </bean>
  4. <bean id="myProductService" class="product.ProductServiceImpl">
  5. <property name="transactionManager" ref="myTxManager"/>
  6. <property name="productDao" ref="myProductDao"/>
  7. </bean>
  8. </beans>
  1. public class ProductServiceImpl implements ProductService {
  2. private TransactionTemplate transactionTemplate;
  3. private ProductDao productDao;
  4. public void setTransactionManager(PlatformTransactionManager transactionManager) {
  5. this.transactionTemplate = new TransactionTemplate(transactionManager);
  6. }
  7. public void setProductDao(ProductDao productDao) {
  8. this.productDao = productDao;
  9. }
  10. public void increasePriceOfAllProductsInCategory(final String category) {
  11. this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
  12. public void doInTransactionWithoutResult(TransactionStatus status) {
  13. List productsToChange = this.productDao.loadProductsByCategory(category);
  14. // do the price increase...
  15. }
  16. });
  17. }
  18. }

Spring的TransactionInterceptor允許任何檢查的應用異常到callback代碼中去,而TransactionTemplate還會非受檢異常觸發進行回調。TransactionTemplate則會因為非受檢異常或者是由應用標記事務回滾(通過TransactionStatus)。TransactionInterceptor也是一樣的處理邏輯,但是同時還允許基於方法配置回滾策略。

16.3.5事務管理策略

無論是TransactionTemplate或者是TransactionInterceptor都將實際的事務處理代理到PlatformTransactionManager實例上來進行處理的,這個實例的實現可以是一個HibernateTransactionManager(包含一個Hibernate的SessionFactory通過使用ThreadLocalSession),也可以是JatTransactionManager(代理到容器的JTA子係統)。開發者甚至可以使用一個自定義的PlatformTransactionManager的實現。現在,如果應用有需求需要需要部署分布式事務的話,隻是一個配置變化,就可以從本地Hibernate事務管理切換到JTA。簡單地用Spring的JTA事務實現來替換Hibernate事務管理器即可。因為引用的PlatformTransactionManager的是通用事務管理API,事務管理器之間的切換是無需修改代碼的。

對於那些跨越了多個Hibernate會話工廠的分布式事務,隻需要將JtaTransactionManager和多個LocalSessionFactoryBean定義相結合即可。每個DAO之後會獲取一個特定的SessionFactory引用。如果所有底層JDBC數據源都是事務性容器,那麼隻要使用JtaTransactionManager作為策略實現,業務服務就可以劃分任意數量的DAO和任意數量的會話工廠的事務。

無論是HibernateTransactionManager還是JtaTransactionManager都允許使用JVM級別的緩存來處理Hibernate,無需基於容器的事務管理器查找,或者JCA連接器(如果開發者沒有使用EJB來實例化事務的話)。

HibernateTransactionManager可以為指定的數據源的Hibernate JDBC的Connection轉成為純JDBC的訪問代碼。如果開發者僅訪問一個數據庫,則開發者完全可以不使用JTA,通過Hibernate和JDBC數據訪問進行高級別事務劃分。如果開發者已經通過LocalSessionFactoryBeandataSource屬性與DataSource設置了傳入的SessionFactoryHibernateTransactionManager會自動將Hibernate事務公開為JDBC事務。或者,開發者可以通過HibernateTransactionManagerdataSource屬性的配置以確定公開事務的類型。

16.3.6對比由容器管理的和本地定義的資源

開發者可以在不修改一行代碼的情況下,在容器管理的JNDISessionFactory和本地定義的SessionFactory之間進行切換。是否將資源定義保留在容器中,還是僅僅留在應用中,都取決於開發者使用的事務策略。相對於Spring定義的本地SessionFactory來說,手動注冊的JNDISessionFactory沒有什麼優勢。通過Hibernate的JCA連接器來發布一個SessionFactory隻會令代碼更符合J2EE服務標準,但是並不會帶來任何實際的價值。

Spring對事務支持不限於容器。使用除JTA之外的任何策略配置,事務都可以在獨立或測試環境中工作。特別是在單數據庫事務的典型情況下,Spring的單一資源本地事務支持是一種輕量級和強大的替代JTA的解決方案。當開發者使用本地EJB無狀態會話Bean來驅動事務時,即使隻訪問單個數據庫,並且隻使用無狀態會話Bean來通過容器管理的事務來提供聲明式事務,開發者的代碼依然是依賴於EJB容器和JTA的。同時,以編程方式直接使用JTA也需要一個J2EE環境的。 JTA不涉及JTA本身和JNDI DataSource實例方麵的容器依賴關係。對於非Spring,JTA驅動的Hibernate事務,開發者必須使用Hibernate JCA連接器或開發額外的Hibernate事務代碼,並為JVM級緩存正確配置TransactionManagerLookup

Spring驅動的事務可以與本地定義的HibernateSessionFactory一樣工作,就像本地JDBC DataSource訪問單個數據庫一樣。但是,當開發者有分布式事務的要求的情況下,隻能選擇使用Spring JTA事務策略。JCA連接器是需要特定容器遵循一致的部署步驟的,而且顯然JCA支持是需要放在第一位的。JCA的配置需要比部署本地資源定義和Spring驅動事務的簡單web應用程序需要更多額外的的工作。同時,開發者還需要使用容器的企業版,比如,如果開發者使用的是WebLogic Express的非企業版,就是不支持JCA的。具有跨越單個數據庫的本地資源和事務的Spring應用程序適用於任何基於J2EE的Web容器(不包括JTA,JCA或EJB),如Tomcat,Resin或甚至是Jetty。此外,開發者可以輕鬆地在桌麵應用程序或測試套件中重用中間層代碼。

綜合前麵的敘述,如果不使用EJB,請盡量使用本地的SessionFactory設置和Spring的HibernateTransactionManagerJtaTransactionManager。開發者能夠得到了前麵提到的所有好處,包括適當的事務性JVM級緩存和分布式事務支持,而且沒有容器部署的不便。隻有必須配合EJB使用的時候,JNDI通過JCA連接器來注冊HibernateSessionFactory才有價值。

16.3.7Hibernate的虛假應用服務器警告

在某些具有非常嚴格的XADataSource實現的JTA環境(目前隻有一些WebLogic Server和WebSphere版本)中,當配置Hibernate時,沒有考慮到JTA的 PlatformTransactionManager對象,可能會在應用程序服務器日誌中顯示虛假警告或異常。這些警告或異常經常描述正在訪問的連接不再有效,或者JDBC訪問不再有效。這通常可能是因為事務不再有效。例如,這是WebLogic的一個實際異常:

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

開發者可以通過配置令Hibernate意識到Spring中同步的JTAPlatformTransactionManager實例的存在,這樣即可消除掉前麵所說的虛假警告信息。開發者有以下兩種選擇:

  • 如果在應用程序上下文中,開發者已經直接獲取了JTA PlatformTransactionManager對象(可能是從JNDI到JndiObjectFactoryBean或者<jee:jndi-lookup>標簽),並將其提供給Spring的JtaTransactionManager(其中最簡單的方法就是指定一個引用bean將此JTA PlatformTransactionManager實例定義為LocalSessionFactoryBeanjtaTransactionManager屬性的值)。 Spring之後會令PlatformTransactionManager對象對Hibernate可見。
  • 更有可能開發者無法獲取JTAPlatformTransactionManager實例,因為Spring的JtaTransactionManager是可以自己找到該實例的。因此,開發者需要配置Hibernate令其直接查找JTA PlatformTransactionManager。開發者可以如Hibernate手冊中所述那樣通過在Hibernate配置中配置應用程序服務器特定的TransactionManagerLookup類來執行此操作。

本節的其餘部分描述了在PlatformTransactionManager對Hibernate可見和PlatformTransactionManager對Hibernate不可見的情況下發生的事件序列:

當Hibernate未配置任何對JTAPlatformTransactionManager的進行查找時,JTA事務提交時會發生以下事件:

  • JTA事務提交
  • Spring的JtaTransactionManager與JTA事務同步,所以它被JTA事務管理器通過afterCompletion回調調用。
  • 在其他活動中,此同步令Spring通過Hibernate的afterTransactionCompletion觸發回調(用於清除Hibernate緩存),然後在Hibernate Session上調用close(),從而令Hibernate嚐試close()JDBC連接。
  • 在某些環境中,因為事務已經提交,應用程序服務器會認為Connection不可用,導致Connection.close()調用會觸發警告或錯誤。

當Hibernate配置了對JTAPlatformTransactionManager進行查找時,JTA事務提交時會發生以下事件:

  • JTA事務準備提交
  • Spring的JtaTransactionManager與JTA事務同步,所以JTA事務管理器通過beforeCompletion方法來回調事務。
  • Spring確定Hibernate與JTA事務同步,並且行為與前一種情況不同。假設Hibernate Session需要關閉,Spring將會關閉它。
  • JTA事務提交。
  • Hibernate與JTA事務同步,所以JTA事務管理器通過afterCompletion方法回調事務,可以正確清除其緩存。

 

16.4 JPA

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

16.4.1 Spring中JPA配置的三個選項

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

LocalEntityManagerFactoryBean

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

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

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

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

從JNDI中獲取EntityManagerFactory

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

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

  1. <beans>
  2. <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
  3. </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定義:

  1. <beans>
  2. <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  3. <property name="dataSource" ref="someDataSource"/>
  4. <property name="loadTimeWeaver">
  5. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
  6. </property>
  7. </bean>
  8. </beans>

下麵的例子是一個典型的persistence.xml文件:

  1. <persistence xmlns="https://java.sun.com/xml/ns/persistence" version="1.0">
  2. <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
  3. <mapping-file>META-INF/orm.xml</mapping-file>
  4. <exclude-unlisted-classes/>
  5. </persistence-unit>
  6. </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:

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

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

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

無論LTW如何配置,使用這種技術,依賴於儀器的JPA應用程序都可以在目標平台(例如:Tomcat)中運行,而不需要代理。這尤其重要的是當主機應用程序依賴於不同的JPA實現時,因為JPA轉換器僅應用於類加載器級,彼此隔離。

處理多個持久化單元

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

  1. <bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
  2. <property name="persistenceXmlLocations">
  3. <list>
  4. <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
  5. <value>classpath:/my/package/**/custom-persistence.xml</value>
  6. <value>classpath*:META-INF/persistence.xml</value>
  7. </list>
  8. </property>
  9. <property name="dataSources">
  10. <map>
  11. <entry key="localDataSource" value-ref="local-db"/>
  12. <entry key="remoteDataSource" value-ref="remote-db"/>
  13. </map>
  14. </property>
  15. <!-- if no datasource is specified, use this one -->
  16. <property name="defaultDataSource" ref="remoteDataSource"/>
  17. </bean>
  18. <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  19. <property name="persistenceUnitManager" ref="pum"/>
  20. <property name="persistenceUnitName" value="myCustomUnit"/>
  21. </bean>

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

轉載自 並發編程網 - ifeve.com

最後更新:2017-05-18 10:33:00

  上一篇:go  寫一個簡單的工作流(二)
  下一篇:go  寫一個簡單的工作流,基於petri網