事务支持
习惯于使用关系型数据库的程序员在接触 LDAP 世界时,常常会对没有事务的概念感到惊讶。LDAP 协议中没有规定事务,也没有 LDAP 服务器支持事务。考虑到这可能是一个主要问题,Spring LDAP 提供了对 LDAP 资源上的客户端补偿事务的支持。
LDAP 事务支持由 ContextSourceTransactionManager
提供,这是一个 PlatformTransactionManager
实现,用于管理 LDAP 操作的 Spring 事务支持。它与协作者一起跟踪事务中执行的 LDAP 操作,记录每个操作之前的状态,并在需要回滚事务时采取措施恢复初始状态。
除了实际的事务管理之外,Spring LDAP 事务支持还确保在同一事务中使用相同的 DirContext
实例。也就是说,DirContext
实际上不会在事务完成之前关闭,从而可以更有效地利用资源。
虽然 Spring LDAP 用于提供事务支持的方法在许多情况下都足够了,但它绝不是传统意义上的“真正”事务。服务器完全不知道事务,因此(例如),如果连接断开,则无法回滚事务。虽然应该仔细考虑这一点,但也应该注意,另一种选择是完全没有事务支持。Spring LDAP 的事务支持已经相当不错了。 |
客户端事务支持除了原始操作所需的工作之外,还会增加一些开销。虽然在大多数情况下,这种开销不应成为需要担心的事情,但如果您的应用程序在同一事务中不执行多个 LDAP 操作(例如,modifyAttributes 后跟 rebind ),或者不需要与 JDBC 数据源进行事务同步(请参阅 JDBC 事务集成),那么使用 LDAP 事务支持的好处并不大。 |
配置
如果您习惯于配置 Spring 事务,那么配置 Spring LDAP 事务看起来应该非常熟悉。您可以使用 @Transactional
注释您的事务类,创建 TransactionManager
实例,并在您的 Bean 配置中包含 <tx:annotation-driven>
元素。以下示例演示了如何执行此操作
<ldap:context-source
url="ldap://127.0.0.1:389"
base="dc=example,dc=com"
username="cn=Manager"
password="secret" />
<ldap:ldap-template id="ldapTemplate" />
<ldap:transaction-manager>
<!--
Note this default configuration will not work for more complex scenarios;
see below for more information on RenamingStrategies.
-->
<ldap:default-renaming-strategy />
</ldap:transaction-manager>
<!--
The MyDataAccessObject class is annotated with @Transactional.
-->
<bean id="myDataAccessObject" class="com.example.MyRepository">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
<tx:annotation-driven />
...
虽然此设置适用于大多数简单的用例,但一些更复杂的场景需要额外的配置。具体来说,如果您需要在事务中创建或删除子树,则需要使用替代的 TempEntryRenamingStrategy ,如 重命名策略 中所述。 |
在实际情况下,您可能会在服务对象级别而不是存储库级别应用事务。前面的示例演示了总体思路。
JDBC 事务集成
在处理 LDAP 时,一个常见的用例是某些数据存储在 LDAP 树中,而其他数据存储在关系型数据库中。在这种情况下,事务支持变得更加重要,因为不同资源的更新应该同步。
虽然不支持实际的 XA 事务,但通过向 <ldap:transaction-manager>
元素提供 data-source-ref
属性,可以提供支持来在概念上将 JDBC 和 LDAP 访问包装在同一事务中。这将创建一个 ContextSourceAndDataSourceTransactionManager
,然后它将这两个事务虚拟地管理为一个事务。执行提交时,始终首先执行操作的 LDAP 部分,如果 LDAP 提交失败,则允许回滚两个事务。事务的 JDBC 部分的管理方式与 DataSourceTransactionManager
完全相同,只是不支持嵌套事务。以下示例显示了一个带有 data-source-ref
属性的 ldap:transaction-manager
元素
<ldap:transaction-manager data-source-ref="dataSource" >
<ldap:default-renaming-strategy />
<ldap:transaction-manager />
提供的支持全部是客户端支持。包装的事务不是 XA 事务。不执行两阶段提交,因为 LDAP 服务器无法对其结果进行投票。 |
您可以通过向 <ldap:transaction-manager>
元素提供 session-factory-ref
属性来实现 Hibernate 集成的相同功能,如下所示
<ldap:transaction-manager session-factory-ref="dataSource" >
<ldap:default-renaming-strategy />
<ldap:transaction-manager />
LDAP 补偿事务详解
Spring LDAP 通过在每个修改操作(bind
、unbind
、rebind
、modifyAttributes
和 rename
)之前记录 LDAP 树中的状态来管理补偿事务。如果需要回滚事务,这允许系统执行补偿操作。
在许多情况下,补偿操作非常简单。例如,bind
操作的补偿回滚操作是解除绑定条目。但是,由于 LDAP 数据库的一些特定特性,其他操作需要不同的、更复杂的方法。具体来说,并非总是可以获取条目的所有 Attributes
的值,这使得上述策略对于(例如)unbind
操作来说是不够的。
这就是为什么在 Spring LDAP 管理的事务中执行的每个修改操作在内部都被拆分为四个不同的操作:记录操作、准备操作、提交操作和回滚操作。下表描述了每个 LDAP 操作
LDAP 操作 | 记录 | 准备 | 提交 | 回滚 |
---|---|---|---|---|
|
记录要绑定的条目的 DN。 |
绑定条目。 |
无操作。 |
使用记录的 DN 解除绑定条目。 |
|
记录原始 DN 和目标 DN。 |
重命名条目。 |
无操作。 |
将条目重命名回其原始 DN。 |
|
记录原始 DN 并计算临时 DN。 |
将条目重命名到临时位置。 |
解除绑定临时条目。 |
将条目从临时位置重命名回其原始 DN。 |
|
记录原始 DN 和新的 |
将条目重命名到临时位置。 |
在原始 DN 处绑定新的 |
将条目从临时位置重命名回其原始 DN。 |
|
记录要修改的条目的 DN 并为要执行的修改计算补偿 |
执行 |
无操作。 |
使用计算出的补偿 |
有关 Spring LDAP 事务支持内部工作原理的更详细说明,请参阅 Javadoc。
重命名策略
如上一节表格中所述,某些操作的事务管理需要在实际修改在提交中进行之前,将受操作影响的原始条目临时重命名。计算条目临时 DN 的方式由 TempEntryRenamingStrategy
管理,该策略在配置中 <ldap:transaction-manager >
声明的子元素中指定。Spring LDAP 包含两个实现
-
DefaultTempEntryRenamingStrategy
(默认策略):通过使用<ldap:default-renaming-strategy />
元素指定。在条目DN的最低有效部分添加后缀。例如,对于DNcn=john doe, ou=users
,此策略返回临时DNcn=john doe_temp, ou=users
。可以通过设置temp-suffix
属性来配置后缀。 -
DifferentSubtreeTempEntryRenamingStrategy
:通过使用<ldap:different-subtree-renaming-strategy />
元素指定。它将子树DN附加到DN的最低有效部分。这样做会使所有临时条目都放置在LDAP树中的特定位置。临时子树DN通过设置subtree-node
属性进行配置。例如,如果subtree-node
为ou=tempEntries
,并且条目的原始DN为cn=john doe, ou=users
,则临时DN为cn=john doe, ou=tempEntries
。请注意,配置的子树节点需要存在于LDAP树中。
DefaultTempEntryRenamingStrategy 在某些情况下不起作用。例如,如果您计划执行递归删除,则需要使用DifferentSubtreeTempEntryRenamingStrategy 。这是因为递归删除操作实际上包括对子树中每个节点的深度优先删除。由于您无法重命名任何具有子节点的条目,并且DefaultTempEntryRenamingStrategy 会将每个节点保留在同一个子树中(但名称不同)而不是实际删除它,因此此操作将失败。如有疑问,请使用DifferentSubtreeTempEntryRenamingStrategy 。 |