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!
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.
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.
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).
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.