Spring LDAP Transaction – Unofficial yet Working Config Manual


之前的一篇文章介绍了Spring的LDAP子项目和ODM框架,其中提到了LDAP事务,但没有深入,而且那个配置中的事务也是不work的。上个周末在和JTA斗智斗勇的同时把项目中的LDAP事务也搞定了,现在可以做到将LDAP和Hibernate的session factory放在同一个事务上下文中进行ACID管理,即LDAP和数据库操作实现“all or none”(虽然是伪事务,具体下文会提到)。当然,对于Spring LDAP事务配置官方和Google上同样没有任何可参考或操作的文档说明,不然我也不用连着两个晚上码字造福大众了。另外,为了造福资本主义国家的程序猿们,同时向他们展示社会主义国家的制度优越性,以下将切换至英文

Spring LDAP is a amazing framework esp. for its LdapTemplate and ODM, providing a consistent point of view of developing LDAP code with the well-known and document-friendly techniques – JdbcTemplate and ORM. Unfortunately, Spring LDAP is not that widely-used, for its sluggish development progress (1.3.1 so far) and lacking of document/samples. This post will focus on a even rare yet important topic of Spring LDAP – transaction. For other information like O-D mapping or LDAP context source, pls refer to the official document, and some tips here if you can read Chinese.

Environment

  • Spring LDAP 1.3.1
  • Spring * 3.1.1
  • Hibernate 4.1.1
Goal
  • Implement a method annotated with @Transactional, which demonstrates a business service
  • The service invokes two persist DAOs, one uses Hibernate’s session factory and the other uses Spring’s ODM
  • The two persist actions should follow “all or none” rule, that is, if JDBC action fails after LDAP action, LDAP action should rollback, vice versa

IMPORTANT

As described in the official document, TX in Spring LDAP is “not real”

it should be noted that the provided support is all client side. The wrapped transaction is not an XA transaction. No two-phase as such commit is performed, as the LDAP server will be unable to vote on its outcome. Once again, however, for the majority of cases the supplied support will be sufficient.

Show time!

Spring configuration

 XML |  copy code |? 
01
<!-- define LDAP connection -->
02
<bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
03
	<property name="url" value="..." />
04
	<property name="base" value="dc=jayxu,dc=com" />
05
	<property name="userDn" value="cn=admin,dc=jayxu,dc=com" />
06
	<property name="password" value="..." />
07
</bean>
08
 
09
<!-- enable pooling -->
10
<bean id="pooledContextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
11
	<property name="contextSource" ref="contextSourceTarget" />
12
	<property name="testOnBorrow" value="true" />
13
	<property name="dirContextValidator" ref="dirContextValidator" />
14
</bean>
15
<!-- for pooled connection validation -->
16
<bean id="dirContextValidator" class="org.springframework.ldap.pool.validation.DefaultDirContextValidator" />
17
 
18
<!-- define LdapTemplate -->
19
<bean class="org.springframework.ldap.core.LdapTemplate">
20
	<property name="contextSource" ref="contextSource" />
21
</bean>
22
 
23
<!-- wrap underlying context source with TX aware proxy -->  
24
<bean id="contextSource" class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy">
25
	<constructor-arg ref="pooledContextSource" />
26
</bean>
27
 
28
<!--
29
define TX manager
30
* if no JDBC resource is mixed in, choose org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager
31
* if dataSource is mixed in, choose org.springframework.ldap.transaction.compensating.manager.ContextSourceAndDataSourceTransactionManager
32
* if Hibernate 3 sessionFactory is mixed in, choose org.springframework.ldap.transaction.compensating.manager.ContextSourceAndHibernateTransactionManager
33
* since Spring LDAP doesn't support Hibernate 4, you need to create your own class, extending org.springframework.orm.hibernate4.HibernateTransactionManager, then copy the rest code from org.springframework.ldap.transaction.compensating.manager.ContextSourceAndHibernateTransactionManager, just as I did
34
 
35
here you SHOULD make a trade-off: since so far there is no support for JTA, you can only mix LDAP tx with Hibernate tx manager
36
I have raised two tickets to Spring LDAP:
37
https://jira.springsource.org/browse/LDAP-242
38
https://jira.springsource.org/browse/LDAP-243
39
-->
40
<bean id="ldapTransactionManager" class="com.jayxu.common.ldap.ContextSourceAndHibernate4TransactionManager">
41
	<property name="contextSource" ref="contextSource" />
