Skip to content

Commit cd2f5c5

Browse files
committed
DATAJPA-839 - Fixed thread-bound lookups of CrudMethodMetadata.
CrudMethodMetadata exposed by the CrudMethodMetadataPostProcessor previously used an AbstractLazyCreationTargetSource to lookup the thread-bound instance. That instance however is cached and never released so that all subsequent calls to it returned the same (and in most cases wrong) instance. We're now implementing TargetSource directly to make sure we obtain a fresh instance on every access of the CrudMethodMetadata proxy.
1 parent 9d6e3a4 commit cd2f5c5

File tree

2 files changed

+75
-6
lines changed

2 files changed

+75
-6
lines changed

src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727

2828
import org.aopalliance.intercept.MethodInterceptor;
2929
import org.aopalliance.intercept.MethodInvocation;
30+
import org.springframework.aop.TargetSource;
3031
import org.springframework.aop.framework.ProxyFactory;
3132
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
32-
import org.springframework.aop.target.AbstractLazyCreationTargetSource;
3333
import org.springframework.beans.factory.BeanClassLoaderAware;
3434
import org.springframework.core.annotation.AnnotationUtils;
3535
import org.springframework.data.jpa.repository.EntityGraph;
@@ -224,17 +224,42 @@ public JpaEntityGraph getEntityGraph() {
224224
}
225225
}
226226

227-
private static class ThreadBoundTargetSource extends AbstractLazyCreationTargetSource {
227+
private static class ThreadBoundTargetSource implements TargetSource {
228228

229229
/*
230230
* (non-Javadoc)
231-
* @see org.springframework.aop.target.AbstractLazyCreationTargetSource#createObject()
231+
* @see org.springframework.aop.TargetSource#getTargetClass()
232232
*/
233233
@Override
234-
protected Object createObject() throws Exception {
234+
public Class<?> getTargetClass() {
235+
return CrudMethodMetadata.class;
236+
}
237+
238+
/*
239+
* (non-Javadoc)
240+
* @see org.springframework.aop.TargetSource#isStatic()
241+
*/
242+
@Override
243+
public boolean isStatic() {
244+
return false;
245+
}
246+
247+
/*
248+
* (non-Javadoc)
249+
* @see org.springframework.aop.TargetSource#getTarget()
250+
*/
251+
@Override
252+
public Object getTarget() throws Exception {
235253

236254
MethodInvocation invocation = ExposeInvocationInterceptor.currentInvocation();
237255
return TransactionSynchronizationManager.getResource(invocation.getMethod());
238256
}
257+
258+
/*
259+
* (non-Javadoc)
260+
* @see org.springframework.aop.TargetSource#releaseTarget(java.lang.Object)
261+
*/
262+
@Override
263+
public void releaseTarget(Object target) throws Exception {}
239264
}
240265
}

src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@
2323

2424
import javax.persistence.LockModeType;
2525

26+
import org.aopalliance.intercept.MethodInterceptor;
2627
import org.aopalliance.intercept.MethodInvocation;
2728
import org.junit.Test;
2829
import org.junit.runner.RunWith;
2930
import org.mockito.Mock;
3031
import org.mockito.runners.MockitoJUnitRunner;
32+
import org.springframework.aop.framework.ProxyFactory;
33+
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
3134
import org.springframework.data.jpa.repository.Lock;
3235
import org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor;
3336
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -48,18 +51,59 @@ public class CrudMethodMetadataPopulatingMethodInterceptorUnitTests {
4851
@Test
4952
public void cleansUpBoundResources() throws Throwable {
5053

51-
Method method = Sample.class.getMethod("someMethod");
52-
when(invocation.getMethod()).thenReturn(method);
54+
Method method = prepareMethodInvocation("someMethod");
5355

5456
CrudMethodMetadataPopulatingMethodInterceptor interceptor = CrudMethodMetadataPopulatingMethodInterceptor.INSTANCE;
5557
interceptor.invoke(invocation);
5658

5759
assertThat(TransactionSynchronizationManager.getResource(method), is(nullValue()));
5860
}
5961

62+
/**
63+
* @see DATAJPA-839
64+
*/
65+
@Test
66+
public void looksUpCrudMethodMetadataForEveryInvocation() throws Throwable {
67+
68+
CrudMethodMetadata metadata = new CrudMethodMetadataPostProcessor().getCrudMethodMetadata();
69+
70+
expectLockModeType(metadata, LockModeType.OPTIMISTIC).someMethod();
71+
expectLockModeType(metadata, LockModeType.PESSIMISTIC_READ).someOtherMethod();
72+
}
73+
74+
private Method prepareMethodInvocation(String name) throws Throwable {
75+
76+
Method method = Sample.class.getMethod(name);
77+
ExposeInvocationInterceptor.INSTANCE.invoke(invocation);
78+
when(invocation.getMethod()).thenReturn(method);
79+
80+
return method;
81+
}
82+
83+
private static Sample expectLockModeType(final CrudMethodMetadata metadata, final LockModeType type) {
84+
85+
ProxyFactory factory = new ProxyFactory(new Object());
86+
factory.addInterface(Sample.class);
87+
factory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
88+
factory.addAdvice(CrudMethodMetadataPopulatingMethodInterceptor.INSTANCE);
89+
factory.addAdvice(new MethodInterceptor() {
90+
91+
@Override
92+
public Object invoke(MethodInvocation invocation) {
93+
assertThat(metadata.getLockModeType(), is(type));
94+
return null;
95+
}
96+
});
97+
98+
return (Sample) factory.getProxy();
99+
}
100+
60101
interface Sample {
61102

62103
@Lock(LockModeType.OPTIMISTIC)
63104
void someMethod();
105+
106+
@Lock(LockModeType.PESSIMISTIC_READ)
107+
void someOtherMethod();
64108
}
65109
}

0 commit comments

Comments
 (0)