《Spring 5 官方文檔》16.ORM和數據訪問(二)
16.3.4編程式事務劃分
開發者可以在應用程序的更高級別上對事務進行標定,而不用考慮低級別的數據訪問執行了多少操作。這樣不會對業務服務的實現進行限製;隻需要定義一個Spring的PlatformTransactionManager
即可。當然,PlatformTransactionManager
可以從多處獲取,但最好是通過setTransactionManager(..)
方法以Bean來注入,正如ProductDAO
應該由setProductDao(..)
方法配置一樣。下麵的代碼顯示Spring應用程序上下文中的事務管理器和業務服務的定義,以及業務方法實現的示例:
<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
允許任何檢查的應用異常到callback
代碼中去,而TransactionTemplate
還會非受檢異常觸發進行回調。TransactionTemplate
則會因為非受檢異常或者是由應用標記事務回滾(通過TransactionStatus
)。TransactionInterceptor
也是一樣的處理邏輯,但是同時還允許基於方法配置回滾策略。
16.3.5事務管理策略
無論是TransactionTemplate
或者是TransactionInterceptor
都將實際的事務處理代理到PlatformTransactionManager
實例上來進行處理的,這個實例的實現可以是一個HibernateTransactionManager
(包含一個Hibernate的SessionFactory
通過使用ThreadLocal
的Session
),也可以是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數據訪問進行高級別事務劃分。如果開發者已經通過LocalSessionFactoryBean
的dataSource
屬性與DataSource
設置了傳入的SessionFactory
,HibernateTransactionManager
會自動將Hibernate事務公開為JDBC事務。或者,開發者可以通過HibernateTransactionManager
的dataSource
屬性的配置以確定公開事務的類型。
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的HibernateTransactionManager
或JtaTransactionManager
。開發者能夠得到了前麵提到的所有好處,包括適當的事務性JVM級緩存和分布式事務支持,而且沒有容器部署的不便。隻有必須配合EJB使用的時候,JNDI通過JCA連接器來注冊HibernateSessionFactory
才有價值。
16.3.7Hibernate的虛假應用服務器警告
在某些具有非常嚴格的XADataSource
實現的JTA環境(目前隻有一些WebLogic Server和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意識到Spring中同步的JTAPlatformTransactionManager
實例的存在,這樣即可消除掉前麵所說的虛假警告信息。開發者有以下兩種選擇:
- 如果在應用程序上下文中,開發者已經直接獲取了JTA
PlatformTransactionManager
對象(可能是從JNDI到JndiObjectFactoryBean
或者<jee:jndi-lookup>
標簽),並將其提供給Spring的JtaTransactionManager
(其中最簡單的方法就是指定一個引用bean將此JTAPlatformTransactionManager
實例定義為LocalSessionFactoryBean
的jtaTransactionManager
屬性的值)。 Spring之後會令PlatformTransactionManager
對象對Hibernate可見。 - 更有可能開發者無法獲取JTA
PlatformTransactionManager
實例,因為Spring的JtaTransactionManager
是可以自己找到該實例的。因此,開發者需要配置Hibernate令其直接查找JTAPlatformTransactionManager
。開發者可以如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
自動檢測機製,並且在大多數情況下,僅要求開發者指定持久化單元的名稱:
<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="https://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-weaver
XML元素來配置上下文範圍的LoadTimeWeaver
。所有JPALocalContainerEntityManagerFactoryBeans
都會自動拾取這樣的全局織入器。這是設置加載時間織入器的首選方式,為平台(WebLogic,GlassFish,Tomcat,Resin,JBoss或VM代理)提供自動檢測功能,並將織入組件自動傳播到所有可以感知織入者的Bean:
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
開發者也可以通過LocalContainerEntityManagerFactoryBean
的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轉換器僅應用於類加載器級,彼此隔離。
處理多個持久化單元
例如,對於依賴存儲在類路徑中的各種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
在內部創建和使用。
轉載自 並發編程網 - ifeve.com
最後更新:2017-05-18 10:33:00