42
	<property name="sessionFactory" ref="sessionFactory" />
43
</bean>
44
 
45
<!-- define ODM -->
46
<bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
47
	<property name="contextSource" ref="contextSource" />
48
	<property name="managedClasses">
49
		<set>
50
			<value>...</value>
51
		</set>
52
	</property>
53
	...
54
</bean>
55
 
56
<!--
57
This part is KEY, wrap your service object with TransactionProxyFactoryBean
58
-->
59
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
60
	<property name="transactionManager" ref="ldapTransactionManager" />
61
	<property name="target" ref="myService" />
62
	<property name="transactionAttributes">
63
		<props>
64
			<prop key="*">PROPAGATION_REQUIRES_NEW</prop>
65
		</props>
66
	</property>
67
</bean>
68
<bean id="myService" class="com.jayxu.service.MyService" />
69
 
70
<!-- enable @Transactional support -->
71
<tx:annotation-driven />

This diagram gives a overview of the beans’ references

In your service class, you should annotate your transactional methods or the whole class with

 Java |  copy code |? 
1
@Transactional(value = "ldapTransactionManager")

here “value” refers to the tx manager name above. My test code looks like

 Java |  copy code |? 
1
@Transactional(value = "ldapTransactionManager")
2
public void ldapTx() {
3
	odm.create(...);
4
	sessionFactory.getCurrentSession().persist(...);
5
 
6
	throw new RuntimeException();
7
}

then config logger level of org.springframework.ldap and org.springframework.transaction to DEBUG, if everything works well, you can find something in the output like

 Java |  copy code |? 
01
[DEBUG] org.springframework.ldap.transaction.compensating.LdapCompensatingTransactionOperationFactory:59 - Bind operation recorded
02
[DEBUG] org.springframework.ldap.transaction.compensating.BindOperationExecutor:97 - Performing bind operation
03
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:140 - Retrieved value [org.springframework.ldap.transaction.compensating.manager.DirContextHolder@3ed024df] for key [org.springframework.ldap.pool.factory.PoolingContextSource@165b3858] bound to thread [main]
04
[DEBUG] org.springframework.ldap.transaction.compensating.manager.TransactionAwareDirContextInvocationHandler:120 - Leaving transactional context open
05
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:140 - Retrieved value [org.springframework.orm.hibernate4.SessionHolder@55661f7b] for key [org.hibernate.internal.SessionFactoryImpl@7fb5438d] bound to thread [main]
06
[DEBUG] org.springframework.transaction.interceptor.TransactionInterceptor:406 - Completing transaction for [com.jayxu.service.UserLdapService.addUserTx] after exception: java.lang.RuntimeException
07
[DEBUG] org.springframework.transaction.interceptor.RuleBasedTransactionAttribute:130 - Applying rules to determine whether transaction should rollback on java.lang.RuntimeException
08
[DEBUG] org.springframework.transaction.interceptor.RuleBasedTransactionAttribute:147 - Winning rollback rule is: null
09
[DEBUG] org.springframework.transaction.interceptor.RuleBasedTransactionAttribute:152 - No relevant rollback rule found: applying default rules
10
[DEBUG] org.springframework.transaction.compensating.support.DefaultCompensatingTransactionOperationManager:79 - Performing rollback
11
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:331 - Clearing transaction synchronization
12
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:243 - Removed value [org.springframework.orm.hibernate4.SessionHolder@55661f7b] for key [org.hibernate.internal.SessionFactoryImpl@7fb5438d] from thread [main]
13
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:243 - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@22013e9b] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@c20e54a] from thread [main]
14
[DEBUG] org.springframework.transaction.compensating.support.AbstractCompensatingTransactionManagerDelegate:121 - Cleaning stored transaction synchronization
15
[DEBUG] org.springframework.transaction.support.TransactionSynchronizationManager:243 - Removed value [org.springframework.ldap.transaction.compensating.manager.DirContextHolder@3ed024df] for key [org.springframework.ldap.pool.factory.PoolingContextSource@165b3858] from thread [main]
16
[DEBUG] org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManagerDelegate:103 - Closing target context

Line 1 indicates save point being created while line 10 indicates the rollback

– EOF –

原创内容,转载请注明: 转载自拈花微笑

本文链接地址: Spring LDAP Transaction – Unofficial yet Working Config Manual



历史上的今天
  1. 2006:  又见好车(2)
  2. 2011:  798(0)