14.4 Oracle TopLink

Since Spring 1.2, Spring supports Oracle TopLink (http://www.oracle.com/technology/products/ias/toplink) as data access strategy, following the same style as the Hibernate support. Both TopLink 9.0.4 (the production version as of Spring 1.2) and 10.1.3 (still in beta as of Spring 1.2) are supported. The corresponding integration classes reside in the org.springframework.orm.toplink package.

Spring's TopLink support has been co-developed with the Oracle TopLink team. Many thanks to the TopLink team, in particular to Jim Clark who helped to clarify details in all areas!

14.4.1 SessionFactory abstraction

TopLink itself does not ship with a SessionFactory abstraction. Instead, multi-threaded access is based on the concept of a central ServerSession, which in turn is able to spawn ClientSession instances for single-threaded usage. For flexible setup options, Spring defines a SessionFactory abstraction for TopLink, enabling to switch between different Session creation strategies.

As a one-stop shop, Spring provides a LocalSessionFactoryBean class that allows for defining a TopLink SessionFactory with bean-style configuration. It needs to be configured with the location of the TopLink session configuration file, and usually also receives a Spring-managed JDBC DataSource to use.

<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>

Usually, LocalSessionFactoryBean will hold a multi-threaded TopLink ServerSession underneath and create appropriate client Sessions for it: either a plain Session (typical), a managed ClientSession, or a transaction-aware Session (the latter are mainly used internally by Spring's TopLink support). It might also hold a single-threaded TopLink DatabaseSession; this is rather unusual, though.

14.4.2 TopLinkTemplate and TopLinkDaoSupport

Each TopLink-based DAO will then receive the SessionFactory through dependency injection, i.e. through a bean property setter or through a constructor argument. Such a DAO could be coded against plain TopLink API, fetching a Session from the given SessionFactory, but will usually rather be used with Spring's TopLinkTemplate:

<beans>
  
  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>
  
</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;
            }
        });
    }
}

A callback implementation can effectively be used for any TopLink data access. TopLinkTemplate will ensure that Sessions are properly opened and closed, and automatically participate in transactions. The template instances are thread-safe and reusable, they can thus be kept as instance variables of the surrounding class. For simple single-step actions such as a single executeQuery, readAll, readById, or merge call, JdoTemplate offers alternative convenience methods that can replace such one line callback implementations. Furthermore, Spring provides a convenient TopLinkDaoSupport base class that provides a setSessionFactory(..) method for receiving a SessionFactory, and getSessionFactory() and getTopLinkTemplate() for use by subclasses. In combination, this allows for simple DAO implementations for typical requirements:

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});
    }
}

Side note: TopLink query objects are thread-safe and can be cached within the DAO, i.e. created on startup and kept in instance variables.

As alternative to working with Spring's TopLinkTemplate, you can also code your TopLink data access based on the raw TopLink API, explicitly opening and closing a Session. As elaborated in the corresponding Hibernate section, the main advantage of this approach is that your data access code is able to throw checked exceptions. TopLinkDaoSupport offers a variety of support methods for this scenario, for fetching and releasing a transactional Session as well as for converting exceptions.

14.4.3 Implementing DAOs based on plain TopLink API

DAOs can also be written against plain TopLink API, without any Spring dependencies, directly using an injected TopLink Session. The latter will usually be based on a SessionFactory defined by a LocalSessionFactoryBean, exposed for bean references of type Session through Spring's TransactionAwareSessionAdapter.

The getActiveSession() method defined on TopLink's Session interface will return the current transactional Session in such a scenario. If there is no active transaction, it will return the shared TopLink ServerSession as-is, which is only supposed to be used directly for read-only access. There is also an analogous getActiveUnitOfWork() method, returning the TopLink UnitOfWork associated with the current transaction, if any (returning null else).

A corresponding DAO implementation looks like as follows:

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);
    }
}

As the above DAO still follows the Dependency Injection pattern, it still fits nicely into a Spring application context, analogous to like it would if coded against Spring's TopLinkTemplate. Spring's TransactionAwareSessionAdapter is used to expose a bean reference of type Session, to be passed into the 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>

The main advantage of this DAO style is that it depends on TopLink API only; no import of any Spring class is required. This is of course appealing from a non-invasiveness perspective, and might feel more natural to TopLink developers.

However, the DAO throws plain TopLinkException (which is unchecked, so does not have to be declared or caught), which means that callers can only treat exceptions as generally fatal - unless they want to depend on TopLink's own exception structure. Catching specific causes such as an optimistic locking failure is not possible without tying the caller to the implementation strategy. This tradeoff might be acceptable to applications that are strongly TopLink-based and/or do not need any special exception treatment.

A further disadvantage of that DAO style is that TopLink's standard getActiveSession() feature just works within JTA transactions. It does not work with any other transaction strategy out-of-the-box, in particular not with local TopLink transactions.

Fortunately, Spring's TransactionAwareSessionAdapter exposes a corresponding proxy for the TopLink ServerSession which supports TopLink's Session.getActiveSession() and Session.getActiveUnitOfWork() methods for any Spring transaction strategy, returning the current Spring-managed transactional Session even with TopLinkTransactionManager. Of course, the standard behavior of that method remains: returning the current Session associated with the ongoing JTA transaction, if any (no matter whether driven by Spring's JtaTransactionManager, by EJB CMT, or by plain JTA).

In summary: DAOs can be implemented based on plain TopLink API, while still being able to participate in Spring-managed transactions. This might in particular appeal to people already familiar with TopLink, feeling more natural to them. However, such DAOs will throw plain TopLinkException; conversion to Spring's DataAccessException would have to happen explicitly (if desired).

14.4.4 Transaction management

To execute service operations within transactions, you can use Spring's common declarative transaction facilities. For example:

<?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 http://www.springframework.org/schema/aop/spring-aop-2.5.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>

Note that TopLink requires an active UnitOfWork for modifying a persistent object. (You should never modify objects returned by a plain TopLink Session - those are usually read-only objects, directly taken from the second-level cache!) There is no concept like a non-transactional flush in TopLink, in contrast to Hibernate. For this reason, TopLink needs to be set up for a specific environment: in particular, it needs to be explicitly set up for JTA synchronization, to detect an active JTA transaction itself and expose a corresponding active Session and UnitOfWork. This is not necessary for local transactions as performed by Spring's TopLinkTransactionManager, but it is necessary for participating in JTA transactions (whether driven by Spring's JtaTransactionManager or by EJB CMT / plain JTA).

Within your TopLink-based DAO code, use the Session.getActiveUnitOfWork() method to access the current UnitOfWork and perform write operations through it. This will only work within an active transaction (both within Spring-managed transactions and plain JTA transactions). For special needs, you can also acquire separate UnitOfWork instances that won't participate in the current transaction; this is hardly needed, though.

TopLinkTransactionManager is capable of exposing a TopLink transaction to JDBC access code that accesses the same JDBC DataSource, provided that TopLink works with JDBC in the backend and is thus able to expose the underlying JDBC Connection. The DataSource to expose the transactions for needs to be specified explicitly; it won't be autodetected.