From 831f09cf48a1cd2242bc1da010a6e0fbb4db293c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 12 Apr 2016 16:02:36 +0200 Subject: [PATCH] SimpleTransactionScope properly suspends and resumes scoped objects Issue: SPR-14148 --- .../support/SimpleTransactionScope.java | 27 +++++-- .../support/SimpleTransactionScopeTests.java | 73 ++++++++++++++++++- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java b/spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java index 60ad8426a1..84a6668c75 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/SimpleTransactionScope.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public class SimpleTransactionScope implements Scope { ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this); if (scopedObjects == null) { scopedObjects = new ScopedObjectsHolder(); - TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization()); + TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects)); TransactionSynchronizationManager.bindResource(this, scopedObjects); } Object scopedObject = scopedObjects.scopedInstances.get(name); @@ -98,13 +98,30 @@ public class SimpleTransactionScope implements Scope { private class CleanupSynchronization extends TransactionSynchronizationAdapter { + private final ScopedObjectsHolder scopedObjects; + + public CleanupSynchronization(ScopedObjectsHolder scopedObjects) { + this.scopedObjects = scopedObjects; + } + + @Override + public void suspend() { + TransactionSynchronizationManager.unbindResource(SimpleTransactionScope.this); + } + + @Override + public void resume() { + TransactionSynchronizationManager.bindResource(SimpleTransactionScope.this, this.scopedObjects); + } + @Override public void afterCompletion(int status) { - ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) - TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this); - for (Runnable callback : scopedObjects.destructionCallbacks.values()) { + TransactionSynchronizationManager.unbindResourceIfPossible(SimpleTransactionScope.this); + for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) { callback.run(); } + this.scopedObjects.destructionCallbacks.clear(); + this.scopedObjects.scopedInstances.clear(); } } diff --git a/spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java b/spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java index c8a16a41c4..8d7f3f37f2 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/support/SimpleTransactionScopeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.transaction.support; +import java.util.HashSet; +import java.util.Set; + import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; @@ -23,6 +26,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.support.GenericApplicationContext; import org.springframework.tests.sample.beans.DerivedTestBean; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.tests.transaction.CallCountingTransactionManager; import static org.junit.Assert.*; @@ -93,7 +97,6 @@ public class SimpleTransactionScopeTests { bean2b = context.getBean(DerivedTestBean.class); assertSame(bean2b, context.getBean(DerivedTestBean.class)); - assertNotSame(bean2, bean2a); assertNotSame(bean2, bean2b); assertNotSame(bean2a, bean2b); } @@ -125,4 +128,70 @@ public class SimpleTransactionScopeTests { } } + @Test + public void getWithTransactionManager() throws Exception { + GenericApplicationContext context = new GenericApplicationContext(); + context.getBeanFactory().registerScope("tx", new SimpleTransactionScope()); + + GenericBeanDefinition bd1 = new GenericBeanDefinition(); + bd1.setBeanClass(TestBean.class); + bd1.setScope("tx"); + bd1.setPrimary(true); + context.registerBeanDefinition("txScopedObject1", bd1); + + GenericBeanDefinition bd2 = new GenericBeanDefinition(); + bd2.setBeanClass(DerivedTestBean.class); + bd2.setScope("tx"); + context.registerBeanDefinition("txScopedObject2", bd2); + + context.refresh(); + + CallCountingTransactionManager tm = new CallCountingTransactionManager(); + TransactionTemplate tt = new TransactionTemplate(tm); + Set finallyDestroy = new HashSet(); + + tt.execute(status -> { + TestBean bean1 = context.getBean(TestBean.class); + assertSame(bean1, context.getBean(TestBean.class)); + + DerivedTestBean bean2 = context.getBean(DerivedTestBean.class); + assertSame(bean2, context.getBean(DerivedTestBean.class)); + context.getBeanFactory().destroyScopedBean("txScopedObject2"); + assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2")); + assertTrue(bean2.wasDestroyed()); + + DerivedTestBean bean2a = context.getBean(DerivedTestBean.class); + assertSame(bean2a, context.getBean(DerivedTestBean.class)); + assertNotSame(bean2, bean2a); + context.getBeanFactory().getRegisteredScope("tx").remove("txScopedObject2"); + assertFalse(TransactionSynchronizationManager.hasResource("txScopedObject2")); + assertFalse(bean2a.wasDestroyed()); + + DerivedTestBean bean2b = context.getBean(DerivedTestBean.class); + finallyDestroy.add(bean2b); + assertSame(bean2b, context.getBean(DerivedTestBean.class)); + assertNotSame(bean2, bean2b); + assertNotSame(bean2a, bean2b); + + Set immediatelyDestroy = new HashSet(); + TransactionTemplate tt2 = new TransactionTemplate(tm); + tt2.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); + tt2.execute(status2 -> { + DerivedTestBean bean2c = context.getBean(DerivedTestBean.class); + immediatelyDestroy.add(bean2c); + assertSame(bean2c, context.getBean(DerivedTestBean.class)); + assertNotSame(bean2, bean2c); + assertNotSame(bean2a, bean2c); + assertNotSame(bean2b, bean2c); + return null; + }); + assertTrue(immediatelyDestroy.iterator().next().wasDestroyed()); + assertFalse(bean2b.wasDestroyed()); + + return null; + }); + + assertTrue(finallyDestroy.iterator().next().wasDestroyed()); + } + }