Programmers used to working with relational databases coming to the LDAP world often express surprise to the fact that there is no notion of transactions. It is not specified in the protocol, and thus no servers support it. Recognizing that this may be a major problem, Spring LDAP provides support for client-side, compensating transactions on LDAP resources.
LDAP transaction support is provided by
PlatformTransactionManager implementation that manages Spring transaction
support for LDAP operations. Along with its collaborators it keeps track of the LDAP operations
performed in a transaction, making record of the state before each operation and taking steps to
restore the initial state should the transaction need to be rolled back.
In addition to the actual transaction management, Spring LDAP transaction support also
makes sure that the same
DirContext instance will be used throughout the same transaction,
DirContext will not actually be closed until the transaction is finished,
allowing for more efficient resources usage.
modifyAttributesfollowed by a
rebind), or if transaction synchronization with a JDBC data source is not required (see below) there will be nothing to gain by using the LDAP transaction support.
Configuring Spring LDAP transactions should look very familiar if you're used to configuring Spring transactions.
You will create a
TransactionManager instance and wrap your target object using a
TransactionProxyFactoryBean. In addition to this, you will also need to wrap your
ContextSource in a
<beans> ... <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource"> <property name="url" value="ldap://localhost:389" /> <property name="base" value="dc=example,dc=com" /> <property name="userDn" value="cn=Manager" /> <property name="password" value="secret" /> </bean> <bean id="contextSource" class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy"> <constructor-arg ref="contextSourceTarget" /> </bean> <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> <constructor-arg ref="contextSource" /> </bean> <bean id="transactionManager" class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager"> <property name="contextSource" ref="contextSource" /> </bean> <bean id="myDataAccessObjectTarget" class="com.example.MyDataAccessObject"> <property name="ldapTemplate" ref="ldapTemplate" /> </bean> <bean id="myDataAccessObject" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="myDataAccessObjectTarget" /> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRES_NEW</prop> </props> </property> </bean> ...
In a real world example you would probably apply the transactions on the service object level rather than the DAO level; the above serves as an example to demonstrate the general idea.
ContextSourceand DAO instances get ids with a "Target" suffix. The beans you will actually refer to are the Proxies that are created around the targets;
A common use case when working against LDAP is that some of the data is stored in the LDAP tree, but other data is stored in a relational database. In this case, transaction support becomes even more important, since the update of the different resources should be synchronized.
While actual XA transactions is not supported, support is provided to conceptually wrap JDBC and LDAP
access within the same transaction using the
DataSource and a
ContextSource is supplied to the
ContextSourceAndDataSourceTransactionManager, which will then manage the two transactions,
virtually as if they were one. When performing a commit, the LDAP part of the operation will always
be performed first, allowing both transactions to be rolled back should the LDAP commit fail. The JDBC
part of the transaction is managed exactly as in
DataSourceTransactionManager, except that
nested transactions is not supported.
Spring LDAP manages compensating transactions by making record of the state in the LDAP tree
before each modifying operation (
This enables the system
to perform compensating operations should the transaction need to be rolled back. In many cases the
compensating operation is pretty straightforward. E.g. the compensating rollback operation for a
bind operation will quite obviously be to unbind the entry. Other operations however require
a different, more complicated approach because of some particular characteristics of LDAP databases. Specifically,
it is not always possible to get the values of all
Attributes of an entry, making the above
strategy insufficient for e.g. an
This is why each modifying operation performed within a Spring LDAP managed transaction is internally split up in four distinct operations - a recording operation, a preparation operation, a commit operation, and a rollback operation. The specifics for each LDAP operation is described in the table below:
|Make record of the DN of the entry to bind.||Bind the entry.||No operation.||Unbind the entry using the recorded DN.|
|Make record of the original and target DN.||Rename the entry.||No operation.||Rename the entry back to its original DN.|
|Make record of the original DN and calculate a temporary DN.||Rename the entry to the temporary location.||Unbind the temporary entry.||Rename the entry from the temporary location back to its original DN.|
|Make record of the original DN and the new ||Rename the entry to a temporary location.||Bind the new ||Rename the entry from the temporary location back to its original DN.|
|Make record of the DN of the entry to modify and calculate compensating ||Perform the ||No operation.||Perform a |
A more detailed description of the internal workings of the Spring LDAP transaction support is available in the javadocs.
TempEntryRenamingStrategysupplied to the
ContextSourceTransactionManager. Two implementations are supplied with Spring LDAP, but if specific behaviour is required a custom implementation can easily be implemented by the user. The provided
DefaultTempEntryRenamingStrategy (the default). Adds a suffix to the least significant
part of the entry DN. E.g. for the DN
cn=john doe, ou=users, this strategy would return the
cn=john doe_temp, ou=users. The suffix is configurable using the
DifferentSubtreeTempEntryRenamingStrategy. Takes the least significant part of the DN
and appends a subtree DN to this. This makes all temporary entries be placed at a specific location in the LDAP tree.
The temporary subtree DN is configured using the
subtreeNode property. E.g., if
ou=tempEntries and the original DN of the entry is
cn=john doe, ou=users, the temporary DN will be
cn=john doe, ou=tempEntries.
Note that the configured subtree node needs to be present in the LDAP tree.
DefaultTempEntryRenamingStrategywill not work. E.g. if your are planning to do recursive deletes you'll need to use
DifferentSubtreeTempEntryRenamingStrategy. This is because the recursive delete operation actually consists of a depth-first delete of each node in the sub tree individually. Since it is not allowed to rename an entry that has any children, and
DefaultTempEntryRenamingStrategywould leave each node in the same subtree (with a different name) in stead of actually removing it, this operation would fail. When in doubt, use