事务支持
习惯于关系型数据库的程序员来到 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://: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,如 重命名策略 中所述。 |
在实际情况中,您可能会在服务对象级别而不是存储库级别应用事务。前面的示例演示了通用思想。
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 的计算方式由配置中 <ldap:transaction-manager > 声明的子元素中指定的 TempEntryRenamingStrategy 管理。Spring LDAP 包含两种实现:
-
DefaultTempEntryRenamingStrategy(默认):使用<ldap:default-renaming-strategy />元素指定。将后缀添加到条目 DN 的最低有效部分。例如,对于 DNcn=john doe, ou=users,此策略返回的临时 DN 为cn=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。 |