Transaction Support

习惯于使用关系数据库的程序员在进入 LDAP 领域时常常会对没有事务这一概念感到惊讶。协议中未指定该概念,没有 LDAP 服务器支持它。Spring LDAP 认识到这可能是一个主要问题,因此为 LDAP 资源中的客户端补偿事务提供了支持。 LDAP 事务支持由 ContextSourceTransactionManager 提供,它是一个 PlatformTransactionManager 实现,管理 LDAP 操作的 Spring 事务支持。它与其协作者一起跟踪事务中执行的 LDAP 操作,记录每个操作之前的状态,并采取措施在需要回滚事务时恢复初始状态。 除了实际的事务管理之外,Spring LDAP 事务支持还确保在整个事务期间使用相同的 DirContext 实例。也就是说,在事务完成之前实际上不会关闭 DirContext,从而可以更有效地使用资源。

尽管 Spring LDAP 提供的事务支持适用于许多情况,但它绝不是传统意义上的 “real” 事务。服务器完全不感知事务,所以(例如)如果连接断开,就无法回滚该事务。虽然需要仔细考虑这一点,还应注意,替代方法是在没有任何事务支持的情况下进行操作。Spring LDAP 的事务支持几乎是尽善尽美了。

客户端事务支持除了原始操作所需的工作之外,还增加了额外的开销。虽然大多数情况下不用担心这种开销,如果您的应用程序不执行同一事务中的多个 LDAP 操作(例如,执行 modifyAttributes 之后执行 rebind),或不需要与 JDBC 数据源进行事务同步(请参见 JDBC Transaction Integration),那么使用 LDAP 事务支持几乎没有好处。

Configuration

如果已经习惯了配置 Spring 事务,那么配置 Spring LDAP 事务应该是非常熟悉的。您可以使用 @Transactional 为您的事务类添加注释,创建一个 TransactionManager 实例,并在 Bean 配置中包含一个 <tx:annotation-driven> 元素。以下示例展示了如何执行此操作:

<ldap:context-source
       url="ldap://localhost: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 ,如 Renaming Strategies 所述。

在实际情况中,您可能更愿意在服务对象级别而非存储库级别应用事务。之前的示例展示了这个基本思想。

JDBC Transaction Integration

使用 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 服务器无法对提交结果表决。

您可以通过下列方法,为 Hibernate 集成执行相同操作:向 <ldap:transaction-manager> 元素提供 session-factory-ref 属性:

<ldap:transaction-manager session-factory-ref="dataSource" >
  <ldap:default-renaming-strategy />
<ldap:transaction-manager />

LDAP Compensating Transactions Explained

Spring LDAP 记录每个修改操作 (bindunbindrebindmodifyAttributesrename) 之前的 LDAP 树状态,从而管理补偿性事务。如果需要回滚事务,这会让系统执行补偿性操作。

在很多情况下,补偿性操作非常简单。例如,bind 操作的补偿性回滚操作是取消绑定条目。但是,由于 LDAP 数据库的某些特定特征,其他操作需要采用不同的、更为复杂的方法。具体来说,并非总能获得条目的所有 Attributes 的值,这使得前述策略不够用(例如)unbind 操作。

这就是在 Spring LDAP 管理的事务中执行的每个修改操作在内部都被分成四个不同操作的原因:一个记录操作、一个准备操作、一个提交操作和一个回滚操作。下表描述了每个 LDAP 操作:

LDAP Operation Recording Preparation Commit Rollback

bind

记录要绑定的条目 DN。

Bind the entry.

No operation.

使用记录的 DN 解绑条目。

rename

记录源 DN 和目标 DN。

Rename the entry.

No operation.

将条目重命名回其原始 DN。

unbind

记录原始 DN 并计算临时 DN。

将条目重命名为临时位置。

Unbind the temporary entry.

将条目从临时位置重命名回其原始 DN。

rebind

记录原始 DN 和新 Attributes 并计算临时 DN。

将条目重命名为临时位置。

在原始 DN 上绑定新 Attributes,并从临时位置解绑原始条目。

将条目从临时位置重命名回其原始 DN。

modifyAttributes

记录要修改的条目的 DN,并为要进行的修改计算补偿 ModificationItem 实例。

Perform the modifyAttributes operation.

No operation.

通过使用计算得出的补偿性操作来执行 modifyAttributes 操作。

Spring LDAP 事务支持的内部工作机制的更详细说明在 Javadoc 中提供。

Renaming Strategies

如前一节中的表格所述,有些操作的事务管理需要在提交中可以进行实际修改之前,临时重命名该操作影响的原始条目。计算条目的临时 DN 的方式由配置中 <ldap:transaction-manager > 声明的子元素中指定的 TempEntryRenamingStrategy 管理。Spring LDAP 包含两个实现:

  • DefaultTempEntryRenamingStrategy(默认):使用 &lt;ldap:default-renaming-strategy /&gt;`元素指定。添加到条目 DN 最不重要的部分的后缀。例如,对于 DN `cn=john doe, ou=users,此策略返回一个临时 DN cn=john doe_temp, ou=users。可以通过设置 `temp-suffix`属性来配置后缀。

  • DifferentSubtreeTempEntryRenamingStrategy:使用 &lt;ldap:different-subtree-renaming-strategy /&gt;`元素指定。将子树 DN 追加到 DN 最不重要的部分。这样做会使所有临时条目都放置在 LDAP 树中的特定位置。通过设置 `subtree-node`属性来配置临时子树 DN。例如,如果 `subtree-node`是 `ou=tempEntries,并且条目的原始 DN 是 cn=john doe, ou=users,则临时 DN 为 cn=john doe, ou=tempEntries。请注意,配置的子树节点需要存在于 LDAP 树中。

DefaultTempEntryRenamingStrategy 在某些情况下不起作用。例如,如果您计划执行递归删除,则需要使用 DifferentSubtreeTempEntryRenamingStrategy。这是因为递归删除操作实际上包括对子树中每个节点的深度优先删除。由于您无法重命名任何具有子项的条目,并且 DefaultTempEntryRenamingStrategy 会将每个节点保留在同一子树中(名称不同)而非将其删除,此操作将失败。如果有疑问,请使用 DifferentSubtreeTempEntryRenamingStrategy