事務支援
習慣於使用關係資料庫的程式設計師來到 LDAP 世界時,常常驚訝於沒有事務的概念。協議中沒有指定,也沒有 LDAP 伺服器支援它。認識到這可能是一個主要問題,Spring LDAP 提供了對 LDAP 資源的客戶端補償事務支援。
LDAP 事務支援由 ContextSourceTransactionManager
提供,這是一個管理 Spring LDAP 操作事務支援的 PlatformTransactionManager
實現。它及其協作物件會跟蹤事務中執行的 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 ,如 命名策略 中所述。 |
在實際應用中,你可能傾向於在服務物件級別而不是倉庫(repository)級別應用事務。前面的示例演示了總體思路。
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
操作的補償回滾操作是 unbind 該條目。然而,由於 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 的最不重要部分新增一個字尾。例如,對於 DN 為cn=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 。 |