12.4. Oracle TopLink

Spring从Spring 1.2版本开始支持Oracle TopLink (http://www.oracle.com/technology/products/ias/toplink)作为数据访问策略,同样遵循类似于Spring对Hibernate的支持风格。 Spring对其的支持包括TopLink 9.0.4(Spring 1.2支持的产品版本)和TopLink 10.1.3(Spring 1.2支持的,依然处于beta版)。 对应的支持与整合类位于 org.springframework.orm.toplink 包中。

Spring的TopLink支持已经和Oracle TopLink团队共同开发。非常感谢TopLink团队,尤其是Jim Clark,帮助我们澄清了所有方面的具体事项!

12.4.1.  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}"/>
  </bean>

  <bean id="mySessionFactory" class="org.springframework.orm.toplink.LocalSessionFactoryBean">
    <property name="configLocation" value="toplink-sessions.xml"/>
    <property name="dataSource" ref="dataSource"/>
  </bean>

  ...
</beans>
<toplink-configuration>

  <session>
    <name>Session</name>
    <project-xml>toplink-mappings.xml</project-xml>
    <session-type>
      <server-session/>
    </session-type>
    <enable-logging>true</enable-logging>
    <logging-options/>
  </session>

</toplink-configuration>

通常情况下,LocalSessionFactoryBean 在底层将持有一个多线程的TopLink ServerSession 并创建合适的客户端 Session: 它或者是一个普通的 Session(典型情况) —— 一个受管理的 ClientSession;或者是一个具备事务功能的 Session (后者主要在Spring内部对TopLink的支持中被使用)。 还有一种情况,LocalSessionFactoryBean 可能会持有一个单线程的TopLink的 DatabaseSession,这是非常特殊的情况了。

12.4.2.  TopLinkTemplateTopLinkDaoSupport

每个基于TopLink的DAO将通过IoC被注入一个 SessionFactory,你可以通过Setter方式注入,也可以用构造函数方式注入。 这样的DAO可以直接操作原生的TopLink API,通过 SessionFactory 来获取一个 Session,但是通常情况下,你更愿意使用Spring的 TopLinkTemplate

<beans>
  ...

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

</beans>
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(final String category) throws DataAccessException {
        TopLinkTemplate tlTemplate = new TopLinkTemplate(this.sessionFactory);
        return (Collection) 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实例不仅是线程安全的,同时它也是可重用的。因而他们可以作为外部对象的实例变量而被持有。对于那些简单的诸如 executeQueryreadAllreadByIdmerge 操作的调用, TopLinkTemplate(译者注:原文误写成JdoTemplate)提供可选择的快捷函数来替换这种回调的实现。 不仅如此,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});
    }
}</programlisting>

边注:TopLink查询对象是线程安全的,并且能够在DAO层被缓存。在一开始被创建时以实例变量的方式被保持。

作为不使用Spring的 TopLinkTemplate 来实现DAO的替代解决方案,你依然可以通过原生TopLink API对那些基于Spring的DAO进行编程,此时你必须明确地打开和关闭一个 Session。 正如在相应的Hibernate章节描述的一样,这种做法的主要优点在于你的数据访问代码可以在整个过程中抛出checked exceptions。 TopLinkDaoSupport 为这种情况提供了多种函数支持,包括获取和释放一个具备事务的 Session 并做相关的异常转化。

12.4.3. 基于原生的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"/>
  </bean>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="session" ref="mySessionAdapter"/>
  </bean>

  ...
</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 的转化。

12.4.4. 事务管理

将事务管理纳入到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.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  ...

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

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

  <aop:config>
    <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
  </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"/>
    </tx:attributes>
  </tx:advice>

</beans>

注意,TopLink要求你必须在一个活跃的 工作单元(UnitOfWork) 中修改一个持久化对象(你通常不能修改由普通的TopLink的 Session 查询返回的对象,因为这些对象通常是一些从二级缓存中读出的只读对象)。 与Hibernate相比,在TopLink中并没有一种类似脱离事务刷出(non-transactional flush)的概念。 基于这种原因,TopLink需要被建立在特定的环境中,尤其是它需要为JTA同步做明确的创建,由此来自行检测一个JTA事务以及暴露一个相应的活跃的 SessionUnitOfWork。 这一点对于本地事务不是必要的,由于它已经被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 必须被明确指定,它是无法被自动检测到的。