Spring中的TopLink ServerSession
SessionFactory 抽象層
TopLink本身并沒有提供SessionFactory抽象層邏輯,多線程的數據訪問是建立在中央 ServerSession 上的。對于單線程訪問, 這個中央 ServerSession 會為它一個 ClientSession 的實例供其使用。為了提供靈活便捷的創建選項, Spring為TopLink定義了一個 SessionFactory 接口,從而使你可以任意地在不同的 Session 創建策略之間進行切換。
作為一個一站式的商店,Spring提供了一個 LocalSessionFactoryBean 類,允許你以bean風格的配置方式來定義一個TopLink SessionFactory。 需要進行配置的地方主要是TopLink session配置文件,通常來說還需配置一個受到Spring管理的JDBC DataSource。
- <beans>
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource " destroy-method="close">
- <property name="driverClassName" value="${jdbc.driverClassName}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- < SPAN>bean>
- <bean id="mySessionFactory" class="org.springframework.orm.toplink. LocalSessionFactoryBean">
- <property name="configLocation" value="toplink-sessions.xml"/>
- <property name="dataSource" ref="dataSource"/>
- < SPAN>bean>
- < SPAN>beans>
- <toplink-configuration>
- <session>
- <name>Session< SPAN>name>
- <project-xml>toplink-mappings.xml< SPAN>project-xml>
- <session-type>
- <server-session/>
- < SPAN>session-type>
- <enable-logging>true< SPAN>enable-logging>
- <logging-options/>
- < SPAN>session>
- < SPAN>toplink-configuration>
通常情況下,LocalSessionFactoryBean 在底層將持有一個多線程的TopLink ServerSession 并創建合適的客戶端 Session: 它或者是一個普通的 Session(典型情況) —— 一個受管理的 ClientSession;或者是一個具備事務功能的 Session (后者主要在Spring內部對TopLink的支持中被使用)。還有一種情況,LocalSessionFactoryBean 可能會持有一個單線程的TopLink的 DatabaseSession,這是非常特殊的情況了。
TopLinkTemplate and TopLinkDaoSupport
每個基于TopLink的DAO將通過IoC被注入一個 SessionFactory,你可以通過Setter方式注入,也可以用構造函數方式注入。這樣的DAO可以直接操作原生的TopLink API,通過 SessionFactory 來獲取一個 Session, 但是通常情況下,你更愿意使用Spring的 TopLinkTemplate:
- <beans>
- <bean id="myProductDao" class="product.ProductDaoImpl">
- <property name="sessionFactory" ref="mySessionFactory"/>
- < SPAN>bean>
- < SPAN>beans>
- public class TopLinkProductDao implements ProductDao {
- private TopLinkTemplate tlTemplate;
- public void setSessionFactory(SessionFactory sessionFactory) {
- this.tlTemplate = new TopLinkTemplate(sessionFactory);
- }
- public Collection loadProductsByCategory(final String category) throws DataAccessException {
- return (Collection) this.tlTemplate.execute(new TopLinkCallback() {
- public Object doInTopLink(Session session) throws TopLinkException {
- ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class);
- findOwnersQuery.addArgument("Category");
- ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder();
- findOwnersQuery.setSelectionCriteria(
- builder.get("category").like(builder.getParameter("Category")));
- Vector args = new Vector();
- args.add(category);
- List result = session.executeQuery(findOwnersQuery, args);
- // do some further stuff with the result list
- return result;
- }
- }
- }
- }
一個回調的實現能夠有效地在任何TopLink數據訪問中使用。TopLinkTemplate 會確保當前的 Session 對象的正確打開和關閉,并自動參與到事務管理中去。 Template實例不僅是線程安全的,同時它也是可重用的。因而他們可以作為外部對象的實例變量而被持有。對于那些簡單的諸如 executeQuery、readAll、readById 和 merge 操作的調用,TopLinkTemplate提供可選擇的快捷函數來替換這種回調的實現。 不僅如此,Spring還提供了一個簡便的 TopLinkDaoSupport 基類,這個類提供了 setSessionFactory(..) 方法來接受一個 SessionFactory 對象,同時提供了 getSessionFactory() 和 getTopLinkTemplate() 方法給子類使用。綜合了這些,對于那些典型的業務需求,就有了一個非常簡單的DAO實現。
- public class ProductDaoImpl extends TopLinkDaoSupport implements ProductDao {
- public Collection loadProductsByCategory(String category) throws DataAccessException {
- ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class);
- findOwnersQuery.addArgument("Category");
- ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder();
- findOwnersQuery.setSelectionCriteria(
- builder.get("category").like(builder.getParameter("Category")));
- return getTopLinkTemplate().executeQuery(findOwnersQuery, new Object[] {category});
- }
- }
邊注:TopLink查詢對象是線程安全的,并且能夠在DAO層被緩存。在一開始被創建時以實例變量的方式被保持。
作為不使用Spring的 TopLinkTemplate 來實現DAO的替代解決方案, 你依然可以通過原生TopLink API對那些基于Spring的DAO進行編程,此時你必須明確地打開和關 閉一個 Session。正如在相應的Hibernate章節描述的一樣,這種做法的主要優點在于你的數據訪問代碼可以在整個過程中拋出checked exceptions。 TopLinkDaoSupport 為這種情況提供了多種函數支持,包括獲取和釋放 一個具備事務的 Session 并做相關的異常轉化。
基于原生的TopLink API的DAO實現
我們可以直接操作TopLink API來實現DAO,直接使用一個注入的 Session 而無需對Spring產生的任何依賴。它通常基于一個由 LocalSessionFactoryBean 定義的 SessionFactory,并通過Spring的 TransactionAwareSessionAdapter 暴露成為一個 Session 類型的引用。
TopLink的 Session 接口中定義的 getActiveSession() 方法將返回當前具備事務管理功能的 Session 對象。如果當前沒有處于活躍狀態的事務, 這個函數將返回一個共享的TopLink ServerSession,也就是說,這種情況應該只是一個直接使用的只讀訪問。另外還有一個 getActiveUnitOfWork() 方法, 返回TopLink的與當前事務綁定的 UnitOfWork (如果沒有當前事務則返回 null)。
一個相應的DAO實現類看上去就像下面那樣:
- public class ProductDaoImpl implements ProductDao {
- private Session session;
- public void setSession(Session session) {
- this.session = session;
- }
- public Collection loadProductsByCategory(String category) {
- ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class);
- findOwnersQuery.addArgument("Category");
- ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder();
- findOwnersQuery.setSelectionCriteria(
- builder.get("category").like(builder.getParameter("Category")));
- Vector args = new Vector();
- args.add(category);
- return session.getActiveSession().executeQuery(findOwnersQuery, args);
- }
- }
上面我們所列出的DAO完全遵循IoC:它如同使用Spring的 TopLinkTemplate 進行編碼那樣,非常適合在application context中進行配置。Spring的 TransactionAwareSessionAdapter 將暴露一個 Session 類型的bean的引用,并傳入到DAO中去:
- <beans>
- <bean id="mySessionAdapter"
- class="org.springframework.orm.toplink.support. TransactionAwareSessionAdapter">
- <property name="sessionFactory" ref="mySessionFactory"/>
- < SPAN>bean>
- <bean id="myProductDao" class="product.ProductDaoImpl">
- <property name="session" ref="mySessionAdapter"/>
- < SPAN>bean>
- < SPAN>beans>
這種DAO風格的主要好處在于它僅僅依賴于TopLink自身的API,而無需引入任何的Spring 的類。從無入侵性的角度來看,這一點非常吸引人。同時,對于TopLink的開發人員來說也更自然。
然而,這樣的DAO訪問方式會拋出 TopLinkException (這是一個無需聲明或捕獲的unchecked exception),這意味著,DAO的調用者只能以普通的錯誤來處理這些異常,除非完全依賴TopLink自身的異常體系。因而,除非你將DAO的調用者綁定到具體的實現策略上去,否則你將無法捕獲特定的異常原因(諸如樂觀鎖異常)。這種折中平衡或許可以被接受,如果你的應用完全基于TopLink或者無需進行特殊的異常處理。
這樣的DAO風格有一個不利因素在于TopLink的標準的 getActiveSession() 函數僅僅在JTA事務中有效。而對于其他的事務管理策略尤其時本地的TopLink事務,它將 無法 工作。
幸運的是,Spring的 TransactionAwareSessionAdapter 為TopLink ServerSession 暴露了一個相應的代理類。 這個代理類能夠在任何的事務策略之上支持TopLink的 Session.getActiveSession() 和 Session.getActiveUnitOfWork() 函數,返回當前收到Spring管理 (即便由 TopLinkTransactionManager 管理)的具備事務管理功能的 Session 實例。當然,這個函數的標準行為依然有效:返回與當前的JTA事務綁定的 Session 對象。 (無論這個JTA事務是由Spring的 JtaTransactionManager、 EJB CMT或者普通的JTA所驅動的事務)。
總體來說,DAO可以基于TopLink的原生API實現,同時,它依舊需要能夠參與到Spring的事務管理中。這對于那些已經對TopLink非常熟悉的人來說很有吸引力,因為這種方式更加自然。不過,這種DAO將拋出 TopLinkException,因而,如果有必要的話需要明確地去做由 TopLinkException 到Spring的 DataAccessException 的轉化。
事務管理
將事務管理納入到Service操作的執行中,你可以使用Spring通用的聲明式的事務管理功能,參加下面的例子:
- 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 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
- http://www.springframework.org/schema/aop >
- <bean id="myTxManager" class="org.springframework.orm.toplink. TopLinkTransactionManager">
- <property name="sessionFactory" ref="mySessionFactory"/>
- < SPAN>bean>
- <bean id="myProductService" class="product.ProductServiceImpl">
- <property name="productDao" ref="myProductDao"/>
- < SPAN>bean>
- <aop:config>
- <aop:pointcut id="productServiceMethods" expression="execution (* product.ProductService.*(..))"/>
- <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
- < SPAN>aop:config>
- <tx:advice id="txAdvice" transaction-manager="myTxManager">
- <tx:attributes>
- <tx:method name="increasePrice*" propagation="REQUIRED"/>
- <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
- <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
- < SPAN>tx:attributes>
- < SPAN>tx:advice>
- < SPAN>beans>
注意,TopLink要求你必須在一個活躍的 工作單元(UnitOfWork) 中修改一個持久化對象(你通常不能修改由普通的TopLink的 Session 查詢返回的對象,因為這些對象通常是一些從二級緩存中讀出的只讀對象)。與Hibernate相比,在TopLink中并沒有一種類似脫離事務刷出(non-transactional flush)的概念。基于這種原因,TopLink需要被建立在特定的環境中,尤其是它需要為JTA同步做明確的創建,由此來 自行檢測一個JTA事務以及暴露一個相應的活躍的 Session 和 UnitOfWork。這一點對于本地事務不是必要的,由于它已經被 Spring的 TopLinkTransactionManager 處理,但是對于 需要參與到JTA事務中的情況,是必須的(無論是由Spring的 JtaTransactionManager、EJB CMT或者普通的JTA所驅動的事務)。
在你的基于TopLink的DAO代碼中,你可以使用 Session.getActiveUnitOfWork() 方法來訪問當前的 UnitOfWork 并通過它來執行寫操作。這將只在一個活躍的事務中有效(在一個收到Spring管理的事務或者JTA事務中)。對于特殊的需求,你同樣可以獲取單獨的 UnitOfWork 實例,它將不參與到當前的事務中去,不過這種情況非常少。
TopLinkTransactionManager 能夠將一個TopLink事務暴露給 訪問相同的JDBC DataSource 的JDBC訪問代碼。 前提條件是,TopLink在底層是以JDBC方式工作的并且能夠暴露底層的JDBC Connection。這種情況下,用于暴露事務的 DataSource 必須被明確指定, 它是無法被自動檢測到的。
【編輯推薦】