Skip to content

Commit 831f09c

Browse files
committed
SimpleTransactionScope properly suspends and resumes scoped objects
Issue: SPR-14148
1 parent 10554a8 commit 831f09c

File tree

2 files changed

+93
-7
lines changed

2 files changed

+93
-7
lines changed

spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public Object get(String name, ObjectFactory<?> objectFactory) {
4646
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
4747
if (scopedObjects == null) {
4848
scopedObjects = new ScopedObjectsHolder();
49-
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization());
49+
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
5050
TransactionSynchronizationManager.bindResource(this, scopedObjects);
5151
}
5252
Object scopedObject = scopedObjects.scopedInstances.get(name);
@@ -98,13 +98,30 @@ static class ScopedObjectsHolder {
9898

9999
private class CleanupSynchronization extends TransactionSynchronizationAdapter {
100100

101+
private final ScopedObjectsHolder scopedObjects;
102+
103+
public CleanupSynchronization(ScopedObjectsHolder scopedObjects) {
104+
this.scopedObjects = scopedObjects;
105+
}
106+
107+
@Override
108+
public void suspend() {
109+
TransactionSynchronizationManager.unbindResource(SimpleTransactionScope.this);
110+
}
111+
112+
@Override
113+
public void resume() {
114+
TransactionSynchronizationManager.bindResource(SimpleTransactionScope.this, this.scopedObjects);
115+
}
116+
101117
@Override
102118
public void afterCompletion(int status) {
103-
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder)
104-
TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
105-
for (Runnable callback : scopedObjects.destructionCallbacks.values()) {
119+
TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this);
120+
for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) {
106121
callback.run();
107122
}
123+
this.scopedObjects.destructionCallbacks.clear();
124+
this.scopedObjects.scopedInstances.clear();
108125
}
109126
}
110127

spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,17 @@
1616

1717
package org.springframework.transaction.support;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1922
import org.junit.Test;
2023

2124
import org.springframework.beans.factory.BeanCreationException;
2225
import org.springframework.beans.factory.support.GenericBeanDefinition;
2326
import org.springframework.context.support.GenericApplicationContext;
2427
import org.springframework.tests.sample.beans.DerivedTestBean;
2528
import org.springframework.tests.sample.beans.TestBean;
29+
import org.springframework.tests.transaction.CallCountingTransactionManager;
2630

2731
import static org.junit.Assert.*;
2832

@@ -93,7 +97,6 @@ public void getFromScope() throws Exception {
9397

9498
bean2b = context.getBean(DerivedTestBean.class);
9599
assertSame(bean2b, context.getBean(DerivedTestBean.class));
96-
assertNotSame(bean2, bean2a);
97100
assertNotSame(bean2, bean2b);
98101
assertNotSame(bean2a, bean2b);
99102
}
@@ -125,4 +128,70 @@ public void getFromScope() throws Exception {
125128
}
126129
}
127130

131+
@Test
132+
public void getWithTransactionManager() throws Exception {
133+
GenericApplicationContext context = new GenericApplicationContext();
134+
context.getBeanFactory().registerScope("tx", new SimpleTransactionScope());
135+
136+
GenericBeanDefinition bd1 = new GenericBeanDefinition();
137+
bd1.setBeanClass(TestBean.class);
138+
bd1.setScope("tx");
139+
bd1.setPrimary(true);
140+
context.registerBeanDefinition("txScopedObject1", bd1);
141+
142+
GenericBeanDefinition bd2 = new GenericBeanDefinition();
143+
bd2.setBeanClass(DerivedTestBean.class);
144+
bd2.setScope("tx");
145+
context.registerBeanDefinition("txScopedObject2", bd2);
146+
147+
context.refresh();
148+
149+
CallCountingTransactionManager tm = new CallCountingTransactionManager();
150+
TransactionTemplate tt = new TransactionTemplate(tm);
151+
Set<DerivedTestBean> finallyDestroy = new HashSet<DerivedTestBean>();
152+
153+
tt.execute(status -> {
154+
TestBean bean1 = context.getBean(TestBean.class);
155+
assertSame(bean1, context.getBean(TestBean.class));
156+
157+
DerivedTestBean bean2 = context.getBean(DerivedTestBean.class);
158+
assertSame(bean2, context.getBean(DerivedTestBean.class));
159+
context.getBeanFactory().destroyScopedBean("txScopedObject2");
160+
assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2"));
161+
assertTrue(bean2.wasDestroyed());
162+
163+
DerivedTestBean bean2a = context.getBean(DerivedTestBean.class);
164+
assertSame(bean2a, context.getBean(DerivedTestBean.class));
165+
assertNotSame(bean2, bean2a);
166+
context.getBeanFactory().getRegisteredScope("tx").remove("txScopedObject2");
167+
assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2"));
168+
assertFalse(bean2a.wasDestroyed());
169+
170+
DerivedTestBean bean2b = context.getBean(DerivedTestBean.class);
171+
finallyDestroy.add(bean2b);
172+
assertSame(bean2b, context.getBean(DerivedTestBean.class));
173+
assertNotSame(bean2, bean2b);
174+
assertNotSame(bean2a, bean2b);
175+
176+
Set<DerivedTestBean> immediatelyDestroy = new HashSet<DerivedTestBean>();
177+
TransactionTemplate tt2 = new TransactionTemplate(tm);
178+
tt2.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
179+
tt2.execute(status2 -> {
180+
DerivedTestBean bean2c = context.getBean(DerivedTestBean.class);
181+
immediatelyDestroy.add(bean2c);
182+
assertSame(bean2c, context.getBean(DerivedTestBean.class));
183+
assertNotSame(bean2, bean2c);
184+
assertNotSame(bean2a, bean2c);
185+
assertNotSame(bean2b, bean2c);
186+
return null;
187+
});
188+
assertTrue(immediatelyDestroy.iterator().next().wasDestroyed());
189+
assertFalse(bean2b.wasDestroyed());
190+
191+
return null;
192+
});
193+
194+
assertTrue(finallyDestroy.iterator().next().wasDestroyed());
195+
}
196+
128197
}

0 commit comments

Comments
 (0